让我们来思考一个问题:如何用Java来开发出一个五子棋项目?首先,没有界面其它的也就无从谈起,这里需要用到Java的SWING与AWT组件;有了五子棋界面,我们要能在界面上画出黑白棋子;最后,能够对输赢进行判断,这里需要用到事件监听。实现了以上三点,一个最基本的五子棋项目就做好了。

一.五子棋界面

最简单的界面分为两部分:棋盘与操作按钮,如果有需要,也可以添加其他部分。首先写出棋盘界面类GobangFrame继承JPanel,设置好界面大小、标题、位置等属性,代码如下:

JFrame frame = new JFrame();

frame.setTitle("五子棋");

frame.setSize(new Dimension(1240, 1010));

frame.setLocationRelativeTo(null);

frame.setResizable(false);

frame.setDefaultCloseOperation(3);

frame.setVisible(true);

属性写完之后来设置它的布局,把棋盘放在中间,按钮放在界面东边:

// 东边面板JPanel eastPanel = new JPanel();

eastPanel.setBackground(Color.white);

String[] array = { "单机对战", "人机对战", "联机对战", "悔棋", "重新开始" };

JLabel label = new JLabel();

label.setPreferredSize(new Dimension(200, 200));

eastPanel.add(label);

// 添加按钮for (int i = 0; i < array.length; i++) {

JButton button = new JButton(array[i]);

button.setPreferredSize(new Dimension(200, 100));

button.setFont(new Font("", Font.BOLD, 30));

eastPanel.add(button);

}

eastPanel.setPreferredSize(new Dimension(250, 750));

frame.add(eastPanel, BorderLayout.EAST);

// 中间面板this.setBackground(new Color(100, 100, 100));

frame.add(this, BorderLayout.CENTER);

画布和按钮部分已经布置好了,但是我们的棋盘现在还没有画出来。别急,我们先来定义一个棋盘类Board,并定义好棋盘的属性:每个格子的边长、左上顶点的坐标,并写出循环画出棋盘的方法:

public void draw(Graphics g){

for(int i=0;i

g.drawLine(x, y+size*i, x+size*(row-1), y+size*i);

}

for(int i=0;i

g.drawLine(x+size*i, y, x+size*i, y+size*(column-1));

}

}

接着我们重写GobangFrame的paint(Graphics g)方法,实例化Board对象并将draw(Graphics g)方法写入paint(Graphics g)即可。点击运行之后,就得到了简易的五子棋界面。

如果想要使五子棋界面更加美观,我们可以从网上下载一些精美的棋盘和棋子图片,使用辅助类ImageIcon封装后在界面上画出。

二.画出棋子

画出棋子其实就是在棋盘画布上画出一个个实心圆形,但是还有以下几个限制:

1.在点击对应按钮之前无法下棋

2.存储棋子的信息,实现重绘

3.点击棋盘某个范围就能画出对应棋子

4.棋子要画在棋盘的交汇点上

5.黑子与白子交替画出

6.在已经画出棋子的地方不能再次画出棋子

要达到以上几点要求,首先我们得定义数组分别存储棋盘每个交叉点的X与Y坐标;并用一个数组来存储该交叉点上的棋子信息——规定若该位置没有棋子则值为0,若该位置为黑子则值为-1,为白子则值为1;最后,我们可以定义一个静态常量来存储棋盘上有多少个棋子。以上这些属性都可以定义在棋盘类Board中,且存储交叉点坐标的数组可以在实例化Board对象时就完成初始化赋值。

|

接下来,我们可以定义一个棋子类Chess并定义属性变量size作为棋子的半径(它应该略小于棋盘每格的长度),它还应该包含画出棋子的方法draw(int x, int y, Graphics g),代码如下:

/*cb为Board对象,localex与localey为交叉点坐标*/

for (int i = 0; i < cb.getRow(); i++) {

for (int j = 0; j < cb.getColumn(); j++) {

// 判断给出的坐标位于哪个交叉点附近if (Math.abs(x - cb.localex[i][j]) < size / 2

&& Math.abs(y - cb.localey[i][j]) < size / 2) {

// bl为存储棋子信息的数组,count为棋子个数 if (cb.bl[i][j] == 0) {

if (cb.count % 2 == 0) {

g.setColor(Color.black);

cb.bl[i][j] = -1;

} else {

g.setColor(Color.white);

cb.bl[i][j] = 1;

}

// 存储最后一个被画出的棋子的行列数this.x[cb.count] = i;

this.y[cb.count] = j;

g.fillOval(cb.localex[i][j] - size / 2,cb.localey[i][j] - size / 2, size, size);

cb.count++;

}

}

}

}

count的初始值为零,黑子先下,则count为偶数时设置颜色为黑色,反之设置为白色。

|

