Files
hexo/QWEN.md
2026-02-22 11:08:02 +03:00

12 KiB
Raw Permalink Blame History

Project Context: hexo

Project Overview

hexo is an educational game project — a clone of DiceWars.

Game Concept

A strategy dice game played on a hexagonal grid where players command armies of dice and battle to conquer territories. The last player standing wins.

Features

  • 2-4 Players: Support for multiple human and/or AI players
  • Dynamic Map Sizes: 10×10, 15×15, 20×20, 25×25 hexagonal grids
  • AI Bots: Computer-controlled players with smart move selection and thinking delay
    • Makes multiple moves per turn until no valid moves remain
    • Prioritizes favorable attacks, then expansion, then reinforcement
    • 1000ms thinking delay between moves for natural gameplay
  • Hexagonal Grid: Proper axial coordinate system with 6-direction adjacency
  • Dice Combat: Roll-based battle system with strength calculation
  • Solid Territory Supply: Supply = size of largest connected territory
  • Flexible Setup: Any combination of human/AI players (hotseat, full AI, or mixed)
  • 88 Passing Tests: Comprehensive test coverage for all game mechanics

Directory Structure

hexo/
├── README.md           # Game rules and documentation (Russian)
├── QWEN.md             # This file - project context
├── package.json        # NPM configuration
├── server.js           # Simple HTTP server for development
├── .gitignore          # Git ignore rules
├── jsdom-pkg/          # Local jsdom library copy
├── public/             # All application files (no src/ directory)
│   ├── index.html      # Main HTML page with start screen and game UI
│   ├── styles.css      # Game UI styles
│   ├── game.js         # Main game logic and canvas rendering (GameUI class)
│   ├── map.js          # HexMap module (map generation, cells, supply)
│   └── ai-bot.js       # AI bot player logic (AIBot class)
└── test/               # Unit tests (Node.js built-in test runner)
    ├── map.test.js     # Map and cell tests
    └── ai-bot.test.js  # AI bot tests

Technology Stack

Layer Technology
Runtime Node.js
Frontend Vanilla JavaScript (ES Modules), HTML5 Canvas, CSS3
Backend Simple HTTP server (server.js)
Testing Node.js built-in test runner (node --test)
Dependencies jsdom (local copy in jsdom-pkg/)

Building and Running

# Start web server (http://localhost:8080)
npm run serve

# Run console demo
npm start

# Run tests (88 tests)
npm test

Development Conventions

  • ES Modules for browser code (import/export)
  • CommonJS for Node.js code (require/module.exports)
  • Dual exports in map.js for both ES and CommonJS compatibility
  • Tests use Node.js built-in node:test module

Core Game Mechanics

Map System

The map system supports dynamic sizes (10×10, 15×15, 20×20, 25×25):

// Create map with custom size
const map = new HexMap(15);  // 15x15 grid

// Default size is 20x20
const defaultMap = new HexMap();

Key characteristics:

  • Hexagonal grid using axial coordinates (q, r)
  • Each cell can be passable or blocked/impassable (~15% blocked)
  • Each cell can hold up to 8 dice
  • Cells connect to 6 neighbors (hexagonal adjacency)
  • Neighbor calculation respects map boundaries

Dice System

  • Standard 6-sided dice
  • Unit strength calculation formula:
    F = (cnt - 1) × 6 + current_dice
    
    Where:
    • cnt = number of dice on the cell
    • current_dice = top die value (1-6)

Examples:

Dice Array Calculation Strength
[4] (1-1)×6 + 4 4
[6] (1-1)×6 + 6 6
[6, 1] (2-1)×6 + 1 7
[6, 6, 2] (3-1)×6 + 2 14
[6, 6, 6, 6, 6, 6, 6, 6] (8-1)×6 + 6 48 (max)

Game Rules

  1. Setup: Each player starts with strength 8 at their starting position
  2. Movement: Can move if strength > 1
    • Source cell left with 1, target receives strength-1
  3. Combat: Both sides roll dice (1 to their strength)
    • Attacker wins: Takes cell with attack_roll - 1, source becomes 1
    • Defender wins: Attacker reduced to 1, defender keeps defense_roll - attack_roll (min 1)
  4. Supply: After turn ends, player receives supply = largest connected territory size
    • Distributed 1 by 1 to random non-max cells
    • Max per cell: 48 (8 dice × 6)
  5. Victory: Last player with cells on the map wins

