This online game supporting following features:
Functional reactive programming was chosen as the architectural style to build front-end. Clojurescript enable to program in functional way with pure function no side effect and immutable data structure. Rx works great for event-heavy frontends and apps. It provides reactive programming in asynchronous data stream in extended observer design pattern. It’s one way data flow in user interface inspired from React.js, that make the system easy to reason about and debug.
Reagent as one of the React.js wrapper in Clojurescript provides a way to write efficient React components. Combined with build-in immutable data structure, the performance of writing user interface in clojurescript might faster than user interface build by native React.js
Here is absolutely right place to all asynchronous data stream in reactive programming. Game control, like keyboard events, events from user interface and all game events from WebSocket are all go through the module of reactive. By programming in reactive, the code are able to write in declarative way than imperative, which means we are not giving a sequence of instructions to execute, we are just telling what something is by defining relationships between streams. Tell what to do instead of how to do it is the core idea in functional reactive programming. By reactive programming, control flow elements such as if, for, while and callback-based that typically expected from a JavaScript application can be eliminated completely. That’s so impressive.
Events for game lobby or actual game are filtered and categorized feed into controller. All controller need to do just mutating states belong to game lobby or actual game world according to game logic. Thanks to concurrency model provided by clojure/script, mutation are thread safe credited to persistent data structure and state/identity separation. Developers are able to easily write concurrent multi threads program without using lock and worry about dead lock, live lock, race condition and starvation.
A map storing all states regarding game lobby and actual game world. Again, no worry about mutation in this shared resource among multiple threads. UI view will render new view based on latest state.
Back end are constructed by Clojure in functional programming paradigm. Building system is like evaluating math expression and focusing on data flow in pipeline.
In order to achieve game synchronization and ensure consistency among multiple players, client-server model with lockstep has been utilized as the synchronization mechanism, which also was used by Warcraft 3. All clients send command message, like keyboard pressing or mouse clicking to central server, and server broadcasting command messages from command message buffer to all clients 20 times per second to achieve game synchronization.
There are other synchronization mechanism for online multiplayer game, like pear-to-pear lockstep, with each computer exchanging information with each other in a fully connected mesh topology. Please check referencing resources.
At least there are three different game synchronization mechanism are candidates for building this game:
The limitation of first approach is that in order to ensure that the game plays out identically on all machines it is necessary to wait until all player’s commands for that turn are received before simulating that turn. This means that each player in the game has latency equal to the most lagged player.
The mechanism of second approach is that all clients modify a shared state stored in central server, and server broadcasting the shared state to all clients at each turn. It’s good for turn-based game. However, the limitation of second approach for game that consist of thousands of units is that it’s too large to broadcasting entire game states among millions of clients
The third one has been us exploited as the synchronization mechanism. The basic idea is to abstract the game into a series of turns and a set of command messages when processed at the beginning of each turn direct the evolution of the game state. For example: move unit, attack unit, construct building. All that is needed to network this is to run exactly the same set of commands and turns on each player’s machine starting from a common initial state.
In terms of this game, clients upload their command message to central server, which will buffered all command messages from all client within each turn. There are twenty turns per second. The server will broadcasting buffered command messages to all connected client at 20 times per second.
It’s impressive to learn the idea and concept behind clojure/script. like functional programming, persistent data structure, separation of state and identity and its concurrency model. Besides, my understanding of asynchronous programming has been further consolidated by learning clojure core.async and reactive programming.
Channels are the first-class citizen and message passing via channel allow developer write asynchronous code but still maintain synchronous readability in concurrent programming. Treating everything as asynchronous data stream allows to write declarative code and eliminate control flow elements, like if for while and callback, that is tell what to do instead of hwo to do. This approach is really powerful and efficient to write maintainable code for event-driven program.
Time play an important role in both concurrency model of clojure and reative programming. It’s time that enable separation of state and identity as building block of concurrency model in clojure. Since values are immutable in clojure, identity is a stable logical entity associated with a series of different immutable values over time. In terms of reactive programming, asynchronous data stream is a sequence of ongoing events ordered in time, that is subscribed by observer. However, in object oriented programming or imperative programming, time is an missing concept, that causes the difficulty in concurrency and asynchronous programming.