首先我们创建一个新项目,新项目中添加两个源文件,一个用来放置该游戏所需的所有函数(game.c),另一个放置主函数来测试(test.c),所有的函数可以包含在新创建的头文件里(game.h),这样在主函数引用时只需要包含一下头文件就可以使用全部的函数了。
目录
主函数的设计
文件创建好以后,先要对主函数进行设计:
#include"game.h"
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//这里在拓展那里解释
do
{
menu();//菜单风格可以自己设计,这里1表示进入游戏,0表示退出
puts("请选择你要进行的操作:>(1/0)");
scanf("%d", &input);
switch (input)
{
case 1:
puts("进入游戏");//这里后面要换成封装成的函数
break;
case 0:
puts("游戏结束");
break;
default:
puts("操作错误,请重新选择");
break;
}
} while (input);
return 0;
}
此时主函数就设计完成了,接下来就要写一个函数来实现三子棋游戏了。
设计游戏函数
设置棋盘
棋类游戏首先需要有棋盘嘛,那我们就首先打印出一个棋盘:
棋盘的样式可以设计成这样,不过我们不能直接用printf或puts直接打印出来,否则后续就没有办法实现在棋盘上下棋的操作了。如果把棋盘设计成数组,在数组内放置' '元素,从而达到在视觉上满足要求,后续用棋子替换掉' '也能达到下子的要求。
具体实现如下:
//首先将数组中内容全部初始化为' '
void init_board(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] = ' ';
}
}
}
//打印出棋盘
void print_board(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("|");
}
puts("");//一行打印完换行
if (i < row - 1)//偶数行打印次数少一次
{
for (j = 0; j < col; j++)//打印偶数行
{
printf("---");
if (j < col - 1)
printf("|");
}
puts("");
}
}
}
这时输出我们的棋盘就如上图所示。
为方便起见,棋盘的长、宽均用宏来定义,这里以及后面的row,col代表传入的宏ROW,COL。用宏来定义后续若要改变棋盘的大小只需改变ROW与COL的值就行。例如直接将ROW和COL改为5可以得到:5 * 5的棋盘
如果在打印棋盘前不将数组中元素改为' '而是直接用0来代替,那么又会出现这样的结果:
这是因为字符0是不打印的,因此在视觉上会造成奇数行少了好几个空格。
打印好棋盘就可以在棋盘上落子了,首先实现玩家下棋。
玩家下棋
该部分需要玩家自己输入坐标(考虑到非程序员都是从1开始的,因此下的棋子坐标x和y都需要-1)。输入的坐标首先要判断是否在有效范围内,除此之外还要考虑所下位置是否已被占用,只有满足这两条件才算有效下棋。因此可以将该过程设计成死循环,只有输入正确的坐标才能进行下一步操作,具体代码实现如下:
void player_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
puts("玩家下");
printf("请选择一个坐标:");
scanf("%d %d", &x, &y);
//首先判断输入坐标是否合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//判断输入的坐标是否已被占用
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';//玩家棋子为'*',电脑棋子为'#'
break;//输入有效坐标跳出循环,完成一步下棋操作
}
else
puts("该坐标已被占用,请重新输入");
}
else
puts("坐标非法,请重新输入");
}
}
电脑下棋
紧接着是电脑下棋,电脑下其不需要对不规范下棋进行提示,直接设置成死循环,直到有效坐标输入后break跳出即可。这里我们简单使用随机数法来模拟电脑下棋。
这里需要用到rand()函数和srand()函数,需要引入头文件<stdlib.h>,同时srand()函数的参数要用到time()函数,要引入头文件<time.h>
获取随机数方法:(拓展)
//用rand()%谁就能得到0~该数-1之间的任意数 //如%100得到的数为0-99之间的任意值 int x = rand()%100;
但是使用rand()函数需要设置种子即srand()函数,srand()函数的参数需要一个不断变化的值才能模拟随机数的实现,因此可以直接调用time()函数获取时间的函数值。time()函数的参数为NULL,同时srand()函数参数类型需要设置成unsigned int 类型,因此种子这样设置:
srand((unsigned int)time(NULL));
随机数的获取有很多次,而种子只设置一次就可以,因此一般我们把rand()函数放在循环的内部,而srand()函数直接放置在主函数内循环外就可以。
电脑下棋只需要考虑是否位置被占用,因此代码要简单不少:
void comer_board(char board[ROW][COL], int row, int col)
{
puts("电脑下");
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;//输入合法坐标跳出循环
}//电脑不需要被提示因此不需要else
}
}
再在主函数内加入srand()函数设置种子就完成了电脑下这一步骤。
接着将玩家下,电脑下这两个步骤封装成循环,就可以实现连续下棋了。
谁先走?
这里可以设计个子菜单供玩家选择电脑先走还是玩家先走,利用函数的返回值进行选择。
int is_advanced()
{
int choose = 0;
do
{
scanf("%d", &choose);
switch (choose)
{
case 1:
puts("玩家先走");
return 1;
case 2:
puts("电脑先走");
return 2;
default:
puts("操作错误,请重新选择");
break;
}
} while (choose);
}
这里函数设计方法类似于主函数,不再过多赘述。
再加上判断局势,我们的game()函数大体也已经完成了。如下:
void game()
{
char ret = 0;
char board[ROW][COL] = { 0 };
//子菜单
submenu();
//选择谁先走
int ret1 = is_advanced();
//初始化内容
init_board(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
//落子于棋盘
while (1)
{
if (ret1 == 1)
{
//玩家下
player_move(board, ROW, COL);
//每下一步都要打印一次
print_board(board, ROW, COL);
ret1 = 2;//这里改变ret1的值保证玩家和电脑的操作交互进行
}
else if (ret1 == 2)
{
//电脑下
comer_board(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
ret1 = 1;
}
}
}
判断局势
设置好玩家和电脑的操作后就要判断局势了,玩家或者电脑每走一步,都会产生四种可能:
1、玩家赢;2、电脑赢;3、平局;4、继续
因此我们可以设计一个函数,根据不同的情况返回不同的值,这里定义函数:
char situation_board(char board[ROW][COL], int row, int col, char set)
情况1:玩家赢,返回'*'(玩家操作的棋子)
情况2:电脑赢,返回'#'(电脑操作的棋子)
情况3:平局,返回'q'
情况4:继续,返回'c'
再判断局势,看上面四种情况很明显前三种都直接结束了本次游戏,只有第四种情况游戏仍继续,因此返回值为'c'时游戏继续,不为'c'时跳出循环来判断输赢。
输赢情况
首先是输赢情况:如果横着三个或者竖着三个或者斜着三个相同(但不为' '),此时可判定输赢。
每次判断时要保证判断的字符是否对应,比如说在玩家下子以后的判断,这时只考虑玩家所控制的子的符号'*',同时注意排除掉' '以免造成错误的判断。
char situation_board(char board[ROW][COL], int row, int col, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
int count1 = 0;
int count2 = 0;
int count3 = 0;
int count4 = 0;
for (j = 0; j < row - 1; j++)
{
//判断横向是否满足条件
if ((board[i][j] == board[i][j + 1]) && board[i][j] == set && board[i][j] != ' ')
count1++;
}
for (j = 0; j < col - 1; j++)
{
//判断竖向是否满足条件
if ((board[j][i] == board[j + 1][i]) && board[j][i] == set && board[j][i] != ' ')
count2++;
}
//判断斜线方向
//1.判断'\'式
for (j = 0,k = 0; j < col - 1; j++, k++)
{
if (board[k][j] == board[k + 1][j + 1] && board[k][j] == set && board[k][j] != ' ')
count3++;
}
//判断'/'式
for (j = col - 1, k = 0; j > 0; j--, k++)
{
if (board[k][j] == board[k + 1][j - 1] && board[k][j] == set && board[k][j] != ' ')
count4++;
}
if (count1 == row - 1 || count2 == row - 1 || count3 == row - 1 || count4 == row - 1)
return set;
}
//下面部分在平局写完后可以体现
int ret2 = isfull_board(board, ROW, COL);
if (ret2 == 1)
return 'q';//满了
else if (ret2 == 0)
return 'c';//没满
}
当然,此时的函数还不完善,还有最后一种平局没有考虑,这里单独写一个函数来判断即可。
平局
如果上述情况玩家和电脑都不满足,游戏会继续进行,但当棋盘被占满以后仍未分胜负的情况,此时就进入了死循环,我们可以设计一个函数来判断棋盘是否满了:
int isfull_board(char board[ROW][COL], int row, int col)
{
//如果棋盘满了返回1,没满返回0
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
这时再将game()函数更新一下为:
void game()
{
char ret = 0;
char board[ROW][COL] = { 0 };
//子菜单
submenu();
//选择谁先走
int ret1 = is_advanced();
//初始化内容
init_board(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
//落子于棋盘
while (1)
{
if (ret1 == 1)
{
//玩家下
player_move(board, ROW, COL);
//判断局势
ret = situation_board(board, ROW, COL, '*');
if (ret != 'c')
break;
//每下一步都要打印一次
print_board(board, ROW, COL);
ret1 = 2;
}
else if (ret1 == 2)
{
//电脑下
comer_board(board, ROW, COL);
//判断局势
ret = situation_board(board, ROW, COL, '#');
if (ret != 'c')
break;
//打印棋盘
print_board(board, ROW, COL);
ret1 = 1;
}
}
//判断输赢
if (ret == '*')
{
puts("玩家赢");
print_board(board, ROW, COL);
}
else if (ret == '#')
{
puts("电脑赢");
print_board(board, ROW, COL);
}
else
puts("平局");
Sleep(3000);//每局结束休眠3秒,再配合菜单函数打印菜单前的前清屏可以保证屏幕上内容不会太多
}
总结
这样我们的小游戏就设计完成了,在5 * 5的棋盘上依旧是可以玩的,几个棋子连可以赢这里也可以设置,只需要将count1,2,3,4这些结果 == 你想要的结果-1就可以满足条件(或者直接将想要的棋子数也定义为宏,这里count的值==该宏-1),因此这个代码也可以是五子棋。如:
当然这里电脑是比较笨的,因为只是普通的随机数法,更高级的AI算法就需要读者自己去探索了。如果感觉我的文章对你有所帮助的话,不妨点个关注吧,实在不行点个赞再走也行呀。源码可以私我。(主要是不知道怎么发)