- 模块化
- game.h - 定义符号和函数
- game.c - 定义函数
- test.c - 测试游戏
- 函数功能具体化
- menu() - 打印菜单
- game() - 搭建游戏框架
- InitBoard() - 初始化棋盘
- DisplayBoard() - 打印棋盘 - 难点
- PlayerMove() - 玩家下棋
- ComputerMove() - 电脑下棋
- IsWin - 判断游戏状态 - 难点
- 头文件game.h - 与游戏有关的函数均在头文件中声明
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//符号的定义 - 这里用宏定义数组大小,方便修改参数,不把代码写死
#define ROW 3
#define COL 3
//函数的声明
void InitBoard(char board[ROW][COL], int row, int col); //初始化棋盘
void DisplayBoard(char board[ROW][COL], int row, int col); //打印棋盘
void PlayerMove(char board[][COL], int row, int col); //玩家下棋
void ComputerMove(char board[][COL], int row, int col); //电脑下棋
char IsWin(char board[ROW][COL], int row, int col); //判断游戏状态
//用返回值更容易判断游戏状态
// 1. 玩家赢 - '*'
// 2. 电脑赢 - '#')
// 3. 平局 - 'Q'
// 4. 游戏继续 - 'C'
- 源文件test.c(记得引头文件)- 游戏测试
menu():
打印游戏菜单,没什么好说的
void menu()
{
printf("**************************\n");
printf("******** 1.play ********\n");
printf("******** 0.exit ********\n");
printf("**************************\n");
}
game():
在这里构建整个游戏的具体框架,注释反映了整个游戏的逻辑
void game()
{
//存储数据 - 二维数组
char board[ROW][COL];
//初始化棋盘 - 初始化为空格
InitBoard(board, ROW, COL);
//打印一下棋盘 - 本质是打印数组的内容
DisplayBoard(board, ROW, COL);
char ret; //用于接收IsWin()函数的返回值来判断游戏状态
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL); //玩家下棋
DisplayBoard(board, ROW, COL); //每次下棋之后打印一下棋盘
ret = IsWin(board, ROW, COL); //每次出棋之后判断游戏状态
if (ret != 'C')
break;
//电脑下棋
ComputerMove(board, ROW, COL); //玩家下棋
DisplayBoard(board, ROW, COL); //每次下棋之后打印一下棋盘
ret = IsWin(board, ROW, COL); //每次出棋之后判断游戏状态
if (ret != 'C')
break; //只有ret == 'C',即游戏继续的时候才不会跳出循环
}
if (ret == '*')
printf("玩家赢了!\n");
else if (ret == '#')
printf("电脑赢了!\n");
else
printf("平局\n");
}
main():
用于测试整个游戏,搭建基本框架
int main()
{
int input = 0;
srand((unsigned int)time(NULL)); //这里先拨下种子,后续电脑随机出棋用得到
do //用do-while循环实现是否开始游戏
{
menu(); //打印菜单
printf("请选择:>\n");
scanf("%d", &input);
switch (input)
{
case 1: //选择1:>游戏开始
game(); //这里游戏框架用game()函数搭建
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input); //只有当input = 0时才会跳出循环,即退出游戏
return 0;
}
- 函数的实现
InitBoard() - 初始化棋盘
//传参的时候数组和数组大小都要传进来
void InitBoard(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++)
{
board[i][j] = ' '; //将数组初始化为空格
}
}
}
DisplayBoard() - 打印棋盘 - 难点
↑这是我们的目标棋盘
打印肯定要通过循环实现,具体代码放在下方,这里只讲部分细节
打印数组的时候两侧均有空格,即printf(“ %c ”, board[ i ][ j ])
每次打印完数组元素之后有一个|,所以再来一个printf("|")
但是,在最后一次打印的时候就没有|, 所以在这条打印前要加一个判断条件if (j < col - 1)
当执行完一次j循环后,就打印出了一行“ | | ”
所以这时候换行,继续打印---和|,与上面大体一样, 只是把第一个printf打印的内容替换为“---”
但是,最后一次打印的时候就没有这一行, 所以在这一套j循环前再加一个判断条件if (i < row - 1)
void DisplayBoard(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++)
{
printf(" %c ", board[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");
}
}
PlayerMove() - 玩家下棋 ‘*’
这一块就相对好实现了
首先让玩家输入下棋的坐标
这里有一个小细节,就是玩家输入坐标只会是 1 1,2 1这种,并不知道棋盘行号还有0,所以接下来处理的时候要记得+1 -1
然后判断坐标的合法性
如果合法,再判断坐标是否已被占用
当两条不同时满足的时候就要提示玩家重新输入,反之就可进行下一步,所以这里设计了一个while循环
void PlayerMove(char board[][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家走:>\n");
printf("请输入下棋的坐标:>\n");
scanf("%d %d", &x, &y);
//判断坐标合法性
while (1)
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//下棋
//判断坐标是否被占用
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("坐标已被占用,请重新输入\n");
}
}
else
{
printf("坐标不存在,请重新输入\n");
}
}
}
ComputerMove() - 电脑下棋 ‘#’
由于作者本是是个小白,对算法了解甚少,水平有限,只能实现电脑随即下棋
这时用随机函数生成一个坐标,这里生成的坐标一定是合法的,且均在0~2之间,直接对其操作即可
只需判断生成的坐标是否已被占用即可,所以这里用了一个简单的while循环
void ComputerMove(char board[][COL], int row, int col)
{
printf("电脑走:>\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
//这里生成的x,y是合法的
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
IsWin - 判断游戏状态 - 难点
游戏一共有三个状态:
输赢已分 - 游戏结束、平局 - 游戏结束、输赢未分 - 游戏继续
思路优化:当输赢已分时,返回值胜方输入的棋子内容,通过主函数的判断可直接打印胜负关系; 当平局时,返回值为‘Q’; 当游戏继续时,返回值为‘C’
所以现只需判断三种状态
当输赢已分时,又有三种情况:行、列、对角线
判断行时我们采用相邻比较的方法,第一行从前往后一对一对比,当没比完一次且符合条件时j++,否则跳出。当j = row-1时,意味着这一行最后一个元素已参加比较且符合条件,所以设置判断是否返回的条件语句为if (j == row - 1)。因为是一行的元素进行比较,所以数组元素引用时行号不变,只需改变列数。
此外,如果一行均为空格,也同样满足判断条件,所以还需加入一个条件board[i][0] != ' ',用来排除都是空格的情况。
判断列时思路与判断行一样,只不过同列比较时列数不变,只需改变行号
判断对角线时又有两种情况,向下的对角线和向上的对角线
对于向下的对角线,很好处理,因为它的行号和列号是一样的,所以只需一次循环,循环继续走下去的条件基本和上边一致。当k = col - 1时,意味着循环已经走完,所以这时就可以它为if语句的判断条件,来判断是否返回。
对于向上的对角线,用同样的处理思路,只是行号和列号的值有所改变,具体参见代码。
当出现平局情况时,意味着棋盘已经满了,所以这时只需要检查一遍,设置一个计数器,当检查一遍之后均不是空格,通过判断计数器的值来确定是否返回
如果上述代码都没实现,也就意味着游戏还在进行,所以最后设置返回值‘C’
char IsWin(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
int k = 0;
int l = 0;
//判断行
for (i = 0; i < row; i++)
{
j = 0;
while (j < row)
{
if (board[i][j] == board[i][j + 1] && board[i][0] != ' ')
{
j++;
if (j == row - 1)
{
return board[i][0];
}
continue;
}
else
{
break;
}
}
}
//判断列
for (i = 0; i < col; i++)
{
j = 0;
while (j < row)
{
if (board[j][i] == board[j + 1][i] && board[0][i] != ' ')
{
j++;
if (j == row - 1)
{
return board[0][i];
}
continue;
}
else
{
break;
}
}
}
//判断对角线
for (k = 0; k < col; k++)
{
if (board[k][k] == board[k + 1][k + 1] && board[0][0] != ' ')
{
continue;
}
else
{
break;
}
}
if (k == col-1)
{
return board[0][0];
}
for (l = row - 1; l > 0; l--)
{
if (board[l][row - 1 - l] == board[l - 1][row - l] && board[0][row - 1] != ' ')
{
continue;
}
else
{
break;
}
}
if (l == 0)
{
return board[0][row - 1];
}
//判断平局
int count = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] != ' ')
{
count++;
}
}
}
if (count == 9)
{
return 'Q';
}
return 'C';
}