游戏已开源,可以集成到个人网站


html
<!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>