1、game.h
game.h:自定义头文件,用于:
- 库函数头文件的包含
- 符号与结构的声明
- 函数的定义
//防止头文件被重复包含 #pragma once //头文件的包含 #include<stdio.h> #include<stdlib.h> #include<time.h> //符号的定义:使棋盘的大小可以跟着row和col的改变而改变 #define ROW 5 #define COL 5 //函数的声明 //棋盘初始化 void BoardInit(char arr[ROW][COL], int row, int col); //打印棋盘 void BoardPrint(char arr[ROW][COL], int row, int col); //玩家下棋 void PlayerMove(char arr[ROW][COL], int row, int col); //电脑下棋 void ComputerMove(char arr[ROW][COL], int row, int col); //判断输赢 char IsWin(char arr[ROW][COL], int row, int col); //判断棋盘是否满了 int IsFull(char board[ROW][COL], int row, int col);
2、test.c
test.c:用于游戏逻辑的测试
#define _CRT_SECURE_NO_WARNINGS 1 //自定义头文件的包含 #include"game.h" void menu() { printf("================================\n"); printf("========= 1. play ==========\n"); printf("========= 0. exit ==========\n"); printf("================================\n"); } //游戏逻辑的实现 void game() { //定义一个二维数组来存储下棋的数据 char arr[ROW][COL] = { 0 }; //棋盘初始化 BoardInit(arr, ROW, COL); //打印棋盘 BoardPrint(arr, ROW, COL); char ch = 0; while (1) { //玩家下棋 PlayerMove(arr, ROW, COL); //打印棋盘 BoardPrint(arr, ROW, COL); //判断输赢 ch = IsWin(arr, ROW, COL); if (ch != 'C') break; //电脑下棋 ComputerMove(arr, ROW, COL); //打印棋盘 BoardPrint(arr, ROW, COL); //判断输赢 ch = IsWin(arr, ROW, COL); if (ch != 'C') break; } if (ch == '*') printf("直接拿下!\n"); else if (ch == '#') printf("你竟然打不过人机!\n"); else printf("平局,得加油啊!\n"); } int main() { int input = 0; //设置随机数种子 srand((unsigned int)time(NULL)); do { //菜单 menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: //玩游戏 game(); break; case 0: printf("退出游戏\n"); break; default: printf("输入错误,请重新输入!\n"); break; } } while (input); return 0; }
3、game.c
game.c:游戏功能的实现
#define _CRT_SECURE_NO_WARNINGS 1 //自定义头文件的包含 #include"game.h" //函数的定义 //棋盘初始化 void BoardInit(char arr[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { arr[i][j] = ' '; } } } //打印棋盘 void BoardPrint(char arr[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { //打印分割竖向分割 for (j = 0; j < col; j++) { printf(" %c ", arr[i][j]); if (j < col - 1) printf("|"); } //一行完毕之后打印分隔符 printf("\n"); //打印横向分割 if (i < row - 1) //最后一行不打印横线分隔符 { for (j = 0; j < col; j++) { printf("---"); if (j < col - 1) printf("|"); } } //一行完毕之后打印分隔符 printf("\n"); } } //玩家下棋 void PlayerMove(char arr[ROW][COL], int row, int col) { //获取玩家坐标 int x = 0; int y = 0; printf("玩家下棋\n"); while (1) { printf("请输入坐标:>"); scanf("%d %d", &x, &y); //判断坐标合法性 if ((x >= 1 && x <= row) && (y >= 1 && y <= col)) { //把玩家坐标对应数组下标 x -= 1; y -= 1; //判断坐标是否被占用 if (arr[x][y] == ' ') { arr[x][y] = '*'; //假设玩家为*号 break; } else { printf("该坐标已被占用\n"); } } else { printf("坐标非法\n"); } } } //电脑下棋 void ComputerMove(char arr[ROW][COL], int row, int col) { printf("电脑下棋\n"); while (1) { //在主函数生成种子srand //随机生成范围内的坐标 int x = rand() % row; int y = rand() % col; //判断坐标是否被占用 if (arr[x][y] == ' ') { arr[x][y] = '#'; //假设电脑为#号 break; } } } //判断输赢 char IsWin(char board[ROW][COL], int row, int col) { /* * 约定返回*代表玩家赢 * 返回#代表电脑赢 * 返回D代表平局 * 返回C代表继续 */ int i = 0; int j = 0; //判断行 for (i = 0; i < row; i++) { int count = 0; //标记相同棋子的个数 for (j = 0; j < col - 1; j++) { if (board[i][j] == board[i][j + 1] && board[i][j] != ' ') count++; } if (count == col - 1) //一次判断有两个棋子 return board[i][j]; } //判断列 for (i = 0; i < col; i++) { int count = 0; for (j = 0; j < row - 1; j++) { if (board[j][i] == board[j + 1][i] && board[j][i] != ' ') { count++; } } if (count == row - 1) return board[j][i]; } //判断两条斜边 //第一条 int count = 0; for (i = 0, j = 0; i < row - 1 && j < col - 1; i++, j++) { if (board[i][j] == board[i + 1][j + 1] && board[i][j] != ' ') count++; } if (count == row - 1) return board[i][j]; //第二条 count = 0; //把count重新置为0(易错) //注意:这里i+1,j-1,所以i小于row-1,j>0,而不是i<row,j>=0(易错) for (i = 0, j = col - 1; i < row - 1 && j > 0; i++, j--) { if (board[i][j] == board[i + 1][j - 1] && board[i][j] != ' ') count++; } if (count == row - 1) return board[i][j]; //判断棋盘是否满了 if (IsFull(board, row, col)) { return 'D'; } //如果上述情况都没有返回,游戏继续 return 'C'; } //判断棋盘是否满了 if (IsFull(board, row, col)) { return 'D'; } //如果上述情况都没有返回,游戏继续 return 'C'; } //判断棋盘是否满了 int IsFull(char board[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') return 0; //有空格就返回0 } } return 1; }
4、游戏功能详解
(1)、棋盘初始化
void BoardInit(char arr[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { arr[i][j] = ' '; } } }
(2)、棋盘的打印
void BoardPrint(char arr[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { //打印分割竖向分割 for (j = 0; j < col; j++) { printf(" %c ", arr[i][j]); if (j < col - 1) printf("|"); } //一行完毕之后打印分隔符 printf("\n"); //打印横向分割 if (i < row - 1) //最后一行不打印横线分隔符 { for (j = 0; j < col; j++) { printf("---"); if (j < col - 1) printf("|"); } } //一行完毕之后打印分隔符 printf("\n"); } }
(3)、玩家下棋
void PlayerMove(char arr[ROW][COL], int row, int col) { //获取玩家坐标 int x = 0; int y = 0; printf("玩家下棋\n"); while (1) { printf("请输入坐标:>"); scanf("%d %d", &x, &y); //判断坐标合法性 if ((x >= 1 && x <= row) && (y >= 1 && y <= col)) { //把玩家坐标对应数组下标 x -= 1; y -= 1; //判断坐标是否被占用 if (arr[x][y] == ' ') { arr[x][y] = '*'; //假设玩家为*号 break; } else { printf("该坐标已被占用\n"); } } else { printf("坐标非法\n"); } } }
(4)、电脑下棋
void ComputerMove(char arr[ROW][COL], int row, int col) { printf("电脑下棋\n"); while (1) { //在主函数生成种子srand //随机生成范围内的坐标 int x = rand() % row; int y = rand() % col; //判断坐标是否被占用 if (arr[x][y] == ' ') { arr[x][y] = '#'; //假设电脑为#号 break; } } }
(5)、判断游戏输赢
char IsWin(char board[ROW][COL], int row, int col) { /* * 约定返回*代表玩家赢 * 返回#代表电脑赢 * 返回D代表平局 * 返回C代表继续 */ int i = 0; int j = 0; //判断行 for (i = 0; i < row; i++) { int count = 0; //标记相同棋子的个数 for (j = 0; j < col - 1; j++) { if (board[i][j] == board[i][j + 1] && board[i][j] != ' ') count++; } if (count == col - 1) //一次判断有两个棋子 return board[i][j]; } //判断列 for (i = 0; i < col; i++) { int count = 0; for (j = 0; j < row - 1; j++) { if (board[j][i] == board[j + 1][i] && board[j][i] != ' ') { count++; } } if (count == row - 1) return board[j][i]; } //判断两条斜边 //第一条 int count = 0; for (i = 0, j = 0; i < row - 1 && j < col - 1; i++, j++) { if (board[i][j] == board[i + 1][j + 1] && board[i][j] != ' ') count++; } if (count == row - 1) return board[i][j]; //第二条 count = 0; //把count重新置为0(易错) //注意:这里i+1,j-1,所以i小于row-1,j>0,而不是i<row,j>=0(易错) for (i = 0, j = col - 1; i < row - 1 && j > 0; i++, j--) { if (board[i][j] == board[i + 1][j - 1] && board[i][j] != ' ') count++; } if (count == row - 1) return board[i][j]; //判断棋盘是否满了 if (IsFull(board, row, col)) { return 'D'; } //如果上述情况都没有返回,游戏继续 return 'C'; } //判断棋盘是否满了 if (IsFull(board, row, col)) { return 'D'; } //如果上述情况都没有返回,游戏继续 return 'C'; }
(6)、判断棋盘是否满了
int IsFull(char board[ROW][COL], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') return 0; //有空格就返回0 } } return 1; }
5、AI算法下棋
大家可以发现,在上面的代码中,电脑下棋是非常笨拙的,因为电脑产生的坐标是随机的,即不会拦截玩家,也不会判断自己,所以这里我们可以设计一个小小的算法来让电脑变得聪明起来,让它拥有拦截和判断功能。具体思路和代码如下:
(1)、判断自己是否会赢(CheckComputer)
//电脑检查自己是否会赢 //约定如果在函数内部成功判断就返回1 //判断失败则返回0 int CheckComputer(char board[ROW][COL], int row, int col) { int i = 0; int j = 0; //判断每一行是否有两个相连的棋子,如果有,且第三个棋格为空,则落棋 for (i = 0; i < row; i++) { if (board[i][0] == board[i][1] && board[i][0] == '#' && board[i][2] == ' ') { board[i][2] = '#'; return 1; //成功判断,返回1 } if (board[i][0] == board[i][2] && board[i][0] == '#' && board[i][1] == ' ') { board[i][1] = '#'; return 1; } if (board[i][1] == board[i][2] && board[i][1] == '#' && board[i][0] == ' ') { board[i][0] = '#'; return 1; } } //判断每一列是否有两个相连的棋子,如果有,且第三个棋格为空,则落棋 for (j = 0; j < col; j++) { if (board[0][j] == board[1][j] && board[0][j] == '#' && board[2][j] == ' ') { board[2][j] = '#'; return 1; } if (board[0][j] == board[2][j] && board[0][j] == '#' && board[1][j] == ' ') { board[1][j] = '#'; return 1; }if (board[1][j] == board[2][j] && board[1][j] == '#' && board[0][j] == ' ') { board[0][j] = '#'; return 1; } } //判断两条对角线是否有两个相连的棋子,如果有,且第三个棋格为空,则落棋 { //第一条 if (board[0][0] == board[1][1] && board[0][0] == '#' && board[2][2] == ' ') { board[2][2] = '#'; return 1; } if (board[0][0] == board[2][2] && board[0][0] == '#' && board[1][1] == ' ') { board[1][1] = '#'; return 1; } if (board[1][1] == board[2][2] && board[1][1] == '#' && board[0][0] == ' ') { board[0][0] = '#'; return 1; } //第二条 if (board[0][2] == board[1][1] && board[0][2] == '#' && board[2][0] == ' ') { board[2][0] = '#'; return 1; } if (board[0][2] == board[2][0] && board[0][2] == '#' && board[1][1] == ' ') { board[1][1] = '#'; return 1; } if (board[1][1] == board[2][0] && board[1][1] == '#' && board[0][2] == ' ') { board[0][2] = '#'; return 1; } //如果上面都没返回,说明不符合赢的条件,返回0 return 0; } }
(2)、对玩家进行拦截(CheckPlayer)
//电脑检查玩家是否会赢(逻辑和CheckComputer完全相同) //约定成功拦截返回1 //无需拦截或者拦截不了返回0 int CheckPlayer(char board[ROW][COL], int row, int col) { int i = 0; int j = 0; //判断每一行是否有两个相连的棋子,如果有,且第三个棋格为空,则拦截 for (i = 0; i < row; i++) { if (board[i][0] == board[i][1] && board[i][0] == '*' && board[i][2] == ' ') { board[i][2] = '#'; return 1; //成功拦截,返回1 } if (board[i][0] == board[i][2] && board[i][0] == '*' && board[i][1] == ' ') { board[i][1] = '#'; return 1; } if (board[i][1] == board[i][2] && board[i][1] == '*' && board[i][0] == ' ') { board[i][0] = '#'; return 1; } } //判断每一列是否有两个相连的棋子,如果有,且第三个棋格为空,则拦截 for (j = 0; j < col; j++) { if (board[0][j] == board[1][j] && board[0][j] == '*' && board[2][j] == ' ') { board[2][j] = '#'; return 1; } if (board[0][j] == board[2][j] && board[0][j] == '*' && board[1][j] == ' ') { board[1][j] = '#'; return 1; }if (board[1][j] == board[2][j] && board[1][j] == '*' && board[0][j] == ' ') { board[0][j] = '#'; return 1; } } //判断两条对角线是否有两个相连的棋子,如果有,且第三个棋格为空,则拦截 { //第一条 if (board[0][0] == board[1][1] && board[0][0] == '*' && board[2][2] == ' ') { board[2][2] = '#'; return 1; } if (board[0][0] == board[2][2] && board[0][0] == '*' && board[1][1] == ' ') { board[1][1] = '#'; return 1; } if (board[1][1] == board[2][2] && board[1][1] == '*' && board[0][0] == ' ') { board[0][0] = '#'; return 1; } //第二条 if (board[0][2] == board[1][1] && board[0][2] == '*' && board[2][0] == ' ') { board[2][0] = '#'; return 1; } if (board[0][2] == board[2][0] && board[0][2] == '*' && board[1][1] == ' ') { board[1][1] = '#'; return 1; } if (board[1][1] == board[2][0] && board[1][1] == '*' && board[0][2] == ' ') { board[0][2] = '#'; return 1; } //如果上面都没返回,说明不符合拦截的条件,返回0 return 0; } }
注意:我这里采用的判断方法是枚举,由于五子棋的枚举情况比较复杂,而我目前也没想到更好的算法来进行判断,所以这里我只写了三子棋的AI判断代码,如果有大佬有更好的算法或者判断思路,欢迎在评论区留言。
(3)、加入AI算法后game.c的改动
上面我们已经完成了CheckComputer和CheckPlayer这两个函数的定义,现在我们只需要把这两个函数实现放入到game.c中并且在在电脑下棋(ComputerMove)中调用这两个函数即可。
//电脑下棋 void ComputerMove(char board[ROW][COL], int row, int col) { printf("电脑下棋\n"); //定义两个标识符变量来接收两个判断函数的返回值 int flag1 = 0; int flag2 = 0; flag1 = CheckComputer(board, row, col); //如果flag1 == 0 时才进行flag2 的判断,避免当二者都为1时下两步棋(易错) if (flag1 == 0) { flag2 = CheckPlayer(board, row, col); } if (flag1 == 0 && flag2 == 0) //当CheckComputer和CheckPlayer都没落棋时,就随机下 { while (1) { //在主函数生成种子srand //随机生成范围内的坐标 int x = rand() % row; int y = rand() % col; //判断坐标是否被占用 if (board[x][y] == ' ') { board[x][y] = '#'; //假设电脑为#号 break; } } } }
注意:这里的AI算法只适用于三子棋,如果要使用的话需要把头文件中的ROW和COL改为3,同时不要忘记在头文件中对两个判断函数进行声明。