一、棋盘的绘制

使用JFrame容器制作五子棋的窗体

创建一个类——UI,如下:

public class UI {

private JFrame frame = new JFrame();

public void init() {

frame.setTitle("五子棋");

frame.setSize(518, 540);

frame.setLocationRelativeTo(null); //居中

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setVisible(true); //让窗体显示

}

public static void main(String[] args) {

new UI().init();

}

}

使用JPanel和Graphics画出大小为15X15的棋盘

再创建一个新的类——Chessboard,继承JPanel:

public class Chessboard extends JPanel {

//规定由15条横竖线组成

private static final int CHESSBOARD_SIZE = 15;

//外边距

private int margin = 20;

/**

* 绘图工具

*

* @param g 画笔工具

*/

@Override

public void paint(Graphics g) {

super.paint(g);

drawChessBoard(g);

drawPieces(g);

}

/**

* 画棋盘

*

* @param g 画笔工具

*/

private void drawChessBoard(Graphics g) {

int cellSize = (getWidth() - 2 * margin) / (CHESSBOARD_SIZE - 1);

for (int i = 0; i < CHESSBOARD_SIZE; i++) {

//画横线

g.drawLine(margin, margin + cellSize * i, getWidth() - margin, margin + cellSize * i);

//画竖线

g.drawLine(margin + cellSize * i, margin, margin + cellSize * i, getHeight() - margin);

}

}

}

实现点击鼠标落子的功能

在UI类中添加以下代码:

public class UI {

private JFrame frame = new JFrame();

private Chessboard chessboard = new Chessboard();

public void init() {

frame.setTitle("五子棋");

frame.setSize(518, 540);

frame.setLocationRelativeTo(null); //居中

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setVisible(true); //让窗体显示

//给棋盘添加鼠标监听事件,具体来说就是鼠标点击事件

chessboard.addMouseListener(new MouseAdapter() { //匿名内部类

@Override

public void mouseClicked(MouseEvent e) {

super.mouseClicked(e);

//调用画棋子的方法

play(e);

}

});

}

/**

* 处理鼠标点击事件的方法

*/

private void play(MouseEvent e) {

int cellSize = chessboard.getCellSize();

int x = (e.getX() - 5) / cellSize;

int y = (e.getY() - 5) / cellSize;

chessboard.move(new Pieces(x, y, 1));

}

public static void main(String[] args) {

new UI().init();

}

}

再在Chessboard类中添加drawPieces、move、getCellSize方法,如下:

public class Chessboard extends JPanel {

private List piecesList = new ArrayList<>();

/**

* 绘图工具

*

* @param g 画笔工具

*/

@Override

public void paint(Graphics g) {

super.paint(g);

drawChessBoard(g);

drawPieces(g);

}

public int getCellSize() {

return (getWidth() - 2 * margin) / (CHESSBOARD_SIZE - 1);

}

public void drawPieces(Graphics g) {

for (Pieces piece : piecesList) {

if (piece.getPlayer() == 1) {

//默认值为1代表人类,棋子颜色为黑色

g.setColor(Color.black);

} else {

g.setColor(Color.white);

}

int cellSize = (getWidth() - 2 * margin) / (CHESSBOARD_SIZE - 1);

g.fillOval(piece.getX() * cellSize + margin - cellSize / 2, piece.getY() * cellSize + margin - cellSize / 2, cellSize, cellSize);

}

}

/**

* 落子的方法

*/

public void move(Pieces piece) {

piecesList.add(piece);

repaint();

}

}

然后创建一个新的类——Pieces,如下:

/***

* 棋子对象

*/

public class Pieces {

private int x;

private int y;

private int player; //表示黑棋还是白棋,1代表黑棋,-1代表白棋

public Pieces(int x, int y, int player) {

this.x = x; //非实际坐标,而是格子数,第x格

this.y = y; //同上,第y格

this.player = player;

}

public int getX() {

return x;

}

public int getY() {

return y;

}

public int getPlayer() {

return player;

}

}

二、判断胜负

实现五子棋胜负判断的思路是:

判断胜负就看哪个颜色的棋子首先达到五子相连。若黑棋先达到五子相连,则黑棋胜,否则,白棋胜;

