源本科技 | 码上会

Java 井字棋小游戏

2025/12/26
68
0

学习目标

  • 掌握使用数组模拟 3×3 游戏棋盘

  • 熟练运用条件判断与循环控制游戏流程

  • 理解胜负判定逻辑的实现方式

  • 能够处理用户输入验证与异常情况

  • 构建完整的控制台交互式游戏程序


游戏规则

游戏规则

  • 两名玩家轮流在 3×3 网格 上放置自己的标记(X 或 O)

  • X 先手,O 后手

  • 目标:横、竖、斜任一方向连成三个相同标记

  • 若所有格子填满仍未分胜负,则为平局

棋盘布局(编号 1~9)

|---|---|---|
| 1 | 2 | 3 |
|-----------|
| 4 | 5 | 6 |
|-----------|
| 7 | 8 | 9 |
|---|---|---|

设计技巧:用 长度为 9 的一维字符串数组 表示棋盘


核心数据结构

static String[] board = new String[9]; // 棋盘
static String turn = "X";              // 当前轮到谁

初始化棋盘:

for (int i = 0; i < 9; i++) {
    board[i] = String.valueOf(i + 1); // ["1", "2", ..., "9"]
}

关键功能实现

1. 打印棋盘

static void printBoard() {
    System.out.println("|---|---|---|");
    System.out.println("| " + board[0] + " | " + board[1] + " | " + board[2] + " |");
    System.out.println("|-----------|");
    System.out.println("| " + board[3] + " | " + board[4] + " | " + board[5] + " |");
    System.out.println("|-----------|");
    System.out.println("| " + board[6] + " | " + board[7] + " | " + board[8] + " |");
    System.out.println("|---|---|---|");
}

输出效果清晰直观,符合用户预期


2. 判定胜负

胜利组合(共 8 种)

  • :(0,1,2), (3,4,5), (6,7,8)

  • :(0,3,6), (1,4,7), (2,5,8)

  • 对角线:(0,4,8), (2,4,6)

实现逻辑

static String checkWinner() {
    // 检查 8 种胜利情况
    for (int i = 0; i < 8; i++) {
        String line = switch (i) {
            case 0 -> board[0] + board[1] + board[2];
            case 1 -> board[3] + board[4] + board[5];
            case 2 -> board[6] + board[7] + board[8];
            case 3 -> board[0] + board[3] + board[6];
            case 4 -> board[1] + board[4] + board[7];
            case 5 -> board[2] + board[5] + board[8];
            case 6 -> board[0] + board[4] + board[8];
            case 7 -> board[2] + board[4] + board[6];
            default -> "";
        };
        
        if (line.equals("XXX")) return "X";
        if (line.equals("OOO")) return "O";
    }

    // 检查是否平局(所有格子都被占用)
    boolean isDraw = true;
    for (int i = 0; i < 9; i++) {
        if (board[i].equals(String.valueOf(i + 1))) {
            isDraw = false; // 还有空位
            break;
        }
    }
    if (isDraw) return "draw";

    // 游戏继续
    System.out.println(turn + "'s turn; enter a slot number to place " + turn + " in:");
    return null;
}

优化建议:可改用二维数组或常量数组存储胜利组合,提升可读性


3. 主游戏循环

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    // 初始化...
    
    while (winner == null) {
        try {
            int numInput = in.nextInt();
            
            // 1. 输入范围校验 (1~9)
            if (numInput < 1 || numInput > 9) {
                System.out.println("Invalid input; re-enter slot number:");
                continue;
            }
            
            // 2. 检查格子是否已被占用
            if (board[numInput - 1].equals(String.valueOf(numInput))) {
                board[numInput - 1] = turn;           // 放置标记
                turn = turn.equals("X") ? "O" : "X";  // 切换回合
                printBoard();                         // 重绘棋盘
                winner = checkWinner();               // 检查结果
            } else {
                System.out.println("Slot already taken; re-enter slot number:");
            }
            
        } catch (InputMismatchException e) {
            System.out.println("Invalid input; please enter a number.");
            in.nextLine(); // 清除无效输入,防止死循环
        }
    }
    
    // 输出最终结果
    if ("draw".equals(winner)) {
        System.out.println("It's a draw! Thanks for playing.");
    } else {
        System.out.println("Congratulations! " + winner + "'s have won!");
    }
}

完整代码

import java.util.InputMismatchException;
import java.util.Scanner;

public class TicTacToe {
  static String[] board;
  static String turn;

