I was addicted to Tetris 99 for awhile and was frustrated that there was no web equivalent, so I set off to make one with the help of Tom Holmes. This project turned out to be an exploration in two main things: realtime multiplayer game server logic, and WebAssembly using Go.
If you're wondering why we chose to use Go on the frontend, we don't have a great reason. Tom liked writing servers in Go, I wanted to share game logic between the client and server, and this project wasn't very serious, so why not try something new and risky?
Go uses a garbage collector, and WebAssembly doesn't have garbage collection built in. So any Go application compiled to WebAssembly needs to include a garbage collector, making it take a minimum of 3MB, which is quite large for a website's script.
The way that Go interfaces with Javascript is also strange. When using Rust for WebAssembly, the Rust code essentially serves as a library of functions that can be called from Javascript. I expected Go to be similar. Instead, with Go you essentially write a separate that runs in parallel with your Javascript, using channels to communicate. This isn't a big problem because you can architect it to behave essentially like a library, but it is somewhat more awkward.
I initially had each player's tetris board be its own React component, with tetromino data stored in the component's state. This turned out to be unusably slow. I found that no matter how simple a component was, re-rendering it by updating its state cost at least 2ms. This meant that with 100 tetris boards, the frame rate was limited to 5fps. So I decided to render the entire game on one canvas and do away with React state updates altogether, which made the game run smooth as silk.