Player Configuration

Player Color HEX Code Starting Position
P1 🟢 Green #4ecca3 (offset, offset)
P2 🔴 Red #e94560 (size-1-offset, size-1-offset)
P3 🟡 Yellow #f9ed69 (offset, size-1-offset)
P4 🔵 Cyan #00adb5 (size-1-offset, offset)

Where offset = max(1, floor(mapSize / 10))

Each player starts with strength 8 at their starting position.

Starting Positions by Map Size

Map Size Offset P1 P2 P3 P4
10×10 1 (1,1) (8,8) (1,8) (8,1)
15×15 1 (1,1) (13,13) (1,13) (13,1)
20×20 2 (2,2) (17,17) (2,17) (17,2)
25×25 2 (2,2) (22,22) (2,22) (22,2)

AI Bot Logic

Implementation Details (ai-bot.js)

The AI bot is implemented in the AIBot class with the following behavior:

Multiple Moves Per Turn

Unlike a simple single-move AI, this bot makes multiple moves per turn:

async playTurn() {
  let moveCount = 0;
  const maxMovesPerTurn = 50;  // Prevent infinite loops
  const maxConsecutiveNoMoves = 3;

  while (consecutiveNoMoves < maxConsecutiveNoMoves && moveCount < maxMovesPerTurn) {
    const moves = this.findPossibleMoves(playerCells);
    
    if (moves.length === 0) {
      consecutiveNoMoves++;
      continue;
    }

    consecutiveNoMoves = 0;
    moves.sort((a, b) => this.movePriority(b) - this.movePriority(a));
    
    await this.wait(this.thinkingTime);  // 1000ms delay
    
    this.executeMove(moves[0]);
    moveCount++;
  }

  this.gameUI.endTurn();
}

Thinking Delay

  • Delay: 1000ms between moves
  • Purpose: Creates natural gameplay feel, allows human players to follow AI actions

Move Evaluation

The AI evaluates all possible moves using a priority system:

movePriority(move) {
  let priority = 0;

  // Attack weak enemies (highest priority)
  if (move.type === 'attack') {
    if (move.attackStrength > move.defenseStrength) {
      priority += 100; // Likely to win
      priority += move.attackStrength - move.defenseStrength;
    } else {
      priority -= 50; // Risky attack
    }
  }

  // Expand to empty cells (medium priority)
  if (move.type === 'expand') {
    priority += 50;
    priority += move.attackStrength; // Stronger placement = better
  }

  // Prefer moves that create strong positions
  priority += move.attackStrength * 0.5;

  return priority;
}

Move Types and Priorities

Type Condition Priority Description
Attack (favorable) attack > defense 100+ Attack enemy with higher strength
Expand empty cell 50+ Capture empty cells
Attack (risky) attack ≤ defense -50 Attack enemy with equal/higher strength

Turn Flow

  1. Get all player cells
  2. Find all possible moves (neighbors that are not own cells)
  3. Sort moves by priority
  4. Wait for thinking delay (1000ms)
  5. Execute best move
  6. Repeat until no moves available (max 50 moves per turn)
  7. End turn

Integration with Game UI

The AI bot integrates with the main game through:

  • gameUI.selectedCell / gameUI.currentTarget: Set before executing moves
  • gameUI.executeAttack(): Called to perform the actual attack
  • gameUI.endTurn(): Called when AI has no more moves
  • gameUI.isAIThinking: Flag to prevent user interaction during AI turn

Key Implementation Areas

1. Map Generator (map.js)

class HexMap {
  constructor(size = 20) {
    this.size = size;
    this.cells = new Map();  // Key: "q,r", Value: HexCell
    this.generate();
  }

  generate() {
    for (let q = 0; q < this.size; q++) {
      for (let r = 0; r < this.size; r++) {
        const type = Math.random() < 0.15 ? CELL_TYPES.BLOCKED : CELL_TYPES.EMPTY;
        this.cells.set(`${q},${r}`, new HexCell(q, r, type));
      }
    }
  }
}

