Choose Any
Valid Move.
The simplest opponent selects randomly from the columns that can still accept a piece.
A realtime multiplayer strategy game that grew from a local React experiment into a room-based Socket.IO system with public matchmaking, spectators, synchronized rematches, and adversarial AI.
Connect Four started as a straightforward local game. Each new capability changed what the system needed to handle: stronger AI introduced search complexity, online play introduced shared state, and spectator mode exposed the challenges of synchronizing clients that could join at different points in a game.
Board state, turn handling, and win detection.
Difficulty levels, minimax, evaluation heuristics, and alpha-beta pruning.
Socket.IO rooms, player assignment, and realtime move propagation.
Discoverable games alongside private room codes.
Read-only participants sharing the same live game.
Current-state snapshots for clients joining an existing game.
Online games are organized around a shared room. React clients communicate through Socket.IO while the Node server coordinates room membership, players, spectators, board snapshots, and the current turn.
Creates the room and participates as one of two active players.
PlayerJoins a public or private room and receives live game updates.
PlayerReceives shared game state without occupying a player slot.
Read OnlyRenders the board, handles interaction, and emits player actions.
Socket.IO moves events between connected clients and the shared room.
Maintains room-level shared state and coordinates connected participants.
The server coordinates shared room state rather than acting as a fully authoritative game engine. Clients still participate in game logic and local state management. An architectural tradeoff I revisit later in the case study.
Spectator mode exposed a flaw in the original realtime model. Clients already inside a room could stay synchronized through live move events, but a spectator joining mid-game had missed every event that came before.
If a spectator joined from the beginning, each move arrived normally and their board stayed in sync. If they joined later, their client started from an empty board because it had no history of the moves that already happened.
The server began keeping a current board snapshot and active-player value for each room. A late spectator could first receive that snapshot, then continue listening for normal realtime events.
joinAsSpectatorboard + currentPlayersyncBoardboard / turnopponentMoveThe bug changed how I thought about realtime systems. A stream of updates can keep existing clients synchronized, but new clients still need a trustworthy representation of current state. That distinction became important once the application had participants entering the same session at different times.
The computer opponent evolved through four difficulty levels. Each one uses a different decision strategy, from simple randomness to recursive game-tree search with pruning and board evaluation.
The simplest opponent selects randomly from the columns that can still accept a piece.
Medium difficulty looks for tactical opportunities such as winning moves, blocking the opponent, and preferring stronger board positions.
Hard difficulty uses depth-limited minimax, recursively exploring possible responses before selecting the strongest evaluated move.
The strongest opponent combines minimax, alpha-beta pruning, move ordering, and heuristic evaluation to reduce unnecessary search while still comparing future game states.
Ask what happens after the opponent responds, then after the AI responds again. Minimax recursively evaluates those future states while assuming both players continue choosing their strongest available moves.
The challenge was not implementing recursion. It was making the search useful without exploring every possible future board state.
Recursive search becomes expensive quickly. The strongest AI combines bounded search, move ordering, heuristic board evaluation, and alpha-beta pruning so it can spend more computation on branches that are actually capable of changing the decision.
At any position, the AI can have as many as seven legal columns to consider. Each of those moves creates another set of possible opponent responses, and the tree expands recursively.
Real searches are smaller because columns fill, games terminate, and alpha-beta pruning removes branches early.
The AI prioritizes columns near the center of the board. Center positions participate in more possible Connect Four lines, and evaluating promising moves earlier can tighten alpha-beta bounds sooner.
Minimax cannot always search until the game is finished. At the depth limit, a heuristic estimates whether the position favors the AI or the opponent.
Reward pieces placed in the center column.
Strongly reward completed winning windows.
Reward positions one move away from four.
Preserve developing offensive opportunities.
Penalize positions containing an immediate threat.
Minimax still produces the same decision, but alpha-beta pruning avoids evaluating parts of the tree once the current bounds prove that a branch cannot outperform an already-known move.
Search depth is bounded rather than allowed to expand indefinitely. The implementation adjusts its depth based on the remaining board state, balancing how far ahead the AI looks against the amount of work required for each move.
The expensive part is not scanning a 6 × 7 board. It is placing that work inside a recursively expanding tree. This project made the difference between code that is merely correct and code whose computational cost still makes sense as the amount of work grows.
Search Space recreates the same minimax and alpha-beta concepts as an interactive experiment, exposing candidate moves, representative branches, pruning, and search metrics.
Connect Four started as a small personal project. As multiplayer, spectators, stronger AI, public rooms, and rematches were added, decisions that seemed minor early on began affecting the rest of the system.
Realtime events were enough while every client entered the game at the same point. Spectators joining later exposed the weakness of relying only on local state and incremental updates.
A board evaluation is inexpensive by itself. Repeating that work across a recursively expanding game tree completely changes the computational cost.
Local play needed board state and turn logic. Multiplayer introduced rooms and event transport. Spectators required shared state. Stronger AI introduced search and performance constraints.
The server maintains room-level shared state and coordinates connected participants, but clients still participate in portions of the game logic and local state management.
That architecture was enough for the project, but it also showed me the difference between adding coordination to an existing client-driven system and designing an authoritative server model from the beginning.
The same questions show up in larger software systems: where state should live, how clients stay consistent, how computational work scales, what happens when requirements expand, and when an existing architecture needs to evolve instead of accumulating another workaround.
Shared data becomes easier to reason about when there is a clear source of truth.
Small operations become expensive when they sit inside loops, recursion, or high-frequency paths.
The best architecture is not the one with the most abstractions. It is the one that can absorb the next reasonable change.
A decision that made sense for version one may become the constraint in version four.
Connect Four became an exercise in realtime state, algorithmic search, synchronization, and evolving architecture. All from a game that started with a single board and two players.