写一个五子棋小项目

今天来写一个五子棋的小项目,最终实现如下功能:
Begin~

  1. 进行对战并判断输赢
  2. 悔棋
  3. 复盘
  4. 人机对战

不急,我们一步一步来实现这些功能

首先,我们需要创建一个棋盘:

TODO:

  • 设置参数
  • 绘制棋盘
    • 背景、19*19格、特殊点位标记
    • 添加按钮实现游戏的开始暂停、悔棋等功能

设置参数

在设置参数的时候,我们使用接口(interface)来定义常量,以便其他类使用

1
2
3
4
5
6
public interface GoData {
int ROW = 19, COL = 19;//行、列
int SIZE = 40;//格子size
int GX = 60, GY = 60;//棋盘左上角
int BX = 45, BY = 45;//背景左上角
}

某个类想要调用这个接口,只需要implements即可:
1
public class GoUI implements GoData{Do something...}

绘制棋盘

注意:19条线,18个格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class GoUI extends JFrame implements GoData {
public GoUI() {
setTitle("五子棋V2.0");
setSize(1100, 900);
setResizable(true);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
setLayout(null);
String[] Btns = {"开始游戏", "清空棋盘", "悔棋", "复盘", "人机对战"};
for (int i = 0; i < Btns.length; i++) {
JButton btn = new JButton(Btns[i]);
btn.setBackground(Color.white);
btn.setBounds(GX + (COL - 1) * SIZE + 30, GY + 100 * i, 150, 90);
add(btn);
}
}

public void DrawBoard(Graphics g) {
g.setColor(new Color(247, 211, 122));
g.fillRect(BX, BY, (GX - BX) * 2 + (ROW - 1) * SIZE, (GY - BY) * 2 + (COL - 1) * SIZE);
g.setColor(Color.black);
for (int i = 0; i < ROW; i++) {
g.drawLine(GX, GY + SIZE * i, GX + (COL - 1) * SIZE, GY + SIZE * i);
}
for (int i = 0; i < COL; i++) {
g.drawLine(GX + SIZE * i, GY, GX + SIZE * i, GY + (ROW - 1) * SIZE);
}
g.fillOval(GX + 3 * SIZE - 5, GY + 3 * SIZE - 5, 10, 10);
g.fillOval(GX + 15 * SIZE - 5, GY + 3 * SIZE - 5, 10, 10);
g.fillOval(GX + 9 * SIZE - 5, GY + 3 * SIZE - 5, 10, 10);
g.fillOval(GX + 3 * SIZE - 5, GY + 9 * SIZE - 5, 10, 10);
g.fillOval(GX + 9 * SIZE - 5, GY + 9 * SIZE - 5, 10, 10);
g.fillOval(GX + 15 * SIZE - 5, GY + 9 * SIZE - 5, 10, 10);
g.fillOval(GX + 3 * SIZE - 5, GY + 15 * SIZE - 5, 10, 10);
g.fillOval(GX + 9 * SIZE - 5, GY + 15 * SIZE - 5, 10, 10);
g.fillOval(GX + 15 * SIZE - 5, GY + 15 * SIZE - 5, 10, 10);
}

public void DrawChess(Graphics g) {
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
if (gol.GoArr[i][j] == 1) {
g.setColor(Color.black);
g.fillOval(GX + j * SIZE - SIZE / 2, GY + i * SIZE - SIZE / 2, SIZE, SIZE);
} else if (gol.GoArr[i][j] == 2) {
g.setColor(Color.white);
g.fillOval(GX + j * SIZE - SIZE / 2, GY + i * SIZE - SIZE / 2, SIZE, SIZE);
}
}
}
}

@Override
public void paint(Graphics g) {
super.paint(g);
DrawBoard(g);
DrawChess(g);
}

到此,棋盘就画好了
098i4e.png


下面实现下棋

TODO:

  • 黑白交替下棋
    • 判断游戏是否开始,未开始则无法下棋
    • 鼠标点击下棋
    • 黑白交替
    • 自动调整下棋坐标
    • 判断此处是否能落子
  • 判断游戏输赢
  • 棋子重绘(防止因改变窗体大小导致棋子丢失)
  • 清空棋盘
  • 悔棋
  • 复盘
  • 人机对战

黑白交替下棋

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class GoListener implements MouseListener, ActionListener, GoData {

int ChessFlag = 0;//0:未开始 1:黑棋 2:白棋
private Graphics g;

private int[][] GoArr = new int[ROW][COL];

public void GetGraphics(Graphics g) {
this.g = g;
}

private void print() {
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
System.out.print(GoArr[i][j] + " ");
}
System.out.println();
}
}

@Override
public void actionPerformed(ActionEvent e) {
JButton btn = (JButton) e.getSource();
String act = btn.getText();
if (act.equals("开始游戏")) {
btn.setText("结束游戏");
btn.setBackground(Color.red);
ChessFlag = 1;
} else if (act.equals("结束游戏")) {
btn.setText("开始游戏");
btn.setBackground(Color.white);
ChessFlag = 2;
print();
}
}

