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 };
+}