使用 Java 實作 Connect 4 遊戲
一、簡介
在本文中,我們將了解如何用 Java 實作Connect 4遊戲。我們將了解遊戲的外觀和玩法,然後研究如何實作這些規則。
2. Connect 4是什麼?
在我們實現遊戲之前,我們需要了解遊戲規則。
Connect 4 是一款相對簡單的遊戲。玩家輪流將棋子放到一組棋子的頂端。每回合後,如果任何玩家的棋子在任何直線方向(水平、垂直或對角線)形成四排,那麼該玩家就是贏家:
如果沒有,下一個玩家就可以繼續前進。然後重複此過程,直到一名玩家獲勝或遊戲無法獲勝。
值得注意的是,玩家可以自由選擇放置棋子的列,但該棋子必須放在堆的頂部。他們無法自由選擇自己的作品位於該列的哪一行。
要將其建構成電腦遊戲,我們需要考慮幾個不同的組件:遊戲板本身、玩家放置令牌的能力以及檢查遊戲是否獲勝的能力。我們將依次討論其中的每一個。
3. 定義遊戲板
在玩遊戲之前,我們首先需要有地方玩。這是一個遊戲板,其中包含玩家可以玩的所有單元格,並指示玩家已經放置棋子的位置。
我們將首先編寫一個枚舉來表示玩家可以在遊戲中使用的棋子:
public enum Piece {
PLAYER_1,
PLAYER_2
}
這假設遊戲中只有兩名玩家,這是 Connect 4 的典型情況。
現在,我們將創建一個代表遊戲板的類別:
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();
}
}
在這裡,我們用一個清單來代表遊戲板。每個清單代表遊戲中的一整列,清單中的每個條目代表該列中的一個棋子。
碎片必須從底部堆疊,因此我們不需要考慮間隙。相反,所有間隙都位於插入件上方的列頂部。因此,我們實際上是按照新增到列中的順序儲存這些片段的。
接下來,我們將新增一個助手來取得目前位於棋盤上任何給定儲存格中的棋子:
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 座標和從底行開始的 Y 座標。然後,我們將傳回該儲存格的正確Piece
,如果該儲存格中還沒有任何內容,則傳回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("That column is full");
}
column.add(player);
}
我們還在這裡添加了額外的檢查。如果相關列中已經有太多棋子,那麼這將引發異常,而不是允許玩家移動。
5. 檢查獲勝條件
一旦玩家移動,下一步就是檢查他們是否獲勝。這意味著在棋盤上的任何位置尋找來自同一玩家的水平、垂直或對角線的四個棋子。
然而,我們可以做得更好。我們從遊戲的玩法中了解到一些事實,可以讓我們簡化搜尋。
首先,因為遊戲在下完獲勝棋子後就結束了,所以只有剛下棋的玩家才能獲勝。這意味著我們只需要檢查該玩家棋子的行數。
其次,獲勝線必須包含剛剛放置的棋子。這意味著我們不需要搜尋整個棋盤,而只需搜尋包含已玩棋子的子集。
第三,由於遊戲的列性質,我們可以忽略某些不可能的情況。例如,如果最新的部分至少位於第 4 行,則我們只能有一條垂直線。低於該值的任何內容,且一行中不能有四個。
最終,這意味著我們需要搜尋以下集合:
- 從最新的作品開始並向下三行的單垂直線
- 四種可能的水平線之一 - 第一條從左側三列開始,在我們最新的作品上結束,而最後一條從我們最新的作品開始,在右側三列結束
- 四種可能的前導對角線之一 - 第一條從我們最新的作品左側三列和上方三行開始,而最後一條從我們最新的作品開始並在右側三列和下方三行結束
- 四種可能的尾隨對角線之一 - 第一條從我們最新的作品左側三列和下方三行開始,而最後一條從我們最新的作品開始並在右側三列和上方三行結束
這意味著每次移動後,我們必須檢查最多 13 條可能的線——考慮到棋盤的大小,其中一些可能是不可能的:
例如,在這裡,我們可以看到有幾條線落在遊戲區域之外,因此永遠不可能是獲勝線。
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;
}
我們也會檢查這些細胞是否存在,如果我們檢查過一個不存在的細胞,我們會立即返回這不是一條獲勝線。我們可以在循環之前執行此操作,但我們只檢查四個單元格,並且計算行的開頭和結尾的額外複雜性在這種情況下沒有好處。
5.2.檢查所有可能的線路
接下來,我們需要檢查所有可能的行。如果其中任何一個返回true
,那麼我們可以立即停止並宣布玩家獲勝。畢竟,他們是否能夠在同一步棋中獲得多條獲勝線並不重要:
private boolean checkWin(int x, int y, Piece player) {
// Vertical line
if (checkLine(x, y, 0, -1, player)) {
return true;
}
for (int offset = 0; offset < 4; ++offset) {
// Horizontal line
if (checkLine(x - 3 + offset, y, 1, 0, player)) {
return true;
}
// Leading diagonal
if (checkLine(x - 3 + offset, y + 3 - offset, 1, -1, player)) {
return true;
}
// Trailing diagonal
if (checkLine(x - 3 + offset, y - 3 + offset, 1, 1, player)) {
return true;
}
}
return false;
}
這適用於從左到右的滑動偏移,並使用它來確定每條線上的起始位置。這些行首先向左滑動三個單元格,因為第四個單元格是我們目前正在玩的單元格,必須將其包括在內。檢查的最後一行從剛剛播放的單元格開始,向右移動三個單元格。
最後,我們更新move()
函數來檢查獲勝狀態並相應地返回true
或false
:
public boolean move(int x, Piece player) {
// Unchanged from before.
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));
這組動作正是我們一開始看到的,我們可以看到最後一個活動如何返回,現在已經贏得了比賽。
六,結論
在這裡,我們了解了 Connect 4 遊戲的玩法以及如何用 Java 實作規則。為什麼不嘗試自己建造它並用它製作一個完整的遊戲呢?
與往常一樣,本文中的程式碼可以在 GitHub 上取得。