C语言实现五子棋游戏


C语言课设项目

基于C语言和EasyX图形库实现的五子棋小游戏

AI算法采用极大极小值搜索中应用Alpha-Beta剪枝

实现人机对战等功能

引言

博弈算法

零和博弈,又称零和游戏零和赛局,与非零和博弈相对,是博弈论的一个概念,属非合作博弈。零和博弈表示所有博弈方的利益之和为零或一个常数,即一方有收入,其他方必有所失。

这里所提到的博弈算法适用于有完备信息的,确定性的,轮流行动的两个游戏者的零和游戏,像大多数的棋类游戏,如五子棋,象棋,围棋都满足这些条件,可以通过这套算法实现。

五子棋在今日已经被证明了是一种“不公平”的游戏,黑棋先手绝对占优,并且有先手必胜的套路。因此,五子棋发展出了各种限制黑棋的规则(如三三禁手),此类五子棋被称为“连珠”。

本文实现的五子棋为无禁手五子棋,AI算法较为简单,棋力水平达到业余水平。

界面设计

此项目使用vs2019为开发工具,借助了EasyX图形库,实现较为简单的UI

程序基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
while (1) {
if (startGame() == 1) { //startGame函数为开始界面初始化
int win = game1(); //game1,game2函数分别实现人机和玩家pk
gameOver1(win);
}
else {
int win = game2();
gameOver2(win);
}
}
return 0;
}

开始页面

其中涉及音乐和图片素材调用,素材文件路径可改变

涉及的EasyX图形库函数,可在EasyX文档中查询

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
int startGame() {
initgraph(600, 600);

IMAGE im_start;
loadimage(&im_start, _T("D:\\素材\\start.jpg")); //开始界面图片
putimage(0, 0, &im_start);

setlinecolor(BLACK); //绘制选择按钮
setfillcolor(RGB(254, 220, 130));
fillrectangle(50, 490, 170, 545);
fillrectangle(240, 490, 360, 545);
fillrectangle(430, 490, 550, 545);

setbkmode(TRANSPARENT);
settextcolor(BLACK);
LOGFONT nowstyle;
gettextstyle(&nowstyle);
settextstyle(25, 0, _T("黑体"));
outtextxy(60, 510, _T("人机对战"));
outtextxy(250, 510, _T("双人游戏"));
outtextxy(440, 510, _T("退出游戏"));
settextstyle(&nowstyle);

mciSendString(_T("close stmusic"), NULL, 0, NULL);
mciSendString(_T("open D:\\素材\\background.mp3 alias bgmusic"), NULL, 0, NULL);
mciSendString(_T("play bgmusic repeat"), NULL, 0, NULL);
int gamemode = 0;

init_tuple6type(); //调用初始化棋形函数,具体在AI算法中实现

while (1) {
MOUSEMSG mouse = GetMouseMsg(); //

if (mouse.x >= 50 && mouse.x <= 170 && mouse.y >= 490 && mouse.y <= 545
&& mouse.mkLButton) {
gamemode = 1;
mciSendString(_T("open D:\\素材\\start.mp3 alias stmusic"), NULL, 0, NULL);
mciSendString(_T("play stmusic"), NULL, 0, NULL);
break;
}
if (mouse.x >= 240 && mouse.x <= 360 && mouse.y >= 490 && mouse.y <= 545
&& mouse.mkLButton) {
gamemode = 2;
mciSendString(_T("open D:\\素材\\start.mp3 alias stmusic"), NULL, 0, NULL);
mciSendString(_T("play stmusic"), NULL, 0, NULL);
break;
}
if (mouse.x >= 430 && mouse.x <= 550 && mouse.y >= 490 && mouse.y <= 545
&& mouse.mkLButton) {
mciSendString(_T("open D:\\素材\\start.mp3 alias stmusic"), NULL, 0, NULL);
mciSendString(_T("play stmusic wait"), NULL, 0, NULL);
exit(0);
}


}
mciSendString(_T("close bgmusic"), NULL, 0, NULL);
return gamemode;
}

棋盘数据

  • 棋盘数据由结构体数组储存
1
2
3
4
5
6
7
8
typedef struct node { //棋盘每个节点的数据储存
int x; //左上顶点坐标
int y;
int model; //格子边框样式
int value; //是否落子(0代表无, 黑棋1, 白棋2)
bool isnew; //是否显示选择框
COLORREF color; //格子颜色
}NODE;
  • 全局变量
1
2
NODE map[15][15];
COLORREF COLOR = RGB(255, 183, 111);

