Update documentation for all recent features
This commit is contained in:
299
QWEN.md
299
QWEN.md
@@ -6,16 +6,21 @@
|
|||||||
|
|
||||||
### Game Concept
|
### 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
|
### Features
|
||||||
|
|
||||||
- **2-4 Players**: Support for multiple human and/or AI players
|
- **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
|
- **AI Bots**: Computer-controlled players with smart move selection and thinking delay
|
||||||
- **Hexagonal Grid**: 20×20 map with proper adjacency
|
- Makes multiple moves per turn until no valid moves remain
|
||||||
- **Dice Combat**: Roll-based battle system
|
- 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
|
- **Solid Territory Supply**: Supply = size of largest connected territory
|
||||||
- **Flexible Setup**: Any combination of human/AI players (hotseat, full AI, or mixed)
|
- **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
|
## Directory Structure
|
||||||
|
|
||||||
@@ -27,23 +32,26 @@ hexo/
|
|||||||
├── server.js # Simple HTTP server for development
|
├── server.js # Simple HTTP server for development
|
||||||
├── .gitignore # Git ignore rules
|
├── .gitignore # Git ignore rules
|
||||||
├── jsdom-pkg/ # Local jsdom library copy
|
├── 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
|
│ ├── index.html # Main HTML page with start screen and game UI
|
||||||
│ ├── styles.css # Game UI styles
|
│ ├── 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)
|
│ ├── map.js # HexMap module (map generation, cells, supply)
|
||||||
│ └── ai-bot.js # AI bot player logic
|
│ └── ai-bot.js # AI bot player logic (AIBot class)
|
||||||
└── test/ # Unit tests
|
└── test/ # Unit tests (Node.js built-in test runner)
|
||||||
├── map.test.js # Map and cell tests
|
├── map.test.js # Map and cell tests
|
||||||
└── ai-bot.test.js # AI bot tests
|
└── ai-bot.test.js # AI bot tests
|
||||||
```
|
```
|
||||||
|
|
||||||
## Technology Stack
|
## Technology Stack
|
||||||
|
|
||||||
- **Runtime**: Node.js
|
| Layer | Technology |
|
||||||
- **Frontend**: Vanilla JavaScript (ES Modules), HTML5 Canvas, CSS3
|
|-------|------------|
|
||||||
- **Backend**: Simple HTTP server (server.js)
|
| **Runtime** | Node.js |
|
||||||
- **Testing**: Node.js built-in test runner (`node --test`)
|
| **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
|
## Building and Running
|
||||||
|
|
||||||
@@ -54,41 +62,61 @@ npm run serve
|
|||||||
# Run console demo
|
# Run console demo
|
||||||
npm start
|
npm start
|
||||||
|
|
||||||
# Run tests
|
# Run tests (88 tests)
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development Conventions
|
## Development Conventions
|
||||||
|
|
||||||
- ES Modules for browser code (`import`/`export`)
|
- **ES Modules** for browser code (`import`/`export`)
|
||||||
- CommonJS for Node.js code (`require`/`module.exports`)
|
- **CommonJS** for Node.js code (`require`/`module.exports`)
|
||||||
- Map module exports both ES and CommonJS for compatibility
|
- **Dual exports** in `map.js` for both ES and CommonJS compatibility
|
||||||
- Tests use Node.js built-in `node:test` module
|
- **Tests** use Node.js built-in `node:test` module
|
||||||
|
|
||||||
## Core Game Mechanics
|
## Core Game Mechanics
|
||||||
|
|
||||||
### Map System
|
### Map System
|
||||||
|
|
||||||
- Generatable hexagonal grid map (20×20 cells)
|
The map system supports **dynamic sizes** (10×10, 15×15, 20×20, 25×25):
|
||||||
- Each cell can be passable or blocked/impassable
|
|
||||||
- Each field can hold up to 8 dice
|
```javascript
|
||||||
- Cells are connected to 6 neighbors (hexagonal adjacency)
|
// 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
|
### Dice System
|
||||||
|
|
||||||
- Standard 6-sided dice
|
- 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:
|
Where:
|
||||||
- `cnt` = number of dice on the field
|
- `cnt` = number of dice on the cell
|
||||||
- `full_dice` = maximum die value (6)
|
- `current_dice` = top die value (1-6)
|
||||||
- `current_dice` = top die current 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
|
### 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
|
2. **Movement**: Can move if strength > 1
|
||||||
- Source cell left with 1, target receives strength-1
|
- Source cell left with 1, target receives strength-1
|
||||||
3. **Combat**: Both sides roll dice (1 to their strength)
|
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
|
4. **Supply**: After turn ends, player receives supply = largest connected territory size
|
||||||
- Distributed 1 by 1 to random non-max cells
|
- Distributed 1 by 1 to random non-max cells
|
||||||
- Max per cell: 48 (8 dice × 6)
|
- Max per cell: 48 (8 dice × 6)
|
||||||
|
5. **Victory**: Last player with cells on the map wins
|
||||||
|
|
||||||
### Player Configuration
|
### Player Configuration
|
||||||
|
|
||||||
| Player | Color | HEX Code |
|
| Player | Color | HEX Code | Starting Position |
|
||||||
|--------|-------|----------|
|
|--------|-------|----------|-------------------|
|
||||||
| P1 | Green | `#4ecca3` |
|
| P1 | 🟢 Green | `#4ecca3` | (offset, offset) |
|
||||||
| P2 | Red | `#e94560` |
|
| P2 | 🔴 Red | `#e94560` | (size-1-offset, size-1-offset) |
|
||||||
| P3 | Yellow | `#f9ed69` |
|
| P3 | 🟡 Yellow | `#f9ed69` | (offset, size-1-offset) |
|
||||||
| P4 | Cyan | `#a8e6cf` |
|
| 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:
|
Each player starts with **strength 8** at their starting position.
|
||||||
- **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.
|
### 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
|
## 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:
|
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
|
#### Thinking Delay
|
||||||
|
|
||||||
- **Delay**: 1000ms between moves
|
- **Delay**: 1000ms between moves
|
||||||
@@ -159,13 +222,13 @@ movePriority(move) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Move Types
|
#### Move Types and Priorities
|
||||||
|
|
||||||
| Type | Priority | Description |
|
| Type | Condition | Priority | Description |
|
||||||
|------|----------|-------------|
|
|------|-----------|----------|-------------|
|
||||||
| **Attack (favorable)** | 100+ | Attack enemy with higher strength |
|
| **Attack (favorable)** | attack > defense | 100+ | Attack enemy with higher strength |
|
||||||
| **Expand** | 50+ | Capture empty cells |
|
| **Expand** | empty cell | 50+ | Capture empty cells |
|
||||||
| **Attack (risky)** | -50 | Attack enemy with equal/higher strength |
|
| **Attack (risky)** | attack ≤ defense | -50 | Attack enemy with equal/higher strength |
|
||||||
|
|
||||||
#### Turn Flow
|
#### Turn Flow
|
||||||
|
|
||||||
@@ -174,7 +237,8 @@ movePriority(move) {
|
|||||||
3. Sort moves by priority
|
3. Sort moves by priority
|
||||||
4. Wait for thinking delay (1000ms)
|
4. Wait for thinking delay (1000ms)
|
||||||
5. Execute best move
|
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
|
### Integration with Game UI
|
||||||
|
|
||||||
@@ -189,42 +253,162 @@ The AI bot integrates with the main game through:
|
|||||||
|
|
||||||
### 1. Map Generator (`map.js`)
|
### 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
|
- Hexagonal grid creation with passable/impassable cells
|
||||||
- Cell ownership tracking
|
- Cell ownership tracking
|
||||||
- Neighbor calculation (6 directions)
|
- Neighbor calculation (6 directions)
|
||||||
- Supply calculation (largest connected territory)
|
- Supply calculation (largest connected territory via BFS)
|
||||||
|
|
||||||
### 2. Dice Engine (`game.js`)
|
### 2. Dice Engine (`game.js`)
|
||||||
|
|
||||||
- Randomization for combat rolls
|
```javascript
|
||||||
- Strength calculation
|
// Strength calculation
|
||||||
- Dice distribution during supply phase
|
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`)
|
### 3. Combat System (`game.js`)
|
||||||
|
|
||||||
- Attack/defense resolution logic
|
```javascript
|
||||||
- Victory/defeat outcomes
|
executeAttack() {
|
||||||
- Cell ownership transfer
|
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`)
|
### 4. Game State (`game.js`)
|
||||||
|
|
||||||
- Player turns management
|
- **Player turns management**: Circular turn order (P1 → P2 → P3 → P4 → P1)
|
||||||
- Unit positions tracking
|
- **Unit positions tracking**: Via `HexMap` cell ownership
|
||||||
- Victory conditions (last player standing)
|
- **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`)
|
### 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
|
- HTML5 Canvas rendering
|
||||||
- Hexagon drawing with proper coordinates
|
- Hexagon drawing with proper coordinates
|
||||||
- Player color indicators
|
- Player color indicators
|
||||||
- Dice visualization
|
- Dice visualization (strength + die count dots)
|
||||||
- Battle log
|
- Battle log
|
||||||
|
|
||||||
### 6. AI Bot (`ai-bot.js`)
|
### 6. AI Bot (`ai-bot.js`)
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
- Move evaluation and prioritization
|
- Move evaluation and prioritization
|
||||||
- Thinking delay for natural gameplay
|
- Thinking delay for natural gameplay
|
||||||
|
- Multiple moves per turn
|
||||||
- Integration with game UI for move execution
|
- 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
|
## 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
|
- 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
|
- Game rules are documented in Russian in README.md
|
||||||
- All game logic runs in the browser (no server-side game state)
|
- 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
|
```bash
|
||||||
npm run serve # Запустить веб-сервер (http://localhost:8080)
|
npm run serve # Запустить веб-сервер (http://localhost:8080)
|
||||||
npm test # Запустить тесты
|
npm test # Запустить тесты (88 тестов)
|
||||||
npm start # Консольная версия карты
|
npm start # Консольная версия карты
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -15,10 +15,15 @@ npm start # Консольная версия карты
|
|||||||
При запуске игры открывается экран настройки:
|
При запуске игры открывается экран настройки:
|
||||||
|
|
||||||
1. **Выберите количество игроков**: 2, 3 или 4
|
1. **Выберите количество игроков**: 2, 3 или 4
|
||||||
2. **Настройте тип каждого игрока**:
|
2. **Выберите размер карты**:
|
||||||
|
- **Small (10×10)** — Быстрая игра на маленькой карте
|
||||||
|
- **Medium (15×15)** — Сбалансированный размер
|
||||||
|
- **Large (20×20)** — Стандартный размер (по умолчанию)
|
||||||
|
- **Extra Large (25×25)** — Большая карта для длительных игр
|
||||||
|
3. **Настройте тип каждого игрока**:
|
||||||
- **Human** — управление человеком (клики мышью)
|
- **Human** — управление человеком (клики мышью)
|
||||||
- **AI Bot** — управление компьютером
|
- **AI Bot** — управление компьютером
|
||||||
3. Нажмите **Start Game** для начала игры
|
4. Нажмите **Start Game** для начала игры
|
||||||
|
|
||||||
### Комбинации игроков
|
### Комбинации игроков
|
||||||
|
|
||||||
@@ -34,7 +39,7 @@ npm start # Консольная версия карты
|
|||||||
| P1 | 🟢 Зелёный | `#4ecca3` |
|
| P1 | 🟢 Зелёный | `#4ecca3` |
|
||||||
| P2 | 🔴 Красный | `#e94560` |
|
| P2 | 🔴 Красный | `#e94560` |
|
||||||
| P3 | 🟡 Жёлтый | `#f9ed69` |
|
| P3 | 🟡 Жёлтый | `#f9ed69` |
|
||||||
| P4 | 🔵 Бирюзовый | `#a8e6cf` |
|
| P4 | 🔵 Бирюзовый | `#00adb5` |
|
||||||
|
|
||||||
## AI Bot
|
## AI Bot
|
||||||
|
|
||||||
@@ -44,10 +49,11 @@ AI бот автоматически играет за выбранного иг
|
|||||||
|
|
||||||
1. **Анализ поля** — бот оценивает все возможные ходы
|
1. **Анализ поля** — бот оценивает все возможные ходы
|
||||||
2. **Приоритеты ходов**:
|
2. **Приоритеты ходов**:
|
||||||
- 🎯 Атака слабого противника (высокий шанс победы)
|
- 🎯 Атака слабого противника (высокий шанс победы) — приоритет 100+
|
||||||
- 📈 Захват пустых клеток (расширение территории)
|
- 📈 Захват пустых клеток (расширение территории) — приоритет 50+
|
||||||
- 💪 Укрепление позиций (перемещение к сильным клеткам)
|
- 💪 Укрепление позиций (перемещение к сильным клеткам)
|
||||||
3. **Задержка мышления** — 1000 мс перед каждым ходом для естественности геймплея
|
3. **Задержка мышления** — 1000 мс перед каждым ходом для естественности геймплея
|
||||||
|
4. **Несколько ходов за turn** — AI делает все возможные ходы подряд, затем завершает ход
|
||||||
|
|
||||||
### Индикаторы AI
|
### Индикаторы AI
|
||||||
|
|
||||||
@@ -55,14 +61,43 @@ AI бот автоматически играет за выбранного иг
|
|||||||
- Во время хода AI в панели действий показано: *"AI is thinking..."*
|
- Во время хода AI в панели действий показано: *"AI is thinking..."*
|
||||||
- Кнопки управления отключены во время хода AI
|
- Кнопки управления отключены во время хода 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. Карта
|
### 1. Карта
|
||||||
|
|
||||||
- Гексагональная сетка 20×20 ячеек
|
- Гексагональная сетка **10×10**, **15×15**, **20×20** или **25×25** ячеек
|
||||||
- Каждая ячейка может быть:
|
- Каждая ячейка может быть:
|
||||||
- **Проходима** (пустая или принадлежит игроку)
|
- **Проходима** (пустая или принадлежит игроку)
|
||||||
- **Непроходима** (заблокирована, серый цвет)
|
- **Непроходима** (заблокирована, серый цвет, ~15% карты)
|
||||||
|
|
||||||
### 2. Игровые единицы
|
### 2. Игровые единицы
|
||||||
|
|
||||||
@@ -72,6 +107,15 @@ AI бот автоматически играет за выбранного иг
|
|||||||
- `cnt` — количество кубиков
|
- `cnt` — количество кубиков
|
||||||
- `current_dice` — значение верхнего кубика
|
- `current_dice` — значение верхнего кубика
|
||||||
|
|
||||||
|
**Примеры расчёта силы:**
|
||||||
|
| Кубики | Сила |
|
||||||
|
|--------|------|
|
||||||
|
| [4] | 4 |
|
||||||
|
| [6] | 6 |
|
||||||
|
| [6, 1] | 7 |
|
||||||
|
| [6, 6, 2] | 14 |
|
||||||
|
| [6, 6, 6, 6, 6, 6, 6, 6] | 48 (максимум) |
|
||||||
|
|
||||||
### 3. Ход игры
|
### 3. Ход игры
|
||||||
|
|
||||||
#### Перемещение/Атака
|
#### Перемещение/Атака
|
||||||
@@ -124,5 +168,38 @@ AI бот автоматически играет за выбранного иг
|
|||||||
|
|
||||||
Игра поддерживает:
|
Игра поддерживает:
|
||||||
- **2-4 игрока** (любая комбинация людей и 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