也就是下在每个棋子时都要判断它的八个方向的棋子相连个数,如果判断某一方向与其相邻点的颜色相同,将循环计数;

还需要注意边界点的范围,在循环的时候应注意设置边界条件;

可以建一个类,在类中写判断输赢的方法;

在Chessboard类中添加如下代码:

public class Chessboard extends JPanel {

private static final int CHESSBOARD_SIZE = 15;

private int margin = 20;

private List piecesList = new ArrayList<>();

//创建一个数组,用来表示棋盘上被占用的位置

private int[][] location = new int[CHESSBOARD_SIZE][CHESSBOARD_SIZE];

}

但是,落子的位置是有限制的,具体来说就是:

落子的位置不能有其它棋子;

不能超过棋盘的边界;

所以要在UI类中的play()方法里添加一个落子合法性的判定:

private void play(MouseEvent e) {

int cellSize = chessboard.getCellSize();

int x = (e.getX() - 5) / cellSize;

int y = (e.getY() - 5) / cellSize;

if (chessboard.isLegal(x, y)) {

//添加棋子

chessboard.move(new Pieces(x, y, 1));

//记录人类落子的位置

chessboard.setLocation(x, y, 1);

//判断输赢

if (chessboard.checkWinner(x, y, 1)) {

JOptionPane.showMessageDialog(frame, "人类获胜", "您赢了!", JOptionPane.PLAIN_MESSAGE);

return;

}

}

}

而对应Chessboard类中新增的isLegal方法、setLocation方法以及checkWinner方法就是:

/**

* 判断是否重复落子以及落子位置是否合法

*/

public boolean isLegal(int x, int y) {

return x >= 0 && x <= CHESSBOARD_SIZE && y >= 0 && y <= CHESSBOARD_SIZE && location[x][y] == 0;

}

/**

* 记录落子后,棋子占用棋盘的位置

*/

public void setLocation(int x, int y, int player) {

location[x][y] = player;

}

/**

* 判断胜负

*/

public boolean checkWinner(int x, int y, int player) {

int sum = 0;

//判断水平方向,水平左侧

for (int i = x - 1; i >= 0; i--) {

if (location[i][y] == player) {

sum++;

} else {

break;

}

}

//水平右侧

for (int i = x + 1; i <= CHESSBOARD_SIZE; i++) {

if (location[i][y] == player) {

sum++;

} else {

break;

}

}

if (sum >= 4) {

return true;

}

//判断垂直方向

sum = 0;

for (int i = y - 1; i >= 0; i--) {

if (location[x][i] == player) {

sum++;

} else {

break;

}

}

for (int i = y + 1; i <= CHESSBOARD_SIZE; i++) {

if (location[x][i] == player) {

sum++;

} else {

break;

}

}

if (sum >= 4) {

return true;

}

//判断左上到右下这个对角线方向

sum = 0;

for (int i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--) {

if (location[i][j] == player) {

sum++;

} else {

break;

}

}

for (int i = x + 1, j = y + 1; i <= CHESSBOARD_SIZE && j <= CHESSBOARD_SIZE; i++, j++) {

if (location[i][j] == player) {

sum++;

} else {

break;

}

}

if (sum >= 4) {

return true;

}

//判断右上到左下这个对角线方向

sum = 0;

for (int i = x + 1, j = y - 1; i <= CHESSBOARD_SIZE && j >= 0; i++, j--) {

if (location[i][j] == player) {

sum++;

} else {

break;

}

}

for (int i = x - 1, j = y + 1; i >= 0 && j <= CHESSBOARD_SIZE; i--, j++) {

if (location[i][j] == player) {

sum++;

} else {

break;

}

}

if (sum >= 4) {

return true;

}

sum = 0;

return false;

}

三、简易的AI算法

思路:

五元组:棋盘中横竖斜四个方向的所有相邻五个可连成一条线的格子

五元组的表示:用某格子的横纵坐标以及+1/-1来表示相邻四个格子的坐标,从而计算五元组坐标

五子棋棋盘大小是15X15,横竖斜四个方向共有572个五元组,给每个五元组一个评分

这个五元组将为它的每个位置贡献的分数就是这个五元组自身的得分

