上次我们基本实现了五子棋游戏的功能,这次我们进行一些优化和添加一些便于用户使用的功能。
新增功能及优化
一、复盘功能
复盘功能就是指在下完一局棋后,我们可以通过复盘按钮使本局棋的所有棋子重头开始自动下一遍。分析得知,我们首先要保存以及下过的棋子数据,然后清空棋盘上的数据并刷新棋盘,然后利用保存的数据开始自动有间隔的绘制。
①保存数据
我们重绘所需要的数据都存在一个一维数组shapeArr中,我们需要取出并保存。
由于下一步需要清空这个数组的数据,我们需要定义一个新的数据依次保存shapeArr中的数据,我们命名为tempArr(临时数组)。需要注意的是,我们不能直接tempArr=shapeArr保存,否则这两个对象会公用同一个内存空间,到时候清空时两个会一起清空而无法保存数据。我们应该采用遍历的方法保存数组数据。最后,调用一下下棋面板的重绘方法刷新棋盘即可。⭐⭐⭐
java
//先保存所有下过的棋子数据
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方法(绘制棋子)就可以重新绘制棋子,再加一个延时操作就可以实现复盘功能。
java
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赋值即可。注意:要写在点击方法中,以实现每点击下一颗棋子就更新一次数据。
java
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);
}
java
//实时更新棋子的坐标情况(写在点击方法中)
centerPanel.x = chessX;
centerPanel.y = chessY;
完成上述步骤后,发现点击悔棋、复盘按钮时红色标注无法移动到上一颗棋子中,点击重新开始按钮时无法将红色标点去掉的问题,下面来逐个解决优化。
①悔棋移动
点击悔棋时,我们需要的效果是将红色标注移动到最近的上一颗棋子,其实就是我们需要的chessX和chessY发生了变化,而由于每步的点坐标数据都存储到一维数组shapeArr中,我们只需要定义一个同类对象保存数组下标为(index-1)对应的数据,并通过这个数据给MPanel里面的x,y重新赋值就可以了。
java
case "悔棋": {
index--;
//保存上一颗最近棋子的数据
Shape s1 = shapeArr[index - 1];
centerPanel.x = s1.x;
centerPanel.y = s1.y;
}
②重新开始、复盘时清空红色标注
点击重新开始和复盘时,我们需要棋盘上什么都没有,所以我们需要将传过去的chessX和chessY设置成不能用于绘制红色标点的数值,这样就不会残存红色标点了。
java
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方法。
java
JLabel jl=new JLabel("黑方执子:"+0);
jl.setFont(new Font("宋体",Font.BOLD,20));
java
//显示当前下棋方以及步数
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,并显示在北边的容器中。
java
JLabel jl1=new JLabel("谁会获得最终胜利?敬请期待!");
jl1.setForeground(Color.BLACK);
jl1.setFont(new Font("宋体",Font.BOLD,20));
northPanel.add(jl1);
java
//判断输赢
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("白方获胜!");
}
}
}
经历以上的步骤,五子棋游戏的第一版本功能添加以及优化就基本完成了!
完整代码以及效果
完整代码
java
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();
}
}
java
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();
}
}
}
java
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);
}
}
java
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);
}
}
}
}
java
public interface Conbig {
public int X0 = 50;
public int Y0 = 36;
public int LINE = 15;
public int SIZE = 40;
}
效果
