- 示例工程
【免费下载链接】awesome-low-level-design
Learn Low Level Design (LLD) and prepare for interviews using free resources.
导读
本文围绕 solutions/java/src/chessgame/README.md 展开,深入讲解该开源仓库中象棋(Chess)系统的 Low Level Design(LLD)实战方案:如何在 8x8 标准棋盘上实现双人对弈、棋子移动规则校验、回合管理与对局状态判定。读完本文,你将掌握一套可复用的面向对象建模思路(Board/Cell/Piece 分层、策略化棋子移动逻辑、统一异常处理),并能直接阅读、运行、扩展仓库中的 Java 实现,为面试中的 LLD 题目与真实系统设计提供参考。
Chess Game 类图
一、问题陈述与核心需求
原文档给出的问题陈述是:设计并实现一个国际象棋游戏,支持两名玩家在标准 8x8 棋盘上对弈,强制遵守国际象棋规则、校验走法合法性,并判定对局状态(将军 check、将死 checkmate、逼和 stalemate)。
对应的需求清单(README 原文要点)如下:
- 双人对弈:游戏在两名玩家(白方 White 与黑方 Black)之间进行;
- 标准棋盘:使用标准 8x8 棋盘;
- 全部标准棋子:王(King)、后(Queen)、车(Rook)、象(Bishop)、马(Knight)、兵(Pawn)及其各自的移动规则;
- 移动校验:按国际象棋规则校验走法,阻止非法移动;
- 回合管理:双方交替走棋;
- 对局状态:检测 check、checkmate、stalemate;
- 异常处理:对非法走法抛出异常;
- 可扩展性:易于增加走法历史、悔棋、AI 对手等功能。
从源码角度看,上述需求中的「双人对弈」「标准棋盘」「全部棋子」「移动校验」「回合管理」均已落地;而 check/checkmate/stalemate 的检测逻辑在 Board.java 中仍以// TODO: Implement checkmate logic/// TODO: Implement stalemate logic标注,当前返回false——这是阅读源码时值得注意的实现边界(详见第五节)。
二、核心实体总览
原文档给出了系统的核心实体清单,与仓库源码一一对应(全部位于 solutions/java/src/chessgame):
| 实体 | 文件 | 职责 |
|---|---|---|
| ChessGame | ChessGame.java | 主控类,管理游戏流程、玩家回合与对局状态 |
| Board | Board.java | 8x8 棋盘,维护 64 个 Cell 与棋子位置 |
| Cell | Cell.java | 棋盘上的一个格子,持有行列坐标与棋子 |
| Player | Player.java | 玩家,含姓名与颜色 |
| Color | Color.java | 枚举:WHITE、BLACK |
| Move | Move.java | 一次走法:起点 Cell → 终点 Cell |
| Piece(抽象) | pieces/Piece.java | 所有棋子的基类 |
| King/Queen/Rook/Bishop/Knight/Pawn | pieces/ | 具体棋子类,各自实现移动逻辑 |
| InvalidMoveException | InvalidMoveException.java | 非法走法异常 |
三、类设计详解(结合源码)
3.1 ChessGame:游戏主控与回合循环
ChessGame.java 是系统的「门面」,核心字段与逻辑如下:
- 字段:
Board board、Player whitePlayer/blackPlayer、Player currentPlayer; - 构造与开局:无参构造
new ChessGame()会创建new Board();随后通过setPlayers(whiteName, blackName)创建两名玩家,并将currentPlayer初始化为白方; - 主循环
start():只要isGameOver()不为真就持续执行——打印当前玩家、通过getPlayerMove()读取控制台输入(源行/列、目标行/列)、调用board.movePiece(move)落子、成功后switchTurn()切换玩家;若捕获到InvalidMoveException则打印错误信息并提示重试; - 回合切换
switchTurn():currentPlayer = currentPlayer == whitePlayer ? blackPlayer : whitePlayer,实现简单的三元切换; - 结束判定
isGameOver():组合board.isCheckmate(...)与board.isStalemate(...)四种情况; - 结果展示
displayResult():按将死/逼和分别输出「Black/White wins by checkmate!」或「The game ends in a stalemate!」。
源码级细节值得注意:getPlayerMove()中会校验piece == null || piece.getColor() != player.getColor(),不满足则抛出IllegalArgumentException("Invalid piece selection!"),防止玩家移动对方棋子或空格——这是回合管理在输入层的体现。
3.2 Board:棋盘状态与落子
Board.java 的关键实现:
- 初始化:
Cell[][] board为 8x8;setupPieces()按标准开局布局放子——第 1 行(row=1)放 8 个白兵,第 6 行(row=6)放 8 个黑兵;白方主将棋子在 row=0(Rook Knight Bishop Queen King Bishop Knight Rook),黑方在 row=7; movePiece(Move):声明为synchronized(线程安全的落子接口),先取起点棋子,若piece == null || !piece.canMove(this, from, to)则返回false,否则把棋子写入终点Cell并将起点置空;- 查询接口:
getCell(row, col)、getPiece(row, col)、setPiece(row, col, piece); - 状态检测:
isCheck(Color)、isCheckmate(Color)、isStalemate(Color)三个方法的签名已经设计好,但 checkmate/stalemate 的完整逻辑尚未填充(TODO)。
一个需要留意的实现差异:README 中描述Board.movePiece会抛出InvalidMoveException,而当前源码中movePiece直接返回boolean(非法走法返回false);ChessGame.start()的try/catch捕获的InvalidMoveException在本次落子链路中暂未实际抛出。阅读与扩展时需以 Board.java 的实际行为为准。
3.3 Cell、Move、Player、Color
- Cell.java:不可变的行列坐标(
row、colfinal),可变的Piece;提供isOccupied()、getPiece()、setPiece(); - Move.java:持有起点与终点两个
Cell引用,提供getStart()/getEnd(); - Player.java:
name与color均为 final,构造时一次性注入; - Color.java:极简枚举,仅
WHITE、BLACK,作为「阵营」的类型安全标记贯穿所有棋子与玩家。
3.4 Piece 抽象基类与多态设计
pieces/Piece.java 是所有棋子的抽象基类:
public abstract class Piece { protected final Color color; public Piece(Color color) { this.color = color; } public abstract boolean canMove(Board board, Cell from, Cell to); public Color getColor() { return color; } }设计要点是:把「该棋子能否从 A 走到 B」抽象为canMove(Board, Cell, Cell),由每个子类通过多态各自实现。Board.movePiece只依赖这个统一接口做校验,因此新增棋子类型无需改动棋盘层——这正是 LLD 面试中「开闭原则」的典型示范。注意抽象方法还额外接收Board参数,为后续实现「阻挡路径」「吃子」「将军检测」等需要查询棋盘上下文的规则预留了接口。
四、六种棋子的移动规则源码剖析
原文档只列出棋子类名,这里结合 pieces/ 源码把每类棋子的判定逻辑拆解清楚(rowDiff = |to.row - from.row|,colDiff = |to.col - from.col|):
| 棋子 | 文件 | 判定规则(源码逻辑) |
|---|---|---|
| 兵 Pawn | Pawn.java | 白方:前进 1 格(rowDiff==1 && colDiff==0)、起始行 row=1 可走 2 格(rowDiff==2)、斜吃子(rowDiff==1 && colDiff==1且目标格有子);黑方对称(方向取反、起始行为 row=6)。该实现已覆盖方向性移动与斜向吃子,但尚未处理「升变」「吃过路兵」 |
| 马 Knight | Knight.java | 经典 L 形:(rowDiff==2 && colDiff==1) || (rowDiff==1 && colDiff==2),天然可越过棋子 |
| 象 Bishop | Bishop.java | 仅对角线:rowDiff == colDiff(当前版本未校验路径上是否被阻挡) |
| 车 Rook | Rook.java | 仅直线:from.row == to.row || from.col == to.col(当前版本未校验路径阻挡) |
| 后 Queen | Queen.java | 车 + 象的组合:(rowDiff == colDiff) || (from.row == to.row || from.col == to.col) |
| 王 King | King.java | 任意方向 1 格:rowDiff <= 1 && colDiff <= 1(未实现王车易位 castling) |
从源码结构可以推断:当前版本聚焦「棋子自身几何走法」这一核心校验;路径阻挡(sliding pieces 中间有子不可穿过)、将军约束等更完整的规则属于后续可扩展点,且canMove的Board参数已为这类规则留好了扩展入口。
五、示例用法(对照源码的可行版本)
README 给出了示例,但其中new ChessGame(white, black)的带参构造在 ChessGame.java 中并不存在——实际实现是无参构造 +setPlayers(whiteName, blackName)。以当前源码为准的可运行示例:
import chessgame.ChessGame; public class Main { public static void main(String[] args) { ChessGame game = new ChessGame(); game.setPlayers("Alice", "Bob"); // 白方 Alice,黑方 Bob game.start(); // 进入回合循环,控制台输入走法 } }若希望像 README 那样构造一个显式的Move对象,可参照源码的 Cell/Move 接口:
// 假设:new ChessGame() + setPlayers 后拿到棋盘 Board board = game.getBoard(); // getBoard() 为示例示意,需按需补充访问器 Move move = new Move(board.getCell(6, 4), board.getCell(4, 4)); // 黑方 row=6 col=4 → row=4 col=4(类似 e2→e4 的下移一格概念)说明:仓库当前
ChessGame未暴露getBoard()访问器,README 中的game.getBoard()属于文档侧示意;实际控制台交互由ChessGame.getPlayerMove()读取 row/col 数字完成,上述 Move 直接构造更适合用于单元测试或程序化走棋。
六、Demo 运行方式
仓库提供了演示入口 ChessGameDemo.java:
public class ChessGameDemo { public static void run() { ChessGame chessGame = new ChessGame(); chessGame.setPlayers("Alice", "Bob"); chessGame.start(); } }README 中说明「参见ChessGameDemo.java获取示例用法与对局模拟」。该 Demo 以控制台交互为核心:每回合提示当前玩家名、依次输入源行/列与目标行/列(范围 0–7),非法选择会收到Illegal piece selection!提示,非法走法则被start()循环内的异常捕获逻辑拦下并要求重试。玩家可通过 solutions/java/src/chessgame 目录下的ChessGameDemo入口在本地 Java 环境中运行体验。
七、扩展框架:下一步演进方向
README 在「Extending the Framework」中给出了三个官方建议方向,结合源码现状可进一步细化为:
- 走法历史 / 悔棋 / 回放:可在
ChessGame中维护List<Move>或引入 Memento 风格的快照(仓库在 design-patterns 目录下提供多种语言的 Memento 模式参考实现);Move已封装起点/终点,天然适合入栈记录; - AI 对手:实现一个实现同一
Player抽象(或独立策略接口)的电脑玩家,用canMove枚举所有合法走法并配合评估函数选子; - GUI:
Board.printBoard()尚未出现在当前 Board.java 中(README 类设计里列有该方法),可将其实现为文本棋盘输出,或在此基础上对接 Swing/JavaFX 等图形界面。
此外,从源码缺失处可推断出更贴近「完整规则」的扩展清单:填充isCheck/isCheckmate/isStalemate的 TODO 逻辑、滑动棋子(车/象/后)的路径阻挡校验、兵升变、王车易位、吃过路兵,以及把Board.movePiece的false返回值与InvalidMoveException统一起来,形成 README 所描述的「非法走法抛异常」语义。
八、快速索引
- 需求与设计文档:solutions/java/src/chessgame/README.md
- 主控与流程:ChessGame.java
- 棋盘与落子:Board.java
- 格子 / 走法 / 玩家 / 颜色:Cell.java · Move.java · Player.java · Color.java
- 棋子体系:pieces/Piece.java 及 pieces/ 下六种棋子
- 演示入口:ChessGameDemo.java
- 类图:class-diagrams/chessgame-class-diagram.png
- 示例工程
【免费下载链接】awesome-low-level-design
Learn Low Level Design (LLD) and prepare for interviews using free resources.
相关推荐
电梯调度系统 LLD 实战指南:从需求建模到 C++ 并发调度实现(awesome-low-level-design 仓库解析)
电梯调度系统 LLD 实战指南:从需求建模到 C++ 并发调度实现(awesome low level design 仓库解析) 导读 本文以开源仓库 awes
示例工程awesome-low-level-design 仓库全解析:从 OOP 基础到 LLD 面试实战的完整学习路线
awesome low level design 仓库全解析:从 OOP 基础到 LLD 面试实战的完整学习路线 这篇指南围绕 awesome low leve
示例工程Snake and Ladder Game 低层设计实战:从需求拆解到多会话并发架构(awesome-low-level-design)
Snake and Ladder Game 低层设计实战:从需求拆解到多会话并发架构(awesome low level design) 本文以 awesome
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考