通过java语言开发了一个简单的井字棋游戏。主要有6个类,其中有一个是主类(Main.java),一个是抽象类(PiecesMove.java)组成。
下面对各个类简单介绍一下:
TicTicToe.java:
主要负责创建棋盘,管理棋盘。
TicTicToeUI.java:
主要由判断谁是先手的对话框、对战界面和提示谁输谁赢三个对话框组成,负责管理用户交互。
PiecesMove.java:
里面有2个方法,move(TicTacToe tict)
是抽象方法由子类实现,isWinning(int[][] cur)
判断是否游戏结束。
Computer.java:
负责计算机的落子位置计算,核心算法为博弈算法。
Player.java:
负责获取人落子的位置。
Main.java:
负责创建以上类的实例,控制谁先走子,游戏结束是否继续等逻辑处理。
下面贴出源代码:
TicTicToe.java:
/*
* Created by shphuang on 2020/4/4.
* */
import javax.swing.*;
import java.awt.*;
public class TicTacToe {
public static int num = 0;//计数走的步数
//存储棋盘矩阵值
private int[][] chessBd = new int[3][3];
//按钮矩阵
private JButton[][] buttons;
//判断该谁走
public boolean PLAYER_MOVE = true;
//实例化Tictactoe
private static TicTacToe tict = null;
public static TicTacToe instanceTict(JButton[][] buttons){
if (tict == null){
tict = new TicTacToe(buttons);
}
return tict;
}
//初始化棋盘
private TicTacToe(JButton[][] buttons){
this.buttons = buttons;
clearBoard();
}
//设置棋盘的值
public void setChessBd(int x , int y , int checkBd) {
this.chessBd[x][y] = checkBd;
}
//得到棋盘
public int[][] getChessBd() {
return chessBd;
}
//得到按钮矩阵
public JButton[][] getButtons() {
return buttons;
}
//清空棋盘
public void clearBoard() {
for (int i=0 ; i < 3 ; i++){
for (int j=0 ; j < 3 ; j++){
chessBd[i][j] = 0 ;
}
}
num = 0;
}
//将棋盘画出来
public void drawBoard(){
updateUI();
}
//更新ui
private boolean updateUI(){
for (int i = 0; i < chessBd.length; i++) {
for (int j = 0; j < chessBd.length; j++) {
switch (chessBd[i][j]){
case -1:
buttons[i][j].setBackground(Color.white);
break;
case 0:
buttons[i][j].setBackground(Color.lightGray);
break;
case 1:
buttons[i][j].setBackground(Color.black);
break;
}
}
}
return false;
}
}
TicTicToeUI.java:
/*
* Created by shphuang on 2020/4/4.
* */
import javax.swing.*;
import java.awt.*;
public class TicTacToeUI {
private JButton[][] buttons = new JButton[3][3];
private JFrame jf;
private JPanel jp;
//实例化TictactoeUI
private static TicTacToeUI tictactoeUI = null;
public static TicTacToeUI instanceTictactoeUI(){
if (tictactoeUI == null){
tictactoeUI = new TicTacToeUI();
}
return tictactoeUI;
}
private TicTacToeUI(){
init();
}
private void init(){
//创建并设置frame
jf = new JFrame();
jf.setTitle("designed by 爱编程的大鹏"); //设置显示窗口标题
// 得到显示器屏幕的宽高
int screenWidth = Toolkit.getDefaultToolkit().getScreenSize().width;
int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height;
int width = 470;int height = 470;
int offset_x = screenWidth/2-width/2;
int offset_y = screenHeight/2-height/2;
jf.setBounds(offset_x,offset_y,width,height); //设置窗口显示尺寸
Container c= jf.getContentPane(); //获取当前窗口的内容窗格
//创建并设置panel
jp = new JPanel();
jp.setBackground(new Color(159, 156, 157, 20)); //设置背景色
jp.setLayout(new GridLayout(3,3,10,10)); //设置布局
//初始化按钮矩阵
for (int i = 0; i < buttons.length; i++) {
for (int j = 0; j < buttons.length; j++) {
JButton button = new JButton();
buttons[i][j] = button;
button.setBackground(Color.lightGray);
jp.add(button);
}
}
c.add(jp); //将panel面板添加到frame
jf.setVisible(true); //设置窗口是否可见
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //置窗口是否可以关闭
}//初始化TictactoeUI
//得到JFrame对象
public JFrame getJf() {
return jf;
}
//得到JPanel对象
public JPanel getJp() {
return jp;
}
//返回buttons对象
public JButton[][] getButtons() {
return buttons;
}
public boolean DialogUI(String msg) {
try {
Thread.sleep(200);
int temp = JOptionPane.showConfirmDialog(jp,msg,"提示",0,1);
if (temp == 0){
return true;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
}
PiecesMove.java:
/*
* Created by shphuang on 2020/4/4.
* */
abstract class PiecesMove {
public abstract void move(TicTacToe tict);
//返回0表示没有人赢,返回-1表示人赢了,返回1表示计算机赢了,返回2表示平局
public static int isWinning(int[][] cur){
for (int i = 0; i<3; i++)
{
if (cur[i][0] == 1 && cur[i][1] == 1 && cur[i][2] == 1)
return 1;
if (cur[i][0] == -1 && cur[i][1] == -1 && cur[i][2] == -1)
return -1;
}
for (int i = 0; i<3; i++)
{
if (cur[0][i] == 1 && cur[1][i] == 1 && cur[2][i] == 1)
return 1;
if (cur[0][i] == -1 && cur[1][i] == -1 && cur[2][i] == -1)
return -1;
}
if ((cur[0][0] == 1 && cur[1][1] == 1 && cur[2][2] == 1) || (cur[2][0] == 1 && cur[1][1] == 1 && cur[0][2] == 1))
return 1;
if ((cur[0][0] == -1 && cur[1][1] == -1 && cur[2][2] == -1) || (cur[2][0] == -1 && cur[1][1] == -1 && cur[0][2] == -1))
return -1;
if(TicTacToe.num >= 9){
return 2;
}
return 0;
}
}
Computer.java:
/*
* Created by shphuang on 2020/4/4.
* */
public class Computer extends PiecesMove {
//电脑的棋子类型
private static final int COMPUTER = 1;
private static final int depth = 3;
//实例化Player
private static Computer computer = null;
public static Computer instanceComputer() {
if (computer == null) {
computer = new Computer();
}
return computer;
}
@Override
public void move(TicTacToe tict) {
tict.num += 1;//步数+1
int[][] curQP = createTempArray(tict.getChessBd());
int[] xyVal = getXY(curQP);
if (tict.getChessBd()[xyVal[0]][xyVal[1]] == 0) {
tict.setChessBd(xyVal[0], xyVal[1], COMPUTER);
tict.drawBoard();
}
}
//得到x、y的坐标
private int[] getXY(int[][] curQP) {
int[] xy = new int[2];
int[] val = new int[1];
val[0] = -10000;
int m = -10000, dep = 1;
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
if (curQP[x][y] == 0) {
curQP[x][y] = 1;
if (isWinning(curQP) == 1) {
xy[0] = x;
xy[1] = y;
return xy;
}
val[0] = cut(curQP, val, dep, true);
if (val[0] > m) {
m = val[0];
xy[0] = x;
xy[1] = y;
}
val[0] = -10000;
curQP[x][y] = 0;
}
}
}
return xy;
}
//主算法部分,实现A-B剪枝的算法,val为上一层的评价值,dep为搜索深度,max记录上一层是否为极大层
private int cut(int[][] curQP, int[] val, int dep, boolean max){
//如果搜索深度达到最大深度,或者深度加上当前棋子数已经达到9,就直接调用评价函数
if (dep == depth || dep + TicTacToe.num == 9){
return value(createTempArray(curQP));
}
int i, j , temp;
int[] flag = new int[1];
boolean out = false; //out记录是否剪枝,初始为false
//如果用户玩家输了,就置上一层的评价值为无穷(用很大的值代表无穷)
if (isWinning(curQP) == 1){
val[0] = 10000;
return 0;
}
if (max) //如果上一层是极大层,本层则需要是极小层,记录flag为无穷大;反之,则为记录为负无穷大
flag[0] = 10000; //flag记录本层节点的极值
else
flag[0] = -10000;
for (i = 0; i < 3 && !out; i++) //两重循环,遍历棋盘所有位置
{
for (j = 0; j < 3 && !out; j++) {
if (curQP[i][j] == 0) //如果该位置上没有棋子
{
if (max) //并且为上一层为极大层,即本层为极小层,轮到用户玩家走了。
{
curQP[i][j] = -1; //该位置填上用户玩家棋子
if (isWinning(curQP) == -1) //如果用户玩家赢了
temp = -10000; //置棋盘评价值为负无穷
else
temp = cut(curQP,flag, dep + 1, !max); //否则继续调用ab剪枝函数
if (temp < flag[0]) //如果下一步棋盘的评价值小于本层节点的极值,则置本层极值为更小者,即后辈结点极小值<=祖先节点极大值
flag[0] = temp;
} else{
//如果上一层为极小层,算法与上面刚好相反
curQP[i][j] = 1;//该位置填上电脑玩家棋子
if (isWinning(curQP) == 1)//如果电脑玩家赢了,置棋盘评价值为正无穷
temp = 10000;
else
temp = cut(curQP,flag, dep + 1, !max); // 否则继续调用ab剪枝函数
if (temp > flag[0]) //如果下一步棋盘的评价值大于本层节点的极值,则置本层极值为更小者,即后辈结点极大值>=祖先节点极小值
flag[0] = temp;
if (flag[0] >= val[0]) //如果本层的极值已经大于上一层的评价值,则不需要搜索下去,剪枝 B剪枝
out = true;
}
curQP[i][j] = 0; //把模拟下的一步棋还原,回溯
}
}
}
if (max){
//根据上一层是否为极大层,用本层的极值修改上一层的评价值
if (flag[0] > val[0])
val[0] = flag[0];
} else {
if (flag[0] < val[0])
val[0] = flag[0];
}
return flag[0]; //函数返回的是本层的极值
}
//评估函数
private int value(int[][] curQP) {
int p = 0;
int q = 0;
int[][] tmpQP = new int[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
tmpQP[i][j] = curQP[i][j];
}
}
//将棋盘中的空格填满自己的棋子,既将棋盘数组中的0变为1
for (int i = 0; i<3; i++)
for (int j = 0; j<3; j++)
if (curQP[i][j] == 0)
tmpQP[i][j] = 1;
else
tmpQP[i][j] = curQP[i][j];
//电脑一方
//计算每一行中有多少行的棋子连成3个的
for (int i = 0; i<3; i++)
p += (tmpQP[i][0] + tmpQP[i][1] + tmpQP[i][2]) / 3;
//计算每一列中有多少列的棋子连成3个的
for (int i = 0; i<3; i++)
p += (tmpQP[0][i] + tmpQP[1][i] + tmpQP[2][i]) / 3;
//斜行有没有连成3个的?
p += (tmpQP[0][0] + tmpQP[1][1] + tmpQP[2][2]) / 3;
p += (tmpQP[2][0] + tmpQP[1][1] + tmpQP[0][2]) / 3;
//将棋盘中的空格填满对方的棋子,既将棋盘数组中的0变为-1
for (int i = 0; i<3; i++)
for (int j = 0; j<3; j++)
if (curQP[i][j] == 0)tmpQP[i][j] = -1;
else tmpQP[i][j] = curQP[i][j];
//对方
//计算每一行中有多少行的棋子连成3个的
for (int i = 0; i<3; i++)
q += (tmpQP[i][0] + tmpQP[i][1] + tmpQP[i][2]) / 3;
//计算每一列中有多少列的棋子连成3个的
for (int i = 0; i<3; i++)
q += (tmpQP[0][i] + tmpQP[1][i] + tmpQP[2][i]) / 3;
//斜行有没有连成3个的?
q += (tmpQP[0][0] + tmpQP[1][1] + tmpQP[2][2]) / 3;
q += (tmpQP[2][0] + tmpQP[1][1] + tmpQP[0][2]) / 3;
return p + q;
}
//返回一个局部变量的数组,防止干扰棋盘值
private int[][] createTempArray(int[][] array) {
int[][] temp = new int[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
temp[i][j] = array[i][j];
}
}
return temp;
}
}
Player.java:
/*
* Created by shphuang on 2020/4/4.
* */
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Player extends PiecesMove {
private static final int PLAYER = -1;
//实例化Player
private static Player player = null;
public static Player instancePlayer(){
if (player == null){
player = new Player();
}
return player;
}
//Player走一步
@Override
public void move(TicTacToe tict) {
while(true){
int[] xy = getXY(tict);
int position_x = xy[0];
int position_y = xy[1];
if (tict.getChessBd()[position_x][position_y] == 0){
tict.num += 1;//步数+1
tict.setChessBd(position_x, position_y , PLAYER);
tict.drawBoard();
break;
}
}
}
//监听用户走的位置
private int[] getXY(TicTacToe tict) {
int[] xy = new int[2];
xy[0] = -1;xy[1] = -1;
JButton[][] buttons = tict.getButtons();
while (xy[0]==-1 || xy[1] == -1){
for (int i = 0; i < buttons.length; i++) {
for (int j = 0; j < buttons.length; j++) {
int finalI = i;
int finalJ = j;
buttons[i][j].addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
if (actionEvent.getSource() == buttons[finalI][finalJ]) {
xy[0] = finalI;
xy[1] = finalJ;
}
}
});
}
}
}
return xy;
}
}
Main.java:
/*
* Created by shphuang on 2020/4/4.
*
*
* 1、产生棋盘
* 2、判断谁先(默认player先)
* 3、player走一步
* 4、判断player是否胜利
* 5、computer走一步
* 6、判断computer是否胜利
* */
public class Main {
public static void main(String[] args) {
//实例化TictactoeUI、Tictactoe、Player和Computer
TicTacToeUI ui = TicTacToeUI.instanceTictactoeUI();
TicTacToe tict = TicTacToe.instanceTict(ui.getButtons());
Player player = Player.instancePlayer();
Computer computer = Computer.instanceComputer();
//记录是否进行游戏
boolean flag = true;
while (flag){
//清空棋盘
tict.clearBoard();
//画出棋盘
tict.drawBoard();
//记录谁先走
boolean wfm = ui.DialogUI("你要先走吗?");
//记录游戏是否结束,flag=false则游戏结束
flag = startGame(tict,computer,player,wfm);
//记录游戏结束时谁赢。-1:player胜利 1:computer胜利 2:平局
int whoWin = PiecesMove.isWinning(tict.getChessBd());
if (whoWin == -1){
flag = ui.DialogUI("恭喜,你赢了 !\n\t\t你还要继续?");
}else if (whoWin == 1){
flag = ui.DialogUI("很抱歉,你输了 !\n\t\t你还要继续?");
}else if(whoWin == 2){
flag = ui.DialogUI("你真厉害,居然打成平手了 !\n\t\t你还要继续?");
}
}
//关闭窗口
ui.getJf().dispose();
}
//游戏开始
private static boolean startGame(TicTacToe tict, Computer computer, Player player, boolean wfm){
while (true){
if (wfm){
player.move(tict);
switch (PiecesMove.isWinning(tict.getChessBd())){
case -1:
return false;
case 1:
return false;
case 2:
return false;
}
computer.move(tict);
switch (PiecesMove.isWinning(tict.getChessBd())){
case -1:
return false;
case 1:
return false;
case 2:
return false;
}
} else {
computer.move(tict);
switch (PiecesMove.isWinning(tict.getChessBd())){
case -1:
return false;
case 1:
return false;
case 2:
return false;
}
player.move(tict);
switch (PiecesMove.isWinning(tict.getChessBd())){
case -1:
return false;
case 1:
return false;
case 2:
return false;
}
}
}
}
}
百度网盘链接:传送门 提取码:xmy7