Java五子棋最全教程

Java五子棋最全教程

Ps:首先当我们做一个项目时应该培养这样一种思维即这个项目怎么开展,分为哪几个功能,这每个功能又该分为哪几步去实现,只有确定了基本路线,才有利于我们设计程序的基本结构,也让我们的开发效率更高,所以我推荐大家在做项目的时候要刻意地去思考这些问题,思考多了,我们做起项目来就会得心应手。

第一步:
落到实处我们这个项目第一步即创建界面,并在界面上绘出棋盘。在Java中我们有一个专门创建界面的类JFrame,利用类里面的一些方法,我们可以设置一些基本功能。例如:

JFrame jf = new JFrame("五子棋");
jf.setSize(900,800);
jf.setLocationRelativeTo(null);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setVisible(true);

还有一些操作可根据自身需求自行设定。接下来就是绘制棋盘,首先确定棋盘线条数,格子大小,与界面边界距离,利用Graphics这个类获取画笔,接下来的步骤就很简单了……棋盘画好后。当我们运行时会发现一个问题—移动窗口时棋盘就会消失不见了,这当时确实令我也感到很疑惑,在经过资料查阅后我发现在Java中每个Component类(所有组件的父类)都有一个paint方法用于重绘,每次重画该Component时都自动调用paint方法,有了这个知识点我们很快想到,应该把画棋盘这步操作写在paint方法中,这里我们可以创建新类继承JFrame这个类,并重写paint方法。

第二步:
我们应该思考有了棋盘就应该画棋子了啊,可是怎样实现鼠标一点,电脑就会我们点击的地方画棋子呢,而且还要准确画在棋盘交界处?Java中给我们提供了一个监听器接口MouseListener(当然还有许多其他监听器种类,这里不一一介绍),这些接口通常是要我们自定义类,并实现接口里的方法。它的工作模式采用了著名的授权事件模型,由三部分组成:事件源,事件监听器,事件;当事件源产生一个事件后,事件记录发生的一切事件,并从事件源发送给事件监听器对象,事件监听器根据事件的类型确定调用的方法。
具体可参见
listener监听器的工作原理是什么
理解了这个技术点同时还要注意之前提到的重绘问题,但是继承监听器的类里无法实现重绘,这里我提供一种方法,即把棋盘看成二维数组,我们每下一个棋子,就相当于把二维数组的对应位置值置为1或-1(代表黑棋白棋),然后再把这个二维数组传到之前写的窗口类里,依据数组值,即可实现重绘。这里我提一个技术细节:我们如何准确地将棋子下在棋盘交界处呢,我的方法是建立自定义坐标模型,将棋盘看成另一个坐标系,左上角第一个点为(0,0)以此类推,当点击一个位置时,我们获取位置基于窗口的坐标,然后减掉边距,利用整除性质,将这个坐标换算成基于棋盘的坐标,如此,即使我们点击的位置没有落在棋盘交界处,电脑也会准确地将棋子画在交界处。

3.此时我们已经实现了棋盘和棋子的重绘,接下来应思考五子棋应具备那些功能,我们玩五子棋的时候,基本上都会看到这样几个功能,①人人对战 ②人机对战 ③悔棋 ④清空棋盘这样几个功能,并通过点击按钮来实现,接下来就应该思考如何给界面添加按钮。这里涉及到几个知识点。

一.界面的布局管理:更详细的讲解参考布局管理
JFrame默认使用边界布局
在这里插入图片描述
这种模式根据其首选大小和容器大小的约束 (constraints) 对组件进行布局。NORTH 和 SOUTH 组件可以在水平方向上拉伸;而 EAST 和 WEST 组件可以在垂直方向上拉伸;CENTER 组件可同时在水平和垂直方向上拉伸,从而填充所有剩余空间。 因此在一个方向上添加了按钮如果再添加按钮会覆盖原来的,而想到JPanel默认是流式布局,流布局一般用来安排面板中的按钮。它使得按钮呈水平放置,直到同一条线上再也没有适合的按钮。线的对齐方式由 align 属性确定。可能的值为:
在这里插入图片描述
故可以这样添加按钮:

JPanel jp = new JPanel();
jp.setLayout(new FlowLayout(FlowLayout.CENTER,0,30));//0为水平间距
JButton Btu1 = new JButton("mybutton");
jp.add(Btu1);
jp.setPreferredSize(new Dimension(120,0));//设置画布的宽度,因为画布会添加到采用边界布局的JFrame中,故不用设置高度
jf.add(jp,BoderLayout.EAST);
同时对于按钮可以这样设置
Btu1.setText(title);
Btu1.setFont(font);
Btu1.setPreferredSize(dimen);
Btu1.setMargin(Ins);

