原生JS五子棋项目重构:从300行代码到3个模块化组件
原生JS五子棋工程化重构从300行代码到模块化架构实战五子棋作为经典策略游戏常被用作前端开发练手项目。但大多数初学者实现的版本往往将所有逻辑堆砌在单一文件中随着功能增加代码会变得难以维护。本文将展示如何将一个300行的一镜到底式五子棋游戏重构为模块化、可维护的现代前端项目。1. 为什么需要模块化重构原始的五子棋实现通常存在以下典型问题全局变量污染所有变量直接暴露在全局作用域功能耦合严重棋盘绘制、游戏逻辑、AI计算混杂在一起难以扩展添加新功能如悔棋、保存棋局需要修改核心代码维护困难300行的单一文件使得定位问题变得困难// 典型原始实现的问题示例 var board []; // 全局变量 var currentPlayer 1; function initGame() { // 混合了初始化、绘制、事件绑定 } function handleClick() { // 包含游戏逻辑、胜负判断、AI调用 }通过模块化重构我们可以获得以下优势职责分离各模块专注单一功能可测试性独立模块更易于单元测试可维护性代码组织清晰修改不影响其他模块可扩展性新功能通过添加模块实现2. 项目结构与模块划分重构后的项目采用以下目录结构/src ├── index.html # 主页面 ├── styles │ └── main.css # 全局样式 └── modules ├── BoardManager.js # 棋盘管理 ├── GameLogic.js # 游戏规则 ├── AIPlayer.js # AI算法 └── EventBus.js # 模块通信2.1 棋盘管理模块 (BoardManager.js)负责棋盘的绘制和交互不包含游戏逻辑class BoardManager { constructor(size, container) { this.size size; this.canvas document.createElement(canvas); container.appendChild(this.canvas); this.setupCanvas(); } setupCanvas() { this.canvas.width this.size * 30 40; this.canvas.height this.size * 30 40; this.ctx this.canvas.getContext(2d); this.drawGrid(); } drawGrid() { // 绘制棋盘网格 this.ctx.strokeStyle #333; for (let i 0; i this.size; i) { // 横线 this.ctx.beginPath(); this.ctx.moveTo(20, 20 i * 30); this.ctx.lineTo(20 (this.size-1)*30, 20 i * 30); this.ctx.stroke(); // 竖线 this.ctx.beginPath(); this.ctx.moveTo(20 i * 30, 20); this.ctx.lineTo(20 i * 30, 20 (this.size-1)*30); this.ctx.stroke(); } } drawPiece(x, y, color) { // 绘制棋子 this.ctx.beginPath(); this.ctx.arc(20 x * 30, 20 y * 30, 13, 0, Math.PI * 2); this.ctx.fillStyle color 1 ? #000 : #fff; this.ctx.fill(); this.ctx.stroke(); } clearPiece(x, y) { // 清除指定位置棋子 this.ctx.clearRect(x * 30, y * 30, 30, 30); // 重绘网格线 this.redrawGridLines(x, y); } }2.2 游戏逻辑模块 (GameLogic.js)处理游戏规则和状态管理class GameLogic { constructor(size) { this.boardSize size; this.resetGame(); } resetGame() { this.board Array(this.boardSize).fill() .map(() Array(this.boardSize).fill(0)); this.currentPlayer 1; // 1: 黑棋, -1: 白棋 this.gameOver false; this.moveHistory []; } makeMove(x, y) { if (this.gameOver || this.board[x][y] ! 0) { return false; } this.board[x][y] this.currentPlayer; this.moveHistory.push({x, y, player: this.currentPlayer}); if (this.checkWin(x, y)) { this.gameOver true; return win; } if (this.checkDraw()) { this.gameOver true; return draw; } this.currentPlayer -this.currentPlayer; return true; } checkWin(x, y) { const directions [ [1, 0], [0, 1], [1, 1], [1, -1] // 横、竖、斜 ]; return directions.some(([dx, dy]) { let count 1; // 当前落子点 // 正向检查 for (let i 1; i 5; i) { const nx x dx * i; const ny y dy * i; if (this.isValidPosition(nx, ny) this.board[nx][ny] this.currentPlayer) { count; } else { break; } } // 反向检查 for (let i 1; i 5; i) { const nx x - dx * i; const ny y - dy * i; if (this.isValidPosition(nx, ny) this.board[nx][ny] this.currentPlayer) { count; } else { break; } } return count 5; }); } }2.3 AI玩家模块 (AIPlayer.js)实现AI决策逻辑采用评分算法class AIPlayer { constructor(gameLogic) { this.game gameLogic; } getBestMove() { let bestScore -Infinity; let bestMove null; // 遍历所有空位 for (let i 0; i this.game.boardSize; i) { for (let j 0; j this.game.boardSize; j) { if (this.game.board[i][j] 0) { // 模拟落子 this.game.board[i][j] -1; // AI执白 // 评估位置 const score this.evaluatePosition(i, j); // 撤销模拟 this.game.board[i][j] 0; if (score bestScore) { bestScore score; bestMove {x: i, y: j}; } } } } return bestMove; } evaluatePosition(x, y) { // 评分表 const patterns { 11111: 100000, // 五连 011110: 10000, // 活四 01110: 1000, // 活三 011010: 500, // 跳活三 010110: 500, 001100: 100 // 活二 }; let totalScore 0; const directions [[1,0],[0,1],[1,1],[1,-1]]; directions.forEach(([dx, dy]) { let line ; // 收集5x5区域内的棋子状态 for (let i -2; i 2; i) { const nx x dx * i; const ny y dy * i; if (this.game.isValidPosition(nx, ny)) { line this.game.board[nx][ny] -1 ? 1 : this.game.board[nx][ny] 1 ? 2 : 0; } else { line 2; // 边界视为对手棋子 } } // 匹配评分模式 for (const [pattern, score] of Object.entries(patterns)) { if (line.includes(pattern)) { totalScore score; } } }); return totalScore; } }3. 模块间通信与事件总线为了避免模块间直接依赖我们实现一个简单的事件总线class EventBus { constructor() { this.events {}; } on(event, callback) { if (!this.events[event]) { this.events[event] []; } this.events[event].push(callback); } emit(event, data) { if (this.events[event]) { this.events[event].forEach(cb cb(data)); } } } // 使用示例 const bus new EventBus(); // 棋盘模块订阅落子事件 bus.on(moveMade, (move) { board.drawPiece(move.x, move.y, move.player); }); // 游戏逻辑模块触发事件 bus.emit(moveMade, {x: 3, y: 3, player: 1});4. 主入口与模块整合在index.js中整合所有模块import BoardManager from ./modules/BoardManager.js; import GameLogic from ./modules/GameLogic.js; import AIPlayer from ./modules/AIPlayer.js; import EventBus from ./modules/EventBus.js; // 初始化 const bus new EventBus(); const game new GameLogic(15); const board new BoardManager(15, document.getElementById(game-container)); const ai new AIPlayer(game); // 事件绑定 board.canvas.addEventListener(click, (e) { const rect board.canvas.getBoundingClientRect(); const x Math.floor((e.clientX - rect.left - 20) / 30); const y Math.floor((e.clientY - rect.top - 20) / 30); if (game.makeMove(x, y) true) { board.drawPiece(x, y, game.currentPlayer); // AI回合 setTimeout(() { const aiMove ai.getBestMove(); if (aiMove game.makeMove(aiMove.x, aiMove.y)) { board.drawPiece(aiMove.x, aiMove.y, game.currentPlayer); } }, 500); } }); // 重新开始按钮 document.getElementById(restart).addEventListener(click, () { game.resetGame(); board.clear(); });5. 进阶优化与扩展完成基础模块化后可以考虑以下进阶优化5.1 状态管理优化使用状态模式管理游戏状态class GameState { constructor(game) { this.game game; } onMove() {} onRestart() {} } class PlayingState extends GameState { onMove(x, y) { const result this.game.makeMove(x, y); if (result win) { this.game.setState(new GameOverState(this.game)); } } } class GameOverState extends GameState { onRestart() { this.game.resetGame(); this.game.setState(new PlayingState(this.game)); } }5.2 性能优化对于AI计算进行优化// 使用位运算加速评估 const evaluateLine (line) { const patterns { 0b11111: 100000, // 五连 0b011110: 10000, // 活四 0b01110: 1000 // 活三 }; let score 0; let mask 0b11111; for (let i 0; i line.length - 5; i) { const segment (line i) mask; if (patterns[segment]) { score patterns[segment]; } } return score; };5.3 功能扩展添加悔棋功能实现class GameLogic { // ...其他代码 undoMove() { if (this.moveHistory.length 0) return false; const lastMove this.moveHistory.pop(); this.board[lastMove.x][lastMove.y] 0; this.currentPlayer lastMove.player; this.gameOver false; return lastMove; } } // 在BoardManager中添加 clearPiece(x, y) { this.ctx.clearRect(x * 30 - 15, y * 30 - 15, 30, 30); this.redrawGridLines(x, y); }6. 工程化与构建最后我们可以使用现代前端工具链对项目进行构建使用Webpack打包模块添加Babel支持ES6语法配置ESLint保证代码质量添加Jest进行单元测试示例webpack配置module.exports { entry: ./src/index.js, output: { filename: bundle.js, path: path.resolve(__dirname, dist) }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: babel-loader, options: { presets: [babel/preset-env] } } } ] } };通过这样的模块化重构我们将原本300行的单一文件拆分为职责分明的多个模块代码量虽然有所增加但可维护性和可扩展性得到了显著提升。这种架构也更容易添加新功能如保存棋局、多种AI难度、在线对战等。

相关新闻