diff --git a/public/ai-bot.js b/public/ai-bot.js
new file mode 100644
index 0000000..16eba89
--- /dev/null
+++ b/public/ai-bot.js
@@ -0,0 +1,121 @@
+/**
+ * AI Bot for Hexo game
+ * Controls computer-controlled players
+ */
+
+import { CELL_TYPES } from './map.js';
+
+export class AIBot {
+ constructor(playerId, map, gameUI) {
+ this.playerId = playerId;
+ this.map = map;
+ this.gameUI = gameUI;
+ this.thinkingTime = 1000; // ms delay between moves
+ }
+
+ /**
+ * Execute AI turn
+ */
+ async playTurn() {
+ // Get all player cells
+ const playerCells = this.map.getPlayerCells(this.playerId);
+
+ if (playerCells.length === 0) return;
+
+ // Find all possible moves
+ const moves = this.findPossibleMoves(playerCells);
+
+ if (moves.length === 0) {
+ // No moves available, end turn
+ this.gameUI.endTurn();
+ return;
+ }
+
+ // Sort moves by priority (attack > expand > reinforce)
+ moves.sort((a, b) => this.movePriority(b) - this.movePriority(a));
+
+ // Execute best move
+ const bestMove = moves[0];
+
+ // Wait for thinking time
+ await this.wait(this.thinkingTime);
+
+ // Execute the move
+ this.executeMove(bestMove);
+ }
+
+ /**
+ * Find all possible moves for AI
+ */
+ findPossibleMoves(playerCells) {
+ const moves = [];
+
+ for (const cell of playerCells) {
+ if (cell.getStrength() <= 1) continue;
+
+ const neighbors = this.map.getNeighbors(cell.q, cell.r);
+
+ for (const neighbor of neighbors) {
+ // Skip own cells
+ if (neighbor.getOwner() === this.playerId) continue;
+
+ const attackStrength = cell.getStrength() - 1;
+ const defenseStrength = neighbor.getStrength();
+
+ moves.push({
+ from: cell,
+ to: neighbor,
+ attackStrength,
+ defenseStrength,
+ type: neighbor.type === CELL_TYPES.EMPTY ? 'expand' : 'attack'
+ });
+ }
+ }
+
+ return moves;
+ }
+
+ /**
+ * Calculate move priority (higher = better)
+ */
+ movePriority(move) {
+ let priority = 0;
+
+ // Prefer attacks on weak enemies
+ if (move.type === 'attack') {
+ if (move.attackStrength > move.defenseStrength) {
+ priority += 100; // Likely to win
+ priority += move.attackStrength - move.defenseStrength;
+ } else {
+ priority -= 50; // Risky attack
+ }
+ }
+
+ // Prefer expanding to empty cells
+ if (move.type === 'expand') {
+ priority += 50;
+ priority += move.attackStrength; // Stronger placement = better
+ }
+
+ // Prefer moves that create strong positions
+ priority += move.attackStrength * 0.5;
+
+ return priority;
+ }
+
+ /**
+ * Execute a move
+ */
+ executeMove(move) {
+ this.gameUI.selectedCell = move.from;
+ this.gameUI.currentTarget = move.to;
+ this.gameUI.executeAttack();
+ }
+
+ /**
+ * Wait for specified time
+ */
+ wait(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+}
diff --git a/public/game.js b/public/game.js
index f574e99..d0c9b78 100644
--- a/public/game.js
+++ b/public/game.js
@@ -1,20 +1,32 @@
/**
* Hexo Game UI - Canvas Rendering and Interactions
+ * Supports 2-4 players with AI bots
*/
import { HexMap, CELL_TYPES } from './map.js';
+import { AIBot } from './ai-bot.js';
// Game constants
const HEX_SIZE = 18;
const MAP_SIZE = 20;
const ANIMATION_DURATION = 300;
+// Player colors
+const PLAYER_COLORS = {
+ 1: '#4ecca3',
+ 2: '#e94560',
+ 3: '#f9ed69',
+ 4: '#a8e6cf'
+};
+
// Colors
const COLORS = {
blocked: '#2a2a4a',
empty: '#3a5a6a',
player1: '#4ecca3',
player2: '#e94560',
+ player3: '#f9ed69',
+ player4: '#a8e6cf',
highlight: 'rgba(255, 255, 255, 0.3)',
selected: 'rgba(233, 69, 96, 0.6)',
target: 'rgba(78, 204, 163, 0.5)',
@@ -36,8 +48,12 @@ class GameUI {
this.selectedCell = null;
this.currentTarget = null;
this.currentPlayer = 1;
- this.gamePhase = 'movement'; // movement, supply, gameover
+ this.playerCount = 2;
+ this.playerTypes = {}; // 1: 'human', 2: 'ai', etc.
+ this.aiBots = {};
+ this.gamePhase = 'movement';
this.hasMoved = false;
+ this.isAIThinking = false;
this.offsetX = 0;
this.offsetY = 0;
@@ -46,48 +62,129 @@ class GameUI {
}
init() {
+ this.setupStartScreen();
this.setupEventListeners();
+ }
+
+ setupStartScreen() {
+ const playerCountSelect = document.getElementById('player-count');
+ const playerTypeRows = document.querySelectorAll('.player-type-row');
+
+ playerCountSelect.addEventListener('change', (e) => {
+ const count = parseInt(e.target.value);
+ playerTypeRows.forEach((row, index) => {
+ row.style.display = index < count ? 'flex' : 'none';
+ });
+ });
+
+ document.getElementById('start-game-btn').addEventListener('click', () => {
+ this.startGame();
+ });
+
+ document.getElementById('back-menu-btn').addEventListener('click', () => {
+ this.showStartScreen();
+ });
+ }
+
+ showStartScreen() {
+ document.getElementById('start-screen').style.display = 'flex';
+ document.getElementById('game-screen').style.display = 'none';
+ }
+
+ startGame() {
+ // Get settings
+ this.playerCount = parseInt(document.getElementById('player-count').value);
+
+ for (let i = 1; i <= this.playerCount; i++) {
+ const typeSelect = document.getElementById(`player${i}-type`);
+ this.playerTypes[i] = typeSelect.value;
+
+ if (this.playerTypes[i] === 'ai') {
+ this.aiBots[i] = new AIBot(i, this.map, this);
+ }
+ }
+
+ // Show game screen
+ document.getElementById('start-screen').style.display = 'none';
+ document.getElementById('game-screen').style.display = 'flex';
+
this.newGame();
- console.log('Game initialized');
}
newGame() {
this.map = new HexMap(MAP_SIZE);
- console.log('Map created, cells:', this.map.cells.size);
this.selectedCell = null;
this.currentTarget = null;
this.currentPlayer = 1;
this.gamePhase = 'movement';
this.hasMoved = false;
+ this.isAIThinking = false;
- // Initialize starting positions
+ // Initialize starting positions for all players
this.initializePlayers();
this.centerMap();
this.render();
+ this.createPlayerCards();
this.updateUI();
- this.log('New game started! Player 1\'s turn.');
+ this.log(`New game started with ${this.playerCount} players!`);
+
+ // Start first player's turn (AI if needed)
+ this.checkAndRunAITurn();
+ }
+
+ createPlayerCards() {
+ const container = document.getElementById('players-container');
+ container.innerHTML = '';
+
+ for (let i = 1; i <= this.playerCount; i++) {
+ const card = document.createElement('div');
+ card.className = `player-card player-${i}${i === 1 ? ' active' : ''}${this.playerTypes[i] === 'ai' ? ' ai-controlled' : ''}`;
+ card.id = `player${i}-card`;
+
+ card.innerHTML = `
+
Player ${i}${this.playerTypes[i] === 'ai' ? ' (AI)' : ''}
+
+
+ Cells:
+ 0
+
+
+ Supply:
+ 0
+
+
+ Strength:
+ 0
+
+
+ `;
+
+ container.appendChild(card);
+ }
}
initializePlayers() {
- // Get random empty cells for each player
const emptyCells = this.map.getEmptyCells();
-
- // Shuffle and pick starting positions
const shuffled = emptyCells.sort(() => Math.random() - 0.5);
- // Player 1 starting position (top-left area)
- const p1Cell = shuffled.find(c => c.q < 8 && c.r < 8);
- if (p1Cell) {
- this.map.setOwner(p1Cell.q, p1Cell.r, 1);
- p1Cell.setStrength(8); // Starting strength
- }
+ // Place starting units for each player
+ const positions = [
+ { q: 2, r: 2 }, // Player 1 - top area
+ { q: MAP_SIZE - 3, r: MAP_SIZE - 3 }, // Player 2 - bottom area
+ { q: 2, r: MAP_SIZE - 3 }, // Player 3
+ { q: MAP_SIZE - 3, r: 2 } // Player 4
+ ];
- // Player 2 starting position (bottom-right area)
- const p2Cell = shuffled.find(c => c.q > 10 && c.r > 10 && c.type === CELL_TYPES.EMPTY);
- if (p2Cell) {
- this.map.setOwner(p2Cell.q, p2Cell.r, 2);
- p2Cell.setStrength(8);
+ for (let i = 1; i <= this.playerCount; i++) {
+ const pos = positions[i - 1] || shuffled[i];
+ if (pos) {
+ const cell = this.map.getCell(pos.q, pos.r);
+ if (cell && cell.isPassable()) {
+ this.map.setOwner(pos.q, pos.r, i);
+ cell.setStrength(8);
+ }
+ }
}
}
@@ -96,7 +193,6 @@ class GameUI {
const canvasHeight = this.canvas.height;
const sqrt3 = Math.sqrt(3);
- // Map dimensions for pointy-top hex grid
const mapWidth = HEX_SIZE * sqrt3 * (MAP_SIZE + MAP_SIZE/2);
const mapHeight = HEX_SIZE * 1.5 * (MAP_SIZE - 1) + HEX_SIZE * 2;
@@ -109,21 +205,13 @@ class GameUI {
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('cancel-btn').addEventListener('click', () => this.cancelSelection());
}
hexToPixel(q, r) {
- // Pointy-top hex grid with proper adjacency
- // For pointy-top: width = sqrt(3) * size, height = 2 * size
- // Horizontal spacing = width = sqrt(3) * size
- // Vertical spacing = 3/4 * height = 1.5 * size
const sqrt3 = Math.sqrt(3);
-
- // Convert axial to pixel coordinates for pointy-top hex
const x = this.offsetX + HEX_SIZE * sqrt3 * (q + r/2);
const y = this.offsetY + HEX_SIZE * 1.5 * r;
-
return { x, y };
}
@@ -144,16 +232,6 @@ class GameUI {
return null;
}
- /**
- * Get neighboring cells for pointy-top hex grid
- * Directions for pointy-top hex in axial coordinates:
- * - north-east: (+1, -1)
- * - north-west: (0, -1)
- * - west: (-1, 0)
- * - south-west: (-1, +1)
- * - south-east: (0, +1)
- * - east: (+1, 0)
- */
getValidTargets(q, r) {
const directions = [
[+1, -1], // north-east
@@ -180,14 +258,11 @@ class GameUI {
drawHex(q, r, fillStyle, strokeStyle = COLORS.stroke, lineWidth = 2) {
const { x, y } = this.hexToPixel(q, r);
+ const size = HEX_SIZE * 0.98;
- // Draw pointy-top hexagon - size matches grid spacing
- const size = HEX_SIZE * 0.98; // Almost full size for tight fit
-
- // Pointy-top hexagon vertices (flat sides left/right)
const vertices = [];
for (let i = 0; i < 6; i++) {
- const angle = Math.PI / 6 + (Math.PI / 3) * i; // Start at 30 degrees
+ const angle = Math.PI / 6 + (Math.PI / 3) * i;
vertices.push({
x: x + size * Math.cos(angle),
y: y + size * Math.sin(angle)
@@ -215,14 +290,12 @@ class GameUI {
const { x, y } = this.hexToPixel(cell.q, cell.r);
const strength = cell.getStrength();
- // Draw strength number
this.ctx.fillStyle = COLORS.text;
this.ctx.font = 'bold 11px Arial';
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillText(strength.toString(), x, y - 3);
- // Draw dice count indicator (small dots)
if (cell.dice.length > 1) {
const dotY = y + 8;
for (let i = 0; i < Math.min(cell.dice.length, 5); i++) {
@@ -238,9 +311,7 @@ class GameUI {
if (!cell.isOwned()) return;
const { x, y } = this.hexToPixel(cell.q, cell.r);
- const ownerColor = cell.getOwner() === 1 ? COLORS.player1 : COLORS.player2;
-
- // Draw border matching the hexagon shape
+ const ownerColor = PLAYER_COLORS[cell.getOwner()] || COLORS.player1;
const size = HEX_SIZE * 0.98 - 3;
const vertices = [];
@@ -264,13 +335,11 @@ class GameUI {
}
render() {
- // Clear canvas
this.ctx.fillStyle = COLORS.stroke;
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
if (!this.map) return;
- // Draw all cells
for (let r = 0; r < MAP_SIZE; r++) {
for (let q = 0; q < MAP_SIZE; q++) {
const cell = this.map.getCell(q, r);
@@ -282,11 +351,14 @@ class GameUI {
color = COLORS.player1;
} else if (cell.type === CELL_TYPES.PLAYER2) {
color = COLORS.player2;
+ } else if (cell.type === CELL_TYPES.PLAYER3) {
+ color = COLORS.player3;
+ } else if (cell.type === CELL_TYPES.PLAYER4) {
+ color = COLORS.player4;
} else {
color = COLORS.empty;
}
- // Apply transparency for non-owned cells
if (!cell.isOwned()) {
color = this.hexToRgba(color, 0.6);
}
@@ -297,25 +369,23 @@ class GameUI {
}
}
- // Draw selection highlight
if (this.selectedCell) {
this.drawHex(
- this.selectedCell.q,
- this.selectedCell.r,
+ this.selectedCell.q,
+ this.selectedCell.r,
COLORS.selected,
COLORS.text,
3
);
-
- // Highlight valid targets (not blocked, not own)
+
const targets = this.getValidTargets(this.selectedCell.q, this.selectedCell.r);
for (const target of targets) {
if (target.getOwner() !== this.currentPlayer) {
this.drawHex(
- target.q,
- target.r,
+ target.q,
+ target.r,
COLORS.target,
- COLORS.player1,
+ PLAYER_COLORS[this.currentPlayer] || COLORS.player1,
2
);
}
@@ -332,6 +402,7 @@ class GameUI {
handleClick(e) {
if (this.gamePhase !== 'movement') return;
+ if (this.playerTypes[this.currentPlayer] === 'ai') return;
const rect = this.canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
@@ -343,29 +414,22 @@ class GameUI {
const cell = this.map.getCell(hexPos.q, hexPos.r);
if (!cell || !cell.isPassable()) return;
- // Handle cell selection
if (this.selectedCell === null) {
- // Select own cell with strength > 1
if (cell.getOwner() === this.currentPlayer && cell.getStrength() > 1) {
this.selectedCell = cell;
- this.instruction = `Select target cell or cancel`;
this.updateUI();
this.render();
}
} else {
- // Check if clicking on same cell - deselect
if (cell === this.selectedCell) {
this.cancelSelection();
return;
}
- // Check if valid target using isometric-aware adjacency
const targets = this.getValidTargets(this.selectedCell.q, this.selectedCell.r);
const isValidTarget = targets.some(n => n.q === cell.q && n.r === cell.r);
- // Cannot move to own cells - only attack enemy or capture empty
if (cell.getOwner() === this.currentPlayer) {
- // Select different own cell instead
if (cell.getStrength() > 1) {
this.selectedCell = cell;
this.updateUI();
@@ -384,53 +448,48 @@ class GameUI {
}
handleMouseMove(e) {
- // Could add hover effects here
+ // Hover effects could be added here
}
executeAttack() {
if (!this.selectedCell || !this.currentTarget) return;
-
+
const attacker = this.selectedCell;
const defender = this.currentTarget;
const attackStrength = attacker.getStrength() - 1;
-
+
if (defender.type === CELL_TYPES.EMPTY || defender.getOwner() !== this.currentPlayer) {
- // Attack empty or enemy cell
let defenseStrength = defender.getStrength();
-
+
if (defenseStrength > 0) {
- // Combat! Roll dice
const attackRoll = Math.floor(Math.random() * attackStrength) + 1;
const defenseRoll = Math.floor(Math.random() * defenseStrength) + 1;
- this.log(`Attack: ${attackStrength} vs ${defenseStrength} | Roll: ${attackRoll} vs ${defenseRoll}`);
+ this.log(`P${this.currentPlayer} Attack: ${attackStrength} vs ${defenseStrength} | Roll: ${attackRoll} vs ${defenseRoll}`);
if (attackRoll > defenseRoll) {
- // Attacker wins
const remainingStrength = attackRoll - 1;
attacker.setStrength(1);
if (remainingStrength > 0) {
defender.setStrength(remainingStrength);
this.map.setOwner(defender.q, defender.r, this.currentPlayer);
- this.log(`Victory! Captured cell with strength ${remainingStrength}`, 'victory');
+ this.log(`Victory! Captured with strength ${remainingStrength}`, 'victory');
}
} else {
- // Defender wins - attacker loses all but 1 die
const remainingDefense = defenseRoll - attackRoll;
defender.setStrength(Math.max(1, remainingDefense));
- attacker.setStrength(1); // Attacker reduced to 1
- this.log(`Attack repelled! Attacker reduced to 1, Defender has ${Math.max(1, remainingDefense)} strength`, 'defeat');
+ attacker.setStrength(1);
+ this.log(`Attack repelled! Defender has ${Math.max(1, remainingDefense)}`, 'defeat');
}
} else {
- // Move to empty cell - transfer attackStrength (original - 1)
attacker.setStrength(1);
defender.setStrength(attackStrength);
this.map.setOwner(defender.q, defender.r, this.currentPlayer);
- this.log(`Moved to empty cell with strength ${attackStrength}`);
+ this.log(`Captured empty cell with strength ${attackStrength}`);
}
}
-
+
this.hasMoved = true;
this.cancelSelection();
this.render();
@@ -444,48 +503,58 @@ class GameUI {
this.render();
}
- endTurn() {
+ async endTurn() {
if (this.gamePhase !== 'movement') return;
+ if (this.isAIThinking) return;
// Apply supply
const supply = this.map.calculateSupply(this.currentPlayer);
this.distributeSupply(supply);
-
this.log(`Player ${this.currentPlayer} received ${supply} supply`);
- // Switch player
- this.currentPlayer = this.currentPlayer === 1 ? 2 : 1;
+ // Next player
+ this.currentPlayer = (this.currentPlayer % this.playerCount) + 1;
this.hasMoved = false;
-
+
this.cancelSelection();
this.updateUI();
this.render();
-
+
this.log(`Player ${this.currentPlayer}'s turn`);
+
+ // Check if next player is AI
+ this.checkAndRunAITurn();
+ }
+
+ checkAndRunAITurn() {
+ if (this.playerTypes[this.currentPlayer] === 'ai' && this.aiBots[this.currentPlayer]) {
+ this.isAIThinking = true;
+ this.updateUI();
+
+ // Run AI turn
+ this.aiBots[this.currentPlayer].playTurn().then(() => {
+ this.isAIThinking = false;
+ // AI will call endTurn() when done
+ });
+ }
}
distributeSupply(supply) {
const playerCells = this.map.getPlayerCells(this.currentPlayer);
-
- // Find cells that can receive more dice
const eligibleCells = playerCells.filter(cell => !cell.isMaxStrength());
if (eligibleCells.length === 0 || supply === 0) return;
- // Distribute supply one by one to random eligible cells
let remainingSupply = supply;
while (remainingSupply > 0 && eligibleCells.length > 0) {
- // Pick random cell
const randomCell = eligibleCells[Math.floor(Math.random() * eligibleCells.length)];
const currentStrength = randomCell.getStrength();
if (currentStrength < 48) {
- // Add 1 strength to this cell
randomCell.setStrength(currentStrength + 1);
remainingSupply--;
}
- // Remove cell from eligible if it's now at max strength
if (randomCell.isMaxStrength()) {
eligibleCells.splice(eligibleCells.indexOf(randomCell), 1);
}
@@ -493,29 +562,32 @@ class GameUI {
}
updateUI() {
- // Update current player stats
- const currentCells = this.map.getPlayerCells(this.currentPlayer);
- const currentStrength = currentCells.reduce((sum, c) => sum + c.getStrength(), 0);
- const currentSupply = this.map.calculateSupply(this.currentPlayer);
+ // Update all player cards
+ for (let i = 1; i <= this.playerCount; i++) {
+ const playerCells = this.map.getPlayerCells(i);
+ const playerStrength = playerCells.reduce((sum, c) => sum + c.getStrength(), 0);
+ const playerSupply = this.map.calculateSupply(i);
+
+ document.getElementById(`player${i}-cells`).textContent = playerCells.length;
+ document.getElementById(`player${i}-supply`).textContent = playerSupply;
+ document.getElementById(`player${i}-strength`).textContent = playerStrength;
+
+ const card = document.getElementById(`player${i}-card`);
+ card.classList.toggle('active', i === this.currentPlayer);
+ }
- // Update player 1 card (only player for now)
- document.getElementById('player1-cells').textContent = currentCells.length;
- document.getElementById('player1-supply').textContent = currentSupply;
- document.getElementById('player1-strength').textContent = currentStrength;
-
- // Update game info
document.getElementById('current-turn').textContent = this.currentPlayer;
document.getElementById('game-phase').textContent = this.gamePhase;
- // Update instruction
const instruction = document.getElementById('action-instruction');
if (this.selectedCell) {
- instruction.textContent = `Select target to attack (strength: ${this.selectedCell.getStrength()})`;
+ instruction.textContent = `Select target (strength: ${this.selectedCell.getStrength()})`;
+ } else if (this.playerTypes[this.currentPlayer] === 'ai') {
+ instruction.textContent = 'AI is thinking...';
} else {
instruction.textContent = 'Select a cell with dice to move';
}
- // Update selected cell info - always show with placeholder if nothing selected
const cellInfo = document.getElementById('selected-cell-info');
cellInfo.style.display = 'block';
@@ -527,38 +599,24 @@ class GameUI {
document.getElementById('cell-dice').textContent = '-';
}
- // Update buttons
const cancelBtn = document.getElementById('cancel-btn');
const endTurnBtn = document.getElementById('end-turn-btn');
- cancelBtn.disabled = !this.selectedCell;
- // End turn is always enabled
- endTurnBtn.disabled = false;
+ cancelBtn.disabled = !this.selectedCell || this.playerTypes[this.currentPlayer] === 'ai';
+ endTurnBtn.disabled = this.playerTypes[this.currentPlayer] === 'ai';
}
log(message, type = '') {
const logContainer = document.getElementById('battle-log');
const entry = document.createElement('div');
entry.className = `log-entry ${type}`;
- entry.textContent = `[Turn ${this.currentPlayer}] ${message}`;
+ entry.textContent = `[P${this.currentPlayer}] ${message}`;
logContainer.insertBefore(entry, logContainer.firstChild);
- // Keep only last 50 entries
while (logContainer.children.length > 50) {
logContainer.removeChild(logContainer.lastChild);
}
}
-
- showMessage(message) {
- const overlay = document.getElementById('message-overlay');
- const messageEl = document.getElementById('overlay-message');
- messageEl.textContent = message;
- overlay.classList.add('visible');
-
- setTimeout(() => {
- overlay.classList.remove('visible');
- }, 2000);
- }
}
// Initialize game when DOM is ready
diff --git a/public/index.html b/public/index.html
index 81eafe0..e4501ba 100644
--- a/public/index.html
+++ b/public/index.html
@@ -8,93 +8,139 @@
-
-
+
+
+
+
HEXO
+
DiceWars Clone
+
+
+
Game Setup
+
+
+
+
+
-
-
-
-
diff --git a/public/styles.css b/public/styles.css
index c089359..58a759c 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -30,6 +30,110 @@ body {
overflow: hidden;
}
+/* Start Screen */
+.start-screen {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ background: var(--bg-primary);
+}
+
+.start-panel {
+ background: var(--bg-secondary);
+ padding: 40px;
+ border-radius: 12px;
+ text-align: center;
+ max-width: 450px;
+ width: 90%;
+ border: 2px solid var(--accent-primary);
+}
+
+.start-panel h1 {
+ font-size: 2.5rem;
+ color: var(--accent-primary);
+ text-transform: uppercase;
+ letter-spacing: 4px;
+ margin-bottom: 5px;
+}
+
+.start-panel .subtitle {
+ font-size: 1rem;
+ color: var(--text-secondary);
+ margin-bottom: 30px;
+}
+
+.setup-section {
+ text-align: left;
+}
+
+.setup-section h3 {
+ font-size: 1.1rem;
+ color: var(--text-primary);
+ margin-bottom: 20px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.setup-group {
+ margin-bottom: 20px;
+}
+
+.setup-group label {
+ display: block;
+ font-size: 0.9rem;
+ color: var(--text-secondary);
+ margin-bottom: 8px;
+}
+
+.setup-group select {
+ width: 100%;
+ padding: 10px;
+ background: var(--bg-panel);
+ border: 1px solid var(--accent-secondary);
+ border-radius: 6px;
+ color: var(--text-primary);
+ font-size: 0.9rem;
+}
+
+.player-type-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+ padding: 8px;
+ background: var(--bg-panel);
+ border-radius: 6px;
+}
+
+.player-type-row span {
+ font-size: 0.85rem;
+ color: var(--text-primary);
+}
+
+.player-type-row select {
+ width: 120px;
+ padding: 6px;
+ background: var(--bg-primary);
+ border: 1px solid var(--accent-secondary);
+ border-radius: 4px;
+ color: var(--text-primary);
+ font-size: 0.85rem;
+}
+
+.btn-large {
+ width: 100%;
+ padding: 15px;
+ font-size: 1rem;
+ margin-top: 10px;
+}
+
+.game-screen {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+}
+
.game-container {
display: flex;
flex-direction: column;
@@ -89,6 +193,7 @@ body {
padding: 12px;
border-left: 4px solid transparent;
transition: all 0.3s ease;
+ margin-bottom: 10px;
}
.player-card.active {
@@ -96,7 +201,7 @@ body {
}
.player-1 {
- border-left-color: var(--player1-color);
+ border-left-color: #4ecca3;
}
.player-1.active {
@@ -104,13 +209,44 @@ body {
}
.player-2 {
- border-left-color: var(--player2-color);
+ border-left-color: #e94560;
}
.player-2.active {
background: linear-gradient(135deg, var(--bg-panel), rgba(233, 69, 96, 0.1));
}
+.player-3 {
+ border-left-color: #f9ed69;
+}
+
+.player-3.active {
+ background: linear-gradient(135deg, var(--bg-panel), rgba(249, 237, 105, 0.1));
+}
+
+.player-4 {
+ border-left-color: #a8e6cf;
+}
+
+.player-4.active {
+ background: linear-gradient(135deg, var(--bg-panel), rgba(168, 230, 207, 0.1));
+}
+
+.player-card.ai-controlled {
+ opacity: 0.8;
+}
+
+.player-card.ai-controlled::after {
+ content: 'AI';
+ position: absolute;
+ top: 5px;
+ right: 8px;
+ font-size: 0.7rem;
+ background: var(--accent-secondary);
+ padding: 2px 6px;
+ border-radius: 4px;
+}
+
.player-card h3 {
font-size: 1rem;
margin-bottom: 10px;