二.就是为添加的按钮重写功能,利用ActionListener,并重写里面的public void actionPerformed(ActionEvent e),原理与MouseListener类似

4.关于五子棋的人机对战我这里提供两种思路
4.1.权值算法
当进行五子棋对战时,我们每下一个棋子都要判断这个点的横竖斜情况,对不同的情况采取不同的应对措施,,而这种思考过程就是一种权值法,每下一步棋,我们通过权值找到最优解。
整个棋盘记为Location[15][15];初始全为0,即没有棋子,当棋子下在棋盘上时,这个位置值改变,黑棋为1,白棋2,整个棋局就被保存了;然后创建weight[15][15],来保存每个位置的权重,当遍历Location时首先判断是否为0,然后判断这个位置向左,向右,向上,向下等八个方向的棋局情况,并将各种情况权重加起来就是这个位置的权值,找最大值即可。
怎么将棋局与权值联系起来呢?
HashMap<String, Integer> map = new HashMap<String, Integer>();
设置权值的思路是什么呢?
在这里插入图片描述
上面的矩阵图只是一个思路,其中并没有涉及到具体的很多特殊情况和细节,其中眠和活分别指的是相连的同色棋的两端有没有敌方的棋堵住。我们可以根据这样的一个矩阵图思路清晰的写出自己对不同情况的认知,其中不仅仅需要考虑到单种情况的权值,还需要考虑到其中两个的权值和与其它的权值比较,例如当遍历到一个可下地方的时候发现对方存在两个活二,那么这个点就是十分危险的点了,必须要拦截,所以两个活三的和一定要比较大,才能让电脑发现这个情况的紧急。

4.2 AI算法
对整个棋局或其中的有效位置进行评价。往往会使用一个分表。而评分表却很难确定,也没有所谓最好的,有人根据经验和测试,总结了不错的评分表,我在程序中都是用的别人的评分表。评估当前棋局中,哪个位置的得分最高。五子棋要赢,必然要有五个棋子在一起成线,那么我们就可以计算棋盘中每一个五格相连的线,一下称之为五元组。一般情况(包括专业五子棋)下棋盘是15*15的。那么应该是572个五元组。同时,针对五元组中黑子和白子的数量(可以不考虑相对位置)的不同,给该五元组评不同的分。然后每一个位置的得分就是包含这个位置的所有五元组的得分之和。
这里我给出评分表和一个方向的评分计算:

for(int i=0;<15;i++){
	for(int j=0;j<15;j++){
		int k=j;
		while(k<j+5){
			if(Location[i][k]==1) humanNum++;
			if(Location[i][k]==-1) machineNum++;
			k++;
			}
		int tupleScoreTmp=tupleScore(humanNum,machineNum);
		for(int k=j;k<j+5;k++)
		{
			Score[i][k]=tupleScore;
		}
		humanNum=0;
		machineNum=0;
		tupleScoreTmp=0;
		}
	}

public int tupleScore(int humanChessmanNum, int machineChessmanNum){
        //1.既有人类落子,又有机器落子,判分为0
        if(humanChessmanNum > 0 && machineChessmanNum > 0){
            return 0;
        }
        //2.全部为空,没有落子,判分为7
        if(humanChessmanNum == 0 && machineChessmanNum == 0){
            return 7;
        }
        //3.机器落1子,判分为35
        if(machineChessmanNum == 1){
            return 35;
        }
        //4.机器落2子,判分为800
        if(machineChessmanNum == 2){
            return 800;
        }
        //5.机器落3子,判分为15000
        if(machineChessmanNum == 3){
            return 15000;
        }
        //6.机器落4子,判分为800000
        if(machineChessmanNum == 4){
            return 800000;
        }
        //7.人类落1子,判分为15
        if(humanChessmanNum == 1){
            return 15;
        }
        //8.人类落2子,判分为400
        if(humanChessmanNum == 2){
            return 400;
        }
        //9.人类落3子,判分为1800
        if(humanChessmanNum == 3){
            return 1800;
        }
        //10.人类落4子,判分为100000
        if(humanChessmanNum == 4){
            return 100000;
        }
        return -1;//若是其他结果肯定出错了。这行代码根本不可能执行
    }

具体代码我已上传github,有兴趣的小伙伴可以下载看看
https://github.com/wmdsg/gobang

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值