五子棋的Java实现 详解
来源:互联网 发布:登录淘宝要脸部拍摄 编辑:程序博客网 时间:2024/05/17 23:52
最近用Java写了一个有人机对战和人人对战两种模式的五子棋小游戏,也算是有自己一定的心得,现在把它分享出来。代码我会放在最后面,先把五子棋的思路捋清楚
一、首先上界面图
这是我五子棋的界面,比较简单,大家可以根据自己的想法随意DIY.
二、如何实现界面?
这个五子棋界面,由四个类实现,分别是:
JFrame(作为顶层容器类的JFrame用来添加其他的所有组件)
JPanel(在界面中用来分别添加棋盘界面和按钮界面)
JButton(在界面作为按键如“开始”“认输”“重新开始”按键)
JRadioButton(在界面中作为单选按钮如“人人对战”“人机对战”)
界面中有两个JPanel面板对象,一个在界面的左边用来绘制棋盘,一个在界面的右边用来放置按键。
给出界面实现代码
public class FiveChess extends JPanel{ public Graphics g; //五子棋盘里面的Graphics属性用来画图 public static void main (String args[]){ FiveChess fc=new FiveChess(); //实例化五子棋盘类得到五子棋盘对象fc fc.go(); //五子棋盘对象fc调用go() } public void setG(Graphics g){ //五子棋盘类用来设置Graphics属性的方法 this.g=g; } public void go(){ /* * 创建一个窗体,设置窗体里的属性 */ JFrame frame=new JFrame(); //实例化一个窗体对象frame frame.setTitle("五子棋"); //设置窗体的标题 frame.setSize(800,500); //设置窗体的大小 frame.setDefaultCloseOperation(3); /* * 添加两个面板 利用board布局 一个放中间 用来画五子棋的棋盘 一个放东边用来放置按钮 */ JPanel buttonpanel=new JPanel(); //实例化一个面板对象buttonpanel buttonpanel.setPreferredSize(new Dimension(300,500));//设置buttonpanel大小 只有窗体对象能够用setSize() buttonpanel.setBackground(Color.white); //设置buttonpanel对象的背景颜色 /* * 添加按钮 */ //实例化三个按钮对象 JButton bstart=new JButton("开始"); JButton bquit=new JButton("认输"); JButton bundo=new JButton("悔棋"); //设置三个按钮对象的大小 bstart.setPreferredSize(new Dimension(200,50)); bquit.setPreferredSize(new Dimension(200,50)); bundo.setPreferredSize(new Dimension(200,50)); //将这三个按钮对象添加到面板对象bp中去 buttonpanel.add(bstart); buttonpanel.add(bquit); buttonpanel.add(bundo); //实例化按钮组对象bg ButtonGroup bg=new ButtonGroup(); //实例化两个单选按钮对象 JRadioButton bpvp=new JRadioButton("人人对战"); JRadioButton bpvr=new JRadioButton("人机对战"); //设置两个单选按钮对象的大小 bpvp.setPreferredSize(new Dimension(200,50)); bpvr.setPreferredSize(new Dimension(200,50)); //将这两个单选按钮对象设为透明背景 bpvr.setOpaque(false); bpvp.setOpaque(false); bg.add(bpvp); //将这两个单选按钮对象添加到按钮组对象bg中 bg.add(bpvr); //将这两个单选按钮对象添加到面板对象buttonpanel中 buttonpanel.add(bpvp); buttonpanel.add(bpvr); this.setBackground(Color.gray); //棋盘对象设置背景色 /* * 窗体布局,将棋盘面板和按钮面板合理地布局在frame窗体对象上 */ BorderLayout bl=new BorderLayout(); //实例化一个布局对象 frame.setLayout(bl); //将窗体对象frame设置为BorderLayout frame.add(this,BorderLayout.CENTER); //把棋盘对象放在窗体对象的中间 frame.add(bp,BorderLayout.EAST); //把按钮面板放在窗体对象的东边 frame.setVisible(true); //窗体对象设置为可见 //棋盘对象设置Graphics属性 g=this.getGraphics(); }}
以上这段代码并没有给出如何画出15*15的棋盘
因为棋盘是在左边板上画出来的我们可以利用左边面板的重绘paint()方法画15*15棋盘
/* *画五子棋棋盘的方法 */ public void draw(Graphics ag){ Graphics2D g=(Graphics2D)ag; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ /* * 首先把棋盘重新画一遍 */ g.setColor(Color.black); g.drawLine(30,30+i*30,450,30+i*30); //画横线 g.drawLine(30+i*30,30,30+i*30,450); //画竖线 } }
重写paint()方法
public void paint(Graphics g){ super.paint(g); draw(g); }
有了以上这几段代码我们就能完全实现如图所示的界面了。
有了界面之后,棋子呢?
当我们做好界面之后理所应当地需要在棋盘面板上放置棋子。
Java是一门面向对象的语言,我们可以把棋子作为一个类。
在棋盘上每一个横竖线交叉的地方都应该有一个棋子,在相对应的地方没有下棋的时候,棋子是隐形的棋子状态flag为0,在相对应的位置下完棋之后棋子状态flag为1(白棋)或者2(黑棋)。
public class chess { public int X,Y; // X,Y是棋子对应在棋盘上的坐标,我们之后就是利用棋子的坐标,在棋盘上画出它。 public int flag; //代表棋子的三种状态flag:0 没有落子 flag:1 白子 flag:2 黑子 public int value; //该棋子的权值 public chess(){ X=0; Y=0; flag=0; value=0; } public chess(int x,int y){ X=x; Y=y; flag=0; value=0; }}
我们可以定义一个15*15的二维数组 数组下标代表棋子在棋盘的相应位置,而棋子的X Y属性代表棋子在棋盘上的坐标值。最左上角的棋子坐标为X:30,Y:30;在二维数组中为行下标为0 列下标为0
for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ chess ch=new chess((j+1)*30,(i+1)*30) xy[i][j]=ch; } }
**行列下标和棋子坐标对应关系为:
i=xy[][].Y/30-1; j=xy[][].X/30-1;**
如何让棋子在棋盘上出现呢?我们首先要用setGraphics()方法获得棋盘上的“画笔”g,
在设置为对应的白色或黑色之后
然后利用g.fillOval()方法在棋盘界面上画出白色棋子或黑色棋子。
当棋子出现在棋盘上之后,那么棋子的状态flag属性就应该从“0”(没有落子)转变为“1”(白棋)或“2”(黑棋)
在这里还应当设置一个变量color,用来记录和判断当前下的是白棋还是黑棋,假设用color为“1”代表当前下的是白棋,color为“2”代表当前下的是黑棋。在下完黑棋之后应当把color置为“1”,下次就下白棋,下完白棋之后把color设置为“2”,下次就下黑棋。
下完棋子之后呢?
我们在下完棋子之后就应当判断是否有五颗同颜色棋子相连了,我们不需要遍历整个棋盘去判断,只需要在最后落子的地方,从横向,竖向,正对角线,反对角线四个大方向上去判断是否有相同颜色的棋子连成一条线。
先上这部分代码
/* * 方法的参数是棋子对象 */public void checkChess(chess ch){ //实例化一个弹窗对象,后面用来提白方还是黑方获胜 JOptionPane op=new JOptionPane(); /* * 初始化变量用来存储在 水平 垂直 正对角线 反对角线方向上的连续棋子 */ int count1=1; int count2=1; int count3=1; int count4=1; /* * 将棋子的坐标值 转换为对应的数组下标值 xy[0][0] 的坐标是(30 ,30)xy[0][1]的坐标是(60,30) */ int x=ch.Y/30-1; //行坐标 int y=ch.X/30-1; //列坐标 /* * 水平方向上的检测 */ for(int j=y+1;j<15;j++){ //行坐标不变 列坐标递增 检测ch右边的落子情况 if(xy[x][y].flag==xy[x][j].flag){ //如果ch右边的棋子和ch颜色一样 计数加一 count1++; }/如果ch右边的棋子和ch颜色不一样 则退出计数 else break; //如果不是的话退出判断 } for(int j=y-1;j>=0;j--){ //行坐标不变 列坐标递减 检测ch左边的落子情况 if(xy[x][y].flag==xy[x][j].flag){ count1++; } else break; } //水平方上的落子情况检测完 if(count1>=5){ if(xy[x][y].flag==1) op.showMessageDialog(null, "白棋胜出"); else op.showMessageDialog(null, "黑棋胜出"); } /* * 垂直方向上的检测 */ for(int i=x-1;i>=0;i--){ //列坐标不变 行坐标递减 检测ch上面的落子情况 if(xy[x][y].flag==xy[i][y].flag&&xy[x][y].flag!=0){ count2++; } else break; } //列坐标不变 行坐标递加 检测ch下面的落子情况 for(int i=x+1;i<15;i++){ if(xy[x][y].flag==xy[i][y].flag&&xy[x][y].flag!=0){ count2++; } else break; } // 垂直方向上的棋子情况检测完 if(count2>=5){ if(xy[x][y].flag==1) op.showMessageDialog(null, "白棋胜出"); else op.showMessageDialog(null, "黑棋胜出"); } /* * 正对角线上面的检测 */ for(int i=x-1,j=y+1;i>=0&&j<15;i--,j++){ // 行递减 列递加 if(xy[x][y].flag==xy[i][j].flag&&xy[x][y].flag!=0){ count3++; } else break; } for(int i=x+1,j=y-1;j>=0&&i<15;j--,i++){ //行递加 列递减 if(xy[x][y].flag==xy[i][j].flag&&xy[x][y].flag!=0){ count3++; } else break; } //正对角线上检测完毕 if(count3>=5){ if(xy[x][y].flag==1) op.showMessageDialog(null, "白棋胜出"); else op.showMessageDialog(null, "黑棋胜出"); } /* * 反对角线上的检测 */ for(int i=x+1,j=y+1;i<15&&j<15;i++,j++){ //行递加列递加 if(xy[x][y].flag==xy[i][j].flag&&xy[x][y].flag!=0){ count4++; } else break; } for(int i=x-1,j=y-1;j>=0&&i>=0;j--,i--){ if(xy[x][y].flag==xy[i][j].flag&&xy[x][y].flag!=0){ count4++; } else break; } //反对角线上检测完毕 if(count4>=5){ if(xy[x][y].flag==1) op.showMessageDialog(null, "白棋胜出"); else op.showMessageDialog(null, "黑棋胜出"); } }
如何实现按键点击和落子?
当我们是实现了界面之后,理所应当的,接下来就是实现点击按键和落子的功能了,如何达到在点击按键和落子之后五子棋界面会有相应的反应呢?这需要我们用到事件监听机制,为按键加上监听。
按键点击的事件监听,我们要用到ActionListener接口。
在棋盘上落子实际上就是点击棋盘,棋盘上相应的位置画出棋子。我们可以用到MouseAdapter适配器
定义一个继承了MouseAdapter适配器实现了ActionListenner接口的事件监听类listener
给出对应部分代码
package com.ly.fiveChessV2;import java.awt.Color;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.RenderingHints;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.MouseAdapter;import java.awt.event.MouseEvent;import java.util.HashMap;import javax.swing.JOptionPane;import javax.swing.JPanel;/* * 点击按钮 点击棋盘面板 */public class listener extends MouseAdapter implements ActionListener{ int mode; int x,y; //x y是鼠标在棋盘界面上获得的坐标 private Graphics2D g; //画笔用来绘图 int chesscount; //连成一条线的同一颜色棋子数量 public chess [][]xy; //定义一个棋子数组 int col=1; //定义一个颜色标志位 1:白色2:黑色 chess chessnew=new chess(); //用一个棋子代表最近下的棋 public JPanel panel; /* * 将棋盘上的画笔g赋值给属性g */ public void setG(Graphics g){ this.g=(Graphics2D)g; } /* * 把棋盘面板对象传过来 */ public void setP(JPanel panel){ this.panel=panel; } /* * 将棋子数组传入 */ public void setChess(chess [][]xy){ this.xy=xy; } /* *处理按键事件 */ public void actionPerformed(ActionEvent e){ JOptionPane op=new JOptionPane(); String str= e.getActionCommand(); /* * 认输按键 */ if(str.equals("认输")){ if(col==1){ op.showMessageDialog(null, "白棋认输,黑棋胜"); } else{ op.showMessageDialog(null, "黑棋认输,白棋胜"); } for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ xy[i][j].flag=0; } } panel.paint(g); } /* * 悔棋按键 */ if(str.equals("悔棋")){ //悔棋应该是利用棋盘界面的重画paint()方法 if(chessnew.flag==1){ col=1; System.out.println("悔棋的是白棋,白棋再落一子"); } else if(chessnew.flag==2){ col=2; System.out.println("悔棋的是黑棋,黑棋再落一子"); } chessnew.flag=0; //把最近一次的棋子抹除掉 panel.paint(g); for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ if(xy[i][j].flag!=0){ if(xy[i][j].flag==1){ g.setColor(Color.white); g.fillOval(xy[i][j].X-10, xy[i][j].Y-10,20,20); } else{ g.setColor(Color.black); g.fillOval(xy[i][j].X-10, xy[i][j].Y-10,20,20); } } } } System.out.println("悔棋"); } if(str.equals("人人对战")){ mode=11; } if(str.equals("人机对战")){ mode=12; } } /* * 处理鼠标点击事件 */ public void mouseClicked(MouseEvent e) { /* * 画笔抗锯齿 */ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); /* * 获取鼠标点击的x和y坐标 */ x=e.getX(); y=e.getY(); /* * 由鼠标的位置 得到棋子圆心该放的位置 */ for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ //循环遍历数组找到鼠标点击的最近的格子 if(Math.abs(x-xy[i][j].X)<10&&Math.abs(y-xy[i][j].Y)<10){ if(xy[i][j].flag==0){ //判断点击处是否没有落过子 if(col == 1){ //判断棋子该下的颜色 g.setColor(Color.white); System.out.println("white"); g.fillOval(xy[i][j].X-10, xy[i][j].Y-10,20,20); xy[i][j].flag=1; //flag为1表示下的是白棋 chessnew=xy[i][j]; //将这一步下的棋保存起来 col=2; //下一次下黑旗 } else if(col==2){ g.setColor(Color.black); System.out.println("black"); g.fillOval(xy[i][j].X-10, xy[i][j].Y-10,20,20); xy[i][j].flag=2; //flag为1表示下的是黑旗 col=1; //下一次下白棋 } checkChess( xy[i][j]); // 落完子 判断输赢之后 电脑落子 判断输赢 } else //已经有棋子落下过 System.out.println("这里不能落子"); } } } } }}
在写完事件监听类之后我们应该实例化一个事件监听类对象,并为按键和棋盘添加事件监听。
附上相关代码
/* * 实例化一个监听对象 * 为各个组件加上监听 */ listener l=new listener(); l.setG(g); l.setP(this); l.setChess(xy); this.addMouseListener(l); bquit.addActionListener(l); bundo.addActionListener(l); bstart.addActionListener(l); bpvp.addActionListener(l); bpvr.addActionListener(l); this.setG(g); //棋盘对象设置Graphics属性
有了以上代码我们的五子棋就能实现人人对战的功能。
如何实现人机对战?
要实现人机对战,那么就要让电脑知道棋子该下到哪一个位置,我们可以通过计算每一个位置上隐形棋子的权值(只有没下过的棋子才需要考虑)。电脑优先下权值大的棋子,然后判断一次输赢。
如何确定棋子的权值?
我的方法是用一个字符串记录棋子水平方向的、竖直方向上的、正对角线方向上、反对角线方向上的落子情况。然后根据落子情况,给该棋子赋值。
我们先要给出隐形棋子各个方向上可能的情况,和这种情况对应的权值。
我们可以利用哈希表实现
//首先实例化一个哈希表对象 HashMap<String,Integer> hm = new HashMap<String,Integer>();
隐形棋子为“0”,白子为“1”,黑子为“2”;
上图对应的字符串即为“2022”(黑子、隐形棋子、黑子、黑子)
hm.put("2022",10);
我把上图中情况的棋子的权值设置为10,当我们以后检测到隐形棋子周围的落子情况之后,就能取出对应的权值。
比如字符串S为“2022”;
int value=hp.get(S);
那么value的值就是10;
当我们把一个隐形棋子的水平、竖直、正对角线、斜对角线方向上的情况全都统计完并把四个方向上的权值相加,得到该棋子最终的权值,下面给出计算权值的代码方法caculate()
public void caculate(chess ch){ String str="0"; //用一个字符串来记录该棋子旁边的落子情况 int x=ch.Y/30-1; //把ch棋子对应的横纵坐标转换成在棋子数组里的行和列 int y=ch.X/30-1; hashInit(); /* * 计算该位置棋子的权值 * 参数为一个棋子 棋子flag有三种状态 0:还没有落子 1:落白子 2:落黑子 */ if(ch.flag==0){ //只有没有当棋子没有落下的时候才要计算该棋子的权值 Integer val; for(int j=y+1;j<15;j++){ //记录棋子右边的落子情况 是列在变 if(xy[x][j].flag==0){ //该棋子右边没有落子 break; //不用记录直接退出遍历循环 } else if(xy[x][j].flag==1){ str=str+1; //str在左边 往右加 } else if(xy[x][j].flag==2){ str=str+2; //str在左边 往右加 break; } } // 水平方向上检测棋子 往左看 for(int j=y-1;j>=0;j--){ if(xy[x][j].flag==0){ //该棋子左边没有落子 break; //不用记录直接退出遍历循环 } else if(xy[x][j].flag==1){ str=1+str; //str在右边 往左加 } else if(xy[x][j].flag==2){ str=2+str; //str在右边 往左加 break; } } // 到这里之后棋子ch的左右情况就统计完了 str代表统计情况 利用str在哈希表对象中得到对应的键值 val=hm.get(str); System.out.println("水平方向:"+x+"行"+y+"列棋子 str:"+str); if(val!=null){ xy[x][y].value+=val; //把对应的键值加到棋子中去 } str="0"; //竖直方向上 往上看 for(int i=x-1;i>=0;i--){ if(xy[i][y].flag==0){ break; } else if(xy[i][y].flag==1){ str=1+str; } else if(xy[i][y].flag==2){ str=2+str; break; } } //竖直方向上 往下看 for(int i=x+1;i<15;i++){ if(xy[i][y].flag==0){ break; } else if(xy[i][y].flag==1){ str=str+1; } else if(xy[i][y].flag==2){ str=str+2; break; } } System.out.println("竖直方向:"+x+"行"+y+"列棋子 str:"+str); val=hm.get(str); if(val!=null){ xy[x][y].value+=val; } str="0"; //正对角线方向上 向上 for(int i=x-1,j=y+1;i>=0&&j<15;i--,j++){ if(xy[i][j].flag==0){ break; } else if(xy[i][j].flag==1){ str=1+str; } else if(xy[i][j].flag==2){ str=2+str; break; } } //正对角线方向上 向下 for(int i=x+1,j=y-1;i<15&&j>=0;i++,j--){ if(xy[i][j].flag==0){ break; } else if(xy[i][j].flag==1){ str=str+1; } else if(xy[i][j].flag==2){ str=str+2; break; } } System.out.println("正对角线上"+x+"行"+y+"列棋子str:"+str); val=hm.get(str); if(val!=null) xy[x][y].value+=val; str="0"; //反对角线方向上 向上 for(int i=x-1,j=y-1;i>=0&&j>=0;i--,j--){ if(xy[i][j].flag==0){ break; } else if(xy[i][j].flag==1){ str=1+str; } else if(xy[i][j].flag==2){ str=2+str; break; } } //反对角线方向上 向下 for(int i=x+1,j=y+1;i<15&&j<15;i++,j++){ if(xy[i][j].flag==0){ break; } else if(xy[i][j].flag==1){ str=str+1; } else if(xy[i][j].flag==2){ str=str+2; break; } } System.out.println("反对角线上"+x+"行"+y+"列棋子str:"+str); val=hm.get(str); if(val!=null) xy[x][y].value+=val; str="0"; System.out.println(x+"行"+y+"列棋子权值:"+xy[x][y].value); } }
有了计算一个棋子权值的方法那么,接下来然计算机决定在哪里落子就很容易了。我们可以再写一个方法AI(),循环遍历棋子数组,在隐形棋子里找出权值最大的一个棋子,然后落子。
public void AI(){ int x,y; // 定义一个棋子变量 chess ch =new chess(); //遍历二维棋子数组 计算出每一个未落子棋子权值 for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ System.out.println("计算"+i+"行"+j+"列 棋子"); caculate(xy[i][j]); } } //遍历整个二维棋子数组,找出权值最大的棋子 for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ if(xy[i][j].value>=ch.value){ ch=xy[i][j]; } } } System.out.println("权值最大的棋子 "+(ch.Y/30-1)+"行"+(ch.X/30-1)+"列"); //落子权值最大的棋子 x=ch.Y/30-1; y=ch.X/30-1; xy[x][y].flag=2; g.setColor(Color.black); g.fillOval(xy[x][y].X-10, xy[x][y].Y-10,20,20); //清空棋子的权值 for(int i=0;i<15;i++){ for(int j=0;j<15;j++){ xy[i][j].value=0; } } }
哈希表中五子棋的情况怎么给?
有人可能会说,下一盘五子棋那么多的棋子,怎么事先给出棋子的落子情况,和设置对应的落子情况呢?
其实 五子棋的落子情况需要考虑的不过是 活二 跳二 活三 眠三 活四 眠四之类的
我在这里给出活二 跳二 活三 眠三 活四 眠四之类相关介绍的链接 http://game.onegreen.net/wzq/HTML/142336.html
好了到这里最主要的代码已经讲解完了,大家要是有什么不清楚的地方可以给我留言。
- 五子棋的Java实现 详解
- java实现的五子棋
- java实现简单的五子棋
- Java智能五子棋的实现
- Java简单实现的五子棋
- 幼稚的五子棋界面(java实现)
- JAVA 五子棋 判断输赢的代码实现
- Java五子棋的实现(附源码)
- JAVA-五子棋实现
- java实现简单五子棋
- java五子棋初实现
- java实现控制台五子棋
- Java swing五子棋源码及实现之Java 开发图形界面程序五子棋的实现方式
- 猴子也会懂的C语言五子棋的实现方法详解
- C实现的五子棋
- js五子棋的实现
- 五子棋的实现
- 五子棋的实现
- git diff 生成path
- SSM(五)基于webSocket的聊天室
- 服务器报的漏洞解决办法
- Android NDK编译静态链接库及动态链接库(librtmp编译小白填坑)
- 解决“Visual Studio 要求设计器使用文件中的第一个类。”方法
- 五子棋的Java实现 详解
- 设计模式——结构型模式
- 2.Android注解-编译时生成代码 APT(Annotation Processing Tool ) 实例说明
- 和为S的连续正数序列
- 基于SDK的支付接口服务端——支付宝,微信
- 小记(17831)
- RxJava2操作符之“Delay”
- SSM(六)跨域传输
- 借助扩展事件查看SQL 2016备份和还原操作的内幕