一、两人井字棋游戏

在井字游戏中,两个玩家使用各自的标志(一方用 X 则另一方就用 O ),轮流标记 3 ×3 的网格中的某个空格。当一个玩家在网格的水平方向、垂直方向或者对角线方向上标记了三个相同的 X 或三个相同的 O 时,游戏结束,该玩家获胜。平局(没有赢家)是指当网格中所有的空格都被填满时没有玩家获胜的情况。
 

import java.util.Scanner;
public class Game {
    private static Scanner input = new Scanner(System.in);
    public static void main(String[] args) {
        char[][] board = new char[3][3];
        displayBoard(board);
        while (true) {
            makeAMove(board, 'X');
            displayBoard(board);
            if (isWon('X', board)) {
                System.out.println("玩家 X 获胜");
                System.exit(1);
            }
            else if (isDraw(board)) {
                System.out.println("平局");
                System.exit(2);
            }
            makeAMove(board, 'O');
            displayBoard(board);
            if (isWon('O', board)) {
                System.out.println("玩家 O 获胜");
                System.exit(3);
            }
            else if (isDraw(board)) {
                System.out.println("平局");
                System.exit(4);
            }
        }
    }
    private static void makeAMove(char[][] board, char player) {
        boolean done = false;
        do {
            System.out.print("玩家 " + player + " :(行,列) = ");
            int row = input.nextInt();
            int column = input.nextInt();
            if (board[row][column] == '\0') {
                board[row][column] = player;
                done = true;
            }
            else
                System.out.println("无效输入");
        }
        while (!done);
    }
    private static void displayBoard(char[][] board) {
        System.out.println("\n ——— ——— ———");
        for (int i = 0; i < 3; i++) {
            System.out.print("| ");
            for (int j = 0; j < 3; j++)
                System.out.print(board[i][j] != '\0' ?  board[i][j] + " | ": "  | ");
            System.out.println("\n ——— ——— ———");
        }
    }
    private static boolean isWon(char ch, char[][] board) {
        for (int i = 0; i < 3; i++)
            if (ch == board[i][0] && ch == board[i][1] && ch == board[i][2])
                return true;
        for (int j = 0; j < 3; j++)
            if (ch == board[0][j] && ch == board[1][j] && ch == board[2][j])
                return true;
        if (ch == board[0][0] && ch == board[1][1] && ch == board[2][2])
            return true;
        return ch == board[0][2] && ch == board[1][1] && ch == board[2][0];
    }
    private static boolean isDraw(char[][] board) {
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++)
                if (board[i][j] == '\0')
                    return false;
        return true;
    }
}

二、多人井字棋游戏

今天我们要做的就是对其进行创新。新增规则如下:

(1)支持3~10人同时玩游戏。棋盘大小为玩家人数+1

(2)游戏开始前,设定游戏获胜需要多少个连续的棋子,连续棋子的个数最少为3,最大为玩家人数+1(即棋盘的大小)

代码

设计了3个类

java实现井字棋人机对战 井字棋联机_java

 1. Board 类

package com.tictactoe;

public class Board {
    private char[][] board ;

    //创建棋盘
    //Done!
    public void createBoard(int size){
        board = new char[size][size];  //创建初始棋盘,棋子默认为空
    }

    //绘制棋盘
    //Done!
    public void drawBoard(int size){
        for(int i=0; i< size; i++){  //最上面的线
            System.out.print(" ———");
        }
        System.out.print("\n");
        for (int i = 0; i < size; i++) { //每一列
            System.out.print("| ");
            for (int j = 0; j < size; j++)
                System.out.print(board[i][j] != '\0' ?  board[i][j] + " | ": "  | ");
            System.out.print("\n");
            for(int k=0; k< size; k++){  //最下面的线
                System.out.print(" ———");
            }
            System.out.print("\n");
        }
        System.out.print("\n");
   }

   //玩家下棋后,改变棋盘数组board
   //Done!
    public void changeBoard(int row,int column,char player){
        board[row][column]=player;
    }

    //查看棋盘
    //Done!
    public char[][] getBoard() {
        return board;
    }
}

棋盘类中没有什么难点。主要就是创建一个二维char类型的空棋盘、在控制台画出棋盘和已有的棋子、改变棋盘等操作。

2. GameLogic 类(难点!)

package com.tictactoe;
import java.util.Scanner;


public class GameLogic {
    private static Scanner input = new Scanner(System.in);

    //玩家下一个棋子,改变棋盘布局
    //Done!
    public void makeAMove(Board board, char player) {
        char[][] board2 = board.getBoard();     //获取棋盘目前状态
        boolean done = false;  //用来判断循环,用户第一次输入的是正确位置就一次循环;否则进入下一次
        do {
            System.out.print("玩家 " + player + " :(行,列) = ");
            int row = input.nextInt();  //玩家输入棋子的行数
            int column = input.nextInt();  //玩家输入棋子的列数
            if (board2[row][column] == '\0') {
                board.changeBoard(row,column,player);  //玩家成功下了一棋!
                done = true;  //有位置就放下,跳出这个循环
            }
            else
                System.out.println("无效输入");  //玩家选择的位置已经有棋子了,重新下
        }
        while (!done);
    }

