前言
我们通过5个简单易懂的小游戏来加强我们对C语言的认识,这五个小游戏不仅有单人,还有人机对战和人人对战。让我们在学习之余来上一局紧张刺激的小游戏吧!
一、准备工作
我们要做5个小游戏,我们要分别为5个小游戏创建一个头文件和一个源文件。分别为game1.h/game1.c,game2.h/game2.c,game3.h/game3.c,game4.h/game4.c,game5.h/game5.c。这样做的目的是把每个游戏所分开构建,方便日后对我们小游戏的重构等操作。我们还须要一个main.c,用来包含这5个小游戏的头文件。main函数只负责调用,我们把所有小游戏代码函数在其他文件中实现并用static加以修饰,使用户使用game函数间接的调用我们小游戏的代码。
二、游戏菜单
我们先构建main.c:
1.它需要包含五个小游戏的头文件
2.构建我们的main函数
#include"game1.h"//包含小游戏1的头文件
#include"game2.h"//包含小游戏2的头文件
#include"game3.h"//包含小游戏3的头文件
#include"game4.h"//包含小游戏4的头文件
#include"game5.h"//包含小游戏5的头文件
#include<time.h>
int main()
{
srand((unsigned int)time(NULL));//用来生成随机数
do
{
int optional = 0;//创建一个接收选择的变量
menu();
printf("请输入你的选项:\n");
scanf("%d", &optional);
switch (optional)
{
case 1:
game1();
break;
case 2:
game2();
break;
case 3:
game3();
break;
case 4:
game4();
break;
case 5:
game5();
break;
case 0:
printf("感谢你的游玩,欢迎下次使用。\n");
exit(0);
default:
printf("你的选项输入有误,请重新输入:\n");
break;
}
} while (1);
}
我们在main函数中用了do-while循环,目的是让用户进行选择,直到用户选择退出或者捣乱才进行退出。
scanf函数有风险,当用户捣乱输入字母时,会造成缓冲异常。所以我们把要接收的选择变量放在循环中,并且赋初始值为0,当用户输入字母时使程序退出。
srand函数用来产生随机数,当我们只使用rand函数时所产生的随机数会不变,会使我们重复游玩的体验感变差。
我们在为这几个游戏的合集创造一个菜单,让用户选择具体游玩那个游戏。
void menu()
{
printf("*****************************\n");
printf("*****1.猜数字 2.三子棋*****\n");
printf("*****3.扫 雷 4.五子棋*****\n");
printf("*****5.飞行棋 0.退 出*****\n");
printf("*****************************\n");
}
基本菜单已经做好了,我们进入每个小游戏的菜单。
我们先来创建小游戏的头文件:
#pragma once
#include<stdio.h>
#include<stdlib.h>
void game1();
小游戏几我们在小游戏的头文件中就对应game几,我以小游戏1为例,其他四个小游戏和这个差不多。
我们再为每个创建源文件:
#include"game1.h"
static void menu1()
{
printf("*****************************\n");
printf("***** 猜数字 *****\n");
printf("***** 1.Start the game ******\n");
printf("***** 0.Exit the game ******\n");
printf("*****************************\n");
}
void game1()
{
int optional = 0;//创建一个接收选择的变量
do
{
optional = 0;
menu1();
printf("请输入你的选项:\n");
scanf("%d", &optional);
switch (optional)
{
case 1:
//game();
break;
case 0:
printf("猜数字小游戏以退出。\n");
break;
default:
printf("你的选项输入有误,请重新输入:\n");
break;
}
} while (optional);
}
还是以game1为例,其他的小游戏中的选项和game1相同,只把其中的菜单函数中的小游戏换一下名字。循环中的game函数用来调用这个小游戏中的组成函数。
我们现在来运行一下,来测试一下代码是否是我们所期望的那样
经测试可以按照我们的思路正常进行,现在就让我们正式写出每个小游戏吧。
三、游戏内容
1.猜数字
思路:系统随机生成一个随机数,通过我们输入的数和生成数进行比较,给予我们提示猜大了还是猜小了。
static void game()
{
int num = rand() % 100 + 1;//创建一个变量,用来存放随机数。
int guess = 0;//创建一个变量,用来存放所猜的数字。
//rand函数是用来生成随机数的,我们用rand函数生成的随机数对100取模并加1,可以产生1~100的随机数
while(1)
{
printf("请输入你猜测的数字:");
scanf("%d", &guess);
if (guess == num)
{
printf("恭喜你,猜对了!\n");
break;
}
else if (guess > num)
{
printf("你猜的数字过大,请重新猜测吧!\n");
}
else if (guess < num)
{
printf("你猜的数字过小,请重新猜测吧!\n");
}
}
return;
}
我们简单的运行一下吧!
可以正常运行,且我们可以重复的游玩。
2.三子棋
思路:用数组存储我们的棋子,并判断是否获胜。
1.打印棋盘
2.玩家进行下棋
3.判断玩家是否获胜
4.电脑进行下棋
5.判断电脑是否获胜
我们先定义两个宏:用来决定我们可以设置的棋盘大小。
#define ROW 3 //行
#define COL 3 //列
我们开始构建game函数,看看里面需要什么模块来支持游戏思路:
static void game()
{
char win;//创建一个变量,存放判段获胜条件的字符。
//我们把C代表继续,D代表平局,*代表玩家获胜,#代表电脑获胜
char checkerboard[ROW][COL] = { 0 };//创建一个数组,用来存放棋盘信息
initialization(checkerboard,ROW,COL);//初始化数组,把数组中的每个元素初始化为空格
ptintinitialization(checkerboard, ROW, COL);//打印棋盘
while(1)
{
Player(checkerboard, ROW, COL, '*');//玩家下棋,玩家的标志为 *
win = Iswin(checkerboard, ROW, COL);//判断是否获胜
if (win != 'C')
{
break;
}
ptintinitialization(checkerboard, ROW, COL);//打印棋盘
Computer(checkerboard, ROW, COL, '#');//电脑下棋,电脑的标志为 #
win = Iswin(checkerboard, ROW, COL);
if (win != 'C')
{
break;
}
ptintinitialization(checkerboard, ROW, COL);//打印棋盘
}
if (win == 'D')
{
printf("平局\n");
ptintinitialization(checkerboard, ROW, COL);
}
else if (win == '*')
{
printf("恭喜你获得胜利\n");
ptintinitialization(checkerboard, ROW, COL);
}
else
{
printf("很遗憾,你输掉了比赛\n");
ptintinitialization(checkerboard, ROW, COL);
}
}
我们先创建我们所用到的数组和一个可以判断输赢的变量,我们把数组进行初始化,并且进行打印,让玩家知道可以下棋的位置,通过玩家下棋函数和电脑下棋函数来实现人机对战。
初始化数组函数:
static void initialization(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++)
{
arr[i][j] = ' ';//把数组中的每个元素赋值为空格
}
}
}
打印棋盘函数:
static void ptintinitialization(char arr[ROW][COL], int row, int col)//打印棋盘
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (j < col - 1)
{
printf(" %c |", arr[i][j]);//打印棋盘,如果为列中的最后一个元素,则不打印|
}
else
{
printf(" %c ", arr[i][j]);
}
}
printf("\n");
if(i < row-1)//打印棋盘,如果为行中的最后一个元素,则不打印---
{
for (j = 0; j < col; j++)
{
if (j < col)
{
printf("--- ");
}
}
}
printf("\n");
}
}
打印的效果如图所示。
当我们把宏改为:
#define ROW 9 //行
#define COL 9 //列
我们可以看到打印的棋盘扩大了。这是宏的好处,不需要我们在每个函数的改数值。只需要改宏定义的数值,就可以改变我们棋盘的大小。
玩家下棋函数:
static void Player(char arr[ROW][COL], int row, int col, char ch)//玩家下棋函数
{
int x = 0;
int y = 0;
while(1)
{
printf("请输入你要下棋的坐标:");
scanf("%d %d", &x, &y);
if (x<1 || x>row || y<1 || y>col)//判断坐标是否合法
{
printf("你输入的坐标不合法,请重新输入。\n");
}
else if (arr[x - 1][y - 1] != ' ')//判断输入坐标是否被占用
{
printf("你输入的坐标已被占用,请重新输入。\n");
}
else
{
arr[x - 1][y - 1] = ch;//数组下标从0开始,所以玩家的真实坐标要进行减1
break;
}
}
}
电脑下棋函数:
static void Computer(char arr[ROW][COL], int row, int col, char ch)
{
while (1)
{
int x = rand() % 3;//产生0~2的数字
int y = rand() % 3;
if (arr[x][y] == ' ')//如果坐标未被占用,则电脑下棋,否则产生新的坐标进行判断
{
arr[x][y] = ch;//电脑的坐标的下标和数组下标对应,不需要进行减1操作
break;
}
}
}
判断是否获胜函数:
static char Iswin(char arr[ROW][COL], int row, int col)//判断是否获胜
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)//判断每一排中是否有人获胜
{
if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][1] != ' ')
{
return arr[i][1];
}
}
for (i = 0; i < col; i++)//判断每一列中是否有人获胜
{
if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[1][i] != ' ')
{
return arr[1][i];
}
}
if ((arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1] != ' ') ||
(arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' '))//判断对角线上是否有人获胜
{
return arr[1][1];
}
for (i = 0; i < row; i++)
{
for (j = 0; i < col; i++)
{
if (arr[i][j] == ' ');//判断是否还有空位
{
return 'C';
}
}
}
return 'D';
}
让我们运行一下看看结果吧!
注意:
1.我们所使用的scanf是不安全的,大家可以查找一下对应的解决办法,了解一下为什么不安全。
2.对所用的数组进行初始话是要注意间隔,数组传参是注意形参和实参的类型是否一致,我们使用的是char类型数组,如果形参用int接受会造成什么情况呢?大家可以下去试一试。
3.大家可以对这个代码进行重构,可以实现人人对战。在这里只显示了人机对战,重构一下加入人人对战吧。
如果形参用int接受会造成数组越界,造成栈错误。
3.扫雷
思路:用数组存储雷的信息,通过排查,找到没有雷的地方。
1.打印地图
2.玩家进行扫雷
3.判断玩家是否踩雷
4.更新地图
分析:
在这里插入图片描述
上图是一个9*9的扫雷地图,当我们要判断红色的地方,对周边的判断势必会造成越界访问。如何解决这个问题呢?
我们把这个数组上下左右个扩充一行,这样进行判断就不会产生数组越界了。打印数组的问题我们通过代码来分析吧。
我们设置两个数组,一个用来存储地图信息,一个用来向玩家展示,向玩家展示的数组需要通过存储地图信息的数组来判断地图上的安全地方。
我们先定义五个宏:分别决定我们可以设置的地图大小和我们打印地图的大小,最后一个宏来存放我们地雷的个数,后期也方便我们进行测试。
#define ROW 9 //显示的行
#define COL 9 //显示的列
#define ROWS ROW+2 //真实数组的行
#define COLS COL+2 //真实数组的列
#define MINENUM 10 //地雷的个数
然后对我们的game函数进行补充,来看看具体的思路吧:
static void game()
{
int winnum = ROW * COL - MINENUM;//设置一个变量,用来存放还有多少个安全的地方
char minemap[ROWS][COLS] = { 0 };//雷分布的地图
char showmap[ROWS][COLS] = { 0 };//向玩家展示的地图
Initialization(minemap, showmap, ROWS, COLS);//初始化数组,把雷分布随机分布在地图中,并且把向玩家展示的地图全部替换为 *
//Ptintinitialization(minemap, ROW, COL);//测试代码,用来打印地图
while (winnum)
{
int x = 0;
int y = 0;
Ptintinitialization(showmap, ROW, COL);//打印展示地图
printf("请输入排雷坐标:");
scanf("%d %d", &x, &y);
if (x<1 || x>ROW || y<1 || y>COL)//判断坐标是否合法
{
printf("你输入的坐标不合法,请重新输入。\n");
}
else if (showmap[x][y] != '*')//判断坐标是否已被排查过
{
printf("你输入的坐标已被排过雷了,请重新输入。\n");
}
else
{
if (!Determine(minemap, x, y))//!Determine(minemap, x, y)用来判断是否踩到雷
{
Ptintinitialization(minemap, ROW, COL);//打印地雷地图,让玩家知道地雷在哪里。
break;
}
Renewmap(minemap, showmap, x, y);//用来更新向玩家展示的地图
winnum--;//对安全的地方进行减1
}
}
if(winnum == 0)//当安全地方为0时,证明排雷成功
{
printf("恭喜你排雷成功!\n");
Ptintinitialization(showmap, ROW, COL);//打印展示地图,让玩家知道自己获胜地图
}
}
初始化数组函数:
static void Initialization(char minemap[ROWS][COLS], char showmap[ROWS][COLS], int rows, int cols)
{
int count = MINENUM;//创建一个变量,用来计算放置的雷的数量
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)//把地雷地图全部赋值为字符0
{
for (j = 0; j < cols; j++)
{
minemap[i][j] = '0';
}
}
while (count)//把雷分布随机分布在地雷地图中
//我们把0代表安全,1代表雷区
{
int x = rand() % ROW + 1;//创建随机变量范围在1~9
int y = rand() % COL + 1;//创建随机变量范围在1~9
if (minemap[x][y] == '0')//判断该位置是否已有雷
{
minemap[x][y] = '1';//放置地雷
count--;
}
}
for (i = 0; i < rows; i++)//把展示地图全部赋值为字符*
{
for (j = 0; j < cols; j++)
{
showmap[i][j] = '*';
}
}
}
我们所展示的地图行为1 ~ ROW,列为1 ~ COL,所以把生成的雷的坐标控制在这个范围之间。
打印地图函数:
static void Ptintinitialization(char showmap[ROWS][COLS], int row, int col)//打印地图
{
int i = 1;
int j = 1;
for (i = 1; i <= row; i++)
{
if (i == 1)//当i为1时,需要多一个空格,目的为了和地图对齐
{
printf(" ");
}
printf(" %d", i);
}
printf("\n");
for (i = 1; i <= row; i++)//打印我们所需要的地图
{
printf("%d", i);
for (j = 1; j <= col; j++)
{
printf(" %c", showmap[i][j]);
}
printf("\n");
}
}
我们从1开始打印,打印到ROW处,只打印我们所需要的地方。
判断是否踩到雷:
static bool Determine(char minemap[ROWS][COLS],int x,int y)
{
if (minemap[x][y] == '1')
{
printf("排雷失败,你已死亡!\n");
return false;
}
else
{
return true;
}
}
更新展示的地图:
static void Renewmap(char minemap[ROWS][COLS], char showmap[ROWS][COLS], int x, int y)
{
showmap[x][y] = (minemap[x - 1][y - 1] + minemap[x - 1][y] + minemap[x - 1][y + 1]
+ minemap[x][y - 1] + minemap[x][y + 1]
+ minemap[x + 1][y - 1] + minemap[x + 1][y] + minemap[x + 1][y + 1]) - 8*'0' + '0';
}
更新向玩家展示的地图,把周围的地雷数相加,并用相应的数字代替。我们用的是字符数组,里面存放的是字符1和字符0,所以要减去0的ASCLL码值可以得到整形数字,把他们相加代表这个方格周围8个地方的地雷数,最后加上0的ASCLL码值得到对应的字符数字。
让我们测试一下:我们把测试代码解除注释,并把我们宏定义的地雷个数设置的大一点,我先把它设置为79,真正游玩时可以根据想要玩的难度进行更改
我们看看排雷失败会不会正常显示:
排雷失败也会正常显示。
注意:
1.scanf是不安全的
2.对所用的数组进行初始话是要注意间隔,对数组进行访问时要注意是否越界。
3.重构思路:可以加入展开地图(周围8个地方没有地雷时可以展开,知道一个格子周围有地雷为止),加入小旗(把认为是地雷的地方用小旗替换,只有取消小旗改位置才可以进行扫雷),加入时间(当时间终止时未排完所有雷则游戏结束)等。
4.五子棋
思路:判断那个玩家先进行了五子连珠(一行或一列或者对角线有连续的5颗一样的棋子)
1.打印棋盘
2.玩家1进行下棋
3.判断玩家1是否获胜
4.玩家2进行下棋
5.判断玩家2是否获胜
我们还是先用宏定义棋盘大小:
#define ROW 9 //棋盘的行
#define COL 9 //棋盘的列
再来看game思路:
static void game()
{
char win;//创建一个变量,存放判段获胜条件的字符。
//我们把C代表继续,D代表平局,白棋(@)代表玩家1获胜,黑棋(O)代表玩家2获胜
char checkerboard[ROW][COL] = { 0 };//棋盘数组
Initialization(checkerboard, ROW, COL);//初始化数组,把棋盘的所有位置都赋值为 *
Ptintinitialization(checkerboard, ROW, COL);//打印棋盘
while (1)
{
Player(checkerboard, ROW, COL, '@');//玩家1下棋,玩家1的标志为 @
win = Iswin(checkerboard, ROW, COL);//判断是否获胜
if (win != 'C')
{
break;
}
Ptintinitialization(checkerboard, ROW, COL);//打印棋盘
Player(checkerboard, ROW, COL, 'O');//玩家2下棋,玩家2的标志为 O
win = Iswin(checkerboard, ROW, COL);//判断是否获胜
if (win != 'C')
{
break;
}
Ptintinitialization(checkerboard, ROW, COL);//打印棋盘
}
if (win == 'D')
{
printf("平局\n");
Ptintinitialization(checkerboard, ROW, COL);
}
else if (win == '@')
{
printf("玩家1获胜\n");
Ptintinitialization(checkerboard, ROW, COL);
}
else
{
printf("玩家2获胜\n");
Ptintinitialization(checkerboard, ROW, COL);
}
}
五子棋的思路和三子棋的思路一模一样,只是把电脑换成了另一个玩家。所以game函数代码基本没有太大变化。而玩家下棋思路一致,所以我们只需要一个函数就可以解决两个玩家下棋的思路。
初始化数组函数:
static void Initialization(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++)
{
arr[i][j] = '*';
}
}
}
对数组进行初始化和扫雷中向玩家展示的地图的思路一模一样。
打印地图函数:
static void Ptintinitialization(char arr[ROW][COL], int row, int col)//打印棋盘
{
int i = 0;
int j = 0;
for (i = 1; i <= row; i++)
{
if (i == 1)//当i为1时,需要多两个空格,目的为了和地图对齐
{
printf(" ");
}
if (i < 9)//当i小于9时,需要多一个个空格,目的为了和地图对齐
{
printf(" %d ", i);
}
else//当i大于于9时,不需要多一个个空格
{
printf(" %d", i);
}
}
printf("\n");
for (i = 0; i < row; i++)//打印我们所需要的地图
{
printf("%2d", i + 1);
for (j = 0; j < col; j++)
{
printf(" %c ", arr[i][j]);//需要多一个个空格,目的为了和地图对齐
}
printf("\n");
}
}
五子棋的地图可以随便放大缩小,一定要注意地图是否和上面的行和列进行了对齐。
玩家下棋函数:
int x = 0;//用来接收玩家下棋的坐标
int y = 0;//用来接收玩家下棋的坐标
static void Player(char arr[ROW][COL], int row, int col, char ch)//玩家下棋函数,通过ch来判断是玩家1还是玩家2
{
int player = 0;//创建一个变量来确定是玩家几
if (ch == '@')//如果ch为白棋(@),则代表玩家1
{
player = 1;
}
else//反之则为玩家2
{
player = 2;
}
while (1)
{
printf("请玩家 %d 输入要下棋的坐标:",player);
scanf("%d %d", &x, &y);
if (x<1 || x>row || y<1 || y>col)//判断坐标是否合法
{
printf("你输入的坐标不合法,请重新输入。\n");
}
else if (arr[x - 1][y - 1] != '*')//判断输入坐标是否被占用
{
printf("你输入的坐标已被占用,请重新输入。\n");
}
else
{
arr[x - 1][y - 1] = ch;//数组下标从0开始,所以玩家的真实坐标要进行减1
break;
}
}
}
我们把玩家下棋的坐标设置为全局变量,方便我们判断是否是五子连珠的情况。
判断是否获胜函数:
enum Direction
{
LEFT,
RIGHT,
UP,
DOWN,
LEFTUP,
RIGHTDOWN,
RIGHTUP,
LEFTDOWN
};
static char Iswin(char arr[ROW][COL], int row, int col)//判断是否获胜
{
static chessnum = 0;//创建一个静态变量,用来存放棋盘中使用的格数
chessnum++;//每进行一次判断调用就对棋盘使用格数进行+1
int winnum1 = 1 + Wincount(arr, ROW, COL, LEFT) + Wincount(arr, ROW, COL, RIGHT);
//确定左和右共计多少相同个棋子,需要加上自身的棋子
int winnum2 = 1 + Wincount(arr, ROW, COL, UP) + Wincount(arr, ROW, COL, DOWN);
//确定上和下共计多少相同个棋子,需要加上自身的棋子
int winnum3 = 1 + Wincount(arr, ROW, COL, LEFTUP) + Wincount(arr, ROW, COL, RIGHTDOWN);
//确定左上和右下共计多少相同个棋子,需要加上自身的棋子
int winnum4 = 1 + Wincount(arr, ROW, COL, RIGHTUP) + Wincount(arr, ROW, COL, LEFTDOWN);
//确定右上和左下共计多少相同个棋子,需要加上自身的棋子
if (winnum1 >= 5 || winnum2 >= 5 || winnum3 >= 5 || winnum4 >= 5)//判断是否获胜
{
return arr[x - 1][y - 1];//返回获胜棋子的字符
}
else
{
if (chessnum == ROW * COL)//如果棋盘使用格数等于棋盘的格数,证明平局
{
return 'D';
}
else//如果棋盘使用格数不等于棋盘的格数,证明还有空位,继续进行比赛
{
return 'C';
}
}
}
我们需要统计这8个方向的棋子相同个数。不会产生5个连着 * 的情况,我们保存的地址永远是上一个玩家落子的地址。我们把8个方向用menu类型进行表示。
static int Wincount(char arr[ROW][COL], int row, int col, enum Direction dir)
{
int count = 0;//记录相同棋子个数
int _x = x - 1;//要对坐标进行减1,因为我们的棋盘从1开始,数组下标从0开始
int _y = y - 1;//要对坐标进行减1,因为我们的棋盘从1开始,数组下标从0开始
//用局部变量保存,避免在这里修改全局变量的值
while (1)
{
switch (dir)
{
case LEFT:
_y--;//对中心坐标的列坐标进行减1
break;
case RIGHT:
_y++;//对中心坐标的列坐标进行加1
break;
case UP:
_x++;
break;
case DOWN:
_x--;
break;
case LEFTUP:
_y--;
_x++;
break;
case RIGHTDOWN:
_y++;
_x--;
break;
case RIGHTUP:
_y++;
_x++;
break;
case LEFTDOWN:
_y--;
_x--;
break;
default:
break;
}
if (_x < 0 || _x > ROW || _y < 0 || _y>COL)//判断位置是否合法
{
return count;
}
else if(arr[_x][_y] != arr[x-1][y-1])//判断是否和上一个玩家的棋子相同
{
return count;
}
else
{
count++;
}
}
}
我们设置为死循环,用来判断相同方向中具体棋子个数。menu类型更具我们传的参数不同,在switch分支中走不同的路线。
注意:
数组的越界。
对五子连珠的判断
重构思路:可以加入网络,实现网路对战,可以加入悔棋功能等
5.飞行棋
玩法:1.除了起始位置是一样的,其余时间当一名玩家位置和另一名玩家位置相同时发生踩踏。
2.当玩家踩到道具时,进行相应的道具操作
3.那个玩家先到终点即为获胜
思路:
1.用数组来存储地图信息
2.用一个结构体来构造玩家信息
3.用一个结构体数组来存储玩家信息
4.用随机函数模拟筛子操作
我们先对game思路进行构造:
int map[100];//创建一个地图大小为100的地图。并且把地图元素全部初始化为0
//为了对数组进行方便的操作,我们把数组设置为全局变量,使每个函数可以直接访问数组
static void game()
{
struct Player play[2];//创建一个玩家数组,里面存放玩家信息
Initializationmap();//初始化地图数组,把地图相应位置填上道具元素,
//我们把地图数组设置为了全局函数,所以不要需要传参
Initializationplay(play);//初始化玩家数组,加入玩家信息,并把玩家位置置于地图开始位置
Ptintinitialization(play);//打印棋盘,地图数组为全局函数,所以不要对地图传参。
//需要玩家信息来判断地图要填的字符,所以要对玩家进行传参
while (1)//使玩家交替进行游玩,直到产生胜利者
{
if (play[0].flags == false)//判断玩家1是否处于暂停回合。
{
Play(&play[0], &play[1],play);//玩家1进行游戏
Ptintinitialization(play);//打印棋盘
}
else//处于暂停回合,把暂停回合改为继续
{
play[0].flags = false;
}
if (play[0].position >= 99)//判断玩家1是否获胜
{
printf("%s侥幸赢了%s\n", play[0].name, play[1].name);
break;
}
if (play[1].flags == false)//判断玩家2是否处于暂停回合。
{
Play(&play[1], &play[0],play);//玩家2进行游戏
Ptintinitialization(play);//打印棋盘
}
else
{
play[1].flags = false;//更改暂停选项
}
if (play[1].position >= 99)//判断玩家2是否获胜
{
printf("%s侥幸赢了%s\n", play[1].name, play[0].name);
break;
}
}
}
我们为了方便把地图数组设置位全局数组,这样减少传参。并且数组在初始化后是不需要改变的。我们另外设置了一个结构体数组用来存放玩家信息(位置,是否处于暂停位)。
初始化地图函数:
static void Initializationmap()//初始化地图数组
//地图数组中
//0代表什么都没有
//1代表道具1(幸运轮盘),2代表道具2(地雷),3代表道具3(暂停),4代表道具4(时空隧道)
{
int luckyturn[] = {1, 20, 45, 60, 75, 90};//在相应的位置放入 幸运轮盘
for (int i = 0; i < sizeof(luckyturn) / sizeof(luckyturn[0]); i++)
//sizeof(luckyturn) / sizeof(luckyturn[0])用来求出数组元素的个数
{
map[luckyturn[i]] = 1;
}
int Landmine[] = { 3, 6, 19, 25, 36, 49, 69, 70, 80 };//在相应的位置放入 地雷
for (int i = 0; i < sizeof(Landmine) / sizeof(Landmine[0]); i++)
{
map[Landmine[i]] = 2;
}
int puse[] = { 2, 11, 26, 35, 44, 59, 71, 88 };//在相应的位置放入 暂停
for (int i = 0; i < sizeof(puse) / sizeof(puse[0]); i++)
{
map[puse[i]] = 3;
}
int timetunnel[] = { 5, 15, 30, 50, 77 };//在相应的位置放入 时空隧道
for (int i = 0; i < sizeof(timetunnel) / sizeof(timetunnel[0]); i++)
{
map[timetunnel[i]] = 4;
}
}
初始化地图操作使我们在想要的位置放置合适的道具,道具的位置可以根据自己的喜欢而改变。
初始化玩家函数:
static void Initializationplay(struct Player *play)
{
printf("请输入玩家A的姓名\n");
scanf("%s", &(play[0].name));
while (!strcmp(play[0].name,""))//判断玩家A的姓名是否为空
{
printf("玩家姓名不能为空,请重新输入玩家A的姓名\n");
scanf("%s", &(play[0].name));
}
printf("请输入玩家B的姓名\n");
scanf("%s", &(play[1].name));
while (1)
{
if (!strcmp(play[1].name, ""))//判断玩家B的姓名是否为空
{
printf("玩家姓名不能为空,请重新输入玩家B的姓名\n");
scanf("%s", &(play[1].name));
}
else if(!strcmp(play[1].name, play[0].name))//判断玩家B的姓名和玩家A是否相同
{
printf("玩家姓名不能一致,请重新输入玩家B的姓名\n");
scanf("%s", &(play[1].name));
}
else
{
break;
}
}
play[0].position = 0;//把玩家A的位置置于0位置(地图开始位置)
play[0].flags = false;//把玩家A的暂停条件置为假。
play[1].position = 0;//把玩家B的位置置于0位置(地图开始位置)
play[1].flags = false;//把玩家B的暂停条件置为假。
}
在这个函数中我们进行了字符串比较,字符串比较不可以直接用 ==,必须要用strcmp函数
展示地图函数:
extern char Drawstrmap(const struct Player* play, int i);//对Drawstrmap函数进行声明
static void Ptintinitialization(const struct Player* play) //打印棋盘
//由于不会对玩家信息进行更改,我们把参数设置为const,防止在函数中误改
{
printf("图例:幸运轮盘:# 地雷:@ 暂停:I 时空隧道:> \n");//向玩家展示道具信息
//第一段
for (int i = 0; i < 30; i++)
{
printf("%c ", Drawstrmap(play, i));
}
printf("\n");
//第一竖列
for (int i = 30; i < 35; i++)
{
for (int j = 0; j <= 28; j++)
{
printf(" ");
}
printf("%c ", Drawstrmap(play, i));
printf("\n");
}
//第二段
for (int i = 64; i >= 35; i--)//地图为从前向后打打印
{
printf("%c ", Drawstrmap(play, i));
}
printf("\n");
//第二数列
for (int i = 65; i <= 69; i++)
{
printf("%c ", Drawstrmap(play, i));
printf("\n");
}
//第三行竖列
for (int i = 70; i <= 99; i++)
{
printf("%c ", Drawstrmap(play, i));
}
printf("\n");//画完地图换行
}
static char Drawstrmap(const struct Player* play, int i)//打印地图元素
{
char ch;
if (play[0].position == play[1].position && play[0].position == i)
//当玩家1和玩家2的位置处于起始位置时
{
ch = 'M';
}
else if (play[0].position == i)//当玩家1位于当前位置时
{
ch = 'A';
}
else if (play[1].position == i)//当玩家2位于当前位置时
{
ch = 'B';
}
else
{
switch (map[i])
{
case 0://地图数组元素为0时
ch = 'O';
break;
case 1:
ch = '#';
break;
case 2:
ch = '@';
break;
case 3:
ch = 'I';
break;
case 4:
ch = '>';
break;
}
}
return ch;
}
这里我们构造了一个Drawstrmap函数,目的是把地图数组中的整形元素构造为我们所需要的字符元素,我们传入地图的位置,来判断具体打印什么字符,且不对我们地图数组进行更改。
游玩函数:
static void Play(struct Player *play1, struct Player* play2, struct Player* play)
//play1为当前玩家,play2为另一名玩家,play为玩家数组,传玩家数组方便进行位置判断
{
int points = rand() % 6 + 1;//设置随机变量,范围为1~6
printf("%s请按任意键开始致筛子\n", play1->name);
system("pause");//暂停屏幕
printf("%s筛到了%d,前进%d格,", play1->name, points, points);
system("pause");
play1->position += points;//对玩家位置进行更新
Weizhi(play);//判断玩家位置是否在合法的范围内
if (play1->position == play2->position)//现在玩家的位置和另一名玩家位置相同时
{
printf("%s踩到了%s,%s退6格,", play1->name, play2->name, play2->name);
system("pause");
play2->position -= 6;//对另一名玩家位置进行更新
Weizhi(play);//判断玩家位置是否在合法的范围内
printf("%s已退6格,", play2->name);
system("pause");
}
else
{
switch (map[play1->position])//检查本位玩家是否进行到道具位置
{
case 0:
printf("%s踩到了方块,安全,", play1->name);
system("pause");
break;
case 1:
{
printf("%s踩到了幸运转盘,1————交换位置,2————轰炸对方,请选择按键\n", play1->name);
int a = 0;//用来接收用户的选择
scanf("%d", &a);
while (1)
{
if (a == 1)
{
printf("%s选择了交换位置,", play1->name);
system("pause");
int b = play1->position;
play1->position = play2->position;
play2->position = b;
Weizhi(play);//判断玩家位置是否在合法的范围内
printf("交换完成,");
system("pause");
break;
}
else if (a == 2)
{
printf("%s选择了轰炸对方,%s向后退6格,", play1->name, play2->name);
system("pause");
play2->position -= 6;
Weizhi(play);
printf("执行成功,\n");
system("pause");
break;
}
else
{
printf("输入不正确,请重新输入");
int ch;
while ((ch = fgetc(stdin)) != EOF && ch != '\n');
//清除缓冲区,防止缓冲区问题使程序不能正常进行
scanf("%d", &a);
}
}
}
break;
case 2:
printf("%s踩到了地雷,退6格,", play1->name);
system("pause");
play1->position -= 6;
Weizhi(play);
break;
case 3:
printf("%s踩到了暂停,下一回合禁止行动", play1->name);
system("pause");
play1->flags = true;
break;
case 4:
printf("%s踩到了时空隧道,前进十格", play1->name);
system("pause");
play1->position += 10;
Weizhi(play);
break;
}
}
Weizhi(play);
system("cls");//清除屏幕
}
我们在游玩函数中来进行筛子的模拟和道具的使用。我们设置清除缓冲区是为了解决scanf函数缓冲区问题,避免出现死循环。
注意:
数组的越界。我们要确保每个玩家的位置处于地图中。
字符串的比较
重构思路:1.可以加入网络,实现网路对战,2.把幸运轮盘单独封装为一个函数。3.加入更多道具。4.加入其他玩家。5可以加入积分,用积分换取道具。
总结
我们的五个小游戏已经全部做完。下面是做小游戏要注意的点:
scanf函数是不安全,我们可以寻找一个安全的函数进行替代,或者解决scanf的安全问题
谨防数组越界,我们好几个小游戏都用了数组进行存储,有字符型数组,还有整形数组,要谨防数组越界,注意数组传参。
把经常使用的代码构造为函数,减少代码冗余。