JAVA五子棋
2021/9/24 22:12:33
本文主要是介绍JAVA五子棋,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一个简单的JAVA小项目,主要实现的功能有:
一、下棋功能,在棋盘的交点处落子。
二、悔棋功能,取消最后一颗下的棋子。
三、简单人机对战功能。
1.窗体实现。主要使用了JFrame类
public class GameUI { public static void main(String[] args) { GameUI ui = new GameUI(); ui.showUI(); } //显示游戏界面 public void showUI() { //窗体 JFrame jf = new JFrame(); //自定义的面板和鼠标监听器实现类 GamePanel gp = new GamePanel(jf); GameMouse mouse = new GameMouse(); //将面板添加进窗体 jf.add(gp); //像素点>分辨率,设置窗体大小 jf.setSize(800, 800); jf.setTitle("五子棋游戏"); jf.getContentPane().setBackground(Color.pink); //居中显示 jf.setLocationRelativeTo(null); //设置退出进程 jf.setDefaultCloseOperation(3); //设置可见 jf.setVisible(true); //从面板上获取画笔,一定要在窗体显示可见之后 Graphics g = gp.getGraphics(); //绑定事件处理类 mouse.setG(g); mouse.setGP(gp); gp.setGM(mouse); //给面板添加鼠标监听器方法 gp.addMouseListener(mouse); } }
2.绘制棋盘和棋子。
定义一个画板类对象,基础结构如下,其中定义了一个二维数组,用于存储每个位置棋子的情况。
public class GamePanel extends JPanel implements ActionListener,ChessConfig { private JFrame jf ; private int[][] map = new int[11][11]; public GameMouse gm; public int[][] getMap(){ return map; } public int getChess(int[][]map,int x,int y){ return map[x][y]; } public void setGM(GameMouse gm){this.gm=gm;} public void setMap(int x,int y,int z){ map[x][y]=z; } //构造方法 public GamePanel(JFrame jf) { this.jf = jf; this.setOpaque(false); createMenu(); } //重写paint方法,其目的是在每次需要重绘时(如窗口最小化)保持相同的情况 @Override public void paint(Graphics g) { super.paint(g); drawGrid(g); drawChess(g); g.setColor(Color.PINK); //用于保持落子顺序指示器 g.fillRect(680,25,100,50); g.setColor(Color.black); g.setFont(createFont()); g.drawString("当前:"+gm.message,680,50); } //获取字体 public Font createFont(){ return new Font(LETTER,Font.BOLD,LETTER_SIZE); } }
2.1 绘制棋盘和棋子。
绘制棋盘:此处有多种方法,我采用的是循环画正方形的方法来绘制棋盘:
public void drawGrid(Graphics g){ for (int i = 0; i < SQUARE_NUM; i++) { for (int j = 0; j <SQUARE_NUM ; j++) { g.drawRect((X0+SQUARE*j),(Y0+SQUARE*i),SQUARE,SQUARE); } } }
绘制棋子:我采用的是Graphics类自带的fillOval方法,以黑棋为例:
public void drawBlack(Graphics g,int x,int y ){ g.setColor(Color.BLACK); g.fillOval(x*SQUARE+X0-CHESS/2,y*SQUARE+Y0-CHESS/2,CHESS,CHESS); }
关于fillOval方法:fillOval(int x,int y ,int width ,int height)
前两个参数x和y是椭圆外接正切矩形的左上角顶点,width和height代表了矩形的宽和高,在画圆时要注意坐标的处理,使得棋子的圆心落在十字线交叉处。
此外要注意画笔获取的先后顺序,Graphics类方法调用时出现的空指针异常。
2.2 重写actionPerformed方法。
根据对应指令实现不同操作,以认输为例:
if(command.equals("surrender")){ JOptionPane.showMessageDialog(null, gm.message + "认输"); newGame(); repaint(); }
repaint()的目的是手动重新绘制所有组件。
3.实现落子。
落子的实现主要依靠鼠标监听器的实现类,关键要素有:鼠标点击处的坐标与棋子坐标的转换,防止在同一处重复落子,黑白交替,输赢判断。
public class GameMouse implements MouseListener,ChessConfig { private Graphics g; //定义变量,保存传递过来的画笔对象 private JFrame jf; public GamePanel gp; public int turn; //指示黑白顺序 public int flag ; //指示获胜方 public int xc ,yc; //棋子的交点值 public boolean start; //游戏开始判断 public String message; //落子顺序判断 public Stack<Integer> stckx; //用于保存坐标 实现悔棋功能 public Stack<Integer> stcky; public boolean AI;//人机对战判断 ChessAI ai = new ChessAI(); //set方法,初始化棋盘和画笔 public void setG(Graphics g) { this.g = g; } public void setGP(GamePanel gp){ this.gp = gp; } public GameMouse(){ this.stckx=new Stack<>(); this.stcky=new Stack<>(); this.turn=1; this.flag=0; this.start=true; this.message=""; this.AI= false; }
3.1 棋子坐标处理
以x坐标为例:
public void mouseClicked(MouseEvent e) { //获取当前坐标值 int x = e.getX(); //超出棋盘的点落在边界点上 if(x>XX){ x=XX; } if(x<X0){ x=X0; } //计算棋子坐标 xc = positionX(x); //计算棋子位置,在判定范围内落子都在同一处 public int positionX(int s){ int res=0; if((s-X0)%SQUARE<=SQUARE/2){ res = (s-X0)/SQUARE; }if((s-X0)%SQUARE>SQUARE/2){ res = (s-X0)/SQUARE+1; } return res; } //判断是否已经有棋子 public boolean isFree(int x,int y){ int [][] map = gp.getMap(); return gp.getChess(map, x, y) == 0; }
3.2 落子功能
依靠每次绘制棋子后改变对应变量值来实现黑白交替。
//绘制棋子 if (&isFree(xc,yc)&&start){ if (turn==1){ drawBlack(g,xc,yc); gp.setMap(xc,yc,1); message="白棋"; turn=2; //实现黑白交替 }else if(turn==2){ drawWhite(g,xc,yc); gp.setMap(xc,yc,2); message="黑棋"; turn=1; } } stckx.push(xc); //将棋子坐标入栈 stcky.push(yc); }
3.3 输赢判断
根据落子位置,判断落子位置上下左右,左上右下,右上左下的棋盘情况,之后对应方向相加。
要注意判断循环的边界,否则容易出现数组越界情况。
以右上为例:
//右上相同棋子统计 public int count_RightUp(int[][]map,int i,int j){ int count = 0; int x=i; int y=j; while (x>=0&&y>0&&x<SQUARE_NUM&&y<=SQUARE_NUM){ if (map[x][y]!=0){ if (map[x][y]==map[x+1][y-1]){ count++; x++;y--; }else return count; } else break; } return count; }
注意要处理已有棋子时的情况 用break跳出循环,否则会在获胜判断后,再次点击则进入死循环。
对落子位置的四个方向求和,判断输赢。
//全向统计 public void sumAll(int[][]map,int x,int y){ int sum1 = count_LeftUp(map,x,y)+count_RightDown(map,x,y); int sum2 = count_LeftDown(map,x,y)+count_RightUp(map,x,y); int sum3 = count_Up(map,x,y)+count_Down(map,x,y); int sum4 = count_Left(map,x,y)+count_Right(map,x,y); if(map[x][y]==1&&((sum1>=4)||(sum2>=4)||(sum3>=4)||(sum4>=4))){ flag=1; }else if(map[x][y]==2&&((sum1>=4)||(sum2>=4)||(sum3>=4)||(sum4>=4))){ flag=2; }else flag=0; } //判断获胜方 public void isWinner(int flag){ if (flag == 1){ JOptionPane.showMessageDialog(null,"黑棋胜利"); this.start=false; }else if(flag==2){ JOptionPane.showMessageDialog(null,"白棋胜利"); this.start=false; }else{ System.out.println("暂无获胜者"); } }
3.4 悔棋功能
悔棋方法定义在GamePanel类重写的actionPerformed方法下,从栈中获得最后一颗棋子坐标,取消该处棋子,之后重绘棋盘。
if (command.equals("back")) { //悔棋 if (!gm.stckx.isEmpty()) { System.out.println("悔棋,返回上一步状态"); Integer temp_x = gm.stckx.pop(); Integer temp_y = gm.stcky.pop(); if (map[temp_x][temp_y] == 1) { setMap(temp_x,temp_y,0); gm.message = "黑棋"; gm.turn = 1; repaint(); } else if (map[temp_x][temp_y] == 2) { gm.message = "白棋"; gm.turn = 2; setMap(temp_x,temp_y,0); repaint(); } else if (gm.stckx.size() == 1) { //悔第一颗黑子 gm.message = " "; gm.turn = 1; setMap(temp_x,temp_y,0); repaint(); } }else System.out.println("无法悔棋"); }
4.简单人机对战功能
使用权值法决定落子位置:在落子前对所有空位遍历,由周围棋子情况计算该处权值,之后在权值最大处落子。
首先利用哈希表保存棋子情况和设置的权重:此处只考虑人机下白棋情况
public class ChessAI implements ChessConfig{ public GamePanel gp; public GameMouse gm; public static HashMap<String,Integer> Weight = new HashMap<>(); int[][] value = new int[11][11]; public ChessAI() { Weight.put("1", 20); Weight.put("11",200); Weight.put("111",800); Weight.put("1111",2000); Weight.put("2",20); Weight.put("22",300); Weight.put("222",900); Weight.put("2222",4000); }
遍历棋盘,对空位置分析其所有方向的权重,之后相加。
public int[] ai(int [][]map){ for (int i =0;i<=SQUARE_NUM;i++){ for (int j = 0;j<=SQUARE_NUM;j++){ if (map[i][j]==0){ //向右 String zeroPoint =""; for(int temp=1;i+temp<=SQUARE_NUM;temp++){ int first = map[i+1][j]; if (map[i+temp][j]!=0&&map[i+temp][j]==first){ zeroPoint+=map[i+temp][j]+""; }else break; //碰到空位置就结束 }if (Weight.get(zeroPoint)!=null){ value[i][j]+=Weight.get(zeroPoint); } //向左 zeroPoint =""; for(int temp=1;i-temp>=0;temp++){ int first = map[i-1][j]; if (map[i-temp][j]!=0&&map[i-temp][j]==first){ zeroPoint+=map[i-temp][j]+""; }else break; //碰到空位置就结束 }if (Weight.get(zeroPoint)!=null){ value[i][j]+=Weight.get(zeroPoint); } //向上 zeroPoint =""; for(int temp=1;j-temp>=0;temp++){ int first = map[i][j-1]; if (map[i][j-temp]!=0&&map[i][j-temp]==first){ zeroPoint+=map[i][j-temp]+""; }else break; //碰到空位置就结束 }if (Weight.get(zeroPoint)!=null){ value[i][j]+=Weight.get(zeroPoint); } //向下 zeroPoint =""; for(int temp=1;j+temp<=SQUARE_NUM;temp++){ int first = map[i][j+1]; if (map[i][j+temp]!=0&&map[i][j+temp]==first){ zeroPoint+=map[i][j+temp]+""; }else break; //碰到空位置就结束 }if (Weight.get(zeroPoint)!=null){ value[i][j]+=Weight.get(zeroPoint); } //向左上 zeroPoint =""; for(int temp=1;i-temp>=0&&j-temp>=0;temp++){ int first = map[i-1][j-1]; if (map[i-temp][j-temp]!=0&&map[i-temp][j-temp]==first){ zeroPoint+=map[i-temp][j-temp]+""; }else break; //碰到空位置就结束 }if (Weight.get(zeroPoint)!=null){ value[i][j]+=Weight.get(zeroPoint); } //右下 zeroPoint =""; for(int temp=1;i+temp<=SQUARE_NUM&&j+temp<=SQUARE_NUM;temp++){ int first = map[i+1][j+1]; if (map[i+temp][j+temp]!=0&&map[i+temp][j+temp]==first){ zeroPoint+=map[i+temp][j+temp]+""; }else break; //碰到空位置就结束 }if (Weight.get(zeroPoint)!=null){ value[i][j]+=Weight.get(zeroPoint); } //右上 zeroPoint =""; for(int temp=1;i+temp<=SQUARE_NUM&&j-temp>=0;temp++){ int first = map[i+1][j-1]; if (map[i+temp][j-temp]!=0&&map[i+temp][j-temp]==first){ zeroPoint+=map[i+temp][j-temp]+""; }else break; //碰到空位置就结束 }if (Weight.get(zeroPoint)!=null){ value[i][j]+=Weight.get(zeroPoint); } //左下 zeroPoint =""; for(int temp=1;i-temp>=0&&j+temp<=SQUARE_NUM;temp++){ int first = map[i-1][j+1]; if (map[i-temp][j+temp]!=0&&map[i-temp][j+temp]==first){ zeroPoint+=map[i-temp][j+temp]+""; }else break; //碰到空位置就结束 }if (Weight.get(zeroPoint)!=null){ value[i][j]+=Weight.get(zeroPoint); } } else value[i][j]=0; } } int max = 0; int xx=0,yy=0; for (int i=0;i<=SQUARE_NUM;i++){ for (int j=0;j<=SQUARE_NUM;j++){ if (max<value[i][j]){ max = value[i][j]; xx=i; yy=j; } } } clearValue(value); return new int[]{xx, yy}; }
对每个位置考虑周围相/不同子相连情况,这种算法较为简单,没有将棋子两个对应方向(如左方和右方)的情况共同考虑。容易出现下图情况:
4.1 对落子功能的修改
加入人机对战功能后,将人机落子放在玩家落子后,只考虑人机执白子情况:
if (isFree(xc,yc)&&start){ if (turn==1){ drawBlack(g,xc,yc); gp.setMap(xc,yc,1); message="白棋"; turn=2; sumAll(gp.getMap(),xc,yc); if (AI&&flag!=1){ xc=ai.ai(gp.getMap())[0]; yc=ai.ai(gp.getMap())[1]; drawWhite(g,xc,yc); gp.setMap(xc,yc,2); message="黑棋"; turn=1; } }else if(turn==2&&!AI){ drawWhite(g,xc,yc); gp.setMap(xc,yc,2); message="黑棋"; turn=1; } }
此时下棋黑棋后需要单独使用进行判断输赢的sumAll()方法,否则会出现黑棋五子却没有提示胜利,没有结束游戏的情况。
5.小结
作为第一个java项目,本项目改进之处还有很多,例如算法和人机执黑子功能。
完整项目代码:https://github.com/smdnzhan/FiveChess
这篇关于JAVA五子棋的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南