一、三子棋
1、设计思路
三子棋是一个非常简单有趣的游戏,下面我将介绍如何使用c语言去实现一个三子棋游戏!
1)明确目标
我们思考一下,一个三子棋游戏需要哪些组成部分:
1:玩家、对手;2:棋盘、棋子;3、游戏开始界面;4、胜负判断机制
有了设计目标,我们可以创建main.c,game.c和game.h来分别测试游戏、存放游戏函数、声明相关函数与常量
2)大致思路
(1)首先设计一个游戏菜单,用户可以选择开始游戏还是退出游戏
(2)用二维数组创建一个棋盘,初始化棋盘并打印棋盘
(3)用二维数组记录玩家落子位置(输入坐标)和电脑落子位置(随机数生成坐标)
(4)每次玩家和电脑分别落子后打印棋盘,并判断是输,还是赢,是平局,还是继续游戏。
2、代码实现
2.1游戏外部框架设计
2.1.1构建主函数框架
int main()
{
srand((unsigned int)time(NULL));
int input=0;
do
{
menu();
printf("请选择:>");
scanf("%d",&input);
switch(input)
{
case 1:
printf("Game Start\n");
game();
break;
case 0:
printf("Goodbye!!\n");
break;
default:
printf("Wrong choice\n");
break;
}
}while(input);
}
主函数的框架大致展现了我们在显示游戏菜单界面的操作,以及相关功能的的实现顺序,出现位置。这里使用了dowhile循环(便于判断程序是否结束)+switchcase(便于跳转界面)语句来实现游戏菜单界面中跳转界面、输入的功能。
2.1.2 打印游戏菜单
void menu()
{
printf("***********************\n");
printf("***********************\n");
printf("*****1.PLAY 0.EXIT****\n");
printf("***********************\n");
printf("***********************\n");
}
定义一个menu()来打印菜单,这里要注意menu()在主函数中出现的位置,以免发生跳转失灵的情况.
2.2 游戏过程设计
2.2.1 游戏运行函数game()的实现
void game()
{
int ret =0;
char board[ROW][COL]={0}; //设置一个二维数组表示棋盘,设置棋盘长宽ROW,COL
Initboard(board,ROW,COL); //初始化棋盘函数
Displayboard(board,ROW,COL); //展示棋盘函数
while (1) //进入while循环实现玩家电脑轮流下棋,轮流展示棋盘的顺序
{
Playermove(board,ROW,COL);
Displayboard(board,ROW,COL);
Computermove(board,ROW,COL);
Displayboard(board,ROW,COL);
}
}
我们创建一个game()函数初步我们可以先把我们大致需要的功能的函数现在这里写好,然后再去声明
2.2.2 设计棋盘大小
void game()
{
char board[ROW][COL]={0};
使用一个二维数组来创建棋盘,再在game.h中定义ROW和COL的值。为什么要在头文件中定义这两个量,是因为这样便于后续制作多子棋时方便更改参数,在其他需要输入长宽的情形下也可以借助编译器的弹出菜单快速输入
#define ROW 3
#define COL 3
2.2.3 初始化棋盘
我们在game.c中创建一个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] = ' ';
}
}
}
我们需要一个Initboard(),参数是board二维数组,长,宽。这里使用了两层嵌套for循环对棋盘数组的长宽进行遍历,是每个元素都被设置为空格‘ ’,提供落子空间。
2.2.4绘制棋盘
我们在game.c中创建一个Initboard()函数
void Displayboard(char board[ROW][COL],int row,int col)
{
int i=0;
for(i=0;i<row;i++)
{
int j=0;
for(j=0;j<col;j++)
{
printf(" %c ",board[i][j]); //" %c "使落的棋子在棋盘格的中间
if(j<col-1)
{
printf("|"); //打印棋盘格的竖分割线,col-1是因为右边界不需要分割线
}
}
//为什么会有两次j的for循环是因为,每下一行,需要先打印棋子,再打印分割线
printf("\n");
if(i<row-1)//row-1是因为i下边界不需要分割线
{
int j=0;
for(j=0;j<col;j++)
{
printf("---");
if(j<col-1)
{
printf("|");
}
}
printf("\n");
}
}
printf("\n");
}
这里同样使用了嵌套的for循环,在写代码之前最好在脑子里大致地把绘制地过程走一遍,这样可
以更清晰地写出代码。
void Initboard(char board[ROW][COL],int row,int col);
void Displayboard(char board[ROW][COL],int row,int col);
然后在头文件中声明这两个函数。(记得打分号!!!!)
2.2.5设计下棋过程
1)玩家下棋
在game.c 中创建一个Playermove()来实现玩家下棋
void Playermove(char board[ROW][COL],int row,int col);
我们需要一个Playermove()函数来实现玩家下棋的过程。思考一下,玩家下棋落子的实质就是在数组的对应位置填入字符'*',而获取对应位置自然就是输入坐标。
void Playermove(char board[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)
//如果输入的坐标在row,col的范围内则合法
{
if (board[x - 1][y - 1] == ' ')//为什么使用x-1,y-1?因为数组从0开始计数而我们人习惯从1开始计数,所以这样可以使坐标的输入更加人性化
{
board[x - 1][y - 1] = '*';
break;//玩家输入玩坐标后退出循环,退出程序,轮到电脑下棋
}
else
{
printf("坐标被占有,不能落子,重新输入坐标\n");
}//两个else不出循环,这样使得玩家可以有重新输入坐标的机会
}
else
{
printf("坐标非法,重新输入\n");
}
}
}
这时我们测试一下目前的代码:
2)电脑下棋
我们需要在game.c中创建Computermove()函数
void Computermove(char board[ROW][COL],int row,int col)
思考一下,电脑下棋是不可以有人为干预的,而根据所学的知识我们可以生成随机数来创建随机坐标
x = rand()%row;
y = rand()%col;
我们使用rand()函数生成随机坐标。为什么要%row,%col呢?
我们不妨来了解一下随机数的生成步骤
2))
rand()会返回一个范围在0~RAND_MAX(至少是32767)的伪随机整数。在调用rand()函数前可以先用srand()函数设置随机数种子(seed),如果不做这步设置,那随机数种子就为一,那生成的数字每次都是一样.
至于为什么要取模:如在5X5的棋盘中
rand() % 5
将生成一个0到4之间的随机整数,这代表5行中的任意一行。
同样,rand() % 5
将生成一个0到4之间的随机整数,这代表5列中的任意一列。
使用%
运算符的原因是rand()
函数生成的是一个在0到RAND_MAX之间的随机数(RAND_MAX
是一个宏,表示rand()
函数可以返回的最大随机数)。如果不使用%
运算符,生成的随机数可能会超出你的网格大小,导致索引越界错误。
rand(),srand()函数包含在stdlib.h头文件中。
最后我们还要调用time()函数,这个函数在time.h中
具体操作如下
void Computermove(char board[ROW][COL],int row,int col)
{
//生成随机数
int x=0;
int y =0;
while(1)
{
x = rand()%row;
y = rand()%col;
if(board[x][y]==' ')
{
board[x][y] = '#';
break;
}
}
}
2.2.6设计胜负判断机制
众所周知,在三子棋中,只要有横线,竖线,对角线,都连上才为胜。实质就是在二维数组的横竖对角线位置上填入同一个字符即可判断一方的胜利,那我们该如何实现这个过程呢?
首先我们需要在game.c中创建一个isWin()函数。
char isWin(char board[ROW][COL], int row, int col)
这里的返回类型为char,我们可以返回不同的字符,然后在主函数中根据返回字符的不同来判断胜负平局还是继续游戏。
1)判断胜负
行列遍历:
for (int i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
return board[i][0];//如果是玩家赢就返回'*'如果是电脑赢就返回'#'
}
//列
for (int i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
return board[0][i];
}
我们分别使用两个for循环对行列进行遍历以确保在横竖方向有连起来的棋子
对角线遍历:
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
return board[1][1];
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
return board[1][1];
对于三子棋来说,我们只需要遍历“撇”、“捺”两个方向的对角线上的三个元素。
平局判断:
对于平局判断,当棋盘为满且横竖方向,对角线没有连续的棋子,则为平局。我们不妨创建一个函数isFull()来判断棋盘是否为满,同样是应用for循环遍历整个二位数组,如果满了函数就返回1,否则返回0.
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;
}
}
}
return 1;
}
胜负判断机制完整代码如下
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;
}
}
}
return 1;
}
char isWin(char board[ROW][COL], int row, int col)
{
//赢
//行
for (int i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
return board[i][0];//如果是玩家赢就返回'*'如果是电脑赢就返回'#'
}
//列
for (int i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
return board[0][i];
}
//对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
return board[1][1];
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
return board[1][1];
//平局
if (isFull(board, row, col))
{
return 'Q';
}
//继续
return 'C';
}
2.3整合,运行
到此为止,我们所需的函数功能已经写完,让我们回到main.c去整合全部的功能。
来到game()函数:
void game()
{
char board[ROW][COL]={0};
int ret =0;
//
Initboard(board,ROW,COL);//初始化棋盘
Displayboard(board,ROW,COL);//展示棋盘
while (1)
{
Playermove(board,ROW,COL);//玩家下棋
Displayboard(board,ROW,COL);
ret = isWin(board,ROW,COL); //判断胜负情况
if(ret!='C') //如果返回不是‘C’则游戏继续,否则进行胜负判断
{
break;
}
Computermove(board,ROW,COL);//电脑下棋
Displayboard(board,ROW,COL);
ret = isWin(board,ROW,COL);
if(ret!='C')
{
break;
}
}
if(ret=='*')
{
printf("玩家胜\n");
}
else if(ret=='#')
{
printf("电脑胜\n");
}
else if(ret=='Q')
{
printf("平局\n");
}
}
整合好功能后,我们的三子棋程序就做完啦!!!!
二、多子棋升级
回忆一下我们在game.h中定义的row和col,现在我们可以改变他们的值,打印不同大小的棋盘,实现5子棋,7子棋等等。但是我们需要改变一下胜负判断机制,使其更具普适性。
1.判断行
for (i = 0; i < row; i++)
{
j = 0;
for (j = 0; j < col - 1; j++)
{
if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
{
break;
}
}
if (j == col - 1)
{
return board[i][j];
}
}
这里同样是遍历整个棋盘,但是在if语句的部分要注意是判断board[i][j] != board[i][j + 1] || board[i][j] == ' '。如果是判断前后等于或者不等于空格的话,会提前结束判断。
2、判断列
for (j = 0; j < col; j++)
{
i = 0;
for (i = 0; i <row - 1; i++)
{
if (board[i][j] != board[i + 1][j] || board[i][j] == ' ')
{
break;
}
}
if (i == row - 1)
{
return board[i][j];
}
}
3、主对角线
i = 0;
j = 0;
for (int i = 0; i < ROW && i < COL; i++) {
// 检查当前位置是否非空
if (board[i][i] != ' ') {
// 检查主对角线上的其余位置是否与当前位置相同
for (int j = i + 1; j < ROW && j < COL; j++) {
if (board[i][i] != board[j][j]) {
break;
}
}
// 如果全部一致,返回获胜的符号
if(i==row-1)
{
return board[i][i];
}
}
}
在主对角线从左上角判断到右下角,此处的判断上与行列的思路有些不同,i,j不再是单独的行列。因为主对角线的规律,可以有board[i][i] != board[j][j]这样的判断方式。
4、副对角线
//副对角线
char symbol = board[0][COL - 1]; // 取得右上角的符号作为起始符号
for (int i = 0; i <ROW ; i++)
{ // 假设 ROW 是行数,COL 是列数
if (board[i][COL - 1 - i] != symbol || board[i][COL - 1 - i] == ' ')
{
//如果当前符号与起始符号不一致或为空格,则没有获胜
break;
}
if(i==ROW-1)//4
{
return symbol;
}
}
// 如果成功检查完所有符号且都相同,则返回获胜的符号
副对角线从右上角判断到左下角,与主对角线的判断有些许不同我们可以令j = COL-1-i或者像示例这样些,因为经过观察我发现,就拿5X5的棋盘来说,从左上到右下的行列序号规律为:
1-5,2-4,3-3,4-2,5-1所以board[i][COL - 1 - i]这个表达式就可以表示对角线上的元素。
平局的情况就与三子棋的判断方式相同
三、完整代码
game.h
#define ROW 6
#define COL 6
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void Initboard(char board[ROW][COL],int row,int col);
void Displayboard(char board[ROW][COL],int row,int col);
void Playermove(char board[ROW][COL],int row,int col);
void Computermove(char board[ROW][COL],int row,int col);
char isWin(char board[ROW][COL],int row,int col);
int isFull(char board[ROW][COL],int row,int col);
game.c
#include "game.h"
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] = ' ';
}
}
}
void Displayboard(char board[ROW][COL],int row,int col)
{
int i=0;
for(i=0;i<row;i++)
{
int j=0;
for(j=0;j<col;j++)
{
printf(" %c ",board[i][j]);
if(j<col-1)
{
printf("|");
}
}
printf("\n");
if(i<row-1)
{
int j=0;
for(j=0;j<col;j++)
{
printf("---");
if(j<col-1)
{
printf("|");
}
}
printf("\n");
}
}
printf("\n");
}
void Playermove(char board[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)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("坐标被占有,不能落子,重新输入坐标\n");
}
}
else
{
printf("坐标非法,重新输入\n");
}
}
}
void Computermove(char board[ROW][COL],int row,int col)
{
//生成随机数
int x=0;
int y =0;
while(1)
{
x = rand()%row;
y = rand()%col;
if(board[x][y]==' ')
{
board[x][y] = '#';
break;
}
}
}
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;
}
}
}
return 1;
}
char isWin(char board[ROW][COL], int row, int col)
{
int i=0;
int j=0;
//赢
//行
// for (int i = 0; i < row; i++)
// {
// if (board[i][0] == board[i][i+1] && board[i][i+1] != ' ')
// return board[i][0];//如果是玩家赢就返回'*'如果是电脑赢就返回''
// }
// //列
// for (int i = 0; i < col; i++)
// {
// if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
// return board[0][i];
// }
// //对角线
// if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
// return board[1][1];
// if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
// return board[1][1];
//赢
//行
for (i = 0; i < row; i++)
{
j = 0;
for (j = 0; j <col - 1; j++)
{
if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
{
break;
}
}
if (j == col - 1)
{
return board[i][j];
}
}
//列
for (j = 0; j < col; j++)
{
i = 0;
for (i = 0; i <row - 1; i++)
{
if (board[i][j] != board[i + 1][j] || board[i][j] == ' ')
{
break;
}
}
if (i == row - 1)
{
return board[i][j];
}
}
//主对角线
i = 0;
j = 0;
for (int i = 0; i < ROW && i < COL; i++) {
// 检查当前位置是否非空
if (board[i][i] != ' ') {
// 检查主对角线上的其余位置是否与当前位置相同
for (int j = i + 1; j < ROW && j < COL; j++) {
if (board[i][i] != board[j][j]) {
break;
}
}
// 如果全部一致,返回获胜的符号
if(i==row-1)
{
return board[i][i];
}
}
}
//副对角线
char symbol = board[0][COL - 1]; // 取得右上角的符号作为起始符号
for (int i = 0; i <ROW ; i++)
{ // 假设 ROW 是行数,COL 是列数
if (board[i][COL - 1 - i] != symbol || board[i][COL - 1 - i] == ' ')
{
//如果当前符号与起始符号不一致或为空格,则没有获胜
break;
}
if(i==ROW-1)//4
{
return symbol;
}
}
// 如果成功检查完所有符号且都相同,则返回获胜的符号
//平局
if (isFull(board, row, col))
{
return 'Q';
}
//继续
return 'C';
}
main.c
#include "game.h"
void menu()
{
printf("***********************\n");
printf("***********************\n");
printf("*****1.PLAY 0.EXIT****\n");
printf("***********************\n");
printf("***********************\n");
}
void game()
{
char board[ROW][COL]={0};
int ret =0;
//
Initboard(board,ROW,COL);//初始化棋盘
Displayboard(board,ROW,COL);//展示棋盘
while (1)
{
Playermove(board,ROW,COL);//玩家下棋
Displayboard(board,ROW,COL);
ret = isWin(board,ROW,COL); //判断胜负情况
if(ret!='C') //如果返回不是‘C’则游戏继续,否则进行胜负判断
{
break;
}
Computermove(board,ROW,COL);//电脑下棋
Displayboard(board,ROW,COL);
ret = isWin(board,ROW,COL);
if(ret!='C')
{
break;
}
}
if(ret=='*')
{
printf("玩家胜\n");
}
else if(ret=='#')
{
printf("电脑胜\n");
}
else if(ret=='Q')
{
printf("平局\n");
}
}
int main()
{
srand((unsigned int)time(NULL));
int input=0;
do
{
menu();
printf("请选择:>");
scanf("%d",&input);
switch(input)
{
case 1:
printf("Game Start\n");
game();
break;
case 0:
printf("Goodbye!!\n");
break;
default:
printf("Wrong choice\n");
break;
}
}while(input);
}
说到这里,我们的三子棋+多子棋升级的文章到这里就结束了。希望大家可以从博主的文章中有所收获,如果文章里有任何出错的地方希望大家可以在评论区里告诉我,感谢!!!