The Making of Game of the Generals
The architecture and breakdown of Game of the Generals

Game of the Generals webapp featured on my portfolio is the digital implementation of Filipino board game of the same name. In GOG, opposing players have their own army with ranks not visible to one another. The goal is to capture the opponent's flag or deliver your own flag at the opponent's nearest rank or row. Since players cannot see each other's pieces, a third participant called Arbiter must be present to determine the winning piece or the winning player. Rank of pieces determines the winning piece when a capture or challenge was made. On this blog, we will discuss the planning and design conducted for the implementation of the webapp.
The driving force behind the development of digital GOG is to demonstrate the application of relevant and latest web technologies to build a webapp. Implementation-wise, GOG impose additional complexity due to the fog-of-war, army arrangement and the different ways a player can win as we will see later. Selection of technologies to be used were primarily dictated by the deployment and operational costs. Requiring a platform that can run a backend server with websocket support plus a database that would track data for every move or action made by players would result to expenses or immediate exhaustion of free-tier quotas.
With all the constrains identified and taken in consideration, Next.JS will be used to implement the webapp, both frontend and backend, in a single codebase. The app will be deployed to Vercel as they provide free fullstack NextJS hosting service. For real-time functionalities, Vercel doesn't support custom server deployment to allow websockets, hence, peer-to-peer connection will be use to provide real-time communication. Details and technical implementation of peer-to-peer connection will be expounded on a different blog but establishing connection requires and intermediary server with real-time/subscription functionality which prompted the use of Firebase Firestore. After establishing peer-to-peer connection, firebase will not be use to store and track player actions, instead, the data will be stored on the client side to be track and manage by Redux. And since GOG has a fog-of-war feature preventing players to see each other's pieces, cryptographic techniques were utilized to ensure integrity and security of the data. Server will act as the arbiter, performing encryptions and data signing as well as arbitration of winning pieces or winning player. Communication to the server will be done using GraphQL in preparation for future features expansion of the project. Finally, webapp was converted to progressive web app to allow installation to local devices.
Initial Phase
Characterizing the whole flow of match making, GOG has 4 phases. The first phase is called Initial phase which covers the creation of match, establishment of peer-to-peer connection and syncing of data between each player. Walking through the initial phase starts with the player who creates the match. He or she will automatically be set as P1. P1 then enters required parameters i.e., arrangement and move timer as well as whether to allow annotations and undos and set whose player to move first. Once submitted, frontend validation kicks in before submitting to the server using createMatch mutation. Server validates the settings to ensure the settings are within the allowed thresholds. Once validated, server signs the settings to prevent tampering and generates P1 and P2 IDs, serialized object of encrypted pieces and a serialized pieceLookup containing the corresponding rank of a particular encrypted piece. Raw data of encrypted piece consists of piece rank and uuid v5 based on incrementing index and another uuid v5 based on cipherKey and playerId. This ensures that each encrypted piece of an army is unique even with the same piece rank. Cipher key was generated using HMAC based on server private key and concatenated matchId and playerType. All this data along with match signature are sent and stored by the P1 through redux. After storage, P1 will automatically be redirected to the /play/[matchId]. A side effect automatically executes to check whether matchSettings exists in redux store. This data can only exist at this point if the player is P1, hence if true, it will execute a function to initiate peer-to-peer connection and wait for incoming connection while showing the match link modal. Meanwhile, P1 then copies the invitation link and send to his/her opponent. Opening the invitation directly on the browser's address bar or through Join Match modal will redirect the opponent, set automatically as P2, to /play/[match]. The side effect will automatically run same to P1 but this time, falling to else condition as there is no existing match settings at this point for P2. This trigger answering logic for peer-to-peer connection. Once the connection is established, P2 automatically requests match settings from P1, triggering automatic match settings transmission back to P2. After receiving the match settings, a modal will show allowing P2 to review the details and prompt whether to accept or reject the invitation. Accepting the invitation triggers verifyMatch mutation, sending matchSettings and matchSignature. Server verifies the signature to ensure that the match settings was not tampered. After verification, a process similar to createMatch mutation takes place generating cipher key, serialized army, pieceLookup and playerId, this time for P2, re-sign new matchSettings, (P2 doesn't exist before) and sends everything back to P2. This trigger redux update in P2, saving all the data as well as informing P1 for the updated matchSettings over peer-to-peer connection. P1 performs signature verification, P2 acknowledgement, and state updates allowing P1 to proceed to Arrangement Phase. P2 received the acknowledgement and similarly, proceeds to Arrangement Phase. Take note that only match settings and match signature are exchanged in this phase.
Arrangement Phase
Arrangement phase allows the players to organize their army formation. Compared to chess with predetermined starting positions, GOG allows player to arbitrarily positioned their pieces that suits to their strategy as long as the pieces are within the first three ranks/rows nearest to the owner of the pieces. Player's pieces are available in Game Piece Pool section of UI where the player can either click the target piece and another click for target cell on the board or perform click-drag-drop. This functionality was implemented using react-dnd. Redux state keeps track of encrypted pieces and their cell address in an object. Player knows the value of their own encrypted piece because of the pieceLookup object provided by the server during the initial phase. Once the player is done with the arrangement, validateArmy mutation is called sending player cipherKey, matchId, serializedArmy which is an object containing encrypted piece as key and cell address as value. as well as the player type. Server performs validation of piece placement even after validated by frontend logic to ensure data integrity. Player type is important in validation of army as it determines the three allowable ranks/rows of the board but to prevent falsification, player cipherKey is send. Server recalculates cipherKey based on submitted playerType, if calculated cipherKey is equal to given cipherKey, then the playerType is valid. MatchId is also required for recalculation of cipherKey. Once validated, server returns signature of the army to prevent tampering. Player then sends the army data in a form of object describing the address of encrypted pieces as well as the signature. Opponent verifies the signature and store the data as foeArmy inside redux store. This process takes place vice versa. Only the P1 performs the final endpoint call for this phase. It automatically triggers when both P1 and P2 are ready by checking the existence of ownArmy and foeArmy in the store. ValidateBoard mutation requires matchId, cipherKey of P1, serializedArmy 1 and 2 (own and foe can be interchanged) and army signatures 1 and 2 matching with their counterpart army data. This operation ensures that the final armies to be placed on the board are untampered. Cipher key ensures that only P1 performs this action. Once validated, a board object where army pieces of both players are plotted is generated and signed. Once server responds, P1 store the board data and the signature and informing P2 at the same time for the board data and board signature. P2 validates the signature, sends acknowledgement, store board data and signature and update state to proceed to battle phase. P1 receives acknowledgement and proceeds to battle phase as well. Arrangement timer runs on this phase and only stop after the player commits his army arrangement. Running out of time automatically sets the late player as loser and setting the opponent as the winner. Text chat also became available in this phase up to battle phase using peer-to-peer connection. Take note that only the encrypted army, board data and relevant signatures are exchange but not the pieceLookup which specifies the value of each player's encrypted army.
Battle Phase
Battle phase permits the player to mobilize their pieces and challenge opposing pieces to win the game. First player to move is determined by the firstMove data included in the match settings. Anything player makes in this phase is called Action. Action has three types, MOVE, CAPTURE, and TIMEOUT. Move is made when player simply mobilize a piece without any capture attempt or challenge. When player move, frontend validates the move and after validation, requests opponent a token via peer-to-peer by sending the action data, previousSignature, aggressor piece and defending piece. PreviousSignature plays a crucial role in securing move history. Initially, PreviousAction is undefined at the start of battle phase and falls back to using Board signature. Opponent receives the data and checks whether the move is valid and th involve pieces are valid. This ensures that the involve pieces and addresses of the action is valid because players will not send board data to server every action validation to minimize payload and server responsibility. Once the opponent validates the action by verifying previous signature and involved pieces and addresses, opponent generates HMAC based on stringified action data and opponent cipherKey. This hash serves as the opponentToken to be deliver back to P1 as a proof of opponent approval. P1 then calls validateAction mutation, passing action candidate. Action candidate consists of matchId, previousSignature, cipherKey of the player committing the action, action data, token received from opponent, previous action (initially null at the start of battle phase), serialized board (passed only if previous action is null), aggressor piece, defending piece, and flag-related tokens and signal to be discussed later. Server validates the action using all the data inside action candidate, validating the action, opponent token, and so on. Once validated, server signs the action data, determines the winning piece address if capture action was made, send winner if the action results to endgame, win signature if winner was declared, and hot flag token if action results to hot flag to be discussed later. P1 stores all this data and forward it to the opponent. Opponent validates the action using the signature generated by the server, store the data and change the player turn completing the cycle and starting again. In case of CAPTURE type action, server performs decryption in validateAction mutation to read the ranks of involved pieces and determines the winning piece. Observing and visualizing the flow, every action adds a record in the history which is displayed in the Game Pane. The process of history generation and tracking resembles on how blockchain works. Each action can be thought of of as a block wherein the hash of previous block carries over to the new block and new hash computation making each block, cryptographically linked together. But on this case, signature is used instead of hash which can only be made by the server. This adds additional layer of security in the history of actions preventing tampering of the whole history. For the time aspect, move timer runs on both players and reset every turn. When timer runs out, current player automatically sends TIMEOUT type action followed by the process described earlier and changing the turn making the timed-out player to lose his/her turn. Undo operates by discarding the latest action data and reversing the state of board and its pieces based on the action data to be removed. This can only be done by requesting for undo to the opponent. Request and approval of undo keeps the history of players in sync. Deliberately removing or modifying action history will result into a mismatch at the part where the moving player requests for opponent's token as an approval triggering a fatal error which forfeits the match.
End Phase
Game can end in severals ways aside from fatal error that may arise during runtime. Legal and expected way to end the game is when the player takes an action that wins the game, player resigns, or draw offer was raised and accepted. Starting with the simpler ways to end the game, resign works by triggering a state update to show a confirmation modal to resign. Once the player resigns, validateResign mutation is called requiring matchID, playerType and cipherKey. Server validates the playerType based on cipherKey and matchID and creates a resignSignature and a verdict indicating the winning player. Resign signature is computed using the matchId and playerType of the resignee. This server action as well as the creation of resignSignature is important to prevent malicious declaration of opponent resignation as players doesn't know each other's cipher key. Once the server responds, resignee updates the state to endgame and informs the opponent of the response data. Opponent validates the resign signature and proceeds to endgame state update to show the result of the match. For the draw mechanism, no server interaction is required. A player who wants to offer draw informs the opponent for the offer. Opponent then has the option to accept or decline the offer. Declining the offer sends a reply to the offeree about the offer being rejected but if the opponent accepts the offer, opponent performs endgame state update for draw and informs the offeree that the draw was accepted triggering and automatic endgame state update for draw as well. For the case of a win due to certain action made by the player, it can be broken down into two scenarios. First is if the player captures the opponent flag using any of his/her pieces or if the player manages to mount his/her flag on the rank/row nearest to the opponent. The latter, which we will call flag mount, has two sub scenarios each called hot or safe flag mount. Safe flag mount happens when the player mounts the flag without any adjacent opposing piece/s that can capture the flag. Hot flag mount is the opposite of safe, i.e., there is/are adjacent piece/s that can capture the flag. In this case, mounting a hot flag doesn't result into immediate end of game as the opponent has to opportunity to capture the flag if he decides to do so. If the opponent decides to capture the suspecting piece, Opponent wins the match, but if the opponent performs an action other than capturing the said piece, the player that mounts the hot flag wins. Going back to the flag capture scenario, an action will be raised to be validated by the server with a type of capture. As we may recall, decryption of pieces happens when the action is of type capture. Arbitration logic of the server raises a win scenario if it founds out that the capture piece was a flag, generating the same payload as before but this time, with a winning player type and a win signature based on stringified action data. Player receives the response, performs endgame state update and informs the opponent. Opponent validates the action data as well as the win signature and finally updating state for endgame. For the safe flag mount scenario, moving a piece usually triggers the call for actionHandler that requests opponent token and validateAction mutation but when the frontend validation determined that the action committed can be considered as a safe flag mount, it would trigger a different function handler that requests a safe flag mount token to the opponent. Opponent validates whether the move really is a safe flag mount and creates a token based on HMAC that uses stringified action data and opponent cipher key to serve as a proof of opponent's approval. Opponent replies back triggering our player to send validateAction with included safe mount flag token and a flag mount of type SAFE. Server validates the action and token followed by the process similar to the capture flag scenario. Last case to be taken care of is the hot flag mount scenario. A different flow is executed to prevent the opponent knowing that there is a hot flag mount raised if the opponent tries to inspect web app data. Hot flag mount handling follows the usual case for making an action. Player moves the flag in hot flag situation, request an opponent token similar to making a normal action just to acquire opponent's approval of the action taken. Once the opponent sends back the token, player calls the validateAction mutation with the payload similar to a normal action. Server performs the validation and automatically detects if the action taken is a hot flag, if so, the server generates hot flag mount token based on stringified action data and server private key. player will receive the server response together with the hot flag token. Player informs the opponent with all the necessary payload without the hot flag token just like the previous flow. Opponent validates and execute his/her turn. Opponent has to inform our player about the response of the server's validateAction just like we did on our turn. After validating and recording the action, the action verification also checks if our player has an existing hot flag token. If there is, validateHotFlag mutation is called, passing the hotFlagToken and the action our player executed that causes as hot flag mount i.e., the action before our opponent's response action. Server validates the hot flag token and returns the wining player type and the win signature following the same winning process as we have before after the server responded.
Future and Wrap Up
Reexamining the whole process of GOG web app exposed some data/action redundancy and potential oversights which will be address on the future update and continuation of the development to be discussed on the next blog. If you are an employer wanting to know the technical implementation of the webapp as well as the peer-to-peer connection not covered in this blog, feel free to reach me at my social contacts through Gmail or Linked In.


