游戏规则:
排出所有不是雷的地方,则赢;如选中了是雷的地方,则会被炸死。
每选中不是雷的地方,则会在该位置显示其周围八个坐标内雷的个数
构思
扫雷需埋雷,但是给用户看到的界面必须保持神秘感,因此需要两个二维数组,且大小完全相同,便于坐标一一对应。根据自己需要设置数组类型,建议两个数组类型设置相同,便于之后的函数调用。
每次登录,首先都得给用户提供开始游戏或退出游戏的窗口,选择后会进入扫雷游戏,初始化用户界面的棋盘和布置雷的棋盘(用户不可见),系统布置雷(用户不可见),展示棋盘,开始排雷,如果有雷——被炸死,没雷——显示周围雷的数量。
因此通过函数来实现以上标红的功能。并将项目分为三个文件:
main.c: 存放游戏整体逻辑(需包含自定义头文件)
game.c: 定义和实现函数(需包含自定义头文件)
game.h:声明函数,宏定义,所需头文件声明
为了使思路更加清晰和操作方便,我们可以使用XLSX表格来帮助我们分析:
布雷区:
此处我们想要设置的游戏规格是9*9棋盘,但是需要设置成11*11的数组,因为在后面显示非雷处周围雷的个数时,为了防止数组越界,我们需要在周围套上一圈保护层,这同时也使计算雷个数的函数内容更加清晰简洁。
展示区:
主函数
#include "game.h"
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;
}
其中do while循环中的内容很常用,经常出现在三子棋等游戏中(建议熟练掌握)
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 //此处注意ROWS和COLS的定义,是ROW+2,这样以后要更改游戏规模就可以是改动范围减小,便于代码维护
#define COUNT 10 //设置埋雷的个数
//菜单
void menu();
//进入扫雷游戏
void game();
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ret);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//设置雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//计算周围雷的数量
int MineCount(char mine[ROWS][COLS], int x, int y);
//排查雷
void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col);
功能函数部分:
菜单:
void menu()
{
printf("***********************\n");
printf("******* 1.play ********\n");
printf("******* 2.exit ********\n");
printf("***********************\n");
}
游戏接口:
void game()
{
char mine[ROWS][COLS] = {0};
char show[ROWS][COLS] = {0};//这里只是一个习惯,不初始化也不影响。
//初始化布雷区和展示界面
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//排查雷
FindMine(show, mine, ROW, COL);
}
初始化棋盘:
注意:初始化棋盘包括后面所有函数,定义的都是11*11的数组
此处,除上条所说以外,还应该初始化11*11二维数组内的每一个元素,以防计算雷的个数时,遇到边缘区域会计算错误。
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ret)
{
//注意此处初始化两个数组,是按照ROWS*COLS的大小初始化的,这并不影响,因为后面的展示数组只会打印中间的ROW*COL部分
//之所以必须这样初始化,是为了方便后面计算周围雷个数,可以避免数组越界,也可以避免因周围有一部分什么都没存而计算错误的情况
int x, y;
for(x = 0; x < rows; x++)
{
for(y = 0; y < cols; y++)
{
board[x][y] = ret;
}
}
}
展示棋盘:
打印棋盘时,只会打印中间9*9的部分
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int x, y;
printf("-------扫雷--------\n");//界面优化
for(x = 0; x <= col; x++)//打印列的坐标,方便玩家输入坐标
{
printf("%d ", x);
}
printf("\n");
for(x = 1; x <= row; x++)
{
printf("%d ", x);//打印行的坐标,方便玩家输入坐标
for(y = 1; y <= col; y++)
{
printf("%c ", board[x][y]);
}
printf("\n");
}
printf("\n");//此为美观界面
}
布置雷:
需要用到:rand()函数,且须用srand((unsigned int)time(NULL))进行初始化,以防止伪随机数(即:有规律的随机数)的出现。
rand()%row 表示:产生0 到 row-1之间的数
and()%row+1 表示:产生1 到 row 之间的数
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = 0;
while(count < COUNT)
{
int x = rand()%row + 1;
int y = rand()%col + 1;
if(mine[x][y] == '0')//要考虑是否已被占位
{
mine[x][y] = '1';
count++;//用于计数,且一定要放在if语句中,确保一定布置了指定雷的个数
}
}
}
排查雷:
void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
int x;
int y;
int count = 0;
while(count < row*col-COUNT)
{
printf("请输入排雷坐标:");
scanf("%d %d",&x, &y);
printf("\n");//此为美观界面
if(x >= 1 && x <= row && y >= 1 && y <= col)
{
if(mine[x][y] == '1')
{
printf("很遗憾,你被炸死了!\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int c = MineCount(mine,x,y);
show[x][y] = c + '0';//一个数加上一个字符'0',则会变成‘数’
DisplayBoard(show, ROW, COL);
count++;
}
}
else
printf("坐标非法,请重新输入");
}
if(count == row*col - COUNT)
{
printf("恭喜你!排雷成功!\n");
DisplayBoard(mine, ROW, COL);
}
}
计算周围雷的个数:
int MineCount(char mine[ROWS][COLS], int x, int y)
{
return
mine[x-1][y-1] +
mine[x-1][y] +
mine[x-1][y+1] +
mine[x][y-1] +
mine[x][y+1] +
mine[x+1][y-1] +
mine[x+1][y] +
mine[x+1][y+1] - 8*'0';
}