接下来要做的是给按钮和棋盘画布添加监听方法,定义一个GobangListener继承MouseAdapter类和ActionListener接口,同时将GobangFrame的棋盘画布传递过来,其构造方法如下:

// 构造方法public GobangListener(JPanel panel) {

this.panel = panel;

}

构造方法写完之后,接口ActionListener的抽象方法必须要实现,这里我们要把监听添加给按钮来实现按钮对应的功能。玩家在点击“单机对战”之前无法下棋,所以我们要在玩家点击按钮之后给棋盘面板添加监听,设为模式一,并且保证监听只能添加一次,同时将存储棋子信息的数组都置为空。“重新开始”按钮则是将存储棋子信息的数组置空后重绘面板,移除棋盘面板的监听。实现单机和重新开始的代码如下:

if (e.getActionCommand().equals("单机对战")) {

for (int i = 0; i < chess.cb.getRow(); i++) {

for (int j = 0; j < chess.cb.getColumn(); j++) {

chess.cb.bl[i][j] = 0;

chess.cb.count = 0;

}

}

if (mode != 1) {

mode = 1;// 模式一panel.addMouseListener(this);

}

}

if (e.getActionCommand().equals("重新开始")) {

for (int i = 0; i < chess.cb.getRow(); i++) {

for (int j = 0; j < chess.cb.getColumn(); j++) {

chess.cb.bl[i][j] = 0;

chess.cb.count = 0;

chess.weightArray[i][j] = 0;

}

}

mode = 0;

move = 0;

panel.repaint();

panel.removeMouseListener(this);

}

|

按钮的监听添加好了,接下来给画布添加监听。以玩家释放鼠标时的坐标画出棋子,我们需要重写mouseReleased(MouseEvent e)方法,获得释放处的坐标,并在不分胜负的条件下继续落子:

x = e.getX();

y = e.getY();

if (g == null) {

g = (Graphics2D) panel.getGraphics();

g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

}

if (mode == 1) {

if (!chess.checkB() && !chess.checkW()) {

chess.draw(x, y, g);

}

}

|

最后我们要完成的就是棋子的重绘了,因为我们已经有了一个数组存储每个交叉点的棋子信息,所以在GobangFrame的paint(Graphics g)方法中对棋盘的行列进行遍历,如果数组中某行某列对应的值不为0,那么这个地方就画下棋子,颜色根据值的正负进行判断即可:

for (int i = 0; i < ch.length; i++) {

for (int j = 0; j < ch[i].length; j++) {

// ch即为存储棋子信息的数组if (ch[i][j] == 1) {

g.setColor(Color.white);

g.fillOval(gl.chess.cb.localex[i][j] - gl.chess.size / 2, gl.chess.cb.localey[i][j] - gl.chess.size / 2, gl.chess.size, gl.chess.size);

} else if (ch[i][j] == -1) {

g.setColor(Color.black);

g.fillOval(gl.chess.cb.localex[i][j] - gl.chess.size / 2, gl.chess.cb.localey[i][j] - gl.chess.size / 2, gl.chess.size, gl.chess.size);

}

}

}

三.判断胜负

单机对战的最后一步就是判断胜负了。之前我们已经在棋盘类的数组里存储了每个位置上的棋子信息,所以要判断胜负,只需遍历棋盘并判断每个方向上是否有五个相同的棋子相连即可。遍历方向是从左到右、从上到下,按理说我们只判断四个方向就足够了,但为了保持灵敏度(即一落子就能判断是否有五子相连并马上结束游戏),还是要将八个方向全部写一遍。篇幅所限,只给出判断白子向右是否有五颗相连的方法:

/*** 检查白子是否五子连珠** @return 是则返回true*/

