From f19e17821737b669d2c49baae855a7b7da0aa4da Mon Sep 17 00:00:00 2001 From: sokol Date: Sat, 21 Feb 2026 17:45:44 +0300 Subject: [PATCH] Remove unused Attack button - attack happens automatically on target click --- public/game.js | 9 +- public/index.html | 5 +- public/map.js | 268 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+), 10 deletions(-) create mode 100644 public/map.js diff --git a/public/game.js b/public/game.js index 95bffe1..21056fc 100644 --- a/public/game.js +++ b/public/game.js @@ -2,7 +2,7 @@ * Hexo Game UI - Canvas Rendering and Interactions */ -import { HexMap, CELL_TYPES } from '../src/map.js'; +import { HexMap, CELL_TYPES } from './map.js'; // Game constants const HEX_SIZE = 18; @@ -107,10 +107,9 @@ class GameUI { setupEventListeners() { this.canvas.addEventListener('click', (e) => this.handleClick(e)); this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e)); - + document.getElementById('end-turn-btn').addEventListener('click', () => this.endTurn()); document.getElementById('new-game-btn').addEventListener('click', () => this.newGame()); - document.getElementById('attack-btn').addEventListener('click', () => this.executeAttack()); document.getElementById('cancel-btn').addEventListener('click', () => this.cancelSelection()); } @@ -524,11 +523,9 @@ class GameUI { } // Update buttons - const attackBtn = document.getElementById('attack-btn'); const cancelBtn = document.getElementById('cancel-btn'); const endTurnBtn = document.getElementById('end-turn-btn'); - - attackBtn.disabled = !this.selectedCell; + cancelBtn.disabled = !this.selectedCell; endTurnBtn.disabled = !this.hasMoved; } diff --git a/public/index.html b/public/index.html index 2ec74b1..2944d2a 100644 --- a/public/index.html +++ b/public/index.html @@ -83,10 +83,7 @@

Actions

Select a cell to move