棋盘绘制

  • 初始化棋盘数组中的数据
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
void initNode()
{
for (int i = 75, k = 0; i <= 495; i = i + 30) {
for (int j = 75, g = 0; j <= 495; j = j + 30) {
map[k][g].x = j;
map[k][g].y = i;
map[k][g].value = 0;
map[k][g].isnew = false;
map[k][g].color = COLOR;
if (k == 0 && g == 0) //根据格子位置判断格子边框
{
map[k][g].model = 8;
}
else if (k == 0 && g == 14)
{
map[k][g].model = 7;
}
else if (k == 14 && g == 14)
{
map[k][g].model = 6;
}
else if (k == 14 && g == 0)
{
map[k][g].model = 5;
}
else if (k == 0)
{
map[k][g].model = 3;
}
else if (k == 14)
{
map[k][g].model = 4;
}
else if (g == 0)
{
map[k][g].model = 1;
}
else if (g == 14)
{
map[k][g].model = 2;
}
else if ((k == 3 && g == 3) || (k == 3 && g == 11) || (k == 11 && g == 3) ||
(k == 11 && g == 11) || (k == 7 && g == 7))
{
map[k][g].model = 9;
}
else
{
map[k][g].model = 0;
}
g++;
}
k++;
}

}
  • 下棋时,棋盘会随着鼠标移动显示选择框,避免不断绘制整个棋盘导致的闪屏,于是将棋盘绘制设计为每个格子逐个绘制,在更新选择框时仅重新绘制当前位置的图形
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
void drawNode(NODE* node)
{
int x = node->x; //获得当前节点数据
int y = node->y;
int model = node->model;
int value = node->value;
int isnew = node->isnew;
COLORREF color = node->color;

setfillcolor(color);
solidrectangle(x, y, x + 30, y + 30);

//绘制边框
setlinecolor(BLACK);
switch (model)
{
case 0:
setlinestyle(PS_SOLID, 2);
line(x + 15, y, x + 15, y + 30);
line(x - 1, y + 15, x + 30, y + 15);
break;
// *
// * * *
// *
case 3:
setlinestyle(PS_SOLID, 2);
line(x + 15, y + 15, x + 15, y + 30);
setlinestyle(PS_SOLID, 3);
line(x - 1, y + 15, x + 30, y + 15);
break;
// * * *
// *
// *
case 4:
setlinestyle(PS_SOLID, 2);
line(x + 15, y, x + 15, y + 15);
setlinestyle(PS_SOLID, 3);
line(x - 1, y + 15, x + 30, y + 15);
break;
// *
// *
// * * *
case 1:
setlinestyle(PS_SOLID, 2);
line(x + 14, y + 15, x + 30, y + 15);
setlinestyle(PS_SOLID, 3);
line(x + 15, y, x + 15, y + 30);
break;
// *
// * * *
// *
case 2:
setlinestyle(PS_SOLID, 2);
line(x - 1, y + 15, x + 15, y + 15);
setlinestyle(PS_SOLID, 3);
line(x + 15, y, x + 15, y + 30);
break;
// *
// * * *
// *
case 7:
setlinestyle(PS_SOLID, 3);
line(x - 1, y + 15, x + 15, y + 15);
line(x + 15, y + 15, x + 15, y + 30);
break;
// * * *
// *
// *
case 6:
setlinestyle(PS_SOLID, 3);
line(x + 15, y, x + 15, y + 15);
line(x - 1, y + 15, x + 15, y + 15);
break;
// *
// *
// * * *
case 5:
setlinestyle(PS_SOLID, 3);
line(x + 15, y, x + 15, y + 15);
line(x + 15, y + 15, x + 30, y + 15);
break;
// *
// *
// * * *
case 8:
setlinestyle(PS_SOLID, 3);
line(x + 15, y + 15, x + 30, y + 15);
line(x + 15, y + 15, x + 15, y + 30);
break;
// * * *
// *
// *
case 9:
setlinestyle(PS_SOLID, 2);
line(x + 15, y, x + 15, y + 30);
line(x - 1, y + 15, x + 30, y + 15);
setfillcolor(BLACK);
setlinestyle(PS_SOLID, 2);
fillcircle(x + 15, y + 15, 4);
break;
// *
// * O *
// *
}

//绘制选择框
if (isnew)
{
setlinestyle(PS_SOLID, 2);
setlinecolor(LIGHTGRAY);
line(x + 1, y + 2, x + 8, y + 2);
line(x + 2, y + 1, x + 2, y + 8);
line(x + 29, y + 2, x + 22, y + 2);
line(x + 29, y + 1, x + 29, y + 8);
line(x + 2, y + 29, x + 8, y + 29);
line(x + 2, y + 22, x + 2, y + 29);
line(x + 29, y + 29, x + 22, y + 29);
line(x + 29, y + 22, x + 29, y + 29);
}

//绘制棋子
switch (value)
{
case 1:
setfillcolor(BLACK);
setlinecolor(BLACK);
setlinestyle(PS_SOLID, 1);
fillcircle(x + 15, y + 15, 12);
break;
case 2:
setfillcolor(WHITE);
setlinecolor(BLACK);
setlinestyle(PS_SOLID, 1);
fillcircle(x + 15, y + 15, 12);
break;

}
}
  • 绘制整个棋盘函数,不断调用绘制格子函数
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
void drawBoard()
{

//更新每一个节点
for (int i = 0; i < 15; i++) {
for (int j = 0; j < 15; j++) {
drawNode(&map[i][j]);
if (map[i][j].isnew == true)
{
map[i][j].isnew = false; // 把上一个下棋位置的选择框清除
}
}
}

// 绘制坐标
TCHAR strnum[19][3] = { _T("1"),_T("2") ,_T("3") ,_T("4"),_T("5") ,_T("6") ,_T("7"),_T("8"),_T("9"),_T("10"), _T("11"),_T("12") ,_T("13") ,_T("14"),_T("15") };
TCHAR strabc[19][3] = { _T("A"),_T("B") ,_T("C") ,_T("D"),_T("E") ,_T("F") ,_T("G"),_T("H"),_T("I"),_T("J"), _T("K"),_T("L") ,_T("M") ,_T("N"),_T("O") };

LOGFONT nowstyle;
gettextstyle(&nowstyle);
settextstyle(0, 0, NULL);
settextcolor(BLACK);
int number = 0;
for (int i = 0; i < 15; i++)
{
outtextxy(85 + number, 60, strnum[i]);
outtextxy(60, 85 + number, strabc[i]);
number += 30;
}

}

