目录
- 一、游戏设计的整体思路
- 二、各个步骤的代码实现
- 1. 菜单及循环选择的实现
- 2. 棋盘的初始化和显示
- 3. 轮流下棋及结果判断实现
- 4. 结果判断实现
- 三、所有代码
- 四、总结
一、游戏设计的整体思路
(1)提供一个菜单让玩家选择人机对战、玩家对战或者退出游戏,且一局结束以后还可以继续选择直到退出。
(2)使用一个 3*3 二维数组来表示一个棋盘,存放双方的落子情况并进行显示。且显示时使用一些符号进行分隔让棋盘更加合理。
(3)初始棋盘应为空,然后提示先手方下棋,要对先手方落子位置进行判断(是否合法,是否该位置已经落子),最后先手方下棋完毕后应检查棋局状态(获胜、平局或继续);同理后手方下棋。
(4)若平局或者任意一方获胜则结束对局,显示结果。
本次代码使用多文件形式,一共有三个文件,一个头文件包含各种声明,一个 .c 文件进行测试,一个 .c 文件实现函数功能。
二、各个步骤的代码实现
1. 菜单及循环选择的实现
(1)主函数代码的实现
// 头文件
#include <stdio.h>
#include "Tic_tac_toe.h"
int main()
{
// 所需变量
int select = 0;
// 选择
do
{
// 菜单
menu();
// 选择
scanf("%d", &select);
// 判断
switch (select)
{
case 1 : // 人机对战
PVE();
break;
case 2 :
PVP(); // 玩家对战
break;
case 0 :
printf("游戏结束!\n");
break;
}
} while (select);
return 0;
}
(2)头文件中的声明
// 菜单
void menu();
(3)函数实现
// 菜单
void menu()
{
printf("**************************************\n");
printf("********** 1. PVE **********\n");
printf("********** 2. PVP **********\n");
printf("********** 0. exit **********\n");
printf("**************************************\n");
}
(4)运行效果如下
2. 棋盘的初始化和显示
棋盘的初始化和显示分别设计为两个函数 —— InitBoard()、PrintBoard()。首先,需要在主函数创建棋盘,且大小使用符号常量,方便替换。然后把棋盘的信息传入人机对战和玩家对战函数中,并在头文件和实现文件中进行相应的声明和定义。
(1)主函数创建棋盘
int main()
{
// 所需变量
int select = 0;
char board[ROW][COL] = { 0 }; // 创建棋盘
//...
}
(2)头文件进行相应声明
// 初始化棋盘
void InitBoard(char board[][COL], int row, int col);
// 打印棋盘
void PrintBoard(char board[][COL], int row, int col);
// 人机对战模式
void PVE(char board[][COL], int row, int col);
// 玩家对战模式
void PVP(char board[][COL], int row, int col);
(3)实现文件进行定义
// 初始化棋盘
void InitBoard(char board[][COL], int row, int col)
{
int i;
for (i = 0; i < row; ++i)
{
int j;
for (j = 0; j < col; ++j)
board[i][j] = ' ';
}
}
// 打印棋盘
void PrintBoard(char board[][COL], int row, int col)
{
// 打印棋盘时,需要适当添加符号分隔
int i;
for (i = 0; i < row; ++i)
{
int j;
for (j = 0; j < col; ++j)
printf(" ---");
// 下一行
printf("\n");
for (j = 0; j < col; ++j)
printf("| %c ", board[i][j]);
// 补齐改行分隔
printf("|");
// 下一行
printf("\n");
}
// 补齐最后一行分隔
for (i = 0; i < col; ++i)
printf(" ---");
printf("\n");
}
// 人机对战模式
void PVE(char board[][COL], int row, int col)
{
printf("\n人机对战:\n");
// 初始化棋盘
InitBoard(board, row, col);
// 显示棋盘
PrintBoard(board, row, col);
}
// 玩家对战模式
void PVP(char board[][COL], int row, int col)
{
printf("\n玩家对战:\n");
// 初始化棋盘
InitBoard(board, row, col);
// 显示棋盘
PrintBoard(board, row, col);
}
运行效果如下:
3. 轮流下棋及结果判断实现
这里假设人机对战时,玩家使用符号 ‘#’,机器人使用符号 ‘*’;玩家对战时,一号玩家使用符号 ‘#’,二号玩家使用符号 ‘*’。玩家下棋和机器人下棋分别使用两个函数 —— PMove() 和 EMove()。PMove() 函数需要使用一个额外的变量判断是 1 号玩家下棋,还是 2 号玩家下棋。
判断结果函数 is_win() 需要判断每行是否相同、每列是否相同,倘若其中有一个实现,则有一方获胜,倘若没有则需要使用函数 is_full() 判断棋盘是否已满,若满则平局,否则继续。is_full() 函数也可以直接实现在 is_win() 函数内部。
(1)头文件函数声明
// 玩家下棋
void PMove(char board[][COL], int row, int col, int who);
// 机器人下棋
void EMove(char board[][COL], int row, int col);
// 判断是否获胜
void is_win(char board[][COL], int row, int col);
(2)实现文件函数定义
// 玩家下棋
void PMove(char board[][COL], int row, int col, char who)
{
// 所需变量
int x, y;
do
{
// 输入坐标
printf("玩家下棋(输入坐标中间用空格隔开):\n");
scanf("%d %d", &x, &y);
// 判断合法性
if ((x < 0 || x > row || y < 0 || y > col) || board[x - 1][y - 1] != ' ')
{
printf("坐标非法,请重新输入!\n");
}
else
{
board[x - 1][y - 1] = who;
break;
}
} while (1);
}
// 机器人下棋
void EMove(char board[][COL], int row, int col)
{
// 机器人是随机下棋的,这里就要获取随机数了
// 需要包含头文件 stdlib.h 和 time.h
// 设置随机数种子
srand((unsigned)time(0));
printf("电脑下棋:\n");
while (1)
{
int x = rand() % row; // 0 - row - 1
int y = rand() % col; // 0 - col - 1
if (board[x][y] == ' ')
{
board[x][y] = '*';
break;
}
}
}
// 判断是否获胜
char is_win(char board[][COL], int row, int col)
{
// 判断每行
int r = 1;
int i;
for (i = 0; i < row; ++i)
{
// 重置判断符号
r = 1;
// 若首字符为空则下一行
if (board[i][0] == ' ')
continue;
// 判断改行
int j;
for (j = 1; j < col; ++j)
{
r *= (board[i][j - 1] == board[i][j]);
// 判断
if (!r)
break;
}
if (r)
return board[i][0];
}
// 判断每列
int c = 1;
int j;
for (j = 0; j < col; ++j)
{
// 重置判断符号
c = 1;
// 首字符为空则下一列
if (board[0][j] == ' ')
continue;
// 判断该列
int i;
for (i = 1; i < row; ++i)
{
c *= (board[i - 1][j] == board[i][j]);
// 判断
if (!c)
break;
}
if (c)
return board[0][j];
}
// 判断对角线
int d = 1;
if (board[0][0] != ' ')
{
for (i = 1; i < row; ++i)
{
d *= (board[i - 1][i - 1] == board[i][i]);
// 判断
if (!d)
break;
}
if (d)
return board[0][0];
}
if (board[0][col] != ' ')
{
d = 1;
for (i = 1; i < row; ++i)
{
d *= (board[i - 1][col - i + 1] == board[i][col - i]);
// 判断
if (!d)
break;
}
if (d)
return board[0][col];
}
// 判断棋盘是否已满
int full = 1;
for (i = 0; i < row; ++i)
{
for (j = 0; j < col; ++j)
{
if (board[i][j] == ' ')
{
// 未满
return 'C';
}
}
}
// 已满
return 'D';
}
上述代码中 is_win() 函数中所使用的是通用判断方法,即使改变了符号常量 ROW 和 COL 的值也可以是判断。(如果实在理解不了,可以只当 3*3 的特殊情况来实现判断,当然,也可能是我的代码写的不好)。
(3)程序运行结果:
这里值展示人机对战的三种结果:
玩家获胜:
电脑获胜:
平均:
4. 结果判断实现
该代码直接放在了函数 PMove() 和函数 EMove() 中,也可以单独写一个函数。
(1)实现文件函数定义
// 人机对战模式
void PVE(char board[][COL], int row, int col)
{
printf("\n人机对战:\n");
// 初始化棋盘
InitBoard(board, row, col);
// 显示棋盘
PrintBoard(board, row, col);
// 开始下棋
int ret = 0;
do
{
// 玩家下棋
PMove(board, row, col, '#');
// 显示棋盘
PrintBoard(board, row, col);
// 判断
ret = is_win(board, row, col);
if (ret != 'C')
break;
// 机器人下棋
EMove(board, row, col);
// 显示棋盘
PrintBoard(board, row, col);
// 判断
ret = is_win(board, row, col);
if (ret != 'C')
break;
} while (1);
// 判断最终结果
if (ret == '#')
printf("玩家获胜!\n");
else if (ret == '*')
printf("电脑获胜!\n");
else
printf("平局!\n");
}
// 玩家对战模式
void PVP(char board[][COL], int row, int col)
{
printf("\n玩家对战:\n");
// 初始化棋盘
InitBoard(board, row, col);
// 显示棋盘
PrintBoard(board, row, col);
// 开始下棋
int ret = 0;
do
{
// 玩家 1 号下棋
PMove(board, row, col, '#');
// 显示棋盘
PrintBoard(board, col, row);
// 判断
ret = is_win(board, row, col);
if (ret != 'C')
break;
// 玩家 2 号下棋
PMove(board, row, col, '*');
// 显示棋盘
PrintBoard(board, col, row);
// 判断
ret = is_win(board, row, col);
if (ret != 'C')
break;
} while (1);
// 判断最终结果
if (ret == '#')
printf("玩家 1 号获胜!\n");
else if (ret == '*')
printf("玩家 2 号获胜!\n");
else
printf("平局!\n");
}
(2)演示结果
在上一次演示结果中已经体现。
三、所有代码
分三个文件,分别是头文件 Tic_tac_toe.h,测试文件 test.c,和函数实现文件 Tic_tac_toe.c。
(1)Tic_tac_toe.h
// 常量声明
#define ROW 3
#define COL 3
// 函数声明
// 菜单
void menu();
// 初始化棋盘
void InitBoard(char board[][COL], int row, int col);
// 打印棋盘
void PrintBoard(char board[][COL], int row, int col);
// 玩家下棋
void PMove(char board[][COL], int row, int col, char who);
// 机器人下棋
void EMove(char board[][COL], int row, int col);
// 判断是否获胜
char is_win(char board[][COL], int row, int col);
// 人机对战模式
void PVE(char board[][COL], int row, int col);
// 玩家对战模式
void PVP(char board[][COL], int row, int col);
(2)test.c
// 头文件
#include <stdio.h>
#include "Tic_tac_toe.h"
int main()
{
// 所需变量
int select = 0;
char board[ROW][COL] = { 0 };
// 选择
do
{
// 菜单
menu();
// 选择
scanf("%d", &select);
// 判断
switch (select)
{
case 1 : // 人机对战
PVE(board, ROW, COL);
break;
case 2 :
PVP(board, ROW, COL); // 玩家对战
break;
case 0 :
printf("游戏结束!\n");
break;
default :
printf("选择错误请重新选择!\n");
break;
}
} while (select);
return 0;
}
(3)Tic_tac_toe.c
// 头文件
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "Tic_tac_toe.h"
// 函数定义
// 菜单
void menu()
{
printf("**************************************\n");
printf("********** 1. PVE **********\n");
printf("********** 2. PVP **********\n");
printf("********** 0. exit **********\n");
printf("**************************************\n");
}
// 初始化棋盘
void InitBoard(char board[][COL], int row, int col)
{
int i;
for (i = 0; i < row; ++i)
{
int j;
for (j = 0; j < col; ++j)
board[i][j] = ' ';
}
}
// 打印棋盘
void PrintBoard(char board[][COL], int row, int col)
{
// 打印棋盘时,需要适当添加符号分隔
int i;
for (i = 0; i < row; ++i)
{
int j;
for (j = 0; j < col; ++j)
printf(" ---");
// 下一行
printf("\n");
for (j = 0; j < col; ++j)
printf("| %c ", board[i][j]);
// 补齐改行分隔
printf("|");
// 下一行
printf("\n");
}
// 补齐最后一行分隔
for (i = 0; i < col; ++i)
printf(" ---");
printf("\n");
}
// 玩家下棋
void PMove(char board[][COL], int row, int col, char who)
{
// 所需变量
int x, y;
do
{
// 输入坐标
printf("玩家下棋(输入坐标中间用空格隔开):\n");
scanf("%d %d", &x, &y);
// 判断合法性
if ((x < 0 || x > row || y < 0 || y > col) || board[x - 1][y - 1] != ' ')
{
printf("坐标非法,请重新输入!\n");
}
else
{
board[x - 1][y - 1] = who;
break;
}
} while (1);
}
// 机器人下棋
void EMove(char board[][COL], int row, int col)
{
// 机器人是随机下棋的,这里就要获取随机数了
// 需要包含头文件 stdlib.h 和 time.h
// 设置随机数种子
srand((unsigned)time(0));
printf("电脑下棋:\n");
while (1)
{
int x = rand() % row; // 0 - row - 1
int y = rand() % col; // 0 - col - 1
if (board[x][y] == ' ')
{
board[x][y] = '*';
break;
}
}
}
// 判断是否获胜
char is_win(char board[][COL], int row, int col)
{
// 判断每行
int r = 1;
int i;
for (i = 0; i < row; ++i)
{
// 重置判断符号
r = 1;
// 若首字符为空则下一行
if (board[i][0] == ' ')
continue;
// 判断改行
int j;
for (j = 1; j < col; ++j)
{
r *= (board[i][j - 1] == board[i][j]);
// 判断
if (!r)
break;
}
if (r)
return board[i][0];
}
// 判断每列
int c = 1;
int j;
for (j = 0; j < col; ++j)
{
// 重置判断符号
c = 1;
// 首字符为空则下一列
if (board[0][j] == ' ')
continue;
// 判断该列
int i;
for (i = 1; i < row; ++i)
{
c *= (board[i - 1][j] == board[i][j]);
// 判断
if (!c)
break;
}
if (c)
return board[0][j];
}
// 判断对角线
int d = 1;
if (board[0][0] != ' ')
{
for (i = 1; i < row; ++i)
{
d *= (board[i - 1][i - 1] == board[i][i]);
// 判断
if (!d)
break;
}
if (d)
return board[0][0];
}
if (board[0][col] != ' ')
{
d = 1;
for (i = 1; i < row; ++i)
{
d *= (board[i - 1][col - i + 1] == board[i][col - i]);
// 判断
if (!d)
break;
}
if (d)
return board[0][col];
}
// 判断棋盘是否已满
int full = 1;
for (i = 0; i < row; ++i)
{
for (j = 0; j < col; ++j)
{
if (board[i][j] == ' ')
{
// 未满
return 'C';
}
}
}
// 已满
return 'D';
}
// 人机对战模式
void PVE(char board[][COL], int row, int col)
{
printf("\n人机对战:\n");
// 初始化棋盘
InitBoard(board, row, col);
// 显示棋盘
PrintBoard(board, row, col);
// 开始下棋
int ret = 0;
do
{
// 玩家下棋
PMove(board, row, col, '#');
// 显示棋盘
PrintBoard(board, row, col);
// 判断
ret = is_win(board, row, col);
if (ret != 'C')
break;
// 机器人下棋
EMove(board, row, col);
// 显示棋盘
PrintBoard(board, row, col);
// 判断
ret = is_win(board, row, col);
if (ret != 'C')
break;
} while (1);
// 判断最终结果
if (ret == '#')
printf("玩家获胜!\n");
else if (ret == '*')
printf("电脑获胜!\n");
else
printf("平局!\n");
}
// 玩家对战模式
void PVP(char board[][COL], int row, int col)
{
printf("\n玩家对战:\n");
// 初始化棋盘
InitBoard(board, row, col);
// 显示棋盘
PrintBoard(board, row, col);
// 开始下棋
int ret = 0;
do
{
// 玩家 1 号下棋
PMove(board, row, col, '#');
// 显示棋盘
PrintBoard(board, col, row);
// 判断
ret = is_win(board, row, col);
if (ret != 'C')
break;
// 玩家 2 号下棋
PMove(board, row, col, '*');
// 显示棋盘
PrintBoard(board, col, row);
// 判断
ret = is_win(board, row, col);
if (ret != 'C')
break;
} while (1);
// 判断最终结果
if (ret == '#')
printf("玩家 1 号获胜!\n");
else if (ret == '*')
printf("玩家 2 号获胜!\n");
else
printf("平局!\n");
}
四、总结
本次三子棋的代码编写总体来说还可以,基本上都实现了上述功能,其中的某些代码本人均是根据符号常量来编写的,也就是即使符号常量改变,本代码依旧可以实现。当然,本人水平有限,有的地方看不懂可能是本人代码写的太烂,这里先道个歉。
当然代码还有很多可以提升的地方,比如优化棋盘,可以使用鼠标来下棋,提升电脑的水平等。