Java学习历程14——制作一款五子棋游戏(4)
上次我们基本实现了五子棋游戏的功能,这次我们进行一些优化和添加一些便于用户使用的功能。
新增功能及优化
一、复盘功能
复盘功能就是指在下完一局棋后,我们可以通过复盘按钮使本局棋的所有棋子重头开始自动下一遍。分析得知,我们首先要保存以及下过的棋子数据,然后清空棋盘上的数据并刷新棋盘,然后利用保存的数据开始自动有间隔的绘制。
①保存数据
我们重绘所需要的数据都存在一个一维数组shapeArr中,我们需要取出并保存。
由于下一步需要清空这个数组的数据,我们需要定义一个新的数据依次保存shapeArr中的数据,我们命名为tempArr(临时数组)。需要注意的是,我们不能直接tempArr=shapeArr保存,否则这两个对象会公用同一个内存空间,到时候清空时两个会一起清空而无法保存数据。我们应该采用遍历的方法保存数组数据。最后,调用一下下棋面板的重绘方法刷新棋盘即可。⭐⭐⭐
//先保存所有下过的棋子数据for (int i = 0; i < shapeArr.length; i++) {tempArr[i] = shapeArr[i];}//再清除所有数据,刷新棋盘for (int i = 0; i < tempArr.length; i++) {shapeArr[i] = null;//tempArr数据还在!}
centerPanel.paint(g);
保存好数据之后,我们就可以利用保存的数据逐个绘制棋子。
②绘制棋子
我们希望的是点击复盘按钮后,先等待一会再开始下棋,并且下每颗棋子中间需要有间隔,所以需要使用到延时操作。代码为:
try {
Thread.sleep(延时时间,单位为毫秒);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
然后我们遍历一下新定义的数组tempArr,取出数据调用定义的drawShape方法(绘制棋子)就可以重新绘制棋子,再加一个延时操作就可以实现复盘功能。
try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//再遍历tempArr数组,重新绘制棋子for (int i = 0; i < tempArr.length; i++) {Shape s = tempArr[i];if (s == null) break;s.drawShape(g);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
二、标注所下的最后一颗棋子
我们希望使用者能时刻知道所下最后一颗棋子的位置,所以我们可以添加在最后一颗棋子上绘制一个红点的功能。
分析可知道,我们可以在MPanel类里面书写,因为里面有paint方法,不用担心重绘的问题。我们可以单独书写一个方法point,用于书写绘制的代码,并在paint中调用。同时,我们需要随时更新chessX和chessY的数据以绘制在最后一颗棋子上,而所需的数据在drawListener里,所以在drawListener里调用centerPanel对象中的x,y,用本类中的chessX和chessY赋值即可。注意:要写在点击方法中,以实现每点击下一颗棋子就更新一次数据。
public void point(Graphics g){Graphics2D g1=(Graphics2D)g;((Graphics2D) g).setStroke(new BasicStroke(5));g.setColor(Color.RED);g.drawLine(x * SIZE + X0 , y * SIZE + Y0 , x * SIZE + X0, y * SIZE + Y0);}
//实时更新棋子的坐标情况(写在点击方法中)centerPanel.x = chessX;centerPanel.y = chessY;
完成上述步骤后,发现点击悔棋、复盘按钮时红色标注无法移动到上一颗棋子中,点击重新开始按钮时无法将红色标点去掉的问题,下面来逐个解决优化。
①悔棋移动
点击悔棋时,我们需要的效果是将红色标注移动到最近的上一颗棋子,其实就是我们需要的chessX和chessY发生了变化,而由于每步的点坐标数据都存储到一维数组shapeArr中,我们只需要定义一个同类对象保存数组下标为(index-1)对应的数据,并通过这个数据给MPanel里面的x,y重新赋值就可以了。
case "悔棋": {index--;
//保存上一颗最近棋子的数据Shape s1 = shapeArr[index - 1];centerPanel.x = s1.x;centerPanel.y = s1.y;
}
②重新开始、复盘时清空红色标注
点击重新开始和复盘时,我们需要棋盘上什么都没有,所以我们需要将传过去的chessX和chessY设置成不能用于绘制红色标点的数值,这样就不会残存红色标点了。
case "重新开始": {
//将最后一个红点去除掉centerPanel.x=-1;centerPanel.y=-1;
}
case "复盘": {//将最后一个红点去除掉centerPanel.x=-1;centerPanel.y=-1;
}
三、显示当前下棋方以及步数
我们希望在南边容器中显示当前下棋方以及步数,为了效果更好,建议使用JLabel标签组件,而不是绘制字符串的drawString。(注意:JLabel组件只能在GameUI(窗体)中创建并添加!)
添加好之后,我们需要用标记位flag来判断是哪方即将下棋,用index来显示当前的步数,而这些数据都在drawListener类中,但是千万注意:不能把数据传到GameUI中!因为这个类里面的init方法只能调用一次,即使传递数据也无法实现数据的更新,所以我们应该考虑将JLabel传递到drawLisener中去实现更改。由于在GameUI中已经创建过drawLisener对象,所以直接通过构造方法传递JLabel即可。
传递完毕之后,添加一个方法,按照下棋的情况用setText关键字输入对应的文字即可。设置字体:setFont;设置前颜色:setForeBackground
然后,在控制下棋的switch分支中调用UI方法。
JLabel jl=new JLabel("黑方执子:"+0);
jl.setFont(new Font("宋体",Font.BOLD,20));
//显示当前下棋方以及步数public void UI() {if (flag == 0) {jl.setFont(new Font("宋体", Font.BOLD, 20));jl.setText("黑方执子:" + (index + 1));} else if (flag == 1) {jl.setFont(new Font("宋体", Font.BOLD, 20));jl.setText("白方执子:" + (index + 1));}}
同时,我们可以优化显示胜利的标语,将其改成JLabel,并显示在北边的容器中。
JLabel jl1=new JLabel("谁会获得最终胜利?敬请期待!");jl1.setForeground(Color.BLACK);
jl1.setFont(new Font("宋体",Font.BOLD,20));
northPanel.add(jl1);
//判断输赢if (isWin() >= 5 || isWin1() >= 5 || isWin2() >= 5 || isWin3() >= 5) {flag = 3;System.out.println("本局已结束,请点击重新开始按钮!");if (chess[chessY][chessX] == 1) {jl1.setForeground(Color.RED);jl1.setText("黑方获胜!");} else if (chess[chessY][chessX] == 2) {jl1.setForeground(Color.RED);jl1.setText("白方获胜!");}}}
经历以上的步骤,五子棋游戏的第一版本功能添加以及优化就基本完成了!
完整代码以及效果
完整代码
import javax.swing.*;
import java.awt.*;
public class GameUI implements Conbig {public void init() {JFrame jf = new JFrame();jf.setSize(700, 700);jf.setTitle("五子棋游戏1.0");jf.setLocationRelativeTo(null);jf.setDefaultCloseOperation(3);BorderLayout bl=new BorderLayout();jf.setLayout(bl);JPanel southPanel=new JPanel();JPanel northPanel=new JPanel();Dimension dm=new Dimension(0,35);southPanel.setBackground(Color.pink);southPanel.setPreferredSize(dm);northPanel.setPreferredSize(new Dimension(0,30));northPanel.setBackground(Color.lightGray);jf.add(southPanel,BorderLayout.SOUTH);jf.add(northPanel,BorderLayout.NORTH);JLabel jl=new JLabel("黑方执子:"+0);JLabel jl1=new JLabel("谁会获得最终胜利?敬请期待!");jl1.setForeground(Color.BLACK);jl.setFont(new Font("宋体",Font.BOLD,20));jl1.setFont(new Font("宋体",Font.BOLD,20));MPanel centerPanel=new MPanel();centerPanel.setBackground(Color.YELLOW);jf.add(centerPanel);drawListener listener = new drawListener(centerPanel,jl,jl1);//按钮添加JButton jb1=new JButton("开始");southPanel.add(jb1);JButton jb2=new JButton("悔棋");southPanel.add(jb2);JButton jb3=new JButton("重新开始");southPanel.add(jb3);JButton jb4=new JButton("复盘");southPanel.add(jb4);//添加显示组件southPanel.add(jl);northPanel.add(jl1);jf.setVisible(true);centerPanel.shapeArr=listener.shapeArr;centerPanel.addMouseListener(listener);jb1.addActionListener(listener);jb2.addActionListener(listener);jb3.addActionListener(listener);jb4.addActionListener(listener);Graphics g = centerPanel.getGraphics();listener.g = g;}public static void main(String[] args) {GameUI gu = new GameUI();gu.init();}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class drawListener implements MouseListener, ActionListener, Conbig {public Graphics g;public int x, y;public int chessX, chessY;public int flag;public String name = "null";public Color color;public static gl.game0820.Shape[] shapeArr = new gl.game0820.Shape[200];public int[][] chess = new int[LINE][LINE];public int index = 0;public boolean start = false;public MPanel centerPanel;public JLabel jl, jl1;//引用传递public drawListener(MPanel centerPanel, JLabel jl, JLabel jl1) {this.centerPanel = centerPanel;this.jl = jl;this.jl1 = jl1;}public void actionPerformed(ActionEvent e) {name = e.getActionCommand();switch (name) {case "开始": {start = true;break;}case "悔棋": {index--;jl1.setForeground(Color.BLACK);jl1.setText("谁会获得最终胜利?敬请期待!");gl.game0820.Shape s = shapeArr[index];//保存上一颗最近棋子的数据Shape s1 = shapeArr[index - 1];centerPanel.x = s1.x;centerPanel.y = s1.y;if (index > 0) {shapeArr[index] = null;} else {break;}//手动调用centerPanel里的paint方法,刷新棋盘centerPanel.repaint();if (flag == 0) {flag = 1;jl.setText("白方执子:" + (index));} else if (flag == 1) {flag = 0;jl.setText("黑方执子:" + (index));} else if (flag == 3 && chess[s.y][s.x] == 1) {flag = 0;} else if (flag == 3 && chess[s.y][s.x] == 2) {flag = 1;}if (s == null) break;chess[s.y][s.x] = 0;break;}case "重新开始": {//将最后一个红点去除掉centerPanel.x=-1;centerPanel.y=-1;//初始化标注jl.setText("黑方执子:" + 0);jl1.setForeground(Color.BLACK);jl1.setText("谁会获得最终胜利?敬请期待!");jl.setFont(new Font("宋体", Font.BOLD, 20));for (int i = 0; i < index; i++) {shapeArr[i] = null;}for (int i = 0; i < chess.length; i++) { //行for (int j = 0; j < chess[0].length; j++) { //列chess[i][j] = 0;}}try {Thread.sleep(100);} catch (InterruptedException ex) {throw new RuntimeException(ex);}centerPanel.repaint();flag = 0;index = 0;
// printArr();for (int i = index; i > 0; i--) {}break;}case "复盘": {//将最后一个红点去除掉centerPanel.x=-1;centerPanel.y=-1;drawAgain();break;}}}public void mouseClicked(MouseEvent e) {x = e.getX();y = e.getY();if ((x - X0) % SIZE > SIZE / 2 && (y - Y0) % SIZE < SIZE / 2) {//靠右,靠上chessX = (x - X0) / SIZE + 1;chessY = (y - Y0) / SIZE;} else if ((x - X0) % SIZE > SIZE / 2 && (y - Y0) % SIZE > SIZE / 2) {//靠右,靠下chessX = (x - X0) / SIZE + 1;chessY = (y - Y0) / SIZE + 1;} else if ((x - X0) % SIZE < SIZE / 2 && (y - Y0) % SIZE < SIZE / 2) {//靠左,靠上chessX = (x - X0) / SIZE;chessY = (y - Y0) / SIZE;} else if ((x - X0) % SIZE < SIZE / 2 && (y - Y0) % SIZE > SIZE / 2) {//靠左,靠下chessX = (x - X0) / SIZE;chessY = (y - Y0) / SIZE + 1;}if (!start) {System.out.println("请点击开始按钮!");return;}if (chess[chessY][chessX] != 0) {System.out.println("此处已有棋子,不可下棋!");return;}//实时更新棋子的坐标情况centerPanel.x = chessX;centerPanel.y = chessY;
// printArr();switch (flag) {//标记位法case 0: {chess[chessY][chessX] = 1;//用1代表黑棋color = Color.BLACK;flag++;gl.game0820.Shape shape = new gl.game0820.Shape(chessX, chessY, SIZE, color);shape.drawShape(g);//刷新棋盘,清楚之前的红点标注centerPanel.repaint();UI();shapeArr[index] = shape;index++;break;}case 1: {chess[chessY][chessX] = 2;//用2代表白棋color = Color.WHITE;flag = 0;Shape shape = new Shape(chessX, chessY, SIZE, color);shape.drawShape(g);//刷新棋盘,清楚之前的红点标注centerPanel.repaint();UI();shapeArr[index] = shape;index++;break;}}//判断输赢if (isWin() >= 5 || isWin1() >= 5 || isWin2() >= 5 || isWin3() >= 5) {flag = 3;System.out.println("本局已结束,请点击重新开始按钮!");if (chess[chessY][chessX] == 1) {jl1.setForeground(Color.RED);jl1.setText("黑方获胜!");} else if (chess[chessY][chessX] == 2) {jl1.setForeground(Color.RED);jl1.setText("白方获胜!");}}}//复盘的功能方法public void drawAgain() {gl.game0820.Shape[] tempArr = new gl.game0820.Shape[200];//先保存所有下过的棋子数据for (int i = 0; i < shapeArr.length; i++) {tempArr[i] = shapeArr[i];}//再清除所有数据,刷新棋盘for (int i = 0; i < tempArr.length; i++) {shapeArr[i] = null;//tempArr数据还在!}centerPanel.paint(g);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//再遍历tempArr数组,重新绘制棋子for (int i = 0; i < tempArr.length; i++) {Shape s = tempArr[i];if (s == null) break;s.drawShape(g);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}//显示当前下棋方以及步数public void UI() {if (flag == 0) {jl.setFont(new Font("宋体", Font.BOLD, 20));jl.setText("黑方执子:" + (index + 1));} else if (flag == 1) {jl.setFont(new Font("宋体", Font.BOLD, 20));jl.setText("白方执子:" + (index + 1));}}public void mousePressed(MouseEvent e) {}public void mouseReleased(MouseEvent e) {}public void mouseEntered(MouseEvent e) {}public void mouseExited(MouseEvent e) {}//统计棋子横向public int isWin() {int number = 0;//向左检查for (int i = chessX; i >= 0; i--) {if (chess[chessY][chessX] == chess[chessY][i]) {number++;} else {break;}}//向右检查for (int i = chessX + 1; i < chess.length; i++) {if (chess[chessY][chessX] == chess[chessY][i]) {number++;} else {break;}}return number;}//统计棋子纵向public int isWin1() {int number = 0;//向上检查for (int i = chessY; i >= 0; i--) {if (chess[chessY][chessX] == chess[i][chessX]) {number++;} else {break;}}//向下检查for (int i = chessY + 1; i < chess.length; i++) {if (chess[chessY][chessX] == chess[i][chessX]) {number++;} else {break;}}return number;}//统计棋子斜向(撇)public int isWin2() {int number = 0;//向右上方检查for (int i = chessY, j = chessX; i < chess.length && j > 0; i++, j--) {if (chess[chessY][chessX] == chess[i][j]) {number++;} else {break;}}//向左下方检查for (int i = chessY - 1, j = chessX + 1; i > 0 && j < chess.length; i--, j++) {if (chess[chessY][chessX] == chess[i][j]) {number++;} else {break;}}return number;}//统计棋子斜向(捺)public int isWin3() {int number = 0;//向右下方检查for (int i = chessY + 1, j = chessX + 1; i < chess.length && j < chess.length; i++, j++) {if (chess[chessY][chessX] == chess[i][j]) {number++;} else {break;}}//向左上方检查for (int i = chessY, j = chessX; i > 0 && j > 0; i--, j--) {if (chess[chessY][chessX] == chess[i][j]) {number++;} else {break;}}return number;}public void printArr() {for (int i = 0; i < chess.length; i++) { //行for (int j = 0; j < chess[0].length; j++) { //列System.out.print(chess[i][j] + " ");}System.out.println();}}
}
import javax.swing.*;
import java.awt.*;
public class MPanel extends JPanel implements Conbig {public gl.game0820.Shape[] shapeArr;public int x=-1,y;public ImageIcon image=new ImageIcon("D:\\图片\\12.jpg");public void paint(Graphics g) {super.paint(g);g.drawImage(image.getImage(),0,0,900,900,null);//绘制棋盘for (int i = 0; i < LINE; i++) {g.drawLine(X0, Y0 + i * SIZE, (LINE - 1) * SIZE + X0, Y0 + i * SIZE);g.drawLine(X0 + i * SIZE, Y0, X0 + i * SIZE, (LINE - 1) * SIZE + Y0);}for (int i = 0; i < drawListener.shapeArr.length; i++) {Shape shape = drawListener.shapeArr[i];if (shape != null) {shape.drawShape(g);} else {break;}}if(x>=0){point(g);}}//标注最后一颗棋子的位子public void point(Graphics g){Graphics2D g1=(Graphics2D)g;((Graphics2D) g).setStroke(new BasicStroke(5));g.setColor(Color.RED);g.drawLine(x * SIZE + X0 , y * SIZE + Y0 , x * SIZE + X0, y * SIZE + Y0);}
}
import java.awt.*;public class Shape implements Conbig {public Color color;public int x, y, r;public MPanel centerPanel;public Shape(int x, int y, int r, Color color) {this.x = x;this.y = y;this.color = color;this.r = r;}//标注最后一颗棋子的位置public void drawShape(Graphics g) {if (color.equals(Color.BLACK)) {for (int i = 0; i < SIZE; i++) {g.setColor(new Color(i * 3, i * 3, i * 3));g.fillOval(x * SIZE + X0 - SIZE / 2 + i / 2, y * SIZE + Y0 - SIZE / 2 + i / 2, r - i, r - i);}} else if (color.equals(Color.WHITE)) {Graphics2D g3 = (Graphics2D) g;((Graphics2D) g).setStroke(new BasicStroke(1));for (int i = 0; i < SIZE; i++) {g.setColor(new Color(170 + i * 2, 170 + i * 2, 170 + i * 2));g.fillOval(x * SIZE + X0 - SIZE / 2 + i / 2, y * SIZE + Y0 - SIZE / 2 + i / 2, r - i, r - i);}}}
}
public interface Conbig {public int X0 = 50;public int Y0 = 36;public int LINE = 15;public int SIZE = 40;
}
效果