Features:

  • Hexagonal grid creation with passable/impassable cells
  • Cell ownership tracking
  • Neighbor calculation (6 directions)
  • Supply calculation (largest connected territory via BFS)

2. Dice Engine (game.js)

// Strength calculation
getStrength() {
  if (this.dice.length === 0) return 0;
  const cnt = this.dice.length;
  const currentDice = this.dice[this.dice.length - 1];
  return (cnt - 1) * 6 + currentDice;
}

// Set strength (reconstruct dice array)
setStrength(targetStrength) {
  if (targetStrength <= 0) {
    this.dice = [];
    return;
  }
  const cnt = Math.floor((targetStrength - 1) / 6) + 1;
  const remainder = targetStrength - (cnt - 1) * 6;
  this.dice = new Array(cnt - 1).fill(6);
  if (remainder > 0) this.dice.push(remainder);
}

3. Combat System (game.js)

executeAttack() {
  const attackStrength = attacker.getStrength() - 1;
  const defenseStrength = defender.getStrength();

  const attackRoll = Math.floor(Math.random() * attackStrength) + 1;
  const defenseRoll = Math.floor(Math.random() * defenseStrength) + 1;

  if (attackRoll > defenseRoll) {
    // Attacker wins
    defender.setStrength(attackRoll - 1);
    this.map.setOwner(defender.q, defender.r, this.currentPlayer);
  } else {
    // Defender wins
    defender.setStrength(Math.max(1, defenseRoll - attackRoll));
  }
  attacker.setStrength(1);
}

4. Game State (game.js)

  • Player turns management: Circular turn order (P1 → P2 → P3 → P4 → P1)
  • Unit positions tracking: Via HexMap cell ownership
  • Victory conditions: Last player standing (checked when player loses all cells)
  • AI turn handling: Async/await pattern for sequential AI moves

5. UI/Rendering (game.js)

class GameUI {
  constructor() {
    this.canvas = document.getElementById('game-canvas');
    this.ctx = this.canvas.getContext('2d');
    this.mapSize = 20;  // Dynamic, configurable
    this.playerCount = 2;  // 2-4 players
    this.playerTypes = {};  // {1: 'human', 2: 'ai', ...}
  }

  // Hex to pixel conversion for rendering
  hexToPixel(q, r) {
    const sqrt3 = Math.sqrt(3);
    const x = this.offsetX + HEX_SIZE * sqrt3 * (q + r/2);
    const y = this.offsetY + HEX_SIZE * 1.5 * r;
    return { x, y };
  }
}

Features:

  • HTML5 Canvas rendering
  • Hexagon drawing with proper coordinates
  • Player color indicators
  • Dice visualization (strength + die count dots)
  • Battle log

6. AI Bot (ai-bot.js)

Key features:

  • Move evaluation and prioritization
  • Thinking delay for natural gameplay
  • Multiple moves per turn
  • Integration with game UI for move execution
  • Boundary and blocked cell awareness

Test Coverage

The project has 88 passing tests covering:

Map Tests (map.test.js)

Category Tests
Dynamic Map Sizes 7 tests (10×10, 15×15, 20×20, 25×25)
HexCell 6 tests (creation, strength, max strength, dice, passability)
HexMap 9 tests (generation, neighbors, ownership, supply)
Target Selection 5 tests (attack enemies, capture empty, block own cells)

AI Bot Tests (ai-bot.test.js)

Category Tests
Instantiation 3 tests
findPossibleMoves 8 tests
movePriority 6 tests
AI prefers attacking weak enemies 3 tests
AI does not select moves with strength ≤ 1 4 tests
AI respects map boundaries 4 tests
executeMove 2 tests
playTurn 2 tests
Integration tests 3 tests
Four AI bots stress test 1 test
Different map sizes 8 tests

Running Tests

npm test

# Output:
# ✔ tests 88
# ✔ suites 27
# ✔ pass 88
# ✔ fail 0

Notes

  • The .gitignore file appears to be a Python template and may need to be updated for a JavaScript project
  • The jsdom-pkg directory contains a local copy of jsdom, possibly for offline development or custom modifications
  • Game rules are documented in Russian in README.md
  • All game logic runs in the browser (no server-side game state)
  • The project uses no src/ directory — all source files are in public/