对整个棋盘来说,每个位置的得分就是该位置所在的横竖斜四个方向所有五元组的得分之和

然后从所有空位置中选得分最高的位置即为机器落子的位置

首先,添加机器落子的代码,完善UI类中play方法:

private void play(MouseEvent e) {

int cellSize = chessboard.getCellSize();

int x = (e.getX() - 5) / cellSize;

int y = (e.getY() - 5) / cellSize;

if (chessboard.isLegal(x, y)) {

//添加棋子

chessboard.move(new Pieces(x, y, 1));

//记录人类落子的位置

chessboard.setLocation(x, y, 1);

//判断输赢

if (chessboard.checkWinner(x, y, 1)) {

JOptionPane.showMessageDialog(frame, "人类获胜", "您赢了!", JOptionPane.PLAIN_MESSAGE);

return;

}

//机器落子

Pieces piece = chessboard.searchLocation();

chessboard.move(piece);

chessboard.setLocation(piece.getX(),piece.getY(),piece.getPlayer());

//判断胜负

if (chessboard.checkWinner(piece.getX(),piece.getY(),piece.getPlayer())){

JOptionPane.showMessageDialog(frame, "电脑获胜", "您输了!", JOptionPane.PLAIN_MESSAGE);

}

}

}

其次,还需要一个评估函数(评分表),来对整个棋局中有效位置进行评价,但评分表很难确定,没有所谓最好的,只能根据经验和测试来选择,我选择的评分表如下:

既有人类落子,又有机器落子,判分为0;

全部为空,没有落子,判分为7;

机器落1子,判分为35

机器落2子,判分为800

机器落3子,判分为15000

机器落4子,评分为800000

人类落1子,评分为15

人类落2子,评分为400

人类落3子,评分为1800

人类落4子,评分为100000

在Chessboard类中添加tupleScore方法:

private int tupleScore(int humanChessmanNum, int machineChessmanNum) {

if (humanChessmanNum > 0 && machineChessmanNum > 0) {

return 0;

}

if (humanChessmanNum == 0 && machineChessmanNum == 0) {

return 7;

}

if (machineChessmanNum == 1) {

return 35;

}

if (machineChessmanNum == 2) {

return 800;

}

if (machineChessmanNum == 3) {

return 15000;

}

if (machineChessmanNum == 4) {

return 800000;

}

if (humanChessmanNum == 1) {

return 15;

}

if (humanChessmanNum == 2) {

return 400;

}

if (humanChessmanNum == 3) {

return 1800;

}

if (humanChessmanNum == 4) {

return 100000;

}

return -1;

}

在Chessboard类中添加searchLocation方法:

/**

* 计算机器落子的最佳位置

*/

public Pieces searchLocation() {

//每次都初始化一下score的评分数组

for (int i = 0; i < CHESSBOARD_SIZE; i++) {

for (int j = 0; j < CHESSBOARD_SIZE; j++) {

score[i][j] = 0;

}

}

//每次机器寻找了落子位置,评分都重新计算一遍

int humanChessmanNum = 0;

int machineChessmanNum = 0;

int tupleScoreTmp = 0;

int goalX = -1;

int goalY = -1;

int maxScore = -1;

//纵向扫描棋盘

for (int i = 0; i < 15; i++) {

for (int j = 0; j < 11; j++) {

int k = j;

while (k < j + 5) {

if (location[j][k] == -1) machineChessmanNum++;

else if (location[i][k] == 1) humanChessmanNum++;

k++;

}

// 将每一个五元组中的黑棋个数与白棋个数传入评分表中

tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);

//为该五元组的每一个位置添加分数

for (k = j; k < j + 5; k++) {

score[i][k] += tupleScoreTmp;

}

//归零

humanChessmanNum = 0;

machineChessmanNum = 0;

tupleScoreTmp = 0;

}

}

//横向扫描棋盘

for (int i = 0; i < 15; i++) {

for (int j = 0; j < 11; j++) {

int k = j;

while (k < j + 5) {

if (location[k][i] == -1) machineChessmanNum++;

else if (location[k][i] == 1) humanChessmanNum++;

k++;

}

// 将每一个五元组中的黑棋个数与白棋个数传入评分表中

tupleScoreTmp = tupleScore(humanChessmanNum, machineChessmanNum);

//为该五元组的每一个位置添加分数

for (k = j; k < j + 5; k++) {

score[k][i] += tupleScoreTmp;

}

//归零

humanChessmanNum = 0;

machineChessmanNum = 0;

tupleScoreTmp = 0;

}

}

