{
  "path": "/posts/rust-for-javascript-engineers-interactivity",
  "site": "at://did:plc:pans3xjam4khj7y54dx7gtfg/site.standard.publication/3mdqevmg6w32c",
  "$type": "site.standard.document",
  "title": "Rust for Javascript Engineers - Connect-4 Interactivity",
  "description": "Player turns, move validation, and win detection for Connect-4 in Rust compiled to WASM.",
  "publishedAt": "2025-09-23T23:46:59.000Z",
  "textContent": "<a href=\"/connectors\" target=\"_blank\" rel=\"noopener noreferrer\">Connect Four</a> | <a href=\"https://github.com/tauseefk/connectors\" target=\"_blank\" rel=\"noopener noreferrer\">Repository</a>\n\nIn the last post we looked at setting up the Connect-4 game board and transfering data back and forth between the JavaScript and WASM boundary. This allowed us to render a randomized board to the screen, however for it to be a real game we need allow users to make moves.\n\nIn this post let's take a look at adding a bit of interactivity to allow players to play a full game, and at the very end calculate the winner!\n\nInteractivity\n\nThe first piece of interactivity we can add to our connect-4 is a way to switch between players.\nLet's add a new enum that encodes the player variants, and then add it to our larger game state which is the Board struct.\n\n1. end_player_turn\n\nThe end_player_turn function just flips the player turn state on the overall game state between red and black variants.\n\n2. select_col\n\nThe select_col function is where majority of the game logic will go. Once all the required state has been updated, it'll call end_player_turn and the other player can make their move.\n\nJavaScript to match\n\nNow let's update our JavaScript to accomodate for these changes, by first adding another div that would display the player turn, updating the rendering logic, and adding an event handler.\n\nThat's a wall of code, however most of it is just a small refactor to ensure that we can render after each user event and not just once when the page loads.\n\n1. Render refactor\n\nWe extract the rendering logic into it's own function so that after each user interaction we can update the entire screen to display the updated state.\n\n2. Adding event listeners\n\nThe event handler is applied to the entire grid, it extract the column index from the data attribute and calls the select_col function that we added to the rust code earlier, and finally calls render to update the screen.\n\nIf you rebuild and launch the app now you'll see that clicking anywhere on the grid updates the Player turn back and forth between R and B.\n\nHere's the full commit.\n\nMaking Moves\n\nSo far we've been using random tiles, now let's replace those with empty tiles.\n\n1. private Vec field\n\nOnly simple types are directly exposed across the Wasm/JS boundary, so vector fields cannot be public. have to be converted into typed arrays.\n\n2. Initializing Vec to Empty\n\nHere we initialize entire board to TileType::Empty using the vec! macro instead of a randomized board.\n\nNow let's update the select_col function to update the state based on user's selected column.\n\n1. Bounds Checking\n\nAs we're storing the entire grid as a single array, checking if the selected index is valid is trivial.\n\n2. Chunking\n\nRust Vecs support non overlapping chunks out of the box.\n\n3. Reversing the rows\n\nAs Connect-4 fills bottom up, we have to find the last row that's empty to replace the tile.\n\nThere's another way to combine the chunking and reversing via self.boards.rchunks which gives us an iterator over already reversed chunks but I wanted to break this down for clarity.\n\n4. Updating the board\n\nOnce an empty row is found we can map over the board and replace it after updating the appropriate tile. If we go through all the rows without finding an empty row, we can just return false to signify that no update was made.\n\nThat should be it for interactivity!\n\nWinning Move\n\nAs this requires changes in a lot of places I'll only go over the crucial bits that use Rust features we haven't explored before. The full diff can be found here.\nCurrently the game never ends even if one of the player connects 4 of their tiles. The win condition requires a player to connect four of their tiles either horizontally, vertically, or diagonally. Let's look at the first one.\n\nAs the state only changes when a player makes a move we can narrow things down to a player, a row, and a column.\n\nSimilarly we can check if four tiles in a column belong to the expected player, I won't write the code here but it's part of the commit.\n\nThere are several ways of encoding the winning player but as we don't advance the player turn after the winning move, it's just simpler to combine the player_turn and is_game_over.\n\n1. PartialEq trait\n\nThe partial eq trait is necessary to perform equality checks on enum variants.\nThe derive macro conveniently implements the trait for us here as the Player enum variants are trivial.\n\n2. Option type\n\nRust has no null values, the idiomatic way to encode the absence of a value is to use an Option of the appropriate type. In this case Option<Player> can either store Some(Player::Red) or Some(Player::Black) or None. We initialize the winner to None when the game starts and replace it with the appropriate player that makes the winning move.\n\nOn the JavaScript side None shows up as undefined.\n\n3. does_belong_to\n\nAs we have more TileType variants than Player variants, we need a way to map whether a tile belongs to a player.\n\nThe JavaScript changes are trivial (updating text and disabling click events after game ends) and I'll skip those here.\n\nThis concludes all of the gameplay for Connect-4, we've exposed the smallest amount of API required to play a full game. There's no clean-up required as refreshing the page frees the memory allocated in WASM and reallocates the memory when the game restarts.",
  "canonicalUrl": "https://afloat.boats/posts/rust-for-javascript-engineers-interactivity"
}