It’s been a long time (again) since my last update on my solo project – Reversi.
Since I’m (still) busy with other personal matters, I’ve only been able to work on the project at a (much) slower pace. As these personal matters may take longer to resolve, I thought I’d better write down my progress before I forget everything.
Last time, I briefly talked about the flow and the tech stack decisions for Reversi. Since Gorilla WebSocket provides a workable example of a WebSocket chat server, I started my project based on that.
In Gorilla WebSocket’s example, the chat server defines two types, Client
and Hub
. Each Client
instance serves a single WebSocket connection, and acts as an intermediary between the WebSocket connection and Hub
. The Hub
maintains a set of registered Clients
and broadcasts message to them.
While drafting the game server design, I asked myself: what if more than two clients were registered to the hub? Would they all join the same board and play on it together? I admit this was a premature design question, but I decided to implement multiple rooms first, before integrating the game engine into the server.
As a result, the design of the Reversi game supporting mutliple rooms turned out as follows:
- The original responsibilities of
Client
andHub
remain unchanged. - A
Room
is introduced to maintain 2 registeredClients
, control theGameEngine
, and broadcast message to those 2Clients
. - The
Hub
maintains a set of activeRooms
.
In terms of the functionality:
- A client can register on the hub with a name.
- A client can create a room.
- Each room can only contain 2 clients.
- A client can only be in 1 room at a time.
- When 2 clients are in the room, either can start the game.
- If Client A leaves the room, whether by clickick the surrender button or accidentally disconnecting, it counts as a surrender, and the game ends.
- Once the game ends, if there are still 2 clients in the room. Either can restart the game.
During early development, I often referred to the Gorilla WebSocket example. Since it’s a chat server, it only needs to transmit text, so the data type is quite simple – []byte
. However, applying the same structure to Reversi didn’t work, as the game inovles different message types throughout its lifecycle. For example, clients may join rooms, start games, or make moves. The hub also needs to send different responses for frontend actions like registration and game state updates.
That’s when I recalled something I used frequently at my previous employment – BORIS in Pivotal’s Swift Method.
I had never touched concurrency directly in my career (alright, I understood that RESTful API involves concurrency at the infrastructure level). Therefore, I initially found concurrency model hard to understand. But after drawing the above diagram, I noticed some similarities between microservice communication and concurrency, particularly between message brokers and channels.
Microservices (System Level) | Concurrency in Go (Code Level) |
---|---|
Services = Independent processes | Goroutines |
REST = Sync call between services | Function call that blocks (sync) |
Kafka = Async messaging | Channels |
Message loss, retries, failure | Deadlocks, race conditions, thread safety |
Service A calling B | Function A calling B or goroutine A talking to B |
Load balancing | Goroutine pool |
Timeout, retries | Context with timeout |
Of course, it’s not a perfect one-to-one mapping, but it helped me better understand what concurrency actually means. Concurrency in Go: Tools and Techniques for Developers is now on my to-read list. I know I’ll need to dive deeper if I want to becom a well-rounded developer.
After realising the similarity between microservice communication and and WebSocket communication, the development process became much clearer. I simply defined all the message types – both server and client side – and implemented them one by one.
Visit my GitHub repository for the Reversi codebase.
In terms of design, I could have taken more time to decide which APIs should use WebSocket and which should use REST. But for the moment, my focus is on WebSocket, so all the communications happens through it, except the register
function, which uses a HTTP upgrade request.
Let’s leave the Go RESTful exploration in the later project.
(I’ve already experimented a bit with Go RESTful APIs in parallel with my other personal matters.)
Roadmap
# | Features | Status |
---|---|---|
1 | Console version of local multiplayer | ✅ |
2 | Adopt Gorilla for WebSocket | ✅ |
3 | E2E backend flow from registering to announcing result | ✅ |
4 | Basic frontend rendering | ✅ |
5 | Unhappy flows handling, e.g., client(s) leaving unexpectedly | ✅ |
6 | Cosmetic fine tune for frontend | ⏳ |
7 | Security features | ⏳ |
8 | Production ready | ⏳ |
? | Others, e.g., User account, Ranking, DB/Redis/AWS adoption | ⏳ |