From 62bd946509299e5f610a8d09c53e074babf88e58 Mon Sep 17 00:00:00 2001 From: sokol Date: Sat, 21 Feb 2026 18:33:11 +0300 Subject: [PATCH] Add multi-player support (2-4 players) and AI bots --- public/ai-bot.js | 121 ++++++++++++++++++ public/game.js | 304 +++++++++++++++++++++++++++------------------- public/index.html | 206 +++++++++++++++++++------------ public/styles.css | 140 ++++++++++++++++++++- 4 files changed, 566 insertions(+), 205 deletions(-) create mode 100644 public/ai-bot.js 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

-
+ +
+
+

HEXO

+

DiceWars Clone

+ +
+

Game Setup

+ +
+ + +
- -
- - - - -
- -
- -
-
- - - +
- -
-

Click on your cell with dice, then click adjacent cell to attack

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