Update documentation for all recent features

This commit is contained in:
sokol
2026-02-22 11:08:02 +03:00
parent 3439d04a55
commit ed27ca93ab
2 changed files with 327 additions and 65 deletions

299
QWEN.md
View File

@@ -6,16 +6,21 @@
### Game Concept
A strategy dice game played on a hexagonal grid where players command armies of dice and battle to conquer territories.
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
- **Hexagonal Grid**: 20×20 map with proper adjacency
- **Dice Combat**: Roll-based battle system
- 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
@@ -27,23 +32,26 @@ hexo/
├── server.js # Simple HTTP server for development
├── .gitignore # Git ignore rules
├── jsdom-pkg/ # Local jsdom library copy
├── public/ # All application files
├── 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
│ ├── 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
└── test/ # Unit tests
│ └── 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
- **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`)
| 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
@@ -54,41 +62,61 @@ npm run serve
# Run console demo
npm start
# Run tests
# Run tests (88 tests)
npm test
```
## Development Conventions
- ES Modules for browser code (`import`/`export`)
- CommonJS for Node.js code (`require`/`module.exports`)
- Map module exports both ES and CommonJS for compatibility
- Tests use Node.js built-in `node:test` module
- **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
- Generatable hexagonal grid map (20×20 cells)
- Each cell can be passable or blocked/impassable
- Each field can hold up to 8 dice
- Cells are connected to 6 neighbors (hexagonal adjacency)
The map system supports **dynamic sizes** (10×10, 15×15, 20×20, 25×25):
```javascript
// 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:
- **Unit strength calculation formula:**
```
F = (cnt - 1) × full_dice + current_dice
F = (cnt - 1) × 6 + current_dice
```
Where:
- `cnt` = number of dice on the field
- `full_dice` = maximum die value (6)
- `current_dice` = top die current value (1-6)
- `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 dice on their starting position
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)
@@ -97,25 +125,29 @@ npm test
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 |
|--------|-------|----------|
| P1 | Green | `#4ecca3` |
| P2 | Red | `#e94560` |
| P3 | Yellow | `#f9ed69` |
| P4 | Cyan | `#a8e6cf` |
| 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) |
### Starting Positions
Where `offset = max(1, floor(mapSize / 10))`
Players are placed at fixed positions on the map:
- **P1**: Top area (q: 2, r: 2)
- **P2**: Bottom area (q: MAP_SIZE-3, r: MAP_SIZE-3)
- **P3**: Bottom-left (q: 2, r: MAP_SIZE-3)
- **P4**: Top-right (q: MAP_SIZE-3, r: 2)
Each player starts with **strength 8** at their starting position.
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
@@ -123,6 +155,37 @@ Each player starts with strength 8 at their starting position.
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**:
```javascript
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
@@ -159,13 +222,13 @@ movePriority(move) {
}
```
#### Move Types
#### Move Types and Priorities
| Type | Priority | Description |
|------|----------|-------------|
| **Attack (favorable)** | 100+ | Attack enemy with higher strength |
| **Expand** | 50+ | Capture empty cells |
| **Attack (risky)** | -50 | Attack enemy with equal/higher strength |
| 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
@@ -174,7 +237,8 @@ movePriority(move) {
3. Sort moves by priority
4. Wait for thinking delay (1000ms)
5. Execute best move
6. Repeat until no moves available, then end turn
6. Repeat until no moves available (max 50 moves per turn)
7. End turn
### Integration with Game UI
@@ -189,42 +253,162 @@ The AI bot integrates with the main game through:
### 1. Map Generator (`map.js`)
```javascript
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)
- Supply calculation (largest connected territory via BFS)
### 2. Dice Engine (`game.js`)
- Randomization for combat rolls
- Strength calculation
- Dice distribution during supply phase
```javascript
// 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`)
- Attack/defense resolution logic
- Victory/defeat outcomes
- Cell ownership transfer
```javascript
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
- Unit positions tracking
- Victory conditions (last player standing)
- **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`)
```javascript
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
- 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
```bash
npm test
# Output:
# ✔ tests 88
# ✔ suites 27
# ✔ pass 88
# ✔ fail 0
```
## Notes
@@ -232,3 +416,4 @@ The AI bot integrates with the main game through:
- 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/`

View File

