一、扫雷游戏的分析和设计
目录
1.1扫雷游戏展示
初始界面
排雷成功一次
排雷失败
1.2扫雷游戏功能说明
我们要实现这样的功能:玩家可以通过菜单实现继续玩或者退出游戏,进入游戏后,玩家输入雷的坐标,系统向玩家展示该位置的情况:若是雷,则输出被炸死且游戏结束,重新开始游戏;若不是雷,则输出一次棋盘,并在该棋盘中向玩家展示该位置四周八个位置的雷的数量;若把所有非雷的位置都找出来,则排雷成功,游戏结束。
1.3扫雷游戏功能实现
1.3.1菜单设计
使用printf输出菜单信息后,可以使用switch函数来判断,例如,输入1则进入游戏,输入2则退出游戏,输入其他数字则输出“输入错误”。该switch函数整体封装到while中,以实现循环进入游戏。
int main() {
int input = 0;
srand((unsigned int)time(NULL));
do
{
caidan();
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 2:
break;
default:
printf("你的输入有误,请重新输入\n");
break;
}
} while (input != 2);
return 0;
}
1.3.2初始化棋盘
我们可以初始化两个棋盘来实现以上功能。一个棋盘布置雷(mine棋盘),另一个棋盘标记雷(mark棋盘)。
若我们需要在9*9 的棋盘上设计游戏,首先想到的就是创建一个9*9 的数组来存放信息。使用1来说明此位置是雷,没有雷就存放0(这样方便在统计雷的数量时可直接做加法运算)。在标记雷时,我们扫描该位置一周8个位置的1的数量,并输出在mark棋盘中。但是,若我们检查的位置在边界处,那么就会报错,产生数组越界,所以我们在设计数组时将其扩大一圈,且在布置雷时最外面一圈不去布置雷,这样就有效解决了数组越界问题。所以我们将棋盘设置为11*11的数组。
那么该数组要定义为什么类型的呢?若是定义为int型,则无法很清晰的表达mine棋盘。所以使用char类型定义数组,这样封装初始化棋盘的函数较为方便。
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
char mine[ROWS][COLS] = { '0' };
char mark[ROWS][COLS] = { '0' };
//初始化棋盘
Init_Board(mine,ROWS,COLS,'0');
Init_Board(mark,ROWS,COLS,'*');
void Init_Board(char board[ROWS][COLS], int a, int b, char z) {
for (int i = 0; i < a; i++)
for (int j = 0; j < b; j++)
board[i][j] = z;
}
1.3.3打印棋盘
这里要注意我们不仅需要输出棋盘,还要输出横纵坐标。
void Print_Board(char board[ROWS][COLS],int row,int col){
for (int i = 0; i <= row;i++)
printf("%d", i);
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d", i);
for (int j = 1; j <= col; j++)
printf("%c", board[i][j]);
printf("\n");
}
}
1.3.4布置雷
利用time()函数传递时间戳来更新每一次的随机数种子,确保雷的位置是随机生成。
srand((unsigned int)time(NULL));
void Set_Board(char board[ROWS][COLS], int row, int col,int count_mine) {
while (count_mine) {
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
count_mine--;
}
}
}
其中变量count_mine代表雷的数量,使用全局变量实现。
#define COUNT_MINE 10
1.3.4扫描雷
我们在输入一个雷的坐标时,三种情况已在上述的功能说明中阐述,这里注意游戏成功的条件,即将所有雷都找到,使用win局部变量控制。
void Find_Board(char mine[ROWS][COLS], char mark[ROWS][COLS],int row,int col) {
int x = 0;
int y = 0;
int win = 0;
while (win<ROW*COL- COUNT_MINE) {
printf("请输入雷的坐标:");
scanf("%d %d", &x, &y);
if ((x > 0 && x <=row) && (y > 0 && y <= col)) {
if (mine[x][y] == '1') {
printf("失败,被雷炸死\n");
break;
}
else
{
int count = Getcount(mine, x, y);
mark[x][y] = count+'0';
Print_Board(mark, ROW, COL);
win++;
}
}
else
printf("坐标输入有误,请重新输入");
}
if (win==ROW*COL- COUNT_MINE)
{
printf("恭喜你,扫雷成功\n");
Print_Board(mine, ROW, COL);
}
}
除此之外,我们还需要一个函数来检查某处四周雷的数量
int Getcount(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');
}
二、全部源码
frame.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
#include <stdio.h>
void caidan() {
printf("******************\n");
printf("**** 1.paly ****\n");
printf("**** 2.exit ****\n");
printf("******************\n");
}
void game() {
char mine[ROWS][COLS] = { '0' };
char mark[ROWS][COLS] = { '0' };
//初始化棋盘
Init_Board(mine,ROWS,COLS,'0');
Init_Board(mark,ROWS,COLS,'*');
//打印棋盘
//Print_Board(mine,ROW,COL);
Print_Board(mark,ROW,COL);
//布置雷
Set_Board(mine, ROW, COL, COUNT_MINE);
//Print_Board(mine, ROW, COL);
//扫描雷
Find_Board(mine, mark,ROW,COL);
Print_Board(mine, ROW, COL);
}
int main() {
int input = 0;
srand((unsigned int)time(NULL));
do
{
caidan();
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 2:
break;
default:
printf("你的输入有误,请重新输入\n");
break;
}
} while (input != 2);
return 0;
}
game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define COUNT_MINE 10
//初始化棋盘
void Init_Board(char board[ROWS][COLS], int a, int b, char z);
//打印棋盘
void Print_Board(char board[ROWS][COLS],int row,int col);
//布置雷
void Set_Board(char borad[ROWS][COLS], int row, int col, int count_mine);
//扫描雷
void Find_Board(char mine[ROWS][COLS], char mark[ROWS][COLS], int row, int col);
//检查某处四周雷的数量
int Getcount(char mine, int x, int y);
game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void Init_Board(char board[ROWS][COLS], int a, int b, char z) {
for (int i = 0; i < a; i++)
for (int j = 0; j < b; j++)
board[i][j] = z;
}
void Print_Board(char board[ROWS][COLS],int row,int col){
for (int i = 0; i <= row;i++)
printf("%d", i);
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d", i);
for (int j = 1; j <= col; j++)
printf("%c", board[i][j]);
printf("\n");
}
}
void Set_Board(char board[ROWS][COLS], int row, int col,int count_mine) {
while (count_mine) {
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
count_mine--;
}
}
}
int Getcount(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 Find_Board(char mine[ROWS][COLS], char mark[ROWS][COLS],int row,int col) {
int x = 0;
int y = 0;
int win = 0;
while (win<ROW*COL- COUNT_MINE) {
printf("请输入雷的坐标:");
scanf("%d %d", &x, &y);
if ((x > 0 && x <=row) && (y > 0 && y <= col)) {
if (mine[x][y] == '1') {
printf("失败,被雷炸死\n");
break;
}
else
{
int count = Getcount(mine, x, y);
mark[x][y] = count+'0';
Print_Board(mark, ROW, COL);
win++;
}
}
else
printf("坐标输入有误,请重新输入");
}
if (win==ROW*COL- COUNT_MINE)
{
printf("恭喜你,扫雷成功\n");
Print_Board(mine, ROW, COL);
}
}
三、扫雷游戏拓展
- 是否可以选择游戏难度
- 排雷后,展开其周围非雷的位置(递归)
- 是否可以标记雷
四、总结
通过扫雷游戏的实现,不仅可以加强c语言数组、循环、条件语句、随机数的生成等知识点的学习,更是通过层层封装函数来进一步体会c语言的魅力,初学者可以根据自己的需要来设置游戏参数,让枯燥的代码变得有趣起来。大家可以先根据游戏实现的结果来自己思考实现各个功能,如果有问题可以参考上述的源码部分,或者来私聊我一起探讨。