1. 引言
本文将介绍如何用Java实现经典游戏四子棋。 我们会先了解游戏规则和玩法,然后逐步实现这些规则。
2. 什么是四子棋?
在实现游戏前,必须先理解游戏规则。
四子棋规则相对简单:玩家轮流将棋子放入任意一列的顶部。每次落子后,如果某玩家的棋子在水平、垂直或对角线方向连成四子,则该玩家获胜:
若无人获胜,则轮到下一位玩家。游戏持续进行直到某玩家获胜或棋盘填满(平局)。
⚠️ 关键规则:玩家可自由选择落子的列,但棋子必须落在该列的最上方空位,不能自由选择行。
实现这个游戏需要考虑三个核心组件:游戏棋盘、落子机制和胜负判定。 我们将逐一实现这些功能。
3. 定义游戏棋盘
开始游戏前,需要先创建游戏棋盘。 棋盘包含所有可落子的格子,并记录已放置的棋子位置。
首先定义玩家棋子的枚举类型:
public enum Piece {
PLAYER_1,
PLAYER_2
}
这里假设游戏只有两名玩家,这是四子棋的典型设定。
接下来创建棋盘类:
public class GameBoard {
private final List<List<Piece>> columns;
private final int rows;
public GameBoard(int columns, int rows) {
this.rows = rows;
this.columns = new ArrayList<>();
for (int i = 0; i < columns; ++i) {
this.columns.add(new ArrayList<>());
}
}
public int getRows() {
return rows;
}
public int getColumns() {
return columns.size();
}
}
这里使用嵌套的List表示棋盘:每个内层List代表一列,列表中的元素表示该列中的棋子。
棋子必须从底部向上堆叠,因此无需处理中间空隙——所有空隙都在已放置棋子的上方。 实际存储时,我们按棋子加入列的顺序保存。
添加辅助方法获取指定位置的棋子:
public Piece getCell(int x, int y) {
assert(x >= 0 && x < getColumns());
assert(y >= 0 && y < getRows());
List<Piece> column = columns.get(x);
if (column.size() > y) {
return column.get(y);
} else {
return null;
}
}
参数说明:
x
:列索引(从0开始)y
:行索引(从底部开始为0) 返回该位置的棋子,若为空则返回null
。
4. 实现落子功能
有了棋盘后,需要实现落子功能。 玩家通过将棋子放入指定列的顶部来落子:
public void move(int x, Piece player) {
assert(x >= 0 && x < getColumns());
List<Piece> column = columns.get(x);
if (column.size() >= this.rows) {
throw new IllegalArgumentException("该列已满");
}
column.add(player);
}
✅ 关键点:
- 检查列索引是否有效
- 检查该列是否已满(棋子数超过行数)
- 将新棋子添加到列的末尾(即顶部)
5. 胜负判定逻辑
玩家落子后,需要检查是否获胜。 获胜条件是棋子在水平、垂直或对角线方向连成四子。
但我们可以优化检查逻辑,利用游戏规则的特点:
- 只有刚落子的玩家可能获胜(游戏在有人获胜时立即结束)
- 获胜线必须包含刚落子的棋子(无需检查整个棋盘)
- 利用列堆叠特性排除不可能情况(例如垂直线要求棋子至少在第4行)
最终只需检查以下13条可能的线:
- 1条垂直线:从当前棋子向下延伸3格
- 4条水平线:分别从当前棋子左侧3格到右侧3格(滑动检查)
- 4条主对角线线:从当前棋子左上3格到右下3格
- 4条副对角线线:从当前棋子左下3格到右上3格
实际检查时,部分线可能超出棋盘边界(无效线):
如图所示,部分线会超出棋盘范围,这些线不可能形成获胜条件。
5.1 检查单条线
首先实现检查单条线的方法:给定起点和方向,检查该线上四个格子是否都属于当前玩家:
private boolean checkLine(int x1, int y1, int xDiff, int yDiff, Piece player) {
for (int i = 0; i < 4; ++i) {
int x = x1 + (xDiff * i);
int y = y1 + (yDiff * i);
if (x < 0 || x > columns.size() - 1) {
return false;
}
if (y < 0 || y > rows - 1) {
return false;
}
if (player != getCell(x, y)) {
return false;
}
}
return true;
}
✅ 优化点:
- 循环中同时检查边界和棋子归属
- 发现任何不匹配立即返回
false
- 仅检查4个格子,无需预计算边界
5.2 检查所有可能线
接下来检查所有可能的获胜线:只要有一条线满足条件即判定获胜:
private boolean checkWin(int x, int y, Piece player) {
// 垂直线
if (checkLine(x, y, 0, -1, player)) {
return true;
}
for (int offset = 0; offset < 4; ++offset) {
// 水平线
if (checkLine(x - 3 + offset, y, 1, 0, player)) {
return true;
}
// 主对角线(左上到右下)
if (checkLine(x - 3 + offset, y + 3 - offset, 1, -1, player)) {
return true;
}
// 副对角线(左下到右上)
if (checkLine(x - 3 + offset, y - 3 + offset, 1, 1, player)) {
return true;
}
}
return false;
}
🔍 算法解析:
- 垂直线:从当前棋子向下检查
- 水平线:滑动检查4条可能的线(从左3格到右3格)
- 对角线:类似水平线,但需同时调整行和列
最后更新move()
方法,返回是否获胜:
public boolean move(int x, Piece player) {
// 前面的代码保持不变
return checkWin(x, column.size() - 1, player);
}
5.3 游戏演示
至此已实现完整游戏逻辑。创建棋盘并轮流落子,直到有人获胜:
GameBoard gameBoard = new GameBoard(8, 6);
assertFalse(gameBoard.move(3, Piece.PLAYER_1));
assertFalse(gameBoard.move(2, Piece.PLAYER_2));
assertFalse(gameBoard.move(4, Piece.PLAYER_1));
assertFalse(gameBoard.move(3, Piece.PLAYER_2));
assertFalse(gameBoard.move(5, Piece.PLAYER_1));
assertFalse(gameBoard.move(6, Piece.PLAYER_2));
assertFalse(gameBoard.move(5, Piece.PLAYER_1));
assertFalse(gameBoard.move(4, Piece.PLAYER_2));
assertFalse(gameBoard.move(5, Piece.PLAYER_1));
assertFalse(gameBoard.move(5, Piece.PLAYER_2));
assertFalse(gameBoard.move(6, Piece.PLAYER_1));
assertTrue(gameBoard.move(4, Piece.PLAYER_2));
这段落子序列与文章开头的示例一致,最后一步落子后PLAYER_2
获胜。
6. 总结
本文介绍了四子棋的游戏规则,并展示了如何用Java实现核心逻辑。 不妨尝试自己动手实现,并扩展成完整的游戏应用。
本文的完整代码可在GitHub获取。