    //判断player是否赢了,返回true为赢
    //思路:仿照卷积核进行卷积的过程
    public boolean isWon(char player, Board board,int piece,int size) {
        char[][] board2 = board.getBoard();     //获取棋盘目前状态

        for(int row=0; row<=size-piece;row++){
            for(int col=0;col<=size-piece;col++){
                /*-----------------以piece×piece为大小的内部检查-------------*/
                //行相同
                for(int i=row;i<row+piece;i++){
                    for(int j=col;j<col+piece;j++){
                        if(player==board2[i][j]){
                            if(j==col+piece-1) return true; //找到满足条件的行
                        }else break;  //这一行不满足,跳出内循环,检查下一行
                    }
                }
                //列相同
                for(int j=col;j<col+piece;j++){
                    for(int i=row;i<row+piece;i++){
                        if(player==board2[i][j]){
                            if(i==row+piece-1) return true; //找到满足条件的列
                        }else break; //这一列不满足,检查下一列
                    }
                }
                //正对角线相同
                for(int k=0;k<piece;k++){
                    if(player==board2[row+k][col+k]){
                        if(row+k==row+piece-1) return true; //找到满足条件的正对角线
                    }else break; //正对角线不满足
                }
                //负对角线
                int row2=row+piece-1;
                int col2=col;
                for(int k=0;k<piece;k++){
                    if(player==board2[row2-k][col2+k]){
                        if(row2-k==row) return true; //找到满足条件的副对角线
                    }else break; //负对角线不满足
                }
            }
        }
        return false;
    }

    //判断是否平局:返回false为否,返回true为平局
    //Done!
    public boolean isDraw(Board board,int size) {
        char [][]board2 = board.getBoard();
        for (int i = 0; i < size; i++)
            for (int j = 0; j < size; j++)
                if (board2[i][j] == '\0')
                    return false; //棋盘存在空白,未平局
        return true;
    }
}

makeAMove()方法较容易理解,一行一行看下来基本上就知道逻辑了,注释也都写得很清晰。

难点在于isWon()方法,即对player是否获胜的判断。这里利用的是卷积核的思想(不懂可以去搜“卷积神经网络”)。我们假设玩家人数为n(那么棋盘大小就是n+1),玩家规定连续m个棋子赢得游戏。那么我们就可以把棋盘看成一张(n+1)×(n+1)大小的图像,卷积核大小是m×m。在m×m大小的区域内,检查行、列、主对角线、副对角线是否有连续相同的,一旦有一种情况满足条件,立刻返回true。

isDraw()方法:判断是否平局。棋盘都下满了就是平局,所以关键在于判断棋盘满了没有。

3. tictactoe2 类

package com.tictactoe;

import java.util.Scanner;

public class tictactoe2 {
    private static Scanner input = new Scanner(System.in);
    public static void main(String[] args) {
        /*--------输入玩家人数、输入连续棋子的个数------*/
        System.out.println("请输入玩家的个数(3~10人):");
        int player_num = input.nextInt();
        System.out.println("请输入赢家连续棋子的个数(最少为3,最大为玩家人数+1):");
        int piece_num = input.nextInt();

        /*-------------初始化棋盘---------------*/
        int size = player_num + 1; //棋盘的大小
        Board board = new Board();
        board.createBoard(size); //创建棋盘
        board.drawBoard(size); //绘制初始棋盘

        /*-------------初始化玩家------------*/
        char player[] = new char[player_num];
        for(int i=0; i<player_num;i++){
            int temp = 97+i;  //准备由int向char进行转换
            player[i]= (char) temp;  //用字母a~j表示0号~9号玩家(根据题目要求,最多10人,第10人为j)
        }
        /*--------------开始下棋-------------*/
        GameLogic logic = new GameLogic();
        int play_i=0; //从0号玩家开始
        while (true) {

            logic.makeAMove(board,player[play_i]);  //下棋,更新棋盘
            board.drawBoard(size); //绘制新棋盘
            if (logic.isWon(player[play_i], board,piece_num,size)) {  //判断第i号玩家是否获胜
                System.out.println("玩家"+player[play_i]+"获胜");
                System.exit(1);
            }
            else if (logic.isDraw(board,size)) {  //判断是否平局
                System.out.println("平局");
                System.exit(2);
            }

            if(play_i<player_num-1){
                play_i++; //下一个玩家
            }else {
                play_i=0;  //又轮到0号玩家
            }
        }
    }

}

这里稍微难一点的地方有两点。

1)利用int类型向char类型的转换,把1~10号玩家分别用字母a~j去表示。

2)另一个难点在于如何实现玩家轮着下棋。重点看play_i这个变量就明白了

4. 玩法示例

(1)示例1

java实现井字棋人机对战 井字棋联机_i++_02

java实现井字棋人机对战 井字棋联机_System_03

(2)平局示例

java实现井字棋人机对战 井字棋联机_i++_04

 

java实现井字棋人机对战 井字棋联机_java_05

5. 总结

(1)对类的封装还不是太好,如果再写一个player类,程序的封装性就更好了

(2)学习了Sanner的使用。主要就是四个函数