@@ -6,7 +6,7 @@
```bash
npm run serve # Запустить веб-сервер (http://localhost:8080)
npm test # Запустить тесты
npm test # Запустить тесты (88 тестов)
npm start # Консольная версия карты
```
@@ -15,10 +15,15 @@ npm start # Консольная версия карты
При запуске игры открывается экран настройки:
1. **Выберите количество игроков**: 2, 3 или 4
2. **Настройте тип каждого игрока**:
2. **Выберите размер карты**:
- **Small (10×10)** — Быстрая игра на маленькой карте
- **Medium (15×15)** — Сбалансированный размер
- **Large (20×20)** — Стандартный размер (по умолчанию)
- **Extra Large (25×25)** — Большая карта для длительных игр
3. **Настройте тип каждого игрока**:
- **Human** — управление человеком (клики мышью)
- **AI Bot** — управление компьютером
3. Нажмите **Start Game** для начала игры
4. Нажмите **Start Game** для начала игры
### Комбинации игроков
@@ -34,7 +39,7 @@ npm start # Консольная версия карты
| P1 | 🟢 Зелёный | `#4ecca3` |
| P2 | 🔴 Красный | `#e94560` |
| P3 | 🟡 Жёлтый | `#f9ed69` |
| P4 | 🔵 Бирюзовый | `#a8e6cf` |
| P4 | 🔵 Бирюзовый | `#00adb5` |
## AI Bot
@@ -44,10 +49,11 @@ AI бот автоматически играет за выбранного иг
1. **Анализ поля** — бот оценивает все возможные ходы
2. **Приоритеты ходов**:
- 🎯 Атака слабого противника (высокий шанс победы)
- 📈 Захват пустых клеток (расширение территории)
- 🎯 Атака слабого противника (высокий шанс победы) — приоритет 100+
- 📈 Захват пустых клеток (расширение территории) — приоритет 50+
- 💪 Укрепление позиций (перемещение к сильным клеткам)
3. **Задержка мышления** — 1000 мс перед каждым ходом для естественности геймплея
4. **Несколько ходов за turn** — AI делает все возможные ходы подряд, затем завершает ход
### Индикаторы AI
@@ -55,14 +61,43 @@ AI бот автоматически играет за выбранного иг
- Во время хода AI в панели действий показано: *"AI is thinking..."*
- Кнопки управления отключены во время хода AI
### Логика принятия решений
```javascript
movePriority(move) {
let priority = 0;
// Атака слабого врага (высший приоритет)
if (move.type === 'attack') {
if (move.attackStrength > move.defenseStrength) {
priority += 100; // Высокий шанс победы
priority += move.attackStrength - move.defenseStrength;
} else {
priority -= 50; // Рискованная атака
}
}
// Расширение на пустые клетки (средний приоритет)
if (move.type === 'expand') {
priority += 50;
priority += move.attackStrength;
}
// Бонус за сильную позицию
priority += move.attackStrength * 0.5;
return priority;
}
```
## Правила игры
### 1. Карта
- Гексагональная сетка 20×20 ячеек
- Гексагональная сетка **10×10**, **15×15**, **20×20** или **25×25** ячеек
- Каждая ячейка может быть:
- **Проходима** (пустая или принадлежит игроку)
- **Непроходима** (заблокирована, серый цвет)
- **Непроходима** (заблокирована, серый цвет, ~15% карты)
### 2. Игровые единицы
@@ -72,6 +107,15 @@ AI бот автоматически играет за выбранного иг
- `cnt` — количество кубиков
- `current_dice` — значение верхнего кубика
**Примеры расчёта силы:**
| Кубики | Сила |
|--------|------|
| [4] | 4 |
| [6] | 6 |
| [6, 1] | 7 |
| [6, 6, 2] | 14 |
| [6, 6, 6, 6, 6, 6, 6, 6] | 48 (максимум) |
### 3. Ход игры
#### Перемещение/Атака
@@ -124,5 +168,38 @@ AI бот автоматически играет за выбранного иг
Игра поддерживает:
- **2-4 игрока** (любая комбинация людей и AI)
- **4 размера карты**: 10×10, 15×15, 20×20, 25×25
- **Случайная генерация карты** при каждом запуске
- **Случайные стартовые позиции** для игроков
## Стартовые позиции
Игроки размещаются в углах карты (позиции масштабируются с размером карты):
| Игрок | Позиция | Описание |
|-------|---------|----------|
| P1 | (offset, offset) | Верхний-левый угол |
| P2 | (size-1-offset, size-1-offset) | Нижний-правый угол |
| P3 | (offset, size-1-offset) | Нижний-левый угол |
| P4 | (size-1-offset, offset) | Верхний-правый угол |
`offset = max(1, floor(size / 10))`
Каждый игрок начинает с **силой 8** на своей стартовой позиции.
## Тестирование
Проект покрыт **88 тестами**, проверяющими:
- **Динамические размеры карт** (10×10, 15×15, 20×20, 25×25)
- **HexCell**: создание, расчёт силы, максимальная сила, добавление кубиков
- **HexMap**: генерация, соседи, снабжение, владение
- **Логика выбора целей**: атака врагов, захват пустых клеток, блокировка своих клеток
- **AIBot**: поиск ходов, приоритеты, выполнение ходов, завершение хода
- **Интеграционные тесты**: 4 AI бота одновременно, разные размеры карт
```bash
npm test
```
Все тесты проходят успешно ✅