游戏函数

  • game1为实现人机对战的函数,其中关于AI算法函数稍后实现
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
int game1() {    //人机对战实现
Sleep(500);

setbkcolor(COLOR);
cleardevice();

initNode();
drawBoard();

settextstyle(20, 10, 0, 0, 0, 1000, false, false, false);
settextcolor(BLACK);
settextstyle(25, 0, NULL);
outtextxy(90, 530, _T("玩家执黑先行"));

int whoplay = 0;

int oldi = 0; //更新鼠标位置所储存的坐标
int oldj = 0;

while (1) {
if (whoplay == 0)
{
mciSendString(_T("close domusic"), NULL, 0, NULL);
MOUSEMSG mouse = GetMouseMsg();
if (mouse.x >= 75 && mouse.y >= 75 && mouse.x <= 525 && mouse.y <= 525) {
//判断鼠标所处节点
int j = (mouse.x - 75) / 30;
int i = (mouse.y - 75) / 30;
//判断是否是空位置
if (map[i][j].value == 0)
{
// 如果按下了
if (mouse.mkLButton)
{
map[i][j].value = C_BLACK; // 下棋
map[i][j].isnew = true; //更新选择框

whoplay = 1; //轮到下一个
drawBoard();
mciSendString(_T("open D:\\素材\\down.mp3 alias domusic"),
NULL, 0, NULL);
mciSendString(_T("play domusic wait"), NULL, 0, NULL);

int win;
if ((win = winner()) != 0) { //判断是否获胜
return win;
}

oldi = 0;
oldj = 0;
continue;

}
// 更新节点
map[oldi][oldj].isnew = false;
drawNode(&map[oldi][oldj]);
map[i][j].isnew = true;
drawNode(&map[i][j]);
oldi = i;
oldj = j;
}

}
}
else {

mciSendString(_T("close domusic"), NULL, 0, NULL);

int board[15][15];
mapToBoard(map, board);
//AI下棋
analyse(board, 4, -INT_MAX, INT_MAX); //AI算法实现函数,应用极大极小值搜索

map[decision.best.x][decision.best.y].value = C_WHITE;
map[decision.best.x][decision.best.y].isnew = true;

whoplay = 0;
drawBoard();
mciSendString(_T("open D:\\素材\\down.mp3 alias domusic"), NULL, 0, NULL);
mciSendString(_T("play domusic wait"), NULL, 0, NULL);

int win;
if ((win = winner()) != 0) {
return win;
}

oldi = 0;
oldj = 0;
}
}


}

胜负判断

  • 每次落子后调用胜负判断函数,若有一方胜出则结束游戏
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
int winner()
{
int iswin = 0;
int dx[4] = { 1, 0, 1, 1 }; //向四种方向搜索棋盘
int dy[4] = { 0, 1, 1, -1 };
bool isfull = true;
for (int i = 0; i < 15; i++) {
for (int j = 0; j < 15; j++) {
if (map[i][j].value != 0) {
int nowcolor = map[i][j].value;
int length[4] = { 0, 0, 0, 0 };
for (int k = 0; k < 4; k++) {
int nowi = i;
int nowj = j;
while (map[nowi][nowj].value == nowcolor && nowi >= 0
&& nowi <= 14 && nowj >= 0 && nowj <= 14) {
length[k]++;
nowi += dx[k];
nowj += dy[k];
}
}
for (int k = 0; k < 4; k++) {
if (length[k] >= 5) {
if (nowcolor == C_BLACK) {
iswin = 1;
}
else {
iswin = 2;
}
}
}
}
else {
isfull = false;
}
}
}
if (isfull) {
iswin = 3;
}
return iswin;
}

五子棋界面设计差不多已经完成,AI下棋的算法在下一篇

C语言实现五子棋游游戏(AI算法实现)