public boolean checkW() {

boolean b = false;

for (int i = 0; i < cb.getRow(); i++) {

for (int j = 0; j < cb.getColumn(); j++) {

if (cb.bl[i][j] == 1) {

if (i + 4 < cb.getRow() && j + 4 < cb.getColumn() && i-4>=0 && j-4>=0) {

if (cb.bl[i + 1][j] == 1 && cb.bl[i + 2][j] == 1&& cb.bl[i + 3][j] == 1&& cb.bl[i + 4][j] == 1)

b = true;

// 其他方向的判断语句......}

}

}

}

return b;

}

在进行循环遍历时要加上判断条件以防止数组越界,判断白子的其他方向以及黑子的胜负同理。写好判断方法之后,把它加入到监听方法作为下子的判断条件,如果胜负已分,我们可以在棋盘界面上弹出一个消息提示框显示哪一方胜利。也可以对该功能进行美化,比如在胜负已分后使用画布画出字符串的方法提示哪一方胜利,这样的话在点击“重新开始”按钮之后把它刷新掉就可以了。

四.人机对战

如何实现人机对战功能?博弈树?机器学习?贪心算法?人工智能?很遗憾,这些我目前都不会。所以这里使用的是一种比较简单但不太有效的方法——权值法。它的思想是给所有棋盘上的空位“打分”,分数根据该空位周围情况来判定,分数最高的地方就是电脑该下子的地方,如果有多个分值最高的空位,那么我们只能让电脑随机地挑选其中一个进行落子。该给多少分要提前设定好,这就是“权值”的由来。需要注意的是不同情况下权值相差越大越好,否则电脑可能会在周围落了很多棋子但对于当前棋局毫无用处的空位落子,而不是去围堵对手的“三子相连”。

|

为了能将棋盘上棋子的信息转化为对应的权值,我们需要使用HashMap将String转化为Integer,代码如下:

HashMap map = new HashMap();

map.put("010", 10);

map.put("0110", 100);

map.put("01110", 1000);

这里只是对白子活连的情况进行了转化,相应的还有白子死连、黑子活连和黑子死连,这里就不贴代码了。

|

对应的权值已经设定好之后,接下来就是遍历棋盘上的空位对其进行打分然后落子。这里还有一个小问题:下棋一般黑子先行,电脑执黑子的情况下我们需要对它进行约束,使它在棋盘靠近中心的地方随机落子。篇幅所限,这里只给出向下查找的代码:

/*** 查找方法*/

private void seek(int i, int j) {

// 如果该位置上没有棋子if (cb.bl[i][j] == 0) {

// 查找向下方向是否有棋子 for (int k = i + 1; k < cb.getRow() - 1; k++) {

if (cb.bl[k][j] == 0) {

// 没有则查找下一个位置 break;

} else {

chess = cb.bl[k][j];

code = code + chess;

if (cb.bl[k + 1][j] != cb.bl[k][j]) {

chess = cb.bl[k + 1][j];

code = code + chess;

plusWeight(i, j);

break;

}

}

}

// 向上方向、向左方向......}

}

/*** 加权方法*/

private void plusWeight(int i, int j) {

Integer value = map.get(code);

if (value != null) {

weightArray[i][j] += value;

}

// 初始化数值code = "0";

chess = 0;

}

接下来要做的事情就很简单了:定义电脑下棋的方法,循环遍历,将权值存储到对应的数组里并找出最大值,然后在该处落子即可。需要注意的有以下两点:

1.电脑找出的权值最大处可能已经有了棋子,这时要对下棋的地点进行判断

2.每次电脑落子之后都要将权值数组清空,因为每落一子之后的形势都不同

玩家落子的方法和之前单机对战一样,这里不再赘述。写完电脑和玩家落子的方法之后,给五子棋界面上的“人机对战”按钮添加监听方法,设为模式二。当玩家点击“人机对战”的时候设置弹出一个选择框选择玩家执子的颜色,选好之后给棋盘画布添加鼠标监听方法就可以进行人机对战了。

五.悔棋

所谓“悔棋”,就是在棋盘上将前一步的棋子“拿走”。我们之前已经定义过存储棋子信息的数组,要实现悔棋,只需把与棋子有关的信息全部抹去即可。这时棋盘上已经没有了它的信息,但它却还能显示在棋盘上,怎么办?添加重绘棋盘语句就可以了。

|

首先,我们给界面上的“悔棋”按钮添加监听方法:如果选择的是悔棋按钮,那么抹去棋子信息,重绘棋盘。但仅仅这样是不行的,我们还要对它有所约束:

1.单机对战时第一颗棋子不能被悔棋

2.人机对战时点击悔棋需要将玩家的棋子和电脑的棋子同时抹去,并且也要保留至少一颗棋子

3.在胜负已分之后无法悔棋

代码如下:

if (e.getActionCommand().equals("悔棋") && !chess.checkB()

&& !chess.checkW()) {

if (mode == 1 && chess.cb.count > 1) {

chess.cb.bl[chess.x[--chess.cb.count]][chess.y[chess.cb.count]] = 0;

} else if (mode == 2 && chess.cb.count > 2) {

chess.cb.bl[chess.x[--chess.cb.count]][chess.y[chess.cb.count]] = 0;

chess.cb.bl[chess.x[--chess.cb.count]][chess.y[chess.cb.count]] = 0;

}

panel.repaint();

}

代码中的x和y数组分别存储的是最后一个被画出的棋子的行列数。如果使用的是ArrayList来存储棋子的话就更加方便:只需移除最后一颗存储的棋子就行了。

六.总结

到这里,五子棋项目就告一段落了,但是可以改进的地方还有很多:完成对界面的美化;使用图片代替画出的棋盘和棋子;使用更好的算法来编写电脑如何落子......要做出更好的产品,就要有精益求精的精神。

| 一秋

| 2016年11月17日