-
- - -
+
diff --git a/public/map.js b/public/map.js new file mode 100644 index 0000000..2704f91 --- /dev/null +++ b/public/map.js @@ -0,0 +1,268 @@ +/** + * Hexagonal grid map for the DiceWars game. + * + * Map is a 20x20 hexagonal grid where each cell can be: + * - passable (playable) + * - impassable (blocked) + * + * Uses axial coordinates (q, r) for hexagon positioning. + */ + +const MAP_SIZE = 20; +const CELL_TYPES = { + EMPTY: 0, // Passable, unowned + BLOCKED: 1, // Impassable terrain + PLAYER1: 2, // Player 1 owned + PLAYER2: 3, // Player 2 owned +}; + +/** + * Represents a single hex cell on the map + */ +class HexCell { + constructor(q, r, type = CELL_TYPES.EMPTY) { + this.q = q; // Axial coordinate q + this.r = r; // Axial coordinate r + this.type = type; // Cell ownership/type + this.dice = []; // Array of dice values on this cell + } + + /** + * Calculate unit strength: F = (cnt-1)*full_dice + current_dice + */ + getStrength() { + if (this.dice.length === 0) return 0; + const cnt = this.dice.length; + const fullDice = 6; + const currentDice = this.dice[this.dice.length - 1]; // Top die + return (cnt - 1) * fullDice + currentDice; + } + + /** + * Check if cell has maximum strength (8 dice with 6 on top) + */ + isMaxStrength() { + return this.dice.length >= 8 && this.getStrength() >= 48; + } + + /** + * Add a die to this cell + */ + addDie(value) { + if (this.dice.length < 8) { + this.dice.push(value); + return true; + } + return false; + } + + /** + * Remove dice from cell, leaving specified strength + */ + setStrength(targetStrength) { + if (targetStrength <= 0) { + this.dice = []; + return; + } + + const fullDice = 6; + const cnt = Math.floor((targetStrength - 1) / fullDice) + 1; + const remainder = targetStrength - (cnt - 1) * fullDice; + + this.dice = new Array(cnt - 1).fill(6); + if (remainder > 0) { + this.dice.push(remainder); + } + } + + isPassable() { + return this.type !== CELL_TYPES.BLOCKED; + } + + isOwned() { + return this.type === CELL_TYPES.PLAYER1 || this.type === CELL_TYPES.PLAYER2; + } + + getOwner() { + if (this.type === CELL_TYPES.PLAYER1) return 1; + if (this.type === CELL_TYPES.PLAYER2) return 2; + return 0; + } +} + +/** + * Hexagonal map generator and manager + */ +class HexMap { + constructor(size = MAP_SIZE) { + this.size = size; + this.cells = new Map(); // Key: "q,r", Value: HexCell + this.generate(); + } + + /** + * Generate the hexagonal grid + */ + generate() { + this.cells.clear(); + + for (let q = 0; q < this.size; q++) { + for (let r = 0; r < this.size; r++) { + const key = this.getKey(q, r); + const type = Math.random() < 0.15 ? CELL_TYPES.BLOCKED : CELL_TYPES.EMPTY; + this.cells.set(key, new HexCell(q, r, type)); + } + } + } + + /** + * Get cell by axial coordinates + */ + getCell(q, r) { + const key = this.getKey(q, r); + return this.cells.get(key); + } + + /** + * Get cell by key string + */ + getCellByKey(key) { + return this.cells.get(key); + } + + /** + * Generate key from coordinates + */ + getKey(q, r) { + return `${q},${r}`; + } + + /** + * Get all passable cells + */ + getPassableCells() { + return Array.from(this.cells.values()).filter(cell => cell.isPassable()); + } + + /** + * Get all empty (unowned) passable cells + */ + getEmptyCells() { + return Array.from(this.cells.values()).filter( + cell => cell.isPassable() && !cell.isOwned() + ); + } + + /** + * Get all cells owned by a player + */ + getPlayerCells(playerId) { + const targetType = playerId === 1 ? CELL_TYPES.PLAYER1 : CELL_TYPES.PLAYER2; + return Array.from(this.cells.values()).filter( + cell => cell.type === targetType + ); + } + + /** + * Get neighboring cells (6 directions in hex grid) + */ + getNeighbors(q, r) { + const directions = [ + [1, 0], [1, -1], [0, -1], + [-1, 0], [-1, 1], [0, 1] + ]; + + const neighbors = []; + for (const [dq, dr] of directions) { + const nq = q + dq; + const nr = r + dr; + if (nq >= 0 && nq < this.size && nr >= 0 && nr < this.size) { + const cell = this.getCell(nq, nr); + if (cell && cell.isPassable()) { + neighbors.push(cell); + } + } + } + return neighbors; + } + + /** + * Calculate supply for a player: S = sum of all owned cells + */ + calculateSupply(playerId) { + const playerCells = this.getPlayerCells(playerId); + let supply = 0; + + for (const cell of playerCells) { + supply += 1; // Each owned cell gives +1 supply + } + + return supply; + } + + /** + * Render map to console (simplified ASCII representation) + */ + render() { + let output = ''; + + for (let r = 0; r < this.size; r++) { + // Offset every other row for hex appearance + const offset = r % 2 === 0 ? 0 : 2; + output += ' '.repeat(offset); + + for (let q = 0; q < this.size; q++) { + const cell = this.getCell(q, r); + const symbol = this.getCellSymbol(cell); + output += `[${symbol}]`; + } + output += '\n'; + } + + console.log(output); + } + + /** + * Get symbol for cell visualization + */ + getCellSymbol(cell) { + if (cell.type === CELL_TYPES.BLOCKED) return '██'; + if (cell.type === CELL_TYPES.PLAYER1) return 'P1'; + if (cell.type === CELL_TYPES.PLAYER2) return 'P2'; + if (cell.dice.length > 0) return cell.getStrength().toString().padStart(2, ' '); + return ' '; + } + + /** + * Set cell ownership + */ + setOwner(q, r, playerId) { + const cell = this.getCell(q, r); + if (cell && cell.isPassable()) { + cell.type = playerId === 1 ? CELL_TYPES.PLAYER1 : CELL_TYPES.PLAYER2; + return true; + } + return false; + } + + /** + * Clear cell ownership + */ + clearOwner(q, r) { + const cell = this.getCell(q, r); + if (cell) { + cell.type = CELL_TYPES.EMPTY; + cell.dice = []; + return true; + } + return false; + } +} + +// ES Module exports for browser +export { HexMap, HexCell, CELL_TYPES, MAP_SIZE }; + +// CommonJS exports for Node.js +if (typeof module !== 'undefined' && module.exports) { + module.exports = { HexMap, HexCell, CELL_TYPES, MAP_SIZE }; +}