- C语言-扫雷游戏的实现
- 游戏背景介绍
- 游戏实现思路
- test模块
- menu模块
- _game模块_
- init_board(初始化棋盘)
- DisplayBoard(打印棋盘)
- SetMine(布置雷)
- FindMine(排雷)
- 游戏全部代码
游戏最终需要呈现出以下的画面过程
游戏规则:
(1)游戏可以重复玩
(2)玩家每次选择排雷的位置后,显示其周围8个位置中有雷的个数
(3)玩家确定有雷的位置,可进行雷的标记
(4)玩家可以看到当前还有多少雷未确定位置
(5)当玩家排到有雷的位置或者标记雷的位置并不是真正的雷,则游戏结束,玩家失败
(6)当玩家排查到剩余的位置只有雷时或者标记出所有的雷后,则游戏结束,玩家胜利
游戏实现思路
我们计划创建两个源文件test.c和game.c,一个头文件game.h,来完成对这个游戏的实现。
其中,test.c文件主要用于整体框架的实现,game.c文件主要用来实现游戏中的各个步骤。
游戏的整体模块如下:
在整体框架搭建好的基础上,下面我们分别实现各模块。
test模块
同上篇博客内容一样,框架和代码都是一致的。具体不再详述。
模块代码如下:
void test()
{srand((unsigned int)time(NULL));
int input = 0;
menu();
do
{printf("\n请选择开始游戏(1)或退出游戏(0)\n");
scanf("%d",&input);
switch (input)
{case 1:
printf("\n开始游戏\n");
game();
break;
case 0:
printf("\n退出游戏\n");
break;
default:
printf("\n输入错误,请重新输入\n");
break;
}
} while (input);
}
menu模块
同上篇博客内容一样,框架和代码都是一致的。具体不再详述。
void menu()
{printf("**********************************************************\n");
printf("*********************** 1 play ***********************\n");
printf("*********************** 2 exit ***********************\n");
printf("**********************************************************\n");
}
game模块
为了实现该模块,我们理清一下思路
我们首先希望对棋盘初始化,然后随机的在棋盘内布置好雷;当玩家在选择一个位置排雷时,需要展现给玩家每次排雷后的棋盘的情况;为此,我们需要设计两个棋盘(也即是两个二维数组),一个用来放置雷(以下称为布雷棋盘),另一个用来展示给玩家(以下称为玩家棋盘)。
需要注意如下几点:
(1)对于玩家棋盘,标记出的雷的用字符’M‘表示,未知的区域我们放置字符‘*’,以‘数字字符’来表示周围雷的数量;
对于布雷棋盘,布置雷的位置我们用字符‘M’表示,没有雷的位置用字符‘0’表示
两个数组均为字符数组;
(2)当排查雷的信息时,对于位于棋盘中间的位置,它的周围8个雷的坐标不会产生越界;
而对于边缘的位置,它的周围的坐标可能会越界,这里要判断坐标的合法性
一个解决方案为:申请11x11位的数组,对于边缘的位置,就不会有越界信息报错
基于上述的信息,设计出该模块的框架如下:
模块代码如下:
void game()
{char mine[ROWS][COLS];
char show[ROWS][COLS];
//我们希望mine数组内在开始的时候全为‘0’(还未埋雷)
//show数组在开始的时候全为‘*’(表示全部都是未知量)
//初始化mine和show数组
//InitMine(mine, ROWS, COLS);
//InitShow(show, ROWS, COLS);
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘,因为我们增加的两行的行和列是为了方便显示其周围附近
//的雷,因此在打印棋盘的时候,不需要打印多余的这两行两列。
//DisplayBoard(mine,ROW,COL);
// DisplayBoard(show,ROW,COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//排雷
//排雷就是随意指定mine数组中的元素,
//1 先判断它是不是雷,如果是,那就输掉了
//如果不是,就显示在它周围的8个位置中有雷的个数,并输出到show数组中去
//2 继续排雷,首先确认这个位置是否被排过了,若被排过了,需要重新选择,
//然后进行步骤1
DisplayBoard(show, ROW, COL);
FindMine(mine,show,ROW,COL);
}
game模块部分是该游戏实现的核心,下面我们从函数角度来完成各步骤的实现
init_board(初始化棋盘)
这里我们需要对两个棋盘进行初始化,
对于布雷棋盘,我们全部初始化为‘0’;
对于玩家棋盘,我们全部初始化为‘*’
函数实现如下:
//初始化mine和show数组
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
{for (int i = 0; i< rows; i++)
{for (int j = 0; j< cols; j++)
{ board[i][j] = set;
}
}
}
这里通过
char set
来改变初始化的值
DisplayBoard(打印棋盘)
这里的棋盘我们一般考虑展示玩家棋盘,布雷棋盘只用于调试过程
为了打印出上图所示的棋盘,我们参考上篇博客棋盘的打印过程,在其基础上进行改变
我们可以看到该棋盘的第一行打印的全是数字;
第二行打印下划线和竖线的组合
第三行打印的是竖线
第四行打印的是列的标号,竖线和数组的元素
第五行打印的是下划线和竖线
第六行打印的是竖线
第七行打印的是列的标号,竖线和数组的元素
… …
可以看出第2,3,4行一直在循环进行,可以用for实现,但是这样循环的话,最后一行并没有下划线封底,为此,我们考虑,将第1,2行单独拿出,第3,4,5依次进行循环操作,就可以实现上述的棋盘
模块的框架如下:
模块代码如下:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{ printf(" ");
for (int k = 1; k<= col; k++)
{printf(" %d ", k);
}
printf("\n");//打印出第一行的数字坐标
printf(" _|");
for (int m = 0; m< col; m++)
{printf("____|");
}
printf("\n");//打印出划线分割,单独出来的一行
for (int i = 1; i<=row; i++)//从1到9
{printf(" |");
for (int p = 0; p< col; p++)
{ printf(" |");
}
printf("\n");//打印出第一种类型的划线分割
printf(" %d |", i);//列的数字坐标
for (int j = 1; j<=col; j++)
{ printf(" %c |", board[i][j]);
}
printf("\n");
printf(" _|");
for (int q = 0; q< col; q++)
{ printf("____|");
}
printf("\n");//打印出第二种类型的划线分割
}
}
SetMine(布置雷)
布置雷的过程,我们只在布雷棋盘中进行
布置雷
实际上就是向数组中10个随机位置(可通过标识符常量自由设置雷的数量)写入符号‘M’
存在一种情况,生成的随机组合x,y如果相同,视为此次组合作废
代码如下:
void SetMine(char board[ROWS][COLS], int row, int col)
{for (int i = 0; i< COUNT; i++)
{//生成x,y的随机坐标,x和y均为0-8的数字
int x = rand() % row+1;
int y = rand() % col+1;
if (board[x][y]!='M')
{ board[x][y] = 'M';
}
else
{ i--;
}
}
}
FindMine(排雷)
排雷模块,也是我们最重要且核心的模块,基于游戏规则,我们需要实现以下需求:
(1)玩家选择排雷的位置是否合法
(2)玩家选择排雷的位置是否已经被排查过
(3)玩家排查的位置是否为雷
(4)显示排查的位置的周围有雷的数量
(5)记录玩家排查雷的次数
(6)询问玩家是否需要标记已确定的雷的位置
(7)判断玩家需要标记雷的位置是否为真正的雷
(8)记录玩家标记类的次数,同时显示剩余多少雷未找到
(9)当玩家标记完所有的雷或者排查雷的次数满足一定条件时,玩家胜利,show pass 返回上层函数
(10)当排查的位置是雷或者标记的位置不是雷时,玩家失败,show fail 返回上层函数
基于以上分析,该模块的框架如下:
该模块的代码如下:
//排雷
void FindMine(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col)
{int count = 0;
int flag = 1;
int sign_num = 0;
while (count!=(row*col)-COUNT)
{printf("\n请你选择排雷的位置\n");
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
if ((x >= 1 && x<= 9) && (y >= 1 && y<= 9))
{ if (board2[x][y] != '*')
{ printf("\n该位置被排查过了,请重新选择位置\n");
continue;
}
if (board1[x][y] != 'M')
{ int sum = get_mine_count(board1, x, y);
board2[x][y] = sum + '0';
count++;
DisplayBoard(board2, ROW, COL);
int return_sing_num = sign_mine(board1, board2, ROW, COL);
if(return_sing_num!=2)
{sign_num = sign_num + return_sing_num;
printf("\n剩余未排雷的个数为:%d\n", COUNT - sign_num);
if (COUNT - sign_num == 0)
{flag = 1;
break;
}
}
else
{DisplayBoard(board1, ROW, COL);
printf("\n标记雷错误,LOSE\n");
flag = 0;
show_fail();
break;
}
}
else
{ DisplayBoard(board1, ROW, COL);
printf("\n你中雷了,LOSE\n");
flag = 0;
show_fail();
break;
}
}
else
{ printf("\n坐标非法,请重新输入\n");
continue;
}
}
if(flag==1)
{DisplayBoard(board1, ROW, COL);
printf("\n恭喜你,win\n");
show_pass();
}
}
其中,get_mine_count
函数:
//计算雷的数量
int get_mine_count(char board1[ROWS][COLS], int x, int y)
{int sum = board1[x - 1][y] + board1[x + 1][y] + board1[x][y - 1] + board1[x][y + 1]+ board1[x - 1][y - 1] + board1[x - 1][y + 1] + board1[x + 1][y - 1] +board1[x + 1][y + 1]-8*48;
return sum = sum / 29;
}
sign_mine
函数:
//标记雷的位置
//首先,玩家需不需要对该位置进行标记,如果要标记,则继续进行
//不需要标记,则跳过该过程,向下运行。
int sign_mine(char board1[ROWS][COLS],char board2[ROWS][COLS], int row, int col)
{printf("\n当前你是否需要标记雷的位置?Y/N\n");
printf("标记错误即为失败,请谨慎操作\n");
//当定义一个字符变量时,在键盘上输入一个scanf会吸收回车和空格字符,
//为了进行代码的可行性需在函数结束前加函数getchar()进行吸收
getchar();
char c=0;
scanf("%c", &c);
if (c == 'Y')
{printf("\n请输入你已经判断出是雷的坐标位置,该回合只有一次机会\n");
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
if (board1[a][b]== 'M')
{ board2[a][b] = 'M';
DisplayBoard(board2, ROW, COL);
return 1;
}
else
{ //如果说被标记的位置不是雷,那么也是排雷失败
return 2;
}
}
return 0;
}
show_pass
函数
void show_pass()
{printf("******* ******* ******* *******\n");
printf("* * * * * * \n");
printf("* * * * * * \n");
printf("* * * * * * \n");
printf("******* ******* ******* *******\n");
printf("* * * * *\n");
printf("* * * * *\n");
printf("* * * * *\n");
printf("* * * ******* *******\n");
}
show_fail
函数:
void show_fail()
{printf("******* ******* ******* * \n");
printf("* * * * * \n");
printf("* * * * * \n");
printf("* * * * * \n");
printf("******* ******* * * \n");
printf("* * * * * \n");
printf("* * * * * \n");
printf("* * * * * \n");
printf("* * * ******* *******\n");
printf("\n");
}
至此,所有模块的功能均已实现
游戏全部代码
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//扫雷的游戏规则:点击后出现的数字,表示在这个方格附近含有雷的数量
//(最多有8个,正方向4个,斜方向4个)
//排查出的雷的信息用字符1表示,因为在未知的区域内我们放置*,整个数组为
//字符数组
//而布置雷的数组我们也用字符0和1
//
//思路:布置2个棋盘,一个棋盘用来放置雷,另一个棋盘用来放置雷的信息
//
//当排查雷的信息时,对于位于棋盘中间的位置,它的周围8个雷的坐标不会产生
//越界,而对于边缘的位置,它的周围的坐标可能会越界,这里要判断坐标的合法性
//一个解决方案为:申请11*11位的数组,对于边缘的位置,就不会有越界信息报错
void game()
{char mine[ROWS][COLS];
char show[ROWS][COLS];
//我们希望mine数组内在开始的时候全为‘0’(还未埋雷)
//show数组在开始的时候全为‘*’(表示全部都是未知量)
//初始化mine和show数组
//InitMine(mine, ROWS, COLS);
//InitShow(show, ROWS, COLS);
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘,因为我们增加的两行的行和列是为了方便显示其周围附近
//的雷,因此在打印棋盘的时候,不需要打印多余的这两行两列。
//DisplayBoard(mine,ROW,COL);
// DisplayBoard(show,ROW,COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//排雷
//排雷就是随意指定mine数组中的元素,
//1 先判断它是不是雷,如果是,那就输掉了
//如果不是,就显示在它周围的8个位置中有雷的个数,并输出到show数组中去
//2 继续排雷,首先确认这个位置是否被排过了,若被排过了,需要重新选择,
//然后进行步骤1
DisplayBoard(show, ROW, COL);
FindMine(mine,show,ROW,COL);
}
void menu()
{printf("**********************************************************\n");
printf("*********************** 1 play ***********************\n");
printf("*********************** 2 exit ***********************\n");
printf("**********************************************************\n");
}
void test()
{srand((unsigned int)time(NULL));
int input = 0;
menu();
do
{printf("\n请选择开始游戏(1)或退出游戏(0)\n");
scanf("%d",&input);
switch (input)
{case 1:
printf("\n开始游戏\n");
game();
break;
case 0:
printf("\n退出游戏\n");
break;
default:
printf("\n输入错误,请重新输入\n");
break;
}
} while (input);
}
int main()
{test();
return 0;
}
game.h
#pragma once
#include#include#include#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define COUNT 2
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS],int row,int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排雷
void FindMine(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化mine和show数组
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
{for (int i = 0; i< rows; i++)
{for (int j = 0; j< cols; j++)
{ board[i][j] = set;
}
}
}
//打印棋盘
//前面的设计思路和三子棋的思路是一样的
//添加横纵坐标的思路:对于行,在进行全部的循环之前可以进行一个循环添加
//可以自己调试改变输出的格式,使其适应棋盘
//对于列,考虑在只在输出字符的那一类的行,在开始循环前,进行添加数字
//void DisplayBoard(char board[ROWS][COLS], int row, int col)
//{// printf(" ");
// for (int k = 1; k<= col; k++)
// {// printf(" %d ", k);
// }
// printf("\n");
// for (int i = 0; i< row; i++)
// {// printf("%d ", i+1);
// for (int j = 0; j< col; j++)
// {// if (j< col - 1)
// {// printf(" %c |", board[i][j]);
// }
// else
// {// printf(" %c ", board[i][j]);
// }
// }
// printf("\n");
// printf(" ");
// if (i< row - 1)
// {// for (int j = 0; j< col; j++)
// {// if (j< col - 1)
// {// printf("---|");
// }
// else
// {// printf("---");
// }
// }
// }
// printf("\n");
// }
//}
//打印棋盘,优化
//用矩形补全棋盘
//void DisplayBoard(char board[ROWS][COLS], int row, int col)
//{// printf(" ");
// for (int k = 1; k<= col; k++)
// {// printf(" %d ", k);
// }
// printf("\n");//打印出第一行的数字坐标
//
// printf(" |");
// for (int k = 0; k // printf("---|");
// }
// printf("\n");//打印出划线分割,单独出来的一行
//
// for (int i = 0; i< row; i++)
// {// printf("%d |", i + 1);//列的数字坐标
// for (int j = 0; j< col; j++)
// {// printf(" %c |", board[i][j]);
// }
// printf("\n");
//
//
// printf(" |");
// for (int j = 0; j< col; j++)
// {// printf("---|");
// }
//
// printf("\n");
// }
//}
//继续优化以实心的下划线代替虚线,同时使棋盘移动到中间位置
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{ printf(" ");
for (int k = 1; k<= col; k++)
{printf(" %d ", k);
}
printf("\n");//打印出第一行的数字坐标
printf(" _|");
for (int m = 0; m< col; m++)
{printf("____|");
}
printf("\n");//打印出划线分割,单独出来的一行
for (int i = 1; i<=row; i++)//从1到9
{printf(" |");
for (int p = 0; p< col; p++)
{ printf(" |");
}
printf("\n");//打印出第一种类型的划线分割
printf(" %d |", i);//列的数字坐标
for (int j = 1; j<=col; j++)
{ printf(" %c |", board[i][j]);
}
printf("\n");
printf(" _|");
for (int q = 0; q< col; q++)
{ printf("____|");
}
printf("\n");//打印出第二种类型的划线分割
}
}
//布置雷
//实际上就是向数组中10个随机位置写入符号‘M’
//存在一种情况,生成的随机组合x,y如果相同,视为此次组合作废
void SetMine(char board[ROWS][COLS], int row, int col)
{for (int i = 0; i< COUNT; i++)
{//生成x,y的随机坐标,x和y均为0-8的数字
int x = rand() % row+1;
int y = rand() % col+1;
if (board[x][y]!='M')
{ board[x][y] = 'M';
}
else
{ i--;
}
}
}
//计算雷的数量
int get_mine_count(char board1[ROWS][COLS], int x, int y)
{int sum = board1[x - 1][y] + board1[x + 1][y] + board1[x][y - 1] + board1[x][y + 1]+ board1[x - 1][y - 1] + board1[x - 1][y + 1] + board1[x + 1][y - 1] +board1[x + 1][y + 1]-8*48;
return sum = sum / 29;
}
//标记雷的位置
//首先,玩家需不需要对该位置进行标记,如果要标记,则继续进行
//不需要标记,则跳过该过程,向下运行。
int sign_mine(char board1[ROWS][COLS],char board2[ROWS][COLS], int row, int col)
{printf("\n当前你是否需要标记雷的位置?Y/N\n");
printf("标记错误即为失败,请谨慎操作\n");
//当定义一个字符变量时,在键盘上输入一个scanf会吸收回车和空格字符,
//为了进行代码的可行性需在函数结束前加函数getchar()进行吸收
getchar();
char c=0;
scanf("%c", &c);
if (c == 'Y')
{printf("\n请输入你已经判断出是雷的坐标位置,该回合只有一次机会\n");
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
if (board1[a][b]== 'M')
{ board2[a][b] = 'M';
DisplayBoard(board2, ROW, COL);
return 1;
}
else
{ //如果说被标记的位置不是雷,那么也是排雷失败
return 2;
}
}
return 0;
}
void show_pass()
{printf("******* ******* ******* *******\n");
printf("* * * * * * \n");
printf("* * * * * * \n");
printf("* * * * * * \n");
printf("******* ******* ******* *******\n");
printf("* * * * *\n");
printf("* * * * *\n");
printf("* * * * *\n");
printf("* * * ******* *******\n");
}
void show_fail()
{printf("******* ******* ******* * \n");
printf("* * * * * \n");
printf("* * * * * \n");
printf("* * * * * \n");
printf("******* ******* * * \n");
printf("* * * * * \n");
printf("* * * * * \n");
printf("* * * * * \n");
printf("* * * ******* *******\n");
printf("\n");
}
//排雷
void FindMine(char board1[ROWS][COLS], char board2[ROWS][COLS], int row, int col)
{int count = 0;
int flag = 1;
int sign_num = 0;
while (count!=(row*col)-COUNT)
{printf("\n请你选择排雷的位置\n");
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
if ((x >= 1 && x<= 9) && (y >= 1 && y<= 9))
{ if (board2[x][y] != '*')
{ printf("\n该位置被排查过了,请重新选择位置\n");
continue;
}
if (board1[x][y] != 'M')
{ int sum = get_mine_count(board1, x, y);
board2[x][y] = sum + '0';
count++;
DisplayBoard(board2, ROW, COL);
int return_sing_num = sign_mine(board1, board2, ROW, COL);
if(return_sing_num!=2)
{sign_num = sign_num + return_sing_num;
printf("\n剩余未排雷的个数为:%d\n", COUNT - sign_num);
if (COUNT - sign_num == 0)
{flag = 1;
break;
}
}
else
{DisplayBoard(board1, ROW, COL);
printf("\n标记雷错误,LOSE\n");
flag = 0;
show_fail();
break;
}
}
else
{ DisplayBoard(board1, ROW, COL);
printf("\n你中雷了,LOSE\n");
flag = 0;
show_fail();
break;
}
}
else
{ printf("\n坐标非法,请重新输入\n");
continue;
}
}
if(flag==1)
{DisplayBoard(board1, ROW, COL);
printf("\n恭喜你,win\n");
show_pass();
}
}
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
当前标题:C语言-扫雷游戏的实现-创新互联
链接地址:http://scgulin.cn/article/ddceeh.html