游戏已开源,可以集成到个人网站
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>战推棋 - 极简策略对战</title> <style> :root { /* 配色方案:深空蓝与霓虹色 */ --bg-gradient-start: #1a1c2c; --bg-gradient-end: #4a192c; --primary-color: #4ecca3; /* 玩家/高亮 */ --secondary-color: #e94560; /* AI/敌人 */ --text-color: #ecf0f1; --glass-bg: rgba(255, 255, 255, 0.1); --glass-border: rgba(255, 255, 255, 0.2); /* 棋盘吸盘颜色 */ --board-bg: #16213e; --cell-base: #0f3460; --cell-hover: #1a4b85; } * { box-sizing: border-box; user-select: none; -webkit-tap-highlight-color: transparent; } body { font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif; background: radial-gradient(circle at center, var(--bg-gradient-start), #000); background-size: cover; color: var(--text-color); display: flex; flex-direction: column; align-items: center; min-height: 100vh; margin: 0; padding: 20px; } /* --- 头部区域 --- */ header { text-align: center; margin-bottom: 20px; animation: fadeIn Down 0.8s ease-out; } h1 { margin: 0; font-size: 2.5rem; color: #fff; text-shadow: 0 0 15px rgba(78, 204, 163, 0.6); letter-spacing: 2px; } .subtitle { font-size: 1rem; color: #8d9db6; margin-top: 8px; font-weight: 300; } /* --- 主布局 --- */ main { display: flex; gap: 40px; flex-wrap: wrap; justify-content: center; align-items: flex-start; width: 100%; max-width: 1000px; } /* --- 游戏控制区 (左侧) --- */ .game-wrapper { display: flex; flex-direction: column; align-items: center; } /* 状态栏 */ .status-bar { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-bottom: 15px; background: var(--glass-bg); backdrop-filter: blur(10px); padding: 10px 20px; border-radius: 50px; border: 1px solid var(--glass-border); box-shadow: 0 4px 15px rgba(0,0,0,0.3); } .turn-badge { padding: 6px 16px; border-radius: 20px; font-weight: bold; font-size: 0.9rem; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .turn-player { background: var(--primary-color); color: #1a1c2c; box-shadow: 0 0 10px rgba(78, 204, 163, 0.5); } .turn-ai { background: var(--secondary-color); color: #fff; box-shadow: 0 0 10px rgba(233, 69, 96, 0.5); } /* --- 棋盘 (吸盘风格) --- */ .board-container { padding: 15px; background: #2a2d3e; border-radius: 16px; box-shadow: 20px 20px 60px #15171f, -20px -20px 60px #3f435d; /* 新拟态阴影 */ position: relative; } #board { display: grid; grid-template-columns: repeat(6, 1fr); grid-template-rows: repeat(6, 1fr); gap: 6px; /* 格子间距,模仿吸盘间隔 */ width: 360px; height: 360px; } .cell { background-color: var(--cell-base); border-radius: 50%; /* 圆形模仿吸盘 */ display: flex; justify-content: center; align-items: center; cursor: pointer; position: relative; transition: all 0.2s ease; box-shadow: inset 2px 2px 5px rgba(0,0,0,0.5), inset -2px -2px 5px rgba(255,255,255,0.05); } /* 鼠标悬停 */ .cell:hover { transform: scale(0.95); } /* 可移动提示 (吸盘光晕) */ .cell.highlight { background-color: rgba(78, 204, 163, 0.25); box-shadow: 0 0 15px var(--primary-color), inset 0 0 10px var(--primary-color); animation: pulse 1.5s infinite; } .cell.last-move { background-color: rgba(255, 215, 0, 0.15); box-shadow: inset 0 0 8px rgba(255, 215, 0, 0.4); } /* --- 棋子 --- */ .piece { width: 85%; height: 85%; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 1.2rem; font-weight: bold; transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1); z-index: 2; } .piece.player { background: radial-gradient(circle at 30% 30%, #6ef7c7, #1a8f66); border: 2px solid #a3ffce; color: #052e1e; box-shadow: 0 4px 6px rgba(0,0,0,0.4); } .piece.ai { background: radial-gradient(circle at 30% 30%, #ff7b93, #c82333); border: 2px solid #ffccd5; color: #4a0e15; box-shadow: 0 4px 6px rgba(0,0,0,0.4); } /* 棋子类型符号 */ .piece.king::after { content: '♔'; font-size: 1.4rem; } .piece.pawn::after { content: '●'; font-size: 0.9rem; } /* 选中状态 */ .piece.selected { transform: scale(1.2); box-shadow: 0 0 20px #fff; z-index: 10; } /* --- 侧边栏 (右) --- */ aside { flex: 1; min-width: 280px; max-width: 350px; display: flex; flex-direction: column; gap: 20px; } /* 按钮组 */ .btn-group { display: flex; gap: 10px; } button { flex: 1; background: var(--glass-bg); backdrop-filter: blur(10px); border: 1px solid var(--glass-border); padding: 12px; color: #fff; font-weight: 600; font-size: 0.95rem; border-radius: 8px; cursor: pointer; transition: all 0.2s; text-transform: uppercase; letter-spacing: 1px; } button:hover { background: rgba(255,255,255,0.2); transform: translateY(-2px); } button:active { transform: translateY(1px); } button.primary { background: var(--primary-color); color: #1a1c2c; border: none; } button.primary:hover { background: #6ef7c7; box-shadow: 0 0 15px rgba(78, 204, 163, 0.4); } /* 日志窗口 */ .log-container { flex-grow: 1; background: rgba(0, 0, 0, 0.3); border: 1px solid var(--glass-border); border-radius: 8px; padding: 15px; font-family: 'Consolas', 'Monaco', monospace; font-size: 0.85rem; overflow-y: auto; max-height: 200px; min-height: 150px; box-shadow: inset 0 0 10px rgba(0,0,0,0.5); } /* 自定义滚动条 */ .log-container::-webkit-scrollbar { width: 6px; } .log-container::-webkit-scrollbar-thumb { background: #4a5568; border-radius: 3px; } .log-entry { margin-bottom: 6px; border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 4px; } .log-player { color: var(--primary-color); } .log-ai { color: var(--secondary-color); } .log-system { color: #8899ac; font-style: italic; } /* --- 模态框 (规则 & 结束) --- */ .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(5px); display: flex; justify-content: center; align-items: center; z-index: 1000; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; } .modal-overlay.active { opacity: 1; pointer-events: all; } .modal { background: #1f2233; padding: 30px; border-radius: 16px; text-align: center; border: 1px solid var(--glass-border); max-width: 90%; width: 400px; box-shadow: 0 20px 50px rgba(0,0,0,0.5); transform: translateY(20px); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .modal-overlay.active .modal { transform: translateY(0); } .modal h2 { color: var(--primary-color); margin-top: 0; font-size: 1.8rem; } .modal-content { text-align: left; color: #ccc; margin-bottom: 25px; line-height: 1.6; font-size: 0.95rem; } .modal-content strong { color: #fff; } .modal-content ul { padding-left: 20px; margin: 5px 0; } .modal-content li { margin-bottom: 6px; } .modal-btn { background: var(--primary-color); color: #1a1c2c; width: 100%; font-weight: bold; } /* --- 动画 --- */ @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(78, 204, 163, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(78, 204, 163, 0); } 100% { box-shadow: 0 0 0 0 rgba(78, 204, 163, 0); } } @keyframes fadeIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .shake { animation: shake 0.5s; } @keyframes shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); } 20%, 40%, 60%, 80% { transform: translateX(4px); } } /* 响应式调整 */ @media (max-width: 600px) { #board { width: 300px; height: 300px; gap: 4px; } main { flex-direction: column; align-items: center; } aside { width: 100%; } } </style> </head> <body> <header> <h1>战推棋</h1> <div class="subtitle">Battle Push Chess - 极简推挤策略</div> </header> <main> <div class="game-wrapper"> <!-- 状态栏 --> <div class="status-bar"> <div id="turn-indicator" class="turn-badge turn-player">玩家回合</div> <div id="game-state" style="font-size: 0.85rem; color: #aaa;">进行中</div> </div> <!-- 棋盘 --> <div class="board-container"> <div id="board"></div> </div> </div> <!-- 侧边栏 --> <aside> <div class="btn-group"> <button onclick="openRulesModal()">查看规则</button> <button class="primary" onclick="game.init()">重新开始</button> </div> <div class="log-container" id="game-log"> <div class="log-entry log-system">欢迎来到战推棋!</div> <div class="log-entry log-system">请点击绿色棋子开始。</div> </div> </aside> </main> <!-- 规则弹窗 --> <div id="rules-modal" class="modal-overlay"> <div class="modal"> <h2>游戏规则</h2> <div class="modal-content"> <p><strong>胜利条件:</strong>粉碎对方的主帅(♔)。</p> <ul> <li><strong>移动:</strong>点击己方棋子,再点击高亮光圈移动。</li> <li><strong>推挤:</strong>移动到敌方位置会将其向后推1格。</li> <li><strong>粉碎(Crush):</strong> <br>- 被推棋子后方是墙壁、边界或友军 -> <span style="color:var(--secondary-color)">直接粉碎</span>。 <br>- 主帅被推挤或被粉碎 -> 游戏结束。 </li> <li><strong>棋子:</strong> <br>♔ 主帅:可向周围8个方向移动。 <br>● 先锋:仅可向上下左右4个方向移动。 </li> </ul> </div> <button class="modal-btn" onclick="closeRulesModal()">明白了,开始战斗</button> </div> </div> <!-- 游戏结束弹窗 --> <div id="end-modal" class="modal-overlay"> <div class="modal"> <h2 id="end-title">游戏结束</h2> <p id="end-message" style="color:#fff; font-size:1.1rem;">你赢了!</p> <button class="modal-btn" onclick="closeEndModal()">再来一局</button> </div> </div> <script> /** * 游戏配置与常量 */ const BOARD_SIZE = 6; const PLAYER = 1; // 绿方 const AI = -1; // 红方 const TYPE_KING = 'king'; const TYPE_PAWN = 'pawn'; /** * 游戏核心逻辑类 */ class Game { constructor() { this.board = []; this.turn = PLAYER; this.selectedPiece = null; // {r, c} this.validMoves = []; // Array of {r, c, type...} this.isGameOver = false; this.lastMove = null; // {from: {r,c}, to: {r,c}} this.isAiThinking = false; // DOM 缓存 this.boardEl = document.getElementById('board'); this.turnIndEl = document.getElementById('turn-indicator'); this.stateEl = document.getElementById('game-state'); this.logEl = document.getElementById('game-log'); this.init(); } // 初始化/重置游戏 init() { this.board = Array(BOARD_SIZE).fill(null).map(() => Array(BOARD_SIZE).fill(null)); this.turn = PLAYER; this.isGameOver = false; this.selectedPiece = null; this.validMoves = []; this.lastMove = null; this.isAiThinking = false; this.setupBoard(); this.render(); this.updateStatus("玩家回合"); this.log("游戏开始,绿方先手。", "system"); document.getElementById('end-modal').classList.remove('active'); } // 布局棋子 setupBoard() { // 放置 AI (红方, 上方) this.board[0][2] = { type: TYPE_KING, owner: AI }; this.board[0][1] = { type: TYPE_PAWN, owner: AI }; this.board[0][3] = { type: TYPE_PAWN, owner: AI }; this.board[1][2] = { type: TYPE_PAWN, owner: AI }; // 放置 Player (绿方, 下方) this.board[5][3] = { type: TYPE_KING, owner: PLAYER }; this.board[5][2] = { type: TYPE_PAWN, owner: PLAYER }; this.board[5][4] = { type: TYPE_PAWN, owner: PLAYER }; this.board[4][3] = { type: TYPE_PAWN, owner: PLAYER }; } // 渲染棋盘 render() { this.boardEl.innerHTML = ''; for (let r = 0; r < BOARD_SIZE; r++) { for (let c = 0; c < BOARD_SIZE; c++) { const cell = document.createElement('div'); cell.className = 'cell'; cell.dataset.r = r; cell.dataset.c = c; // 检查是否是合法移动位置 (吸盘高亮) const move = this.validMoves.find(m => m.r === r && m.c === c); if (move) { cell.classList.add('highlight'); } // 检查是否是上一步 if (this.lastMove && ((this.lastMove.from.r === r && this.lastMove.from.c === c) || (this.lastMove.to.r === r && this.lastMove.to.c === c))) { cell.classList.add('last-move'); } // 绘制棋子 const pieceData = this.board[r][c]; if (pieceData) { const piece = document.createElement('div'); piece.className = `piece ${pieceData.owner === PLAYER ? 'player' : 'ai'} ${pieceData.type}`; if (this.selectedPiece && this.selectedPiece.r === r && this.selectedPiece.c === c) { piece.classList.add('selected'); } cell.appendChild(piece); } cell.addEventListener('click', () => this.handleCellClick(r, c)); this.boardEl.appendChild(cell); } } } // 处理点击事件 handleCellClick(r, c) { if (this.isGameOver || this.isAiThinking || this.turn !== PLAYER) return; const clickedPiece = this.board[r][c]; // 1. 选中己方棋子 if (clickedPiece && clickedPiece.owner === PLAYER) { this.selectedPiece = { r, c }; this.validMoves = this.getValidMoves(this.board, r, c); this.render(); return; } // 2. 移动到合法位置 const move = this.validMoves.find(m => m.r === r && m.c === c); if (this.selectedPiece && move) { this.executeMove(this.selectedPiece, move); return; } // 3. 点击空白或无效处 -> 取消选中 this.selectedPiece = null; this.validMoves = []; this.render(); } // 获取合法移动 (包括推挤逻辑) getValidMoves(board, r, c) { const piece = board[r][c]; if (!piece) return []; const moves = []; const directions = []; // 定义移动方向 if (piece.type === TYPE_KING) { // 8个方向 directions.push( {dr: -1, dc: 0}, {dr: 1, dc: 0}, {dr: 0, dc: -1}, {dr: 0, dc: 1}, {dr: -1, dc: -1}, {dr: -1, dc: 1}, {dr: 1, dc: -1}, {dr: 1, dc: 1} ); } else { // 4个方向 directions.push( {dr: -1, dc: 0}, {dr: 1, dc: 0}, {dr: 0, dc: -1}, {dr: 0, dc: 1} ); } for (let d of directions) { const nr = r + d.dr; const nc = c + d.dc; // 边界检查 if (nr < 0 || nr >= BOARD_SIZE || nc < 0 || nc >= BOARD_SIZE) continue; const target = board[nr][nc]; if (!target) { // 空地:直接移动 moves.push({ r: nr, c: nc, type: 'move' }); } else if (target.owner !== piece.owner) { // 敌人:尝试推挤 const pushR = nr + d.dr; const pushC = nc + d.dc; let isCrush = false; // 检查推挤后的位置状态 (边界或被占用) if (pushR < 0 || pushR >= BOARD_SIZE || pushC < 0 || pushC >= BOARD_SIZE) { isCrush = true; // 推出界 } else if (board[pushR][pushC] !== null) { isCrush = true; // 撞到友军或墙壁 } moves.push({ r: nr, c: nc, type: 'push', pushTo: {r: pushR, c: pushC}, crush: isCrush }); } } return moves; } // 执行移动 async executeMove(from, move) { const piece = this.board[from.r][from.c]; const target = this.board[move.r][move.c]; // 目标格原有内容(可能是敌人) // 记录步数 this.lastMove = { from: {...from}, to: {r: move.r, c: move.c} }; if (move.type === 'move') { // 普通移动 this.board[move.r][move.c] = piece; this.board[from.r][from.c] = null; } else if (move.type === 'push') { // 攻击移动 const actorName = piece.owner === PLAYER ? '玩家' : 'AI'; if (target.type === TYPE_KING) { this.log(`${actorName} 粉碎了敌方主帅!`, piece.owner === PLAYER ? 'player' : 'ai'); } else { this.log(`${actorName} 推挤了敌方先锋!`, piece.owner === PLAYER ? 'player' : 'ai'); } // 攻击者占据目标位 this.board[move.r][move.c] = piece; this.board[from.r][from.c] = null; // 处理被推者 if (move.crush) { // 粉碎:直接移除 // 视觉特效:震动 this.shakeBoard(); } else { // 推移:被推者移动到pushTo this.board[move.pushTo.r][move.pushTo.c] = target; } } // 清理状态 this.selectedPiece = null; this.validMoves = []; this.render(); // 检查胜利 if (this.checkWin()) return; // 切换回合 this.turn = this.turn === PLAYER ? AI : PLAYER; if (this.turn === AI) { this.updateStatus("AI 思考中..."); this.isAiThinking = true; setTimeout(() => this.makeAiMove(), 800); } else { this.updateStatus("玩家回合"); } } shakeBoard() { const container = document.querySelector('.board-container'); container.classList.remove('shake'); void container.offsetWidth; // trigger reflow container.classList.add('shake'); } checkWin() { let playerKing = false; let aiKing = false; for(let r=0; r<BOARD_SIZE; r++){ for(let c=0; c<BOARD_SIZE; c++){ const p = this.board[r][c]; if(p && p.type === TYPE_KING){ if(p.owner === PLAYER) playerKing = true; if(p.owner === AI) aiKing = true; } } } if (!playerKing) { this.endGame(AI); return true; } if (!aiKing) { this.endGame(PLAYER); return true; } return false; } endGame(winner) { this.isGameOver = true; this.updateStatus("游戏结束"); const modal = document.getElementById('end-modal'); const title = document.getElementById('end-title'); const msg = document.getElementById('end-message'); modal.classList.add('active'); if (winner === PLAYER) { title.textContent = "胜利!"; title.style.color = "var(--primary-color)"; msg.textContent = "恭喜!你成功粉碎了敌方主帅!"; } else { title.textContent = "失败"; title.style.color = "var(--secondary-color)"; msg.textContent = "你的主帅被粉碎了,再接再厉。"; } } updateStatus(text) { this.stateEl.textContent = text; this.turnIndEl.className = `turn-badge ${this.turn === PLAYER ? 'turn-player' : 'turn-ai'}`; this.turnIndEl.textContent = this.turn === PLAYER ? "玩家回合" : "AI 回合"; } log(msg, type) { const div = document.createElement('div'); div.className = `log-entry log-${type}`; div.textContent = `> ${msg}`; this.logEl.prepend(div); } // --- AI 逻辑 --- makeAiMove() { const depth = 3; const bestMove = this.minimaxRoot(depth, true); if (bestMove) { this.executeMove(bestMove.from, bestMove.move); } else { this.log("AI 无路可走,跳过回合。", "ai"); this.turn = PLAYER; this.updateStatus("玩家回合"); this.isAiThinking = false; } this.isAiThinking = false; } evaluateBoard(board) { let score = 0; for (let r = 0; r < BOARD_SIZE; r++) { for (let c = 0; c < BOARD_SIZE; c++) { const piece = board[r][c]; if (piece) { let val = 0; // 基础分 if (piece.type === TYPE_KING) val = 10000; else val = 100; // 位置分:AI在上方,r越大越接近玩家(攻击性),但也越危险 val += (r * 6); // 中心控制分 const centerDist = Math.abs(2.5 - r) + Math.abs(2.5 - c); val -= (centerDist * 3); if (piece.owner === AI) { score += val; } else { score -= val; } } } } return score; } minimaxRoot(depth, isMaximizing) { const moves = this.getAllMoves(this.board, AI); let bestMove = null; let bestValue = -Infinity; moves.sort(() => Math.random() - 0.5); // 增加随机性 for (let move of moves) { const savedBoard = this.cloneBoard(this.board); this.simulateMove(savedBoard, move.from, move.move); const value = this.minimax(savedBoard, depth - 1, -Infinity, Infinity, !isMaximizing); if (value > bestValue) { bestValue = value; bestMove = move; } } return bestMove; } minimax(board, depth, alpha, beta, isMaximizing) { if (depth === 0) { return this.evaluateBoard(board); } // 检查胜负状态 let aiKing = false; let plKing = false; for(let r=0; r<BOARD_SIZE; r++) { for(let c=0; c<BOARD_SIZE; c++) { if(board[r][c] && board[r][c].type === TYPE_KING) { if(board[r][c].owner === AI) aiKing = true; else plKing = true; } } } if (!aiKing) return -100000; if (!plKing) return 100000; const turn = isMaximizing ? AI : PLAYER; const moves = this.getAllMoves(board, turn); if (moves.length === 0) return isMaximizing ? -1000 : 1000; if (isMaximizing) { let maxEval = -Infinity; for (let move of moves) { const newBoard = this.cloneBoard(board); this.simulateMove(newBoard, move.from, move.move); const evalVal = this.minimax(newBoard, depth - 1, alpha, beta, false); maxEval = Math.max(maxEval, evalVal); alpha = Math.max(alpha, evalVal); if (beta <= alpha) break; } return maxEval; } else { let minEval = Infinity; for (let move of moves) { const newBoard = this.cloneBoard(board); this.simulateMove(newBoard, move.from, move.move); const evalVal = this.minimax(newBoard, depth - 1, alpha, beta, true); minEval = Math.min(minEval, evalVal); beta = Math.min(beta, evalVal); if (beta <= alpha) break; } return minEval; } } getAllMoves(board, player) { const allMoves = []; for (let r = 0; r < BOARD_SIZE; r++) { for (let c = 0; c < BOARD_SIZE; c++) { const piece = board[r][c]; if (piece && piece.owner === player) { const moves = this.getValidMoves(board, r, c); for (let m of moves) { allMoves.push({ from: { r, c }, move: m }); } } } } return allMoves; } cloneBoard(board) { return board.map(row => row.map(cell => cell ? {...cell} : null)); } simulateMove(board, from, move) { const piece = board[from.r][from.c]; const target = board[move.r][move.c]; if (move.type === 'move') { board[move.r][move.c] = piece; board[from.r][from.c] = null; } else if (move.type === 'push') { if (!move.crush) { board[move.pushTo.r][move.pushTo.c] = target; } board[move.r][move.c] = piece; board[from.r][from.c] = null; } } } // --- UI 交互函数 --- function openRulesModal() { document.getElementById('rules-modal').classList.add('active'); } function closeRulesModal() { document.getElementById('rules-modal').classList.remove('active'); } function closeEndModal() { document.getElementById('end-modal').classList.remove('active'); game.init(); } // 启动游戏 const game = new Game(); </script> </body> </html>