//3.扫描右对角线 上侧部分

for (int i = 14; i >= 4; i--) {

for (int k = i, j = 0; j < 15 && k >= 0; j++, k--) {

int m = k; //x轴

int n = j; //y轴

while (m > k - 5 && k - 5 >= -1) {

if (location[m][n] == -1) machineChessmanNum++;

else if (location[m][n] == 1) humanChessmanNum++;

m--;

n++;

}

//注意斜向判断时,可能不构成五元组(靠近四个角落),遇到这种情况要忽略掉

if (m == k - 5) {

tupleScoreTmp = tupleScore(machineChessmanNum, humanChessmanNum);

for (m = k, n = j; m > k - 5; m--, n++) {

score[m][n] += tupleScoreTmp;

}

}

//归零

humanChessmanNum = 0;

machineChessmanNum = 0;

tupleScoreTmp = 0;

}

}

//4.扫描右对角线 下侧部分

for (int i = 1; i < 15; i++) {

for (int k = i, j = 14; j >= 0 && k < 15; j--, k++) {

int m = k;

int n = j;

while (m < k + 5 && k + 5 <= 15) {

if (location[n][m] == -1) machineChessmanNum++;

else if (location[n][m] == 1) humanChessmanNum++;

m++;

n--;

}

if (m == k + 5) {

tupleScoreTmp = tupleScore(machineChessmanNum, humanChessmanNum);

for (m = k, n = j; m < k + 5; m++, n--) {

score[n][m] += tupleScoreTmp;

}

}

//归零

humanChessmanNum = 0;

machineChessmanNum = 0;

tupleScoreTmp = 0;

}

}

//5.扫描左对角线上侧部分

for (int i = 0; i < 11; i++) {

for (int k = i, j = 0; j < 15 && k < 15; j++, k++) {

int m = k;

int n = j;

while (m < k + 5 && k + 5 <= 15) {

if (location[m][n] == -1) machineChessmanNum++;

else if (location[m][n] == 1) humanChessmanNum++;

m++;

n++;

}

if (m == k + 5) {

tupleScoreTmp = tupleScore(machineChessmanNum, humanChessmanNum);

for (m = k, n = j; m < k + 5; m++, n++) {

score[m][n] += tupleScoreTmp;

}

}

//归零

humanChessmanNum = 0;

machineChessmanNum = 0;

tupleScoreTmp = 0;

}

}

//6.扫描左对角线下侧部分

for (int i = 1; i < 11; i++) {

for (int k = i, j = 0; j < 15 && k < 15; j++, k++) {

int m = k;

int n = j;

while (m < k + 5 && k + 5 <= 15) {

if (location[n][m] == -1) machineChessmanNum++;

else if (location[n][m] == 1) humanChessmanNum++;

m++;

n++;

}

if (m == k + 5) {

tupleScoreTmp = tupleScore(machineChessmanNum, humanChessmanNum);

for (m = k, n = j; m < k + 5; m++, n++) {

score[n][m] += tupleScoreTmp;

}

}

//归零

humanChessmanNum = 0;

machineChessmanNum = 0;

tupleScoreTmp = 0;

}

}

//从空位置中找到最大的位置

for (int i = 0; i < 15; i++) {

for (int j = 0; j < 15; j++) {

if (location[i][j] == 0 && score[i][j] > maxScore) {

goalX = i;

goalY = j;

maxScore = score[i][j];

}

}

}

if (goalX != -1) {

return new Pieces(goalX, goalY, -1);

}

//没找到坐标说明平局,暂不处理

return new Pieces(-1, -1, -1);

}

(完)

