一个游戏最基础的部分就说选择菜单,玩家可以选择玩或者退出这个游戏。当然设计的游戏不能只玩一次就得重新打开,所以我们利用循环的方式让玩家选择玩或者不玩。 如果选择玩 -->: 进入游戏->游戏结束->重新进行选择; 如果选择不玩–>:结束循环,跳出选择菜单。
那么如果通过代码进行实现呢? 首先是玩家通过输入来进行选择,我们设置一个变量 input 来接受玩家的选择信息。并且用do{}while;循环 来进行控制。用do{}while;循环的好处是能让循环体至少循环一次。
那么循环体内部又该怎么实现呢? 既然是通过玩家输入,对玩或者不玩进行选择;那么我们直接启动 选择语句switch(); 来进行控制。
我们作为开发者知道可以通过输入进行选择,但是玩家并不知道呀,我们得通过打印对玩家进行提示,并且应该有一个美化的菜单设计,便于玩家进行操作。所以我们设计一个menu()函数 作为我们的菜单。 由于其只需要打印菜单内容,并不需要返回数值,所以函数类型是void。
由于我们是循环体,处于设计考虑我们提示玩家输入1开始游戏,输入0结束游戏,并且让input作为循环条件,正好while(0) 为假,循环结束。
当然在switch语句中,我们也要考虑玩家错误输入的情况,不能因为玩家的一次错误输入程序便彻底崩溃了。我们在default 中应该也进行相对应的提示,让玩家可以重新进入选择菜单进行输入。
我们写道这里已经可以展现出一个逻辑合理的主菜单内容了,先进行测试一下: 下面是代码部分:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void menu()
{
printf("********************************\n");
printf("******* 1. play ******\n");
printf("******* 0. exit ******\n");
printf("********************************\n");
}
int main()
{
int input = 0;
do {
menu();
printf("请输入:->");
scanf("%d",&input);
switch (input)
{
case 1:
printf("游戏进入成功!!\n");
break;
case 0:
printf("游戏已成功退出!!\n");
break;
default:
printf("您输入有误,请重新进行选择\n");
break;
}
} while (input);
return 0;
}
运行结果如图所示:
写到这里的时候,我们的函数就已经开始逐渐变得冗杂了,出于可读性的考虑,我们采用模块化编程。我在这里解释一下模块化编程。
将一个项目中的各个模块的代码放在不同的.c文件里,在.h文件(头文件)里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要 #include “XXX.h” (注:自定义的头文件并不是<>,而是" "双引号引用的)。 模块化编程的优点: 使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等。
在这里,我们的游戏逻辑代码还没有编写,所以我们新建一个 game.c文件和 game.h文件 ,game.h用于存放函数的声明。 — 别忘记在原本的.c文件中进行引用 我们现在原.c文件中设置一个 game()函数 用来展示游戏功能。 并且这个game()函数 需要连接在switch case 1:之后。
我们在设置game函数时,首先要设计一个棋盘。那这个棋盘上的应该是由数组进行存储的,并且这个数组的大小是3*3。我们先设置一个char 类型数组,其大小是3 * 3。 这里不推荐直接写arr[3][3],而是建议利用宏定义 #define,这样便于后续代码信息的修改和和统一 我在这里宏定义了ROW 3和COL 3,代表了行为3和列为3。 有了数组对棋盘信息进行存储了,我们还要对其初始化,让一开始棋盘的内容均为空格。我们再设置一个boardInit()函数来初始化数组信息。 注:这里的函数传递需要传递二维数组,特此补充一下知识点
二维数组的传递形式是(int* parr [ ][ ]),但是数组名通常就指代数组第一个元素的地址;并且通常我们需要利用到二维数组的行数和列数,也要进行传递。 所以我们在建立函数进行接收参数时代码应该如下格式: char arr[ROW][COL]表面接受的是ROW 行 COL列的二维数组
void boardInit(char arr[ROW][COL],int row,int col)
而传递二维数组时,只需要传递地址即可
boardInit(arr,ROW,COL);
到这里,我们棋盘展示的部分已经做完了,运行检测 代码如下: test.c
#define _CRT_SECURE_NO_WARNINGS
#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);
display(arr, ROW, COL);
}
int main()
{
int input = 0;
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;
}
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void boardInit(char arr[ROW][COL],int row,int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < COL; j++)
{
arr[i][j] = ' ';
}
}
}
void display(char arr[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (j != col - 1)
{
printf(" %c |", arr[i][j]);
}
else
printf(" %c \n",arr[i][j]);
}
if (i != row - 1)
{
for (int j = 0; j < col; j++)
{
if (j != col - 1)
printf("---|");
else
printf("---\n");
}
}
}
}
game.h()
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define ROW 3
#define COL 3
//将二维数组里的数据初始化为空格
void boardInit(char arr[ROW][COL], int row, int col);
//通过打印展现出棋盘边界,便于区分
void display(char arr[ROW][COL], int row, int col);
根据上面的逻辑,我们再设置两个函数:一个是playerMove();代表玩家进行下棋操作;另一个是computerMove();代表电脑进行下棋操作。当然,下棋操作是一个循环操作,先是玩家下棋,然后电脑下棋直到一方胜利或者是平局。 我们先不考虑胜利条件,先用死循环while(1)把下棋操作写入。
这里的玩家下棋函数需要接受玩家输入的坐标信息,但是玩家并不是程序员,还是会按照常理判断坐标,即1~3的范围进行输入。 当然这里也一个循环输入过程, 1.如果玩家输入的数不在棋盘范围之内,需要返回重新输入 2.如果玩家输入的数已经被占用,需要返回重新输入 这里还是利用while(1)死循环来输入,如果输入正确则对齐赋值并break跳出。
电脑下棋同理,只不过落子由玩家输入变成了电脑随机生成,这里我们调用 生成随机数的函数。
先在主函数中引用一个srand();函数,表示生成一个随机数种子,让生成随机数变成真随机数 需要调用<stdlib.h>头文件 再利用time();函数使srand始终发生变化: 代码如下:(这里的unsigned int是为了强制类型转换time()函数返回值)
srand((unsigned int)time(NULL));
这样我们使用rand();函数生成的随机数就是真随机数了。 当然我们需要控制随机数的范围,让他在0~2之间,所以用
x = rand() % row;
y = rand() % col;
注:这里判断是否已被占用是字符比较,可以用==,字符串比较则不能用== 而是用strcmp()函数
我们在调用完下棋函数后,记得重新展示一下棋盘,来向玩家进行展示。
运行结果如下:
到这里,我们的下棋操作就已经编写完成了,但是我们还无法判断胜利或者平局,所以我们需要编写判断条件。
当我们在进行下棋循环操作时,棋盘有四种状态 1.玩家胜利 2.电脑胜利 3.平局 4.游戏仍在进行中
由于有这四种状态,所以我们需要调用一个函数,在任一方下棋操作进行后进行判断,并返回相应值来判断棋盘状态。 我们设置一个Iswin();函数,并且我们需要其返回值来判断状态,设其为char 类型函数。 由于只有第四种状态是不结束游戏的,我们在下棋循环中先对状态4进行判断,如果是状态4,我们返回c。如果Iswin()的返回值 != c,那么就说明游戏结束了。如果 == c,下棋循环仍进行。
对于其他三种结束状态,我们通过判Iswin()函数的返回值来判断属于那种结束条件,我们设置以下的返回值: 1.玩家胜利 -> ‘’ * " 2.电脑胜利 -> " # " 3.平局 -> ‘q’
对于胜利结局,无论是电脑还是玩家,都是判断如下三种情况 1.遍历每一行,看哪一行是相同且不等于空格 2.遍历每一列,看那一列是相同的且不等于空格 3.遍历两个对角线,看对角线上是否均相等且不等同于空格
如果有相同情况,则直接返回相同的元素(对照前文设置判断条件)
注意:这里一定要注意还有全为空格的情况,必须要排除
对于平局结局: 1.先判断棋盘是否已经为满 这里可以再设置一个函数isFull()来检测棋盘是否已经满了,遍历棋盘,如果有一个格子 == 空格 return 0; 如果遍历完都没有空格,return -1; 2.如果棋盘已经满且没有胜利,判定为平局。
写到这里,整个三子棋的项目逻辑基本完成,下面是代码展示
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
printf("********************************\n");
printf("******* 1. play ******\n");
printf("******* 0. exit ******\n");
printf("********************************\n");
}
void game()
{
char ret = 0;
char arr[ROW][COL] = { 0 };
//初始化棋盘数据
boardInit(arr,ROW,COL);
//打印棋盘布局
display(arr, ROW, COL);
//电脑和玩家下棋
while (1)
{
//玩家下棋
playerMove(arr, ROW, COL);
//展示棋盘
display(arr, ROW, COL);
//判断游戏是否结束
ret = Iswin(arr, ROW, COL);
if (ret != 'c')
break;
//电脑下棋
computerMove(arr, ROW, COL);
//展示棋盘
display(arr, ROW, COL);
//判断游戏是否结束
ret = Iswin(arr, ROW, COL);
if (ret != 'c')
break;
}
if (ret == '*')
{
printf("玩家胜利!!!\n");
}
else if (ret == '#')
printf("电脑胜利!!!\n");
else
printf("平局了!!!!");
}
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;
}
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void boardInit(char arr[ROW][COL],int row,int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < COL; j++)
{
arr[i][j] = ' ';
}
}
}
void display(char arr[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (j != col - 1)
{
printf(" %c |", arr[i][j]);
}
else
printf(" %c \n",arr[i][j]);
}
if (i != row - 1)
{
for (int j = 0; j < col; j++)
{
if (j != col - 1)
printf("---|");
else
printf("---\n");
}
}
}
}
void playerMove(char arr[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("现在是玩家回合\n");
printf("请输入下棋的坐标信息->:");
scanf("%d %d", &x, &y);
if ((x > 0 && x <= row) && (y > 0 && y <= col))
{
if (arr[x - 1][y - 1] == ' ')
{
arr[x - 1][y - 1] = '*';
break;
}
else
printf("你输入的坐标已被落子,请重新输入\n");
}
else
printf("这是一个错误的坐标,请重新下棋\n");
}
}
void computerMove(char arr[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("现在是电脑下棋:\n");
while (1)
{
x = rand() % row;
y = rand() % col;
if (arr[x][y] == ' ')
{
arr[x][y] = '#';
break;
}
}
}
char Iswin(char arr[ROW][COL], int row, int col)
{
int i = 0;
//遍历每一行,看有没有行相同
for (i = 0; i < row; i++)
{
if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][0] != ' ')
return arr[i][0];
}
//遍历每一列,看看有没有列相同
for (i = 0; i < col; i++)
{
if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[0][i] != ' ')
return arr[0][i];
}
//遍历对角线
if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1] != ' ')
return arr[1][1];
if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ')
return arr[1][1];
//判断平局
if (isFull(arr, ROW, COL) == -1)
return 'q';
else
return 'c';
}
int isFull(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++)
{
if (arr[i][j] == ' ')
return 0;
}
}
return -1;
}
game.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3
//将二维数组里的数据初始化为空格
void boardInit(char arr[ROW][COL], int row, int col);
//通过打印展现出棋盘边界,便于区分
void display(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 arr[ROW][COL], int row, int col);
运行结果: 1.玩家胜利
2.电脑胜利