public class FiveChessFrame extends JFrame { public FiveChessFrame() { //窗口名字 this.setTitle("五子棋"); //大小 this.setSize(700,700); //关闭时退出 this.setDefaultCloseOperation(EXIT_ON_CLOSE); //限制窗口变大 this.setResizable(false); //窗体显示出来居中对齐 this.setLocationRelativeTo(null); //窗口显示出来 this.setVisible(true); } }
在创建一个测试类就可以run了
public class Test { public static void main(String[] args) { // TODO Auto-generated method stub FiveChessFrame f=new FiveChessFrame(); } }
2.画棋盘,添加背景图片
背景图片添加:这里用的是IO流的方式从文件中选取图片
在FIveChessFrame类中写paint方法
public void paint(Graphics g) { //画背景图片 BufferedImage img=null; try { img=ImageIO.read(new File("C:\\Users\\86189\\Desktop\\记录图片\\bj1.jpg")); //这后面的图片位置根据自己的改变 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } g.drawImage(img, 0, 0, this); }
画棋盘:在paint方法中 根据窗口大小在适当的位置画出19*19的棋盘即可
g.setColor(Color.black); for(int i=0;i<19;i++) { g.drawLine(i*33+30, 70, i*33+30, 670); g.drawLine(30, 70+i*33, 630, 70+i*33); }
3.画棋子:
棋子的位置是根据鼠标点击确定,所以我们需要加上一个鼠标监听器,并重写该接口的方法
public class FiveChessFrame extends JFrame implements MouseListener{ ...//(此处为上面所写代码) @Override public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mousePressed(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseReleased(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub } }
可以用鼠标监听器获取到点击的坐标并保存给x,y,在paint()方法中给画出来
在外面定义x,y保存棋子坐标
//棋子坐标 int x; int y;
paint()中:
//画棋子 g.fillOval(x, y, 30, 30);
mousePressed方法中获取该点坐标
public void mousePressed(MouseEvent e) { // 得到坐标 x=e.getX(); y=e.getY(); //重写执行一遍paint()方法,画出棋子 this.repaint(); }
4.用数组保存棋子,并黑白交替下:此时只能画一个棋子,再画下一个时原来的就会消失,原因是没有保存数据,我们用1919的数组保存棋子,最后一并画出
这里我们做一个表示,判断该黑棋或是白棋下,同时用1919的数组保存棋子,当数组的元素值为0表示无子 1表示黑子 2表示白子,在画棋子是用这个来进行判断黑白
//棋子坐标 int x; int y; //数组保存棋子 19*19的二维数组 当数组的元素值为0表示无子 1表示黑子 2表示白子 int allchess[][]=new int[19][19]; //标识该黑棋下还是白棋下 默认黑先 boolean isblack=true;
在监听器中:首先判断是否在棋盘中,只有在棋盘内才能画子;然后用x,y点击的坐标变为最近的交叉点的棋盘坐标,便于存于数组中(因为我们用的是19*19的二维数组,用来表示的是棋盘的交叉点的坐标,后面只需在转换过来即可画棋子);为了防止在已经下过棋子的地方在下棋子,必须还要做一个判断,只有无子的地方才能落子(allchess[x][y]==0);
//判断是否在棋盘内 if(x>=30 && x<=630 && y>=70 && y<=670) { //将x,y点击的坐标变为最近的交叉点的棋盘坐标 if((x-30)%33<16) { x=(x-30)/33; }else { x=(x-30)/33+1; } if((y-70)%33<16) { y=(y-70)/33; }else { y=(y-70)/33+1; } //判断该谁下 //只有在无子的时候可以落子 if(allchess[x][y]==0) { if(isblack) { allchess[x][y]=1; isblack=false; }else { allchess[x][y]=2; isblack=true; } }else { JOptionPane.showMessageDialog(this, "此处已经下过棋子了,请重新下"); } //重写执行一遍paint()方法,画出棋子 this.repaint(); }
然后在paint()中,画棋子的代码如下,其中的i,j表示的是棋盘的坐标,我们需要换成窗口的坐标,再画棋子
//画棋子 for(int i=0;i<19;i++) { for(int j=0;j<19;j++) { //画黑子 if(allchess[i][j]==1) { g.setColor(Color.black); g.fillOval((i*33-16)+30, (j*33-16)+70, 30, 30); } //画白子 if(allchess[i][j]==2) { g.setColor(Color.white); g.fillOval((i*33-16)+30, (j*33-16)+70, 30, 30); } } }
5.核心算法:判断胜负,五子连珠
四个方向,横,纵,正斜,反斜,我们只需要判断最后一个棋子的四个方向是否有相同颜色的五个子即可(如果除去最后一子,能五,那么一定判断出来了),以横向为例,纵坐标相等,只要判断横坐标即可
public boolean checkwin() { boolean flag=false; int i=1; int color=allchess[x][y]; int count=1; //横向 while(x+i<19 && color==allchess[x+i][y]) { count++; i++; } i=1; while(x-i>=0 && color==allchess[x-i][y]) { count++; i++; } if(count>=5) { flag=true; } }
其他三个方向的方法一样,只有相邻两个棋子的横纵坐标关系不同而已,这里不再描述。
在监听器中调用该方法即可
//判断胜负 if(this.checkwin()==true) { JOptionPane.showMessageDialog(this, "游戏结束,"+ (allchess[x][y]==1?"黑棋":"白棋")+"获胜"); }
最后我们发现判断胜负后还能继续下子,所以该加一个限制条件
//标识是否能下子 boolean canplay=true;
在监听器中,加上一个判断即可,同时获胜后,将canplay标识为false
//判断能否下子 if(canplay==true) { //判断是否在棋盘内 if(x>=30 && x<=630 && y>=70 && y<=670) { ... //判断胜负 if(this.checkwin()==true) { JOptionPane.showMessageDialog(this, "游戏结束,"+ (allchess[x][y]==1?"黑棋":"白棋")+"获胜"); canplay=false; } ... } }
6.提示信息的完善
//提示信息,该谁下 String message="黑先"; //黑方剩余时间提示信息 String blackstring="无限制"; //黑方剩余时间提示信息 String whitestring="无限制"; //设置的最大游戏时间 默认600秒 int maxtime=600; //黑方剩余时间 int blacktime; //白方剩余时间 int whitetime; //黑方剩余时间提示信息 String blackstring="无限制"; //黑方剩余时间提示信息 String whitestring="无限制";
在paint()中,画出提示信息
//提示信息 g.setFont(new Font("黑体",Font.BOLD,20)); g.setColor(Color.red); g.drawString("提示:"+message, 50, 50); g.setColor(Color.black); g.drawString("黑:"+blackstring, 50,680); g.drawString("白:"+whitestring, 450, 680);
需要注意的是,黑棋下完后,该白下,反之亦然,所以需要在每判断一次黑下或者是白下后,改变message的值,如:
//改变提示信息 message="该白棋下";
7.加入开始游戏,认输,设置功能
在监听器中
开始游戏,所有的标识要回到初始状态
if(e.getX()>644 && e.getX()<690 && e.getY()>125 && e.getY()<282){ int result=JOptionPane.showConfirmDialog(this, "是否重新开始游戏"); if(result==0) { JOptionPane.showMessageDialog(this, "重新开始"); //清空数组 allchess=new int[19][19]; //全部设为初始的状态 isblack=true; message="黑先"; canplay=true; maxtime=600; blacktime=maxtime; whitetime=maxtime; blackstring=maxtime/3600+":"+(maxtime/60-maxtime/3600*60)+":"+(maxtime-maxtime/60*60); whitestring=maxtime/3600+":"+(maxtime/60-maxtime/3600*60)+":"+(maxtime-maxtime/60*60); this.repaint(); } }
认输:
//认输 if(e.getX()>644 && e.getX()<690 && e.getY()>467 && e.getY()<540) { int result=JOptionPane.showConfirmDialog(this, "是否认输"); if(result==0) { if(isblack) { JOptionPane.showMessageDialog(this, "黑方已认输,游戏结束"); }else { JOptionPane.showMessageDialog(this, "白方已认输,游戏结束"); } canplay=false; } }
设置:设置游戏的时长
if(e.getX()>645 && e.getX()<690 && e.getY()>572 && e.getY()<655) { String input=JOptionPane.showInputDialog(this,"设置游戏实时间(分钟)"); //抛出输入的并非数字的异常,并提醒 try { maxtime=Integer.parseInt(input)*60; if(maxtime<=0) { JOptionPane.showMessageDialog(this, "设置的时间不允许小于等于0"); }else { int result=JOptionPane.showConfirmDialog(this, "设置成功,是否开始新游戏"); if(result==0) { JOptionPane.showMessageDialog(this, "重新开始"); //清空数组 allchess=new int[19][19]; //全部设为初始的状态 isblack=true; message="黑先"; canplay=true; blacktime=maxtime; whitetime=maxtime; blackstring=maxtime/3600+":"+(maxtime/60-maxtime/3600*60)+":"+(maxtime-maxtime/60*60); whitestring=maxtime/3600+":"+(maxtime/60-maxtime/3600*60)+":"+(maxtime-maxtime/60*60); this.repaint(); } } } catch (Exception e2) { JOptionPane.showMessageDialog(this, "请输入非零正整数"); } }
8.用线程来显示时间的变动
public class FiveChessFrame extends JFrame implements MouseListener,Runnable
在创建一个线程并启动
//创建线程 Thread t=new Thread(this); //启动线程 t.start(); //挂起 t.suspend();
在run()方法中:
public void run() { if(maxtime>0) { while(true) { if(isblack) { blacktime--; if(blacktime<=0) { JOptionPane.showMessageDialog(this, "黑方超时,白棋获胜!!!"); canplay=false; t.suspend(); } }else { whitetime--; if(whitetime<=0) { JOptionPane.showMessageDialog(this, "白方超时,黑棋获胜!!!"); canplay=false; t.suspend(); } } blackstring=blacktime/3600+":"+(blacktime/60-blacktime/3600*60)+":"+(blacktime-blacktime/60*60); whitestring=whitetime/3600+":"+(whitetime/60-whitetime/3600*60)+":"+(whitetime-whitetime/60*60); this.repaint(); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
这里需要注意,因为在开始的时候将线程挂起的,所以在点击开始游戏和设置并重新开始是,加入一下代码保证线程的进行,同时在时间用完后,再将线程挂起。
t.resume();
9.解决界面闪烁问题
//双缓冲器解决闪烁问题 BufferedImage bi=new BufferedImage(700,700,BufferedImage.TYPE_INT_ARGB); Graphics g2=bi.getGraphics();
将后面的g改为g2,全部用g2来画,最后加入
g.drawImage(bi, 0, 0, this);
最后用一次性画出来即可
完整代码:
https://blog.csdn.net/qq_49639550/article/details/120326619