五子棋java判断平局_2020-10-03 Java初级项目——从零开始制作一个简易五子棋游戏...相关推荐

  1. java五子棋_Java初级项目——从零开始制作一个简易五子棋游戏

    一.棋盘的绘制 使用JFrame容器制作五子棋的窗体 创建一个类--UI,如下: public 使用JPanel和Graphics画出大小为15X15的棋盘 再创建一个新的类--Chessboard, ...

  2. 从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

    前言 在前面从零开始实现一个简易的Java MVC框架(四)--实现AOP和从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点这两节文章中已经实现了AOP功能并且引用 ...

  3. 从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

    前言 在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化. 优化的目标是1.去除 ...

  4. 使用Java制作一个简易的远控终端

    使用Java制作一个简易的远控终端 远控终端的本质 1.服务端(攻击者)传输消息 ----> socket连接 ----> 客户端(被攻击者)接收消息 2.客户端执行消息内容(即执行服务端 ...

  5. 四节1.5V的5号电池、一个电容、一个12V的报警蜂鸣器、铜线和螺母,在螺母所栓的铜线触发接通电源后,缓慢放电10秒,制作一个简易震动报警器,需要用什么样的电容合适?...

    根据题目描述,需要制作一个简易震动报警器,使用四节1.5V的5号电池作为电源,一个电容,一个12V的报警蜂鸣器,铜线和螺母.在螺母所栓的铜线触发接通电源后,需要缓慢放电10秒. 在这种情况下,需要一个 ...

  6. java 判断是否是list_给Java程序员的20个链表面试题

    全文共4258字,预计学习时长8分钟 图片来源:unsplash.com/@d_mccullough 今天,本文将详细介绍编程面试中常见的链表问题. 什么是链表? 数据结构在程序面试中极其重要.链表则 ...

  7. 用Java写一个简易五子棋游戏

    一. 程序基本思路: 1.写窗口.棋盘面板.控制面板: 2.绘制棋盘: 3.绘制棋子: 4.添加组件功能: 5.判断输赢: 6.悔棋: 7.复盘. 二.实际操作 1.创建窗口.添加面板 package ...

  8. java builder pool_每周10道Java面试题:String, String Pool, StringBuilder

    每周10道 Java 面试题由 ImportNew 整理编译自网络. 1. 写出下面代码的运行结果. int src = 65536; Integer dst = new Integer(65536) ...

  9. Java继承_这10道Java面试题!大部分的人回答不出来

    1.为什么等待和通知是在 Object 类而不是 Thread 中声明的? 2.为什么Java中不支持多重继承? 3.为什么Java不支持运算符重载? 4.为什么 String 在 Java 中是不可 ...

最新文章

  1. Nginx+keepalived 实现高可用,防盗链及动静分离配置
  2. 数据结构实验之求二叉树后序遍历和层次遍历
  3. 自制浮动静态路由实验(简单)
  4. Lecture 15 Dynamic Programming
  5. idea中Terminal显示不全或不显示问题的解决办法
  6. 《Elementary Methods in Number Theory》勘误
  7. heading pitch roll 飞行姿态角度
  8. starway(NOIP模拟测试24)
  9. MMKV 原理以及使用
  10. Minecraft 1.19.2 Forge模组开发 01.Idea开发环境配置
  11. 如何禁止拼音加加自动修改IE首页
  12. 关于DiskGenius 克隆分区和系统迁移问题,以及如何解决缺少系统引导、双硬盘双系统引导问题,多个启动项
  13. 浅谈几款软件的创新点
  14. 如何获取应用宝APP ID
  15. unity教程之Unity引擎
  16. 汇率兑换程序python按温度转换_python复习+实例编写(1)——温度转换、汇率转换...
  17. 小米手机5X获得Root权限的方法
  18. 手机屏幕常见故障_手机测试常见问题总结!
  19. 无法更改edge浏览器启动页(主页)\新标签页
  20. 关于webrtc的多人视频会议的杂乱记录

热门文章

  1. 民主湖呀,不知道是好看还是破烂
  2. 在浏览器中输入网址后的流程
  3. 计算节点宕机了怎么办?- 每天5分钟玩转 OpenStack(43)
  4. 单行文字压缩处理(要指定字体)
  5. Linux命令如何显示光标
  6. jenkins+gradle/maven+sonar+pipline
  7. struct2 开发环境搭建 问题
  8. PKUWC2019游记
  9. SphereFace的原理
  10. PHP+Redis 实例【一】点赞 + 热度 下篇