这是目录哦
总体思路🎉
建立三个文件。
一个头文件game.h用来写游戏主要函数的声明。
两个源文件,一个test.c用来写主函数和菜单,一个game.c用来写game.h中函数的具体实现。
1.主函数(test.c_main();)🐾
我们需要选择玩 或者 不玩,如果玩就进入 game(); 执行游戏,如果不玩就退出程序。(每个源文件都是的单独编译的)
为了让游戏界面更美观,以及提示用户玩或者不玩分别需要输入什么,我们需要一个菜单。
从主函数开始写,明确思路。我们需要写一个game();函数来玩游戏。另外两个选择要有提示语,特别需要注意的是,应当写一个default来应对用户输入错误的情况。
do…while:无论如何,菜单都要执行一次。
❤️菜单至少执行一次、game()、提示语。
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;
}
2.菜单(test.c_menu();)🐾
void menu()
{
printf("***********************\n");
printf("***** 1. play *****\n");
printf("***** 0. exit *****\n");
printf("***********************\n");
}
3.创建两个数组(test.c_game();)🐾
数组的大小
为了计算边缘位置的周围雷的个数,应该扩大一层。
即为了计算下面小黑方块周围雷的个数。
所以数组的行、列长度应该分别为“雷区的行+2”、“雷区的列+2”,如要设置22的雷区,应该设置44的数组。
数组的个数
mine数组:如何放置雷。
show数组:游戏体验感。
设置两个数组的原因:1.游戏体验感 2.避免计算周围雷的个数时出现混淆。
1.游戏体验感
2.避免计算周围雷的个数时出现混淆
(用0表示无雷,用1表示有雷)
下图中的1有歧义:“所排查位置有雷” 或 “所排查位置的周围有1个雷”。
不用其它符号表示的原因:1.棋盘信息太多 2.效率低下。
下图中打印周围雷的个数,每次打印前都要判断3次,“是#吗?” “是*吗?” “是数字吗?” 导致效率低下。
4.初始化棋盘(game.c_InitBoard();)🐾
把mine数组初始化为’0’,即全部无雷。
把show数组初始化为’*',即全部未知(游戏体验感)。
int j = 0 放外面也一样,反正每次进入for都会 j = 0。
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
二维数组作为参数:
• 形参如果是一维数组,数组大小可以省略不写。
• 形参如果是二维数组,行可以省略,但是列不能省略。
• 数组传参,形参是不会创建新的数组的。
• 形参操作的数组和实参的数组是同一个数组。
命名经验✨
参数名小写,由不止一个单词组成的函数名间隔大写,只有一个单词就用小写。
set:不用写死,例如set=‘0’ 、set=‘*’ 。set接收的是字符哦!
❤️二维数组作为参数、命名、不要写死。
去game.h写函数声明:
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
5.打印棋盘(game.c_DisplayBoard();)🐾
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("--------扫雷游戏-------\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
游戏开始的提示语:都是打印,所以直接放打印棋盘这个函数里,不要放主函数,太乱。
printf("--------扫雷游戏-------\n");
打印行列数字:方便排查雷时写坐标。
记得写\n
❤️提示语、坐标、\n。
6.布置雷(game.c_SetMine();)🐾
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
count:
- 含义: 要布置的雷的个数
- 作用: 配合while()语句方便停下。
while经验✨
已知具体循环次数,直接while(循环次数),然后在适当位置 循环次数减减,当循环次数减到0时,while()内就为假,循环就进不去了。
产生随机数:
-
以时间作为随机数种子:game.h包含头文件<time.h>→只需要在主函数里设置一次种子→生成随机数(横纵坐标都是随机数哦!)
-
生成一定范围内的随机数:
int random_number = min + rand() % (max - min + 1);
rand() % (max - min) 中%运算符是取模运算,它返回两个数相除的余数。
余数的范围是 0 到 (除数 - 1)。
为了包含 max,需要将除数的范围扩展到 (max - min + 1)。
防止重复布置雷:只有为0,即该位置无雷时才布置雷。只有成功布置了雷,才count减减。
去game.h写函数声明:
void SetMine(char board[ROWS][COLS], int row, int col);
❤️结束循环的办法(count、while)、随机数、避免重复。
7.排查雷(FindMine();)🐾
思路:写FindMine→中途发现经常要排查周围雷的个数,于是拆分出一个GetMineCount函数→想好GetMineCount函数要实现什么,继续写FindMine(就像先写主函数思路,再写具体函数一样)→写GetMineCount函数。
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y -
1] + mine[x + 1][y] +
mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
GetMineCount();
横坐标不变,纵坐标变;纵坐标不变,横坐标变。
要计算周围雷的个数,只需要把周围坐标上对应的数字(字符形式)加起来再减去8*48就可以了。因为:
- mine是字符型数组。
- 字符’0’的ASCII码值是48。
- 数字的ASCII码值=48+数字本身。如 ‘1’=48+1=49;‘2’=48+2=50。
- 字符形式的直接等于它的ASCII码值。
要方便玩家找坐标。
FindMine();
进入循环的条件是“还没有排完雷”:
while (win < row * col - EASY_COUNT)
- row*col是可排查位置的总数。
- EASY_COUNT是雷的个数。
- row *col - EASY_COUNT是安全位置的个数。
- 每安全一次就win+1,如果中途踩到雷,就break退出while循环(if 不是循环,所以break退出的是while。);如果一直安全,那win就会++到等于row *col - EASY_COUNT,排雷成功。
第一个if:坐标合法(范围是从1到“横、纵坐标的最大值”)才能进入。
if (x >= 1 && x <= row && y >= 1 && y <= col)
第二个if:等于字符’1’就说明踩到雷了。break退出while循环。
if (mine[x][y] == '1')
当前位置的周围雷的个数:
show[x][y] = count + '0';
- show是字符型数组。
- count是整型。
- 字符型数字=整型数字 +‘0’。
DisplayBoard:最后把雷的布置打印出来,让玩家“死”的明白。
win++:每安全一次,就win+1。
第三个if:如果win能够++到与安全位置的个数相等,就说明已经排查了所有安全位置,那剩下的就是雷了,排查成功。
完整代码🎉
test.c:记得包含game.h
#include "game.h"
void menu()
{
printf("***********************\n");
printf("***** 1. play *****\n");
printf("***** 0. exit *****\n");
printf("***********************\n");
}
void game()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
DisplayBoard(show, ROW, COL);
SetMine(mine, ROW, COL);
FindMine(mine, show, ROW, COL);
}
game.h:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+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 mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c:记得包含game.h
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("--------扫雷游戏-------\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y -
1] + mine[x + 1][y] +
mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
}
}
不要马虎哦🙌
- 数组作为参数,直接传递数组名。
- 初始化后要打印show数组,让玩家开始选要排查的坐标。
- 别忘记打印提示横纵坐标的数字,别把这个和平时数学里习惯的列出的坐标等同哦,因为玩家体验感优先。
- 有很多地方需要换行哦,别忘了加上\n。
- 雷区的横纵坐标都是从1开始的哦!
- SetMine()函数里随机数坐标的生成要放进循环里哦,不然只能生成一个。
- 别把“安全位置的个数”和“雷的个数”搞混。
- 判断相等是“==”哦。
- 打印数组的时候,是从1开始,到row和col哦。(i = 1;i <= row;i++)
10.GetMineCount()函数里的坐标不要加错哦,如果前面加少了,后面又减了8*‘0’,那返回的值就是负数了哦 。返回的值还要加’0’变成字符型,很乱诶,这会导致得不到我们想要的东西哦。
💎💎💎💎💎💎 恭喜! 恭喜! 排雷成功! 💎💎💎💎💎💎