  static String checkWinner() {
    // 所有可能的胜利组合
    int[][] winPatterns = {
            {0, 1, 2}, {3, 4, 5}, {6, 7, 8}, // 行
            {0, 3, 6}, {1, 4, 7}, {2, 5, 8}, // 列
            {0, 4, 8}, {2, 4, 6}             // 对角线
    };

    for (int[] pattern : winPatterns) {
      String a = board[pattern[0]];
      String b = board[pattern[1]];
      String c = board[pattern[2]];
      if (a.equals(b) && b.equals(c)) {
        return a; // 返回 "X" 或 "O"
      }
    }

    // 检查平局
    for (String cell : board) {
      if (cell.matches("[1-9]")) {
        System.out.println(turn + " 的回合;请输入一个位置数字放置 " + turn + ":");
        return null; // 游戏继续
      }
    }
    return "平局";
  }

  static void printBoard() {
    System.out.println("|---|---|---|");
    System.out.println("| " + board[0] + " | " + board[1] + " | " + board[2] + " |");
    System.out.println("|-----------|");
    System.out.println("| " + board[3] + " | " + board[4] + " | " + board[5] + " |");
    System.out.println("|-----------|");
    System.out.println("| " + board[6] + " | " + board[7] + " | " + board[8] + " |");
    System.out.println("|---|---|---|");
  }

  public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    board = new String[9];
    turn = "X";

    // 初始化棋盘
    for (int i = 0; i < 9; i++) {
      board[i] = String.valueOf(i + 1);
    }

    System.out.println("欢迎来到 3x3 井字棋游戏");
    printBoard();
    System.out.println("X 先行。请输入一个位置数字 (1-9):");

    String winner = null;
    while (winner == null) {
      try {
        int numInput = in.nextInt();
        if (numInput < 1 || numInput > 9) {
          System.out.println("请输入 1 到 9 之间的数字:");
          continue;
        }

        int index = numInput - 1;
        if (board[index].matches("[1-9]")) {
          board[index] = turn;
          turn = turn.equals("X") ? "O" : "X";
          printBoard();
          winner = checkWinner();
        } else {
          System.out.println("位置已被占用!请选择其他位置:");
        }
      } catch (InputMismatchException e) {
        System.out.println("输入无效!请输入一个数字:");
        in.nextLine();
      }
    }

    if ("平局".equals(winner)) {
      System.out.println("平局!感谢游玩");
    } else {
      System.out.println("恭喜! " + winner + " 获胜!");
    }
    in.close();
  }
}

改进点

  • 使用 winPatterns 二维数组替代 switch,逻辑更清晰

  • 用正则 matches("[1-9]") 判断是否为空位

  • 提示信息更友好


运行示例

欢迎来到 3x3 井字棋游戏
|---|---|---|
| 1 | 2 | 3 |
|-----------|
| 4 | 5 | 6 |
|-----------|
| 7 | 8 | 9 |
|---|---|---|
X 先行。请输入一个位置数字 (1-9):
3
|---|---|---|
| 1 | 2 | X |
|-----------|
| 4 | 5 | 6 |
|-----------|
| 7 | 8 | 9 |
|---|---|---|
O 的回合;请输入一个位置数字放置 O:
5
|---|---|---|
| 1 | 2 | X |
|-----------|
| 4 | O | 6 |
|-----------|
| 7 | 8 | 9 |
|---|---|---|
...
恭喜! X 获胜!

扩展思考

可能的增强功能

  1. 人机对战:加入简单 AI(如随机走或优先占中心)

  2. 图形界面:使用 Swing 或 JavaFX 实现 GUI

  3. 记录战绩:统计胜 / 负 / 平局次数

  4. 重玩机制:游戏结束后询问是否再玩一局

  5. 输入高亮:显示合法可选位置

设计模式应用

  • 可引入 状态模式 管理游戏状态(进行中、X 胜、O 胜、平局)

  • 使用 策略模式 支持不同难度的 AI 玩家


重点总结

  • 一维数组 高效表示 3×3 棋盘

  • 8 种胜利组合 是判定核心,需全面覆盖

  • 输入验证 是健壮性的关键(范围 + 占用检查)

  • final 不适用于此场景,因棋盘内容需动态更新

  • 控制台游戏注重 清晰提示友好交互


思考题

  1. 当前 checkWinner() 方法在每次调用时都遍历全部 8 种组合。能否优化为只检查刚落子位置相关的行 / 列 / 对角线

  2. 如果将棋盘改为 4×4 或 5×5,需要修改哪些部分?如何让程序支持任意 N×N 井字棋?

  3. 如何防止两个玩家输入相同的符号(如都选 X)?请设计一个玩家选择标记的流程。