Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Ashokaas/BeeHex/llms.txt

Use this file to discover all available pages before exploring further.

Overview

BeeHex uses WebSocket for bidirectional real-time communication between clients and the game server. The protocol is packet-based with JSON serialization.
Server Endpoint: ws://{IP_HOST}:3002/All packets are JSON-encoded with a type discriminator field.

Connection Lifecycle

WebsocketHandler Class

Location: src/app/game_mode/WebsocketHandler.ts The client-side handler manages WebSocket connection and packet routing.
interface WebsocketCallbacks {
  errorCallback: (message: string) => void;
  gameSearchCallback: (
    game_parameters: GameParameters, 
    player_count: number, 
    elo_range: [number, number]
  ) => void;
  gameFoundCallback: (game_id: GameId) => void;
  joinGameCallback: (game: Game) => void;
  movePlayedCallback: (
    x: number, 
    y: number, 
    turn: number, 
    grid_array: Array<Array<number>>
  ) => void;
  connectionEndedCallback: () => void;
}

class WebsocketHandler {
  private socket: WebSocket;
  private callbacks: WebsocketCallbacks;
  
  constructor(callbacks: WebsocketCallbacks) {
    this.callbacks = callbacks;
    this.socket = new WebSocket(`ws:/${getEnv()['IP_HOST']}:3002/`);
    
    this.socket.onopen = () => {
      console.log("Connected to game server");
    };
    
    this.socket.onmessage = (e) => {
      const packet = JSON.parse(e.data) as ClientBoundGenericPacket;
      this.handlePacket(packet);
    };
    
    this.socket.onclose = () => {
      console.log("Disconnected from game server");
      this.callbacks.connectionEndedCallback();
    };
  }
}

Sending Packets

sendPacket(packet: ServerBoundGenericPacket) {
  this.socket.send(JSON.stringify(packet));
}

Handling Packets

handlePacket(packet: ClientBoundGenericPacket) {
  switch (packet.type) {
    case ClientBoundPacketType.ERROR_MESSAGE:
      this.callbacks.errorCallback(
        (packet as ClientBoundErrorMessagePacket).message
      );
      break;
      
    case ClientBoundPacketType.GAME_SEARCH:
      const gameSearchPacket = packet as ClientBoundGameSearchPacket;
      this.callbacks.gameSearchCallback(
        gameSearchPacket.game_parameters,
        gameSearchPacket.player_count,
        gameSearchPacket.elo_range
      );
      break;
      
    case ClientBoundPacketType.GAME_FOUND:
      this.callbacks.gameFoundCallback(
        (packet as ClientBoundGameFoundPacket).game_id
      );
      break;
      
    case ClientBoundPacketType.JOIN_GAME:
      this.callbacks.joinGameCallback(
        (packet as ClientBoundJoinGamePacket).game
      );
      break;
      
    case ClientBoundPacketType.MOVE_PLAYED:
      const movePlayedPacket = packet as ClientBoundMovePlayedPacket;
      this.callbacks.movePlayedCallback(
        movePlayedPacket.x,
        movePlayedPacket.y,
        movePlayedPacket.turn,
        movePlayedPacket.grid_array
      );
      break;
  }
}

Packet Types

Location: src/app/definitions.ts

Server-Bound Packets

Packets sent from client to server:
enum ServerBoundPacketType {
  GAME_SEARCH = 0,
  CANCEL_GAME_SEARCH = 1,
  JOIN_GAME = 2,
  JOIN_ROOM = 3,
  PLAY_MOVE = 4,
  FORFEIT_GAME = 5
}

Client-Bound Packets

Packets sent from server to client:
enum ClientBoundPacketType {
  ERROR_MESSAGE = 0,
  GAME_SEARCH = 1,
  GAME_FOUND = 2,
  JOIN_GAME = 3,
  MOVE_PLAYED = 4,
  GAME_END = 5
}

Packet Specifications

Initiates matchmaking for a new game.
interface ServerBoundGameSearchPacket {
  type: ServerBoundPacketType.GAME_SEARCH;
  game_parameters: GameParameters;
}

interface GameParameters {
  ranked: boolean;      // Whether this is a ranked game
  board_size: number;   // 5, 7, or 9
  time_limit: number;   // Currently only 0 (unlimited) supported
}
Example:
{
  "type": 0,
  "game_parameters": {
    "ranked": true,
    "board_size": 9,
    "time_limit": 0
  }
}

Cancels active matchmaking request.
interface ServerBoundCancelGameSearchPacket {
  type: ServerBoundPacketType.CANCEL_GAME_SEARCH;
}
Example:
{
  "type": 1
}

JOIN_GAME

Joins an existing game by ID (used after GAME_FOUND).
interface ServerBoundJoinGamePacket {
  type: ServerBoundPacketType.JOIN_GAME;
  game_id: GameId;
}

type GameId = string;
Example:
{
  "type": 2,
  "game_id": "game_abc123xyz"
}

JOIN_ROOM

Joins a private room (for custom games with friends).
interface ServerBoundJoinRoomPacket {
  type: ServerBoundPacketType.JOIN_ROOM;
  room_id: RoomId;
  game_parameters: GameParameters;
}

