五子棋第四章
人机对战部分和文件存储部分
前言
本篇文章为完整工程实践五子棋项目的第四章。
如未看过前面的章节:
五子棋第一章
五子棋第二章
五子棋第三章
本篇文章是五子棋项目的最后一章,我们将讲解五子棋人机对战部分和文件存储部分的具体实现。
一、大致思路
我们在实现五子棋人机对战部分之前首先需要有一个大的方向即大致思路,这里我运用到的方法是计算权重法。所以权重,说得通俗易懂一点就是重要性,我们可以通过判断每个空位置附近的棋子分布状况,然后给不同的情况按重要程度分配不同的权重值,最后我们再选出权重值最大的位置让电脑进行下棋,这就是计算权重法的大致思路。
二、头文件部分
game.h
#include<windows.h>
#define X 16
#define Y 16
typedef struct player
{
char account[15];
char password[15];
char id[10];
int win;
int lose;
int tie;
int score;
struct player* prev;
struct player* next;
}player;
typedef struct chess
{
int x;
int y;
double weight;
}chess;
void gameprocess(player* p, player* q);
void init(char board[X][Y], int x, int y);
void print(char board[X][Y], int x, int y);
int play1(char board[X][Y], int x, int y);
int play2(char board[X][Y], int x, int y);
int judge(char board[X][Y], int x, int y, int a, int b);
void computerprocess(player* p);
int computer(char board[X][Y], int x, int y, char my);
chess* findmax(chess* pch, int n);
double cale(char board[X][Y], int x, int y, const int a, const int b, char my);
void calesum(double* psum, int n, int m, int t, int k, int d, int h);
由于我们人机部分对战的实现还是属于游戏内容部分的内容,所以我们在这里就将人机对战部分的相关函数声明放在之前的game.h中,此外我们还多定义了一个名为chess的结构体,其中x,y用来代表一个具体位置,weight用来代表该位置的权重值。顺带一提,我们编写人机对战部分的时候建议新建一个computer.c的源文件进行编写,避免game.h的文件过大且不利于我们进行文件的管理。
三、人机对战部分
1.游戏进程
computerprocess(player* p)函数
void computerprocess(player* p)
{
srand((unsigned int)time(NULL));
int r = rand();
if (r % 2 == 0)
{
printf("玩家%s先手\n", p->id);
Sleep(3000);
char board[X][Y] = { 0 };
init(board, X, Y);
print(board, X, Y);
while (1)
{
int sum = 0;
printf("轮到%s玩家(*)下棋\n", p->id);
int ret1 = play1(board, X, Y);
if (ret1 == 1)
{
print(board, X, Y);
printf("\n");
printf("恭喜玩家%s获胜\n", p->id);
printf("\n");
Sleep(5000);
break;
}
sum++;
if (sum == 113)
{
printf("平局\n");
print(board, X, Y);
printf("\n");
Sleep(5000);
break;
}
print(board, X, Y);
printf("轮到电脑(#)下棋\n");
Sleep(2000);
int ret2 = computer(board, X, Y, '#');
if (ret2 == 2)
{
print(board, X, Y);
printf("\n");
printf("恭喜电脑获胜\n");
printf("\n");
Sleep(5000);
break;
}
print(board, X, Y);
}
}
else
{
printf("电脑先手\n");
Sleep(3000);
char board[X][Y] = { 0 };
init(board, X, Y);
print(board, X, Y);
while (1)
{
int sum = 0;
printf("轮到电脑(*)下棋\n");
Sleep(2000);
int ret1 = computer(board, X, Y, '*');
if (ret1 == 1)
{
print(board, X, Y);
printf("\n");
printf("恭喜电脑获胜\n");
printf("\n");
Sleep(5000);
break;
}
sum++;
if (sum == 113)
{
printf("平局\n");
print(board, X, Y);
printf("\n");
Sleep(5000);
break;
}
print(board, X, Y);
printf("轮到%s玩家(#)下棋\n", p->id);
int ret2 = play2(board, X, Y);
if (ret2 == 2)
{
print(board, X, Y);
printf("\n");
printf("恭喜玩家%s获胜\n", p->id);
printf("\n");
Sleep(5000);
break;
}
print(board, X, Y);
}
}
}
这个函数用来控制游戏的总体进程,传入的变量p代表当前登录的玩家。这个函数和我们之前在玩家对战部分实现的gameprocess(player* p,player* q)函数类似。先用随机数随机确定玩家是先手还是后手,然后用死循环控制游戏持续进行,直到有一方获胜或者平局才跳出该死循环,结束游戏。
2.电脑下棋逻辑
computer(char board[X][Y], int x, int y, char my)函数
int computer(char board[X][Y], int x, int y, char my)
{
chess ch[225] = { 0 };
int n = 0;
int i = 0;
for (i = 1; i < x; i++)
{
int j = 0;
for (j = 1; j < y; j++)
{
if (board[i][j] == ' ')
{
ch[n].x = i;
ch[n].y = j;
ch[n].weight = cale(board, X, Y, i, j, my);
n++;
}
}
}
chess* max = findmax(ch, n);
board[max->x][max->y] = my;
printf("电脑下棋坐标:(%d,%d)\n", max->x, max->y);
int ret = judge(board, X, Y, max->x, max->y);
return ret;
}
这个函数用来控制电脑下棋的总体思路,即我们的计算权重法的总体思路。我们通过遍历整个棋盘,然后调用cale(board, X, Y, i, j, my)函数计算每个空位置的权重,放入名为ch的结构体数组中,之后调用findmax(ch, n)函数寻找数组中的权重值最大的位置。
3.寻找权重值最大的位置
findmax(chess* pch, int n)函数
//寻找权重最大的位置
chess* findmax(chess* pch, int n)
{
//判断指针是否为空
assert(pch);
//初始化随机数
srand((unsigned int)time(NULL));
//生成随机数
int r = rand();
int sum = 0;
chess* max = &pch[0];//假定权重最大位置
int i = 0;
//遍历寻找到权重最大的一个位置
for (i = 1; i < n; i++)
{
if (max->weight < pch[i].weight)
{
max = &pch[i];
}
}
chess* maxarr[225] = { 0 };//初始化权重最大位置的数组
//遍历寻找到所有权重最大的位置
for (i = 0; i < n; i++)
{
if (max->weight == pch[i].weight)
{
maxarr[sum] = &pch[i];//将所有权重最大的位置存储在一个数组中
sum++;//统计权重最大的位置的个数
}
}
max = maxarr[r % sum];//用随机数%上统计出的权重最大的位置个数sum,从而在最大权重的数组中随机获得一个位置
return max;
}
这个函数用来寻找权重值最大的位置并将其返回,由于这个函数里面注释写得比较多,所以我就不再多加赘述了。
4.计算权重值逻辑
cale(char board[X][Y], int x, int y, const int a, const int b, char my)函数
double cale(char board[X][Y], int x, int y, const int a, const int b, char my)
{
char you = 77 - my;
double sum = 0;
//heng
while (1)
{
int n = 0;
int m = 0;
int t = 0;
int i = a - 1;
if (i >= 1)
{
if (board[i][b] == you)
{
while (i >= 1)
{
if (board[i][b] == you)
{
n++;
i--;
}
else
{
if (board[i][b] == my)
{
t = 1;
}
else if (board[i][b] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[i][b] == my)
{
while (i >= 1)
{
if (board[i][b] == my)
{
m++;
i--;
}
else
{
if (board[i][b] == you)
{
t = 1;
}
else if (board[i][b] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[i][b] == ' ')
{
t = 2;
}
}
int k = 0;
int d = 0;
int h = 0;
i = a + 1;
if (i < x)
{
if (board[i][b] == you)
{
while (i < x)
{
if (board[i][b] == you)
{
k++;
i++;
}
else
{
if (board[i][b] == my)
{
h = 1;
}
else if (board[i][b] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[i][b] == my)
{
while (i < x)
{
if (board[i][b] == my)
{
d++;
i++;
}
else
{
if (board[i][b] == you)
{
h = 1;
}
else if (board[i][b] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[i][b] == ' ')
{
h = 2;
}
}
calesum(&sum, n, m, t, k, d, h);
break;
}
//shu
while (1)
{
int n = 0;
int m = 0;
int t = 0;
int j = b - 1;
if (j >= 1)
{
if (board[a][j] == you)
{
while (j >= 1)
{
if (board[a][j] == you)
{
n++;
j--;
}
else
{
if (board[a][j] == my)
{
t = 1;
}
else if (board[a][j] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[a][j] == my)
{
while (j >= 1)
{
if (board[a][j] == my)
{
m++;
j--;
}
else
{
if (board[a][j] == you)
{
t = 1;
}
else if (board[a][j] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[a][j] == ' ')
{
t = 2;
}
}
int k = 0;
int d = 0;
int h = 0;
j = b + 1;
if (j < y)
{
if (board[a][j] == you)
{
while (j < y)
{
if (board[a][j] == you)
{
k++;
j++;
}
else
{
if (board[a][j] == my)
{
h = 1;
}
else if (board[a][j] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[a][j] == my)
{
while (j < y)
{
if (board[a][j] == my)
{
d++;
j++;
}
else
{
if (board[a][j] == you)
{
h = 1;
}
else if (board[a][j] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[a][j] == ' ')
{
h = 2;
}
}
calesum(&sum, n, m, t, k, d, h);
break;
}
//xie1
while (1)
{
int n = 0;
int m = 0;
int t = 0;
int i = a - 1;
int j = b - 1;
if (i >= 1 && j >= 1)
{
if (board[i][j] == you)
{
while (i >= 1 && j >= 1)
{
if (board[i][j] == you)
{
n++;
i--;
j--;
}
else
{
if (board[i][j] == my)
{
t = 1;
}
else if (board[i][j] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[i][j] == my)
{
while (i >= 1 && j >= 1)
{
if (board[i][j] == my)
{
m++;
i--;
j--;
}
else
{
if (board[i][j] == you)
{
t = 1;
}
else if (board[i][j] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[i][j] == ' ')
{
t = 2;
}
}
int k = 0;
int d = 0;
int h = 0;
i = a + 1;
j = b + 1;
if (i < x && j < y)
{
if (board[i][j] == you)
{
while (i < x && j < y)
{
if (board[i][j] == you)
{
k++;
i++;
j++;
}
else
{
if (board[i][j] == my)
{
h = 1;
}
else if (board[i][j] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[i][j] == my)
{
while (i < x && j < y)
{
if (board[i][j] == my)
{
d++;
i++;
j++;
}
else
{
if (board[i][j] == you)
{
h = 1;
}
else if (board[i][j] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[i][j] == ' ')
{
h = 2;
}
}
calesum(&sum, n, m, t, k, d, h);
break;
}
//xie2
while (1)
{
int n = 0;
int m = 0;
int t = 0;
int i = a - 1;
int j = b + 1;
if (i >= 1 && j < y)
{
if (board[i][j] == you)
{
while (i >= 1 && j < y)
{
if (board[i][j] == you)
{
n++;
i--;
j++;
}
else
{
if (board[i][j] == my)
{
t = 1;
}
else if (board[i][j] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[i][j] == my)
{
while (i >= 1 && j < y)
{
if (board[i][j] == my)
{
m++;
i--;
j++;
}
else
{
if (board[i][j] == you)
{
t = 1;
}
else if (board[i][j] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[i][j] == ' ')
{
t = 2;
}
}
int k = 0;
int d = 0;
int h = 0;
i = a + 1;
j = b - 1;
if (i < x && j >= 1)
{
if (board[i][j] == you)
{
while (i < x && j >= 1)
{
if (board[i][j] == you)
{
k++;
i++;
j--;
}
else
{
if (board[i][j] == my)
{
h = 1;
}
else if (board[i][j] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[i][j] == my)
{
while (i < x && j >= 1)
{
if (board[i][j] == my)
{
d++;
i++;
j--;
}
else
{
if (board[i][j] == you)
{
h = 1;
}
else if (board[i][j] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[i][j] == ' ')
{
h = 2;
}
}
calesum(&sum, n, m, t, k, d, h);
break;
}
return sum;
}
这个函数用来判断每个空位置周围的棋子分布状况并返回相应的权重值,这个也是人机对战实现部分中最核心的一个函数。和之前的judge函数类似,我们分别用循环对四个方向进行判断,所以在这里我就只讲解一个方向的代码。
while (1)
{
int n = 0;//you棋子的数量
int m = 0;//my棋子的数量
int t = 0;//标记结束时的状态,即对方的棋子或空位置或墙
int i = a - 1;
if (i >= 1)
{
if (board[i][b] == you)
{
while (i >= 1)
{
if (board[i][b] == you)
{
n++;
i--;
}
else
{
if (board[i][b] == my)
{
t = 1;
}
else if (board[i][b] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[i][b] == my)
{
while (i >= 1)
{
if (board[i][b] == my)
{
m++;
i--;
}
else
{
if (board[i][b] == you)
{
t = 1;
}
else if (board[i][b] == ' ')
{
t = 2;
}
break;
}
}
}
else if (board[i][b] == ' ')
{
t = 2;
}
}
int k = 0;
int d = 0;
int h = 0;
i = a + 1;
if (i < x)
{
if (board[i][b] == you)
{
while (i < x)
{
if (board[i][b] == you)
{
k++;
i++;
}
else
{
if (board[i][b] == my)
{
h = 1;
}
else if (board[i][b] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[i][b] == my)
{
while (i < x)
{
if (board[i][b] == my)
{
d++;
i++;
}
else
{
if (board[i][b] == you)
{
h = 1;
}
else if (board[i][b] == ' ')
{
h = 2;
}
break;
}
}
}
else if (board[i][b] == ' ')
{
h = 2;
}
}
calesum(&sum, n, m, t, k, d, h);
break;
}
这段代码是判断水平方向的棋子分布情况。我们在判断每个方向的棋子分布情况时也要分为两边来看。这个代码的第一部分即是判断水平方向的左边的棋子分布情况。n代表you棋子(对方棋子)的数量;m代表my棋子(己方棋子)的数量;t标记结束时的状态,即对方的棋子或空位置或墙(0为墙,1为对方棋子,2为空位置)。而后对水平方向的右边的棋子分布状态的判断与之类似。k,d,h也依次对应前面的n,m,t。当我们把水平方向的两边的状态都判断完成后,我们就可以调用calesum(&sum, n, m, t, k, d, h)函数来对权重进行赋值。我们在这里使用的是sum代表权重值,由于我们要计算四个方向的权重值,所以我们要传sum的地址,这样才能在calesum函数里面改变sum的值。然后当四个方向的权重值都计算完后,我们就将sum作为返回值返回。
5.具体赋值操作
calesum(double* psum, int n, int m, int t, int k, int d, int h)函数
void calesum(double* psum, int n, int m, int t, int k, int d, int h)
{
if (m + d >= 4)
{
*psum = *psum + 10000000;
}
else if (n + k >= 4)
{
*psum = *psum + 1000000;
}
else if (m + d == 3 && t == 2 && h == 2)
{
*psum = *psum + 100000;
}
else if (n + k == 3 && t == 2 && h == 2)
{
*psum = *psum + 10000;
}
else if (m + d == 3 && (t == 2 || h == 2))
{
*psum = *psum + 50;
}
else if (n + k == 3 && (t == 2 || h == 2))
{
*psum = *psum + 50;
}
else if (m + d == 2 && t == 2 && h == 2)
{
*psum = *psum + 150;
}
else if (n + k == 2 && t == 2 && h == 2)
{
*psum = *psum + 100;
}
else if (m + d == 2 && (t == 2 || h == 2))
{
*psum = *psum + 25;
}
else if (n + k == 2 && (t == 2 || h == 2))
{
*psum = *psum + 25;
}
else if (m + d == 1 && t == 2 && h == 2)
{
*psum = *psum + 75;
}
else if (n + k == 1 && t == 2 && h == 2)
{
*psum = *psum + 50;
}
if (t == 0 || h == 0)
{
*psum = *psum - 25;
}
}
这个函数就是进行具体赋权重值的操作。我们根据不同的棋子的分布情况给予不同的权重值。如m + d >= 4,即代表这个位置附近已经有自己的棋子大于或等于4颗,即我们下在这里就一定会赢,所以我们权重值给的最大;又比如m + d == 3 && t == 2 && h == 2,代表这个位置附近已经有3颗自己的棋子,并且两边都为空,我们下在这里就能实现四颗连子,所以这种情况的权重也比较高。依次类推,我们将每种情况给与不同的权重。这里权重值的具体数字并不固定,我们可以进行一定地调整来改变人机的智慧程度,甚至可以运用概率的知识给出更加合理的值。不过其实就使用我这里给出的赋值,你想要下赢这个人机都还是有一定难度的。毕竟人可能会眼瞎,但电脑可不会。
6.人机对战成果
四、文件存储部分
既然是一个完整的工程实践项目,我们就还需要实现最后一部分–文件存储部分。文件存储部分的逻辑其实比较简单,就是在程序运行的开始后进行读取文件信息,然后在程序结束前进行写入文件信息的操作。这里需要说明一下的是,程序运行开始后指的是运行完ListInit()初始化函数之后,程序程序前指的是运行destory(&head)释放空间函数之前。其余逻辑,只要我们熟练掌握文件读写相关的库函数的使用即可实现。文件读写部分其实只需记住一句话,怎么写进去的就怎么读出来。
load(player* head, int* num)函数,实现读取文件信息操作的函数。
void load(player* head, int* num)
{
assert(head);
FILE* fp = fopen("五子棋.txt", "rb");
if (fp == NULL)
{
perror("load");
return;
}
player t = { 0 };
while (fread(&t, sizeof(player), 1, fp) == 1)
{
player* pnew = (player*)malloc(sizeof(player));
if (pnew == NULL)
{
perror("malloc");
exit(-1);
}
*pnew = t;
player* next = head->next;
head->next = pnew;
pnew->prev = head;
pnew->next = next;
next->prev = pnew;
(*num)++;
}
fclose(fp);
fp = NULL;
}
save(player* head)函数,实现写入文件信息操作的函数。
void save(player* head)
{
assert(head);
FILE* fp = fopen("五子棋.txt", "wb");
if (fp == NULL)
{
perror("save");
return;
}
player* cur = head->next;
while (cur != head)
{
fwrite(cur, sizeof(player), 1, fp);
cur = cur->next;
}
fclose(fp);
fp = NULL;
}
总结
本次五子棋项目的第四章就先到这里了,这也是这个工程实践项目的最后一章了。到这里我们的这个完整的五子棋项目就算彻底实现好了。如果我们能完成的实现这个项目,我相信收获一定会很多,我们的C语言水平也一定能提高一个档次。
如需源码,可在我的gitee上找到,下面是链接。
五子棋源码
如对您有所帮助,可以来个三连,感谢大家的支持。
每文推荐
司南–奔赴
陈奕迅—不要说话
林俊杰–茉莉雨
学技术学累了时可以听歌放松一下。