新手java五子棋完整代码判断落子落在线上_JAVA五子棋开发相关推荐

  1. 新手java五子棋完整代码判断落子落在线上_Java初学者,编写小游戏五子棋的问题?...

    首先你需要掌握GUI编程,事件处理,已经监听器,你就掌握Swing的知识就好了Swing框架,JFrame,JPanel,鼠标.键盘监听事件 Java基础,面向对象,异常处理,集合,IO流 网络编程, ...

  2. 经典十大排序算法(含升序降序,基数排序含负数排序)【Java版完整代码】【建议收藏系列】

    经典十大排序算法[Java版完整代码] 写在前面的话 十大排序算法对比 冒泡排序 快速排序 直接选择排序 堆排序 归并排序 插入排序 希尔排序 计数排序 桶排序 基数排序 完整测试类 写在前面的话   ...

  3. java 数据库工资管理系统设计_数据库课程设计—企业工资管理系统(java版完整代码)...

    数据库课程设计-企业工资管理系统(java版完整代码) 数 据 库 课 程 设 计 报 告2016年 5月 20日 目 录企业工资管理系统姓 名 王 素 文班 级 软 133学 号 139074224 ...

  4. java中怎么把数字打印在屏幕上_java中如何打印出蜗牛形状的数字

    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public ...

  5. python写科学计算器代码_Python编程使用tkinter模块实现计算器软件完整代码示例...

    Python编程使用tkinter模块实现计算器软件完整代码示例 来源:中文源码网    浏览: 次    日期:2018年9月2日 Python编程使用tkinter模块实现计算器软件完整代码示例 ...

  6. vue----前端分页完整代码

    首先,做出来的效果如图所示,具体的Ajax请求数据可以写在点击函数中 分页效果算是比较费脑子的,里面计算有些麻烦,本文上完整代码,一起学习进步 "上一页"写两个li元素,如果已经是 ...

  7. P74-前端基础项目开发-首页main部分开发广告栏-项目完整代码

    P74-前端基础项目开发-首页main部分开发广告栏-项目完整代码 1.概述 这篇文章是首页开发最后一个部分,也是这个项目的结束部分.通过这个项目练习让我们掌握了HTML+CSS的基础使用. 2.广告 ...

  8. 判断大文件是否上传成功(一个大文件上传到ftp,判断是否上传完成)

    大文件上传ftp,不知道有没有上传完成,如果没有上传完成另一个程序去下载这个文件,导致下载不完整. 判断一个文件是否上传完成的方法: /*** 间隔一段时间去计算文件的长度来判断文件是否写入完成* @ ...

  9. Java实现单机五子棋,含完整代码

    文章目录 五子棋!! 实现功能 改进方向 主体思路 遇到的困难 完整代码 五子棋!! 实现功能 基本的棋盘绘制,重绘,输赢判断,悔棋,重新开始 改进方向 添加背景音乐,背景图片美化,用棋子图片代替原棋 ...

最新文章

  1. 使用Numpy实现PCA
  2. C++中构造函数调用构造函数
  3. SAP HUM 如何将HU里的物料号换成另外一个物料号?
  4. T-SQL 将存储过程结果插入到表中
  5. 数学在机器学习中的作用
  6. echart雷达图文字挤在一起_【数据可视化·图表篇】雷达图
  7. 这几个动态规划的问题,面试官就爱问
  8. 人脸聚类--最好的防御是进攻
  9. Xftp报no matching outgoing encryption algorithm found
  10. 区域增长 matlab,图像分割 区域增长
  11. 图书馆管理系统前端ajax接口,基于AJAX的图书馆管理系统的设计与实现
  12. OpenWrt--高通QCA9563添加多wan口方法
  13. Java之时间格式转换
  14. C语言——函数定义及用法【内部函数外部函数内联函数】
  15. 安装Aras Innovator
  16. 利用python玩旅行青蛙
  17. 为QNX系统增加定制命令方法
  18. 从拖延到高效,我推荐这7本书
  19. 工程流体力学笔记暂记18(二元漩涡的速度和压强分布)
  20. 贪玩蓝月服务器维护需多少时间,贪玩蓝月一般多久合区 | 手游网游页游攻略大全...

热门文章

  1. plc中MB和VB的区别
  2. K-Means聚类算法以及扩展算法K-Modes、K-Prototype
  3. 搭建openlab网站
  4. C# 网络斗地主源码开源
  5. 微信 html avi视频无法播放,为何MP4不能播放MP4格式视频文件
  6. html动画效果代码模板,7款绚丽的jQuery/HTML5动画及源码
  7. (fucntion(){})()
  8. JavaScript 魔幻代理
  9. 打开计算机 硬盘显示空白,电脑上文件夹的查看选项显示空白如何解决
  10. MySQL连接查询 内连接和外连接的区别