Real-time Multiplayer Tic-Tac-Toe with ActionCable
Build a Rails app where two people play tic-tac-toe against each other in real time from their own browsers. One player creates a game and shares its link; the second joins, and every move appears instantly on both screens through ActionCable, Rails' built-in WebSocket support. The server is the referee: it checks that a move is legal and that it's the right player's turn, detects wins and draws, and handles players who leave. You'll learn ActionCable channels and broadcasting, keeping game state authoritative on the server, preventing cheating, handling race conditions when two moves arrive at once, and testing real-time features.
- Estimate
- ~13.5h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
Rails · ActionCable
Project roadmap
- 01
Create the app and Game model
~2hCreate a Rails app (Rails 7 or newer) with Hotwire or plain JavaScript. Generate a Game model with a board (a 9-character string, one character per cell: -, X or O), the current turn, x_player_token, o_player_token, status (waiting, playing, x_won, o_won, draw) and a lock_version column for optimistic locking. Players are identified by a random token stored in their session, so no login is needed. Done when: games can be created in the Rails console.
- 02
Create and join games
~1.5hAdd a home page with a New game button that creates a game, assigns the creator X and redirects to the game page with a shareable link. Opening the link as a different visitor assigns them O and starts the game. A third visitor becomes a spectator. Done when: two browser windows (one of them private) can create and join the same game.
- 03
Add the game channel
~2hCreate a GameChannel that subscribes to stream_for game, checking that the game exists. Identify the connection by the player's session token in ApplicationCable::Connection. Whenever the game changes, broadcast its full state (board, turn, status, players connected) as JSON. Done when: both browsers receive the game state as soon as they subscribe.
- 04
Validate and apply moves on the server
~2.5hAdd a move(data) action to the channel: check that the game is in the playing state, that the sender is the player whose turn it is, and that the cell index is between 0 and 8 and empty. Update the board and switch the turn, saving with optimistic locking so two moves arriving at once can't both succeed. Reject invalid moves with a message sent only to that player. Done when: legal moves update both screens, and out-of-turn or occupied-cell moves are rejected.
- 05
Detect wins and draws
~1.5hPut the rules in a plain Ruby class, TicTacToe::Rules, which checks the 8 winning lines and a full board. After each move, update the status and broadcast it, including the winning line so the front end can highlight it. Add a Rematch button that resets the board with the players swapped. Done when: wins, draws and rematches work in both browsers.
- 06
Build the board and handle disconnects
~2hBuild the board as a 3×3 grid that sends moves over the channel and redraws itself from every broadcast, never from its own guesses. Show whose turn it is and the game status. Track connections in the channel's subscribed and unsubscribed callbacks, and show Opponent left when the other player disconnects. Done when: the game plays smoothly, and closing one tab shows the message in the other.
- 07
Write tests
~2hWrite unit tests for TicTacToe::Rules (every winning line, a draw, a game still in progress), model tests for the move validation, and channel tests with ActionCable::Channel::TestCase that check a valid move is broadcast and an out-of-turn move is rejected. Done when: bin/rails test passes.
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack