Complete Implementation
In this section, we’ll review the complete implementation of our Tic-Tac-Toe game. This gives us a chance to see how all the pieces fit together and understand the overall architecture.
Project Structure
Our Tic-Tac-Toe game is organized in a clean, modular structure:
tic-tac-toe-shoehive/
├── src/
│ ├── index.ts # Entry point and server setup
│ ├── events.ts # Game-specific events
│ ├── game-logic.ts # Game logic implementation
│ ├── command-handlers.ts # Command handlers
│ └── utils.ts # Utility functions
├── client.js # Test client
├── tsconfig.json # TypeScript configuration
├── package.json # Project dependencies
└── README.md # Project documentation
Code Overview
Let’s review each file and its purpose:
1. src/events.ts
This file defines all the event constants and types used in our game:
- TIC_TAC_TOE_EVENTS: Constants for all game events
- MovePayload: Interface for move event data
- GameEndedPayload: Interface for game end event data
- GameState: Enum of possible game states
2. src/game-logic.ts
This file contains the core game mechanics:
- setupTicTacToeTable: Initializes a new table
- isValidMove: Validates player moves
- makeMove: Updates the board with a move
- checkGameStatus: Checks for win or draw conditions
- getCurrentPlayer: Gets the current player
- resetGame: Resets the game state
3. src/command-handlers.ts
This file handles all player actions:
- tictactoe:create: Creates a new game
- tictactoe:join: Joins an existing game
- tictactoe:start: Starts the game
- tictactoe:makeMove: Makes a move
- tictactoe:forfeit: Forfeits the game
- tictactoe:reset: Resets the game
4. src/utils.ts
This file provides helper functions:
- transitionGameState: Handles state transitions
- getBoardForClient: Gets board data for clients
- getGameStateForPlayer: Gets game state for a specific player
5. src/index.ts
This file ties everything together:
- Creates an HTTP server
- Sets up the Shoehive game server
- Registers the game definition
- Registers command handlers
- Sets up event listeners
- Starts the server
Event Flow
Our game follows a clear event flow:
- Game Creation:
- Player sends
tictactoe:create
command - Server creates a table and emits
GAME_CREATED
event - Player receives confirmation
- Player sends
- Player Joining:
- Player sends
tictactoe:join
command - Server adds player to table and emits
PLAYER_JOINED
event - All players receive notification
- Player sends
- Game Starting:
- Player sends
tictactoe:start
command - Server updates game state and emits
GAME_STARTED
event - All players receive notification
- Player sends
- Making Moves:
- Player sends
tictactoe:makeMove
command - Server validates move, updates board, and emits
MOVE_MADE
event - Server checks for win/draw and emits appropriate events
- All players receive notification
- Player sends
- Game Ending:
- Server detects win/draw or player forfeits
- Server emits
WINNER_DETERMINED
,DRAW_DETERMINED
, orPLAYER_FORFEITED
event - Server emits
GAME_ENDED
event - All players receive notification
- Game Resetting:
- Player sends
tictactoe:reset
command - Server resets game state and emits
GAME_RESET
event - All players receive notification
- Player sends
Key Design Patterns
Our implementation uses several important design patterns:
- Event-Driven Architecture
- Events are used for communication between components
- Components are decoupled and can be modified independently
- State Machine Pattern
- Game has well-defined states (waiting, ready, in-progress, over)
- State transitions are explicit and controlled
- Command Pattern
- Player actions are encapsulated as commands
- Commands are validated before execution
- Observer Pattern
- Players observe the game state
- Changes are broadcast to all observers
- Factory Pattern
- Tables are created through a factory method
- Creation logic is encapsulated
Code Quality Features
Our implementation includes several features to ensure code quality:
- TypeScript Type Safety
- Strong typing for events, payloads, and functions
- Interfaces define expected data structures
- Error Handling
- Comprehensive validation of all inputs
- Meaningful error messages for players
- Logging
- Debug monitoring in development mode
- Event logging for tracing game flow
- Modularity
- Each file has a single responsibility
- Components can be tested and modified independently
- Pure Functions
- Game logic functions are mostly pure
- Makes testing and reasoning about code easier
Example Game Flow
Let’s walk through a typical game flow:
- Player 1 creates a game:
{type: 'tictactoe:create'}
- Player 2 joins the game:
{type: 'tictactoe:join', tableId: 'table-123'}
- Player 1 starts the game:
{type: 'tictactoe:start'}
- Player 1 makes the first move:
{type: 'tictactoe:makeMove', row: 0, col: 0}
- Player 2 makes the second move:
{type: 'tictactoe:makeMove', row: 1, col: 1}
- Player 1 makes the third move:
{type: 'tictactoe:makeMove', row: 0, col: 1}
- Player 2 makes the fourth move:
{type: 'tictactoe:makeMove', row: 2, col: 0}
- Player 1 makes the fifth move and wins:
{type: 'tictactoe:makeMove', row: 0, col: 2}
- Player 2 resets the game:
{type: 'tictactoe:reset'}
Next Steps
Now that we’ve reviewed the complete implementation, let’s look at best practices and potential enhancements for our Tic-Tac-Toe game.