type RoomId = string;
Example:
{
  "type": 3,
  "room_id": "room_friend_game",
  "game_parameters": {
    "ranked": false,
    "board_size": 7,
    "time_limit": 0
  }
}

PLAY_MOVE

Submits a move to the server.
interface ServerBoundPlayMovePacket {
  type: ServerBoundPacketType.PLAY_MOVE;
  x: number;  // Column index (0-based)
  y: number;  // Row index (0-based)
}
Example:
{
  "type": 4,
  "x": 4,
  "y": 3
}
The server validates that:
  • It’s the player’s turn
  • The cell at (y, x) is empty
  • The game is still in progress

FORFEIT_GAME

Forfeits the current game (instant loss).
interface ServerBoundForfeitGamePacket {
  type: ServerBoundPacketType.FORFEIT_GAME;
}
Example:
{
  "type": 5
}

Game Flow Example

Here’s a complete example of a multiplayer game session:
1

Player connects and searches

Client → Server:
{
  "type": 0,
  "game_parameters": {
    "ranked": true,
    "board_size": 9,
    "time_limit": 0
  }
}
Server → Client:
{
  "type": 1,
  "game_parameters": { "ranked": true, "board_size": 9, "time_limit": 0 },
  "player_count": 1,
  "elo_range": [1450, 1550]
}
2

Match found

Server → Client:
{
  "type": 2,
  "game_id": "game_match_001"
}
3

Join game

Client → Server:
{
  "type": 2,
  "game_id": "game_match_001"
}
Server → Client:
{
  "type": 3,
  "game": {
    "game_id": "game_match_001",
    "game_parameters": { "ranked": true, "board_size": 9, "time_limit": 0 },
    "grid": [[0,0,0,0,0,0,0,0,0], ...],
    "first_player_id": "user_alice",
    "second_player_id": "user_bob",
    "turn": 0
  }
}
4

First player makes move

Client (Alice) → Server:
{
  "type": 4,
  "x": 4,
  "y": 4
}
Server → Both Clients:
{
  "type": 4,
  "x": 4,
  "y": 4,
  "turn": 1,
  "grid_array": [[0,0,0,0,0,0,0,0,0], ..., [0,0,0,0,1,0,0,0,0], ...]
}
5

Second player responds

Client (Bob) → Server:
{
  "type": 4,
  "x": 4,
  "y": 3
}
Server → Both Clients:
{
  "type": 4,
  "x": 4,
  "y": 3,
  "turn": 2,
  "grid_array": [[0,0,0,0,0,0,0,0,0], ..., [0,0,0,0,2,0,0,0,0], [0,0,0,0,1,0,0,0,0], ...]
}
6

Game continues...

Players alternate moves until one player connects their edges.
7

Game ends

Server → Both Clients:
{
  "type": 5,
  "status": 1,
  "moves": "e5,e4,d5,f4,c5,f5,b5,g5,a5",
  "winningHexagons": [[4,0], [4,1], [4,2], [4,3], [4,4], [4,5], [4,6], [4,7], [4,8]]
}

Error Handling

The server sends error messages for invalid operations:

Common Errors

{
  "type": 0,
  "message": "Invalid move: cell already occupied"
}
Cause: Attempting to play on a non-empty cell.

User Status States

enum UserStatus {
  IDLE = 0,              // Just connected, authenticating
  IN_GAME = 1,           // Currently playing a game
  SEARCHING_GAME = 2     // In matchmaking queue
}
Server tracks user status to enforce valid transitions:

Database Game Structure

Games are persisted in the database with the following schema:
interface DatabaseGame {
  gameId: GameId;
  gameParameters: GameParameters;
  firstPlayerId: UserId;
  secondPlayerId: UserId;
  gameDate: EpochTimeStamp;
  status: GameStatus;
  moves?: string;  // Comma-separated move notation (e.g., "c3,d4,c5")
}
The moves field uses algebraic notation where each move is encoded as a grid position.

Connection Management

Awaiting Connection

awaitConnection(): Promise<WebsocketHandler> {
  return new Promise<WebsocketHandler>((resolve, reject) => {
    this.socket.onopen = () => {
      resolve(this);
    };
    this.socket.onerror = (e) => {
      reject(e);
    }
  });
}

Graceful Disconnection

When the WebSocket closes, the client is notified:
this.socket.onclose = () => {
  console.log("Disconnected from game server");
  this.callbacks.connectionEndedCallback();
};
The UI should handle this by:
  1. Displaying a “Connection Lost” message
  2. Attempting to reconnect
  3. Restoring game state if reconnection succeeds

Security Considerations

Server-side validation is critical:
  • Always verify it’s the correct player’s turn
  • Validate move coordinates are within bounds
  • Check that target cell is empty
  • Authenticate user identity before accepting moves
The client-side code does NOT perform authentication - this must be handled by the server through session tokens or similar mechanisms.

Next Steps

Architecture Overview

Return to high-level architecture documentation

Game Engine

Explore AI algorithms and game state management

Build docs developers (and LLMs) love