@Override
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
int i = (int) Math.round((double) (x - GX) / SIZE);
int j = (int) Math.round((double) (y - GY) / SIZE);
if (ChessFlag == 0) JOptionPane.showMessageDialog(null, "游戏还未开始!");
else if (ChessFlag == 1) {
if (GoArr[j][i] != 0) {
System.out.println("该处有棋了!");
} else {
g.setColor(Color.black);
GoArr[j][i] = ChessFlag;
g.fillOval(GX + i * SIZE - SIZE / 2, GY + j * SIZE - SIZE / 2, SIZE, SIZE);
ChessFlag = 2;
g.setColor(Color.white);
}

} else if (ChessFlag == 2) {
if (GoArr[i][j] != 0) {
System.out.println("此处有棋了!");
} else {
g.setColor(Color.white);
GoArr[i][j] = ChessFlag;
g.fillOval(GX + i * SIZE - SIZE / 2, GY + j * SIZE - SIZE / 2, SIZE, SIZE);
ChessFlag = 1;
g.setColor(Color.black);
}

}
}

交替下棋效果

判断输赢

显然,我们的胜负只与最后一步棋有关,所以,我们只需以最后落下的棋子为中心,进行米子查询,判断是否满5子即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
private boolean Judge(int i, int j, int flag) {
int dx = i, dy = j - 1, cnt = 0;
while (GoArr[dx][dy] == flag && dx >= 0 && dx < ROW && dy >= 0 && dy < COL) {
cnt++;
dy--;
}
dy = j + 1;
while (GoArr[dx][dy] == flag && dx >= 0 && dx < ROW && dy >= 0 && dy < COL) {
cnt++;
dy++;
}
if (cnt + 1 == 5) return true;


dx = i - 1;
dy = j;
cnt = 0;
while (dx >= 0 && dx < ROW && dy >= 0 && dy < COL && GoArr[dx][dy] == flag) {
cnt++;
dx--;
}
dx = i + 1;
while (dx >= 0 && dx < ROW && dy >= 0 && dy < COL && GoArr[dx][dy] == flag) {
cnt++;
dx++;
}
if (cnt + 1 == 5) return true;


dx = i - 1;
dy = j - 1;
cnt = 0;
while (dx >= 0 && dx < ROW && dy >= 0 && dy < COL && GoArr[dx][dy] == flag) {
cnt++;
dy--;
dx--;
}
dx = i + 1;
dy = j + 1;
while (dx >= 0 && dx < ROW && dy >= 0 && dy < COL && GoArr[dx][dy] == flag) {
cnt++;
dy++;
dx++;
}
if (cnt + 1 == 5) return true;


dx = i - 1;
dy = j + 1;
cnt = 0;
while (dx >= 0 && dx < ROW && dy >= 0 && dy < COL && GoArr[dx][dy] == flag) {
cnt++;
dx--;
dy++;
}
dx = i + 1;
dy = j - 1;
while (dx >= 0 && dx < ROW && dy >= 0 && dy < COL && GoArr[dx][dy] == flag) {
cnt++;
dx++;
dy--;
}
if (cnt + 1 == 5) return true;

return false;
}

判断输赢效果

棋子重绘

利用二维数组GoArr[][]记录棋盘上的棋子,每次绘制棋盘的时候将棋盘上的棋子重绘一次,麻麻再也不用担心棋子因为拖动窗体而消失了~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void DrawChess(Graphics g) {
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
if (gol.GoArr[i][j] == 1) {
g.setColor(Color.black);
g.fillOval(GX + j * SIZE - SIZE / 2, GY + i * SIZE - SIZE / 2, SIZE, SIZE);
} else if (gol.GoArr[i][j] == 2) {
g.setColor(Color.white);
g.fillOval(GX + j * SIZE - SIZE / 2, GY + i * SIZE - SIZE / 2, SIZE, SIZE);
}
}

}
}

悔棋

通过记录每次下棋的坐标,通过更改GoArr[][],重绘来达到目的。

1
2
3
4
5
6
7
(act.equals("悔棋")) {
if (step >= 1) {
GoArr[EveryI[step]][EveryJ[step]] = 0;
step--;
Repaint(g);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    int[] EveryI = new int[ROW * COL];
int[] EveryJ = new int[ROW * COL];
int step = 0;

private GoUI goUI;

public void setGoUI(GoUI goUI) {
this.goUI = goUI;
}

public void Repaint(Graphics g) {
goUI.DrawBoard(g);
goUI.DrawChess(g);
}

清空棋盘

清空EveryI,EveryJ,step,以及GoArr,最后进行重绘即可

1
2
3
4
5
6
7
8
9
10
public void DelBoard(Graphics g) {
step = 0;
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
GoArr[i][j] = 0;
}
}
Repaint(g);
}

复盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(act.equals("复盘")) {
int n = step;
ChessFlag = 0;
DelBoard(g);
int chess = 1;
for (int i = 1; i <= n; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
GoArr[EveryI[i]][EveryJ[i]] = chess;
Repaint(g);
chess = (chess == 1 ? 2 : 1);
}
}

这里面使用Thread.sleep()实现了每隔一秒下一步棋的效果

暂时先这样叭,最后还差一个人机对战,估计是要调用什么库来实现吧~

$-By\ AHC$