今天我们来用Java写一个简易的五子棋游戏。
1.UI设计
首先,我们需要一个显示游戏的UI界面。创建GameUI类,给界面设置合适的标题、大小等属性。设置窗体可见并添加画笔。如此便搭好了UI的整体框架。
public void initUI() {
Mframe jf = new Mframe();//此处Mframe为继承Jframe的子定义子类,后文会详细解释。
jf.setSize(1000, 1000);
jf.setTitle("五子棋游戏");
jf.setLocationRelativeTo(null);
jf.setDefaultCloseOperation(3);
jf.setVisible(true);
Graphics g = jf.getGraphics();
}
接下来选取合适的间距画线(注意适当留白),生成一个五子棋棋盘。为了方便之后的功能添加,我们可在空白处设置按钮面板并添加相应的按钮。这里添加了三个功能按钮,分别为“开始”“悔棋”“复盘”。
//设置面板
JPanel esatPanel = new JPanel();
esatPanel.setPreferredSize(new Dimension(80, 0));
jf.add(esatPanel, BorderLayout.EAST);
//添加功能按钮和动作监听器(后文详解)
GameListener listener = new GameListener();
String[] name = {"开始", "悔棋", "复盘"};
for (int i = 0; i < name.length; i++) {
JButton jbu = new JButton(name[i]);
esatPanel.add(jbu);
jbu.addActionListener(listener);
}
//棋盘绘制
for (int i = 0; i < 9; i++) {
g.drawLine(100, 100 + i * 100, 900, 100 + i * 100);
g.drawLine(100 + i * 100, 100, 100 + i * 100, 900);
}
添加主函数,创建GameUI的对象并调用initUI方法,便能看到简易的五子棋界面。
2.棋子绘制
有棋盘之后,我们需要实现下棋功能,即棋子绘制。棋子绘制本质便是在鼠标点击后在相应位置画实心圆和空心圆(画实心圆的方法为fillOval,画空心圆的方法为drawOval,它们都需要四个参数,x y对应外接矩形的左上角坐标,width和height对应外接矩形宽高,设置宽高相等即可得到圆)。因此,我们首先要给窗体加上鼠标监听器来识别点击的位置。但是棋子的中心应该正好落于棋盘线的交点处,所以我们需要对获取的点击坐标进行一定数学运算处理,并设置一个标志位flag来判断应该下黑棋还是白棋。
public void mouseClicked(MouseEvent e) {
//定义x y存储点击的坐标值
int x = e.getX();
int y = e.getY();
//对x y进行处理使坐标能落在棋盘线交点
if (x % 100 <= 50) {
x1 = x - x % 100;
}
else {
x1 = x + 100 - x % 100;
}
if (y % 100 <= 50) {
y1 = y - y % 100;
}
else {
y1 = y + 100 - y % 100;
}
//边缘情况处理
if (x > 900) x1 = 900;
if (x < 100) x1 = 100;
if (y > 900) y1 = 900;
if (y < 100) y1 = 100;
//提前创建整形二维数组Chess[][]用于记录棋子位置,黑棋置一,白棋置二
if (Chess[y1 / 100 - 1][x1 / 100 - 1] != 0) {
System.out.println("该位置已有棋子!");
return;
}
if (flag % 2 == 0) {
g.fillOval(x1 - 25, y1 - 25, 50, 50);
Chess[y1 / 100 - 1][x1 / 100 - 1] = 1;
} else {
g.drawOval(x1 - 25, y1 - 25, 50, 50);
Chess[y1 / 100 - 1][x1 / 100 - 1] = 2;
}
3.输赢判断
实现下棋功能后,我们来进一步实现输赢判断。当有一方在横竖左右任一方向下出五个连续棋子时,此人获胜。在代码层面,若是每次都遍历二维数组判断连续棋子数则过于麻烦,所以我们选择计算最后落下的棋子各方向的连续同色棋子数。计算时需要注意坐标和对应数组位置的转换和数组越界问题。此处以计算横向棋子数的方法Iswin1为例。
public int Iswin1(int x1, int y1) {
int c = 1;//用c记录棋子数量,初始值为1,即棋子本身
int a = Chess[y1 / 100 - 1][x1 / 100 - 1];//进行坐标转换并用a的值代表黑白
for (int i = y1 / 100 - 2; i >= 0 && i >= y1 / 100 - 5; i--) {
if (Chess[i][x1 / 100 - 1] == a) {
c++;
} else break;
}
for (int i = y1 / 100; i <= 8 && i <= y1 / 100 + 4; i++) {
if (Chess[i][x1 / 100 - 1] == a) {
c++;
} else break;
}
return c;//返回棋子数量
}
当Iswin1到Iswin4任一值为5时,即可通过flag的奇偶判断胜负。此时我们定义变量flag1,当它由0变为1时表示胜负已分,游戏结束,再点击棋盘将显示“游戏已结束!”。
4.图形重绘
在实现游戏基本功能之后,我们应添加图形重绘功能,使窗体状态改变后仍能保留棋盘棋子。定义Shape类以保存棋子数据和画棋方法。
public class Shape {
public int x1,y1,flag;
//保存棋子数据
public Shape(int x1,int y1,int flag){
this.x1 = x1;
this.y1 = y1;
this.flag = flag;
}
//画棋方法
public void drawShape(Graphics g){
if (flag % 2 == 0) {
g.fillOval(x1 - 25, y1 - 25, 50, 50);
} else {
g.drawOval(x1 - 25, y1 - 25, 50, 50);
}
}
}
为了实现重绘,我们需用MPanel类去继承JPanel类,重写paint方法,在保留父类功能的基础上加入画棋子棋盘的功能。
public class Mframe extends JFrame {
public Shape[] shapeArr;
public void paint(Graphics g) {
super.paint(g);//保留父类功能
//画棋盘
for (int i = 0; i < 9; i++) {
g.drawLine(100, 100 + i * 100, 900, 100 + i * 100);
g.drawLine(100 + i * 100, 100, 100 + i * 100, 900);
}
//画棋子
for (int i = 0; i < shapeArr.length; i++) {
Shape shape = shapeArr[i];
if (shape == null) break;
shape.drawShape(g);
}
}
}
接着我们在Gamelistener类中创建Shape类的数组shapeArr存储棋子数据,并把数据传递给GameUI类。窗体状态改变时即会自动执行重绘功能。
5.功能添加
在此基础上,我还添加了“开始”“悔棋”“复盘”三种功能。
1.开始
点击“开始”之后,才可以开始下棋,而且此按钮带有清空棋盘的功能,即重新开始游戏。我们设置boolean型变量start,初始值为false,这时点击棋盘将显示“请点击开始!”。点击“开始”后,start置为true,数组Chess和shapeArr以及标志位全部置零,方可开始游戏。
2.悔棋
“悔棋”的功能是撤销上一个棋子,并可以做到连续撤销。此过程需要将Chess和shapeArr相应位置的数据清空并让索引下标减一,之后进行一次图形重绘,就能达到清除上一步棋子的效果。
3.复盘
“复盘”的功能是从头再现下棋的过程。实现复盘的第一步是清空棋盘。我们可以令设置shapeArr1数组保存和shapeArr相同的图形数据。在复盘时,我们先将shapeArr清空执行重绘,再遍历shapeArr1一个个画出棋子。为了防止画棋子的速度过快,我们需要设计合理的睡眠时间使下两颗棋有一定的间隔。
public void actionPerformed(ActionEvent e) {
String str = e.getActionCommand();
switch (str) {
case "开始":
flag1 = 0;
start = true;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
Chess[i][j] = 0;
}
}
for (int i = 0; i < flag; i++) {
shapeArr[i] = null;
}
flag = 0;
mf.paint(g);
break;
case "悔棋":
if (flag >= 1) {
shapeArr[flag - 1] = null;
Chess[Y[--Index] / 100 - 1][X[--Index] / 100 - 1] = 0;
index1--;
flag--;
}
mf.paint(g);
break;
case "复盘":
for (int i = 0; i < shapeArr.length; i++) {
shapeArr[i] = null;
}
mf.paint(g);
for (int i = 0; i < shapeArr0.length; i++) {
Shape shape = shapeArr0[i];
if (shape == null) break;
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
shape.drawShape(g);
}
break;
}
}
6.GameUI与GameListener完整代码
以下是经过功能添加与细化后的GameUI类与GameListener类的完整代码。
public class GameUI {
public void initUI() {
Mframe jf = new Mframe();
jf.setSize(1000, 1000);
jf.setTitle("五子棋游戏");
jf.setLocationRelativeTo(null);
jf.setDefaultCloseOperation(3);
JPanel esatPanel = new JPanel();
esatPanel.setPreferredSize(new Dimension(80, 0));
jf.add(esatPanel, BorderLayout.EAST);
GameListener listener = new GameListener();
String[] name = {"开始", "悔棋", "复盘"};
for (int i = 0; i < name.length; i++) {
JButton jbu = new JButton(name[i]);
esatPanel.add(jbu);
jbu.addActionListener(listener);
}
jf.setVisible(true);
Graphics g = jf.getGraphics();
jf.addMouseListener(listener);
listener.setG(g);
jf.shapeArr = listener.shapeArr;
listener.mf = jf;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < 9; i++) {
g.drawLine(100, 100 + i * 100, 900, 100 + i * 100);
g.drawLine(100 + i * 100, 100, 100 + i * 100, 900);
}
}
public static void main(String[] args) {
GameUI ui = new GameUI();
ui.initUI();
}
}
public class GameListener extends MouseAdapter implements ActionListener {
private Graphics g;
public int[][] Chess = new int[9][9];
public int flag = 0;
int flag1 = 0;
int index1 = 0;
int index2 = 0;
public Shape[] shapeArr = new Shape[100];
public Shape[] shapeArr0 = new Shape[100];
public boolean start = false;
public Mframe mf;
int x1, y1;
public int[] X = new int[81];
public int[] Y = new int[81];
public int Index = 0;
public void setG(Graphics g) {
this.g = g;
}
public void actionPerformed(ActionEvent e) {
String str = e.getActionCommand();
switch (str) {
case "开始":
flag1 = 0;
start = true;
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
Chess[i][j] = 0;
}
}
for (int i = 0; i < flag; i++) {
shapeArr[i] = null;
}
flag = 0;
mf.paint(g);
break;
case "悔棋":
if (flag >= 1) {
shapeArr[flag - 1] = null;
Chess[Y[--Index] / 100 - 1][X[--Index] / 100 - 1] = 0;
index1--;
flag--;
}
mf.paint(g);
break;
case "复盘":
for (int i = 0; i < shapeArr.length; i++) {
shapeArr[i] = null;
}
mf.paint(g);
for (int i = 0; i < shapeArr0.length; i++) {
Shape shape = shapeArr0[i];
if (shape == null) break;
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
shape.drawShape(g);
}
break;
}
}
public void mouseClicked(MouseEvent e) {
if (flag1 == 1) {
System.out.println("游戏已结束!");
return;
}
if (!start) {
System.out.println("请点击开始!");
return;
}
int x = e.getX();
int y = e.getY();
if (x % 100 <= 50) {
x1 = x - x % 100;
} else {
x1 = x + 100 - x % 100;
}
if (y % 100 <= 50) {
y1 = y - y % 100;
} else {
y1 = y + 100 - y % 100;
}
if (x > 900) x1 = 900;
if (x < 100) x1 = 100;
if (y > 900) y1 = 900;
if (y < 100) y1 = 100;
X[Index++] = x1;
Y[Index++] = y1;
if (Chess[y1 / 100 - 1][x1 / 100 - 1] != 0) {
System.out.println("该位置已有棋子!");
return;
}
if (flag % 2 == 0) {
g.fillOval(x1 - 25, y1 - 25, 50, 50);
Chess[y1 / 100 - 1][x1 / 100 - 1] = 1;
} else {
g.drawOval(x1 - 25, y1 - 25, 50, 50);
Chess[y1 / 100 - 1][x1 / 100 - 1] = 2;
}
if (Iswin1(x1, y1) == 5 || Iswin2(x1, y1) == 5 || Iswin3(x1, y1) == 5 || Iswin4(x1, y1) == 5) {
if (flag % 2 == 0) System.out.println("黑棋赢");
else System.out.println("白棋赢");
flag1 = 1;
}
Shape shape = new Shape(x1, y1, flag);
flag++;
shapeArr[index1++] = shape;
shapeArr0[index2++] = shape;
}
public int Iswin1(int x1, int y1) {
int c = 1;
int a = Chess[y1 / 100 - 1][x1 / 100 - 1];
for (int i = y1 / 100 - 2; i >= 0 && i >= y1 / 100 - 5; i--) {
if (Chess[i][x1 / 100 - 1] == a) {
c++;
} else break;
}
for (int i = y1 / 100; i <= 8 && i <= y1 / 100 + 4; i++) {
if (Chess[i][x1 / 100 - 1] == a) {
c++;
} else break;
}
return c;
}
public int Iswin2(int x1, int y1) {
int c = 1;
int a = Chess[y1 / 100 - 1][x1 / 100 - 1];
for (int i = x1 / 100 - 2; i >= 0 && i >= x1 / 100 - 5; i--) {
if (Chess[y1 / 100 - 1][i] == a) {
c++;
} else break;
}
for (int i = x1 / 100; i <= 8 && i <= x1 / 100 + 4; i++) {
if (Chess[y1 / 100 - 1][i] == a) {
c++;
} else break;
}
return c;
}
public int Iswin3(int x1, int y1) {
int c = 1;
int a = Chess[y1 / 100 - 1][x1 / 100 - 1];
for (int i = 1; i <= 4 && y1 / 100 - 1 - i >= 0 && x1 / 100 - 1 - i >= 0; i++) {
if (Chess[y1 / 100 - 1 - i][x1 / 100 - 1 - i] == a) {
c++;
} else break;
}
for (int i = 1; i <= 4 && y1 / 100 - 1 + i <= 8 && x1 / 100 - 1 + i <= 8; i++) {
if (Chess[y1 / 100 - 1 + i][x1 / 100 - 1 + i] == a) {
c++;
} else break;
}
return c;
}
public int Iswin4(int x1, int y1) {
int c = 1;
int a = Chess[y1 / 100 - 1][x1 / 100 - 1];
for (int i = 1; i <= 4 && y1 / 100 - 1 - i >= 0 && x1 / 100 - 1 + i <= 8; i++) {
if (Chess[y1 / 100 - 1 - i][x1 / 100 - 1 + i] == a) {
c++;
} else break;
}
for (int i = 1; i <= 4 && y1 / 100 - 1 + i <= 8 && x1 / 100 - 1 - i >= 0; i++) {
if (Chess[y1 / 100 - 1 + i][x1 / 100 - 1 - i] == a) {
c++;
} else break;
}
return c;
}
}