🐝 Shoehive Quick Start Guide

Welcome to Shoehive, the flexible WebSocket-based multiplayer game framework. This guide will help you set up a basic game server and understand the core concepts of Shoehive.

Table of Contents

  1. Installation
  2. Basic Server Setup
  3. Core Concepts Overview
  4. Event System
  5. Creating a Simple Game
  6. Card Game Functionality
  7. Player Authentication
  8. Financial Operations
  9. Advanced Patterns
  10. Debugging and Monitoring
  11. Additional Resources

Installation

Install Shoehive using npm:

npm install shoehive

Basic Server Setup

Here’s a minimal example to set up a Shoehive game server:

import * as http from 'http';
import { createGameServer } from 'shoehive';

// Create an HTTP server
const server = http.createServer();

// Create the game server
const gameServer = createGameServer({ server });

// Start the server
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`Game server running on port ${PORT}`);
});

The createGameServer function returns several important objects that you’ll use to build your game:

const {
  eventBus,          // Central event system for communication between components
  messageRouter,     // Handles incoming client messages and routes them to handlers
  tableFactory,      // Creates tables with specific configurations
  gameManager,       // Manages game definitions and tables
  lobby,             // Manages table creation and lobby state broadcasts
  wsManager,         // Manages WebSocket connections and players
  transport          // Handles authentication and financial operations
} = gameServer;

Core Concepts Overview

Players

Players represent connected clients in your game. Each player:

  • Has a unique ID
  • Can join tables
  • Can sit at seats within tables
  • Can have custom attributes for game-specific data
// Get a player
const player = gameServer.wsManager.getPlayer({ playerId: 'player-id' });

// Set custom attributes
player.setAttribute({ key: 'score', value: 100 });
player.setAttribute({ key: 'avatar', value: 'https://example.com/avatar.png' });

// Send messages to a specific player
player.sendMessage({
  message: {
    type: 'gameUpdate',
    data: { message: 'Your turn!' }
  }
});

// Get player attributes
const score = player.getAttribute({ key: 'score' }); // 100

Tables

Tables group players together and represent a single game instance:

// Create a table for a specific game
const table = lobby.createTable({ 
  gameId: 'tic-tac-toe',
  options: { /* Optional table configuration */ }
});

// Add a player to the table
table.addPlayer({ player });

// Seat a player at a specific position
table.sitPlayerAtSeat({ playerId: player.id, seatIndex: 0 });

// Set the table state 
table.setState({ state: TableState.ACTIVE }); // States: WAITING, ACTIVE, ENDED

// Use table attributes for game-specific data
table.setAttribute({ key: 'roundNumber', value: 1 });
table.setAttribute({ key: 'currentPlayerIndex', value: 0 });
table.setAttribute({ key: 'pot', value: 100 });

// Broadcast a message to all players at the table
table.broadcastMessage({
  message: {
    type: 'gameUpdate',
    message: 'Round 1 started'
  }
});

Lobby

The Lobby manages available games and tables, handling the creation of new tables and broadcasting lobby updates:

// Create a table for a game
const table = lobby.createTable({ gameId: 'tic-tac-toe' });

// Force a lobby state update broadcast
lobby.updateLobbyState();

Message Router

The MessageRouter processes incoming messages from clients and routes them to appropriate handlers:

// Register a command handler for "makeMove" messages
messageRouter.registerCommandHandler({
  action: 'makeMove', 
  handler: (player, data) => {
    const { row, col } = data;
    const table = player.getTable();
    
    if (!table) {
      player.sendMessage({
        message: {
          type: 'error',
          message: 'You are not at a table'
        }
      });
      return;
    }
    
    // Notify all players of the move
    table.broadcastMessage({
      message: {
        type: 'moveUpdate',
        playerId: player.id,
        row,
        col
      }
    });
  }
});

Event System

Shoehive uses an event-driven architecture. Important: Event listeners receive a single options object as their only argument.

Using Event Constants

import { PLAYER_EVENTS, TABLE_EVENTS, GAME_EVENTS } from 'shoehive';

// Listen for a player joining a table
eventBus.on({ 
  event: TABLE_EVENTS.PLAYER_JOINED, 
  listener: ({ player, table }) => {
    console.log(`Player ${player.id} joined table ${table.id}`);
  }
});

// Listen for game events
eventBus.on({ 
  event: GAME_EVENTS.STARTED, 
  listener: ({ table }) => {
    console.log(`Game started on table ${table.id}`);
  }
});

// Emit an event (EventBus.emit takes event as first arg, and a payload object as second)
eventBus.emit(GAME_EVENTS.ROUND_STARTED, { table, roundIndex: 1 });

Creating Game-Specific Events

// Define chess-specific events
export const CHESS_EVENTS = {
  PIECE_MOVED: "chess:piece:moved",
  CHECK: "chess:check"
} as const;

// Use these constants
eventBus.on({ 
  event: CHESS_EVENTS.PIECE_MOVED, 
  listener: ({ table, player, move }) => {
    console.log(`Player ${player.id} moved ${move.piece}`);
  }
});

eventBus.emit(CHESS_EVENTS.PIECE_MOVED, {
  table, 
  player, 
  move: { piece: 'pawn', from: 'e2', to: 'e4' }
});

Card Game Functionality

// Draw a card (visible by default)
const card = table.drawCard();

// Draw a hidden card
const hiddenCard = table.drawCard({ isVisible: false });

// Deal a card to a player's main hand
table.dealCardToSeat({ seatIndex });

// Deal a hidden card
table.dealCardToSeat({ seatIndex, isVisible: false });

// Add a new hand to a seat
table.addHandToSeat({ seatIndex, handId: 'split' });

Player Authentication

class JwtAuthModule implements AuthModule {
  async authenticatePlayer({ request }: { request: http.IncomingMessage }): Promise<string | null> {
    // Implementation details...
  }
}

Financial Operations

class DatabaseServerTransportModule implements ServerTransportModule {
  async getPlayerBalance({ player }: { player: Player }): Promise<number> {
    // ...
  }
  
  async createBet({ player, amount, metadata }: { player: Player, amount: number, metadata?: any }): Promise<string> {
    // ...
  }
}

Debugging and Monitoring

gameServer.eventBus.debugMonitor({
  enabled: true,
  filter: (eventName) => eventName.startsWith('poker:'),
  logger: (event, payload) => {
    console.log(`[POKER EVENT] ${event}`, payload);
  }
});