Update documentation for all recent features
This commit is contained in:
299
QWEN.md
299
QWEN.md
@@ -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/`
|
||||
|
||||
93
README.md
93
README.md
@@ -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
|
||||
```
|
||||
|
||||
Все тесты проходят успешно ✅
|
||||
|
||||
Reference in New Issue
Block a user