目录
1 扫雷游戏功能说明
1.1 扫雷游戏介绍
使用控制台实现经典扫雷游戏:
输入“m n”排查雷,输入“f m n”标记/取消标记,m为行数,n为列数;
可以选择游戏难度:
简单 9*9 棋盘,10个雷
中等 16*16 棋盘,40个雷
困难 16*30 棋盘,99个雷
根据当前格雷数及标记,自动展开周围非雷格;
显示游玩时间。
1.2 游戏界面
数字代表周围雷数,*为未排查区域,#为标记。失败时*为地雷。
2 游戏分析与设计
2.1 读入用户指令
为防止用户非法指令造成程序死循环,可以直接使用fgets函数读取整行,再用sscanf解析信息:
int x,y;
char line[100];
while(fgets(line, sizeof(line), stdin)){
if (sscanf(line, "%d%d", &x, &y) == 2){
if (x >= 1 && x <= row && y >= 1 && y <= col){
break;
}else{
printf("坐标非法,重新输入\n");
}
}else{
printf("输入错误,请输入两个整数\n");
}
}
2.2 地雷数据生成,处理与储存
如图,以9*9棋盘为例,用一个 长宽=边界+2 的二维数组储存地雷信息,“0”为无雷,“1”为有雷。通过搜索周围确定当前格显示雷数,存入另一个二维数组。因为多开两行,搜索时无需考虑超出边界的问题。
我们可以设计两个二维数组分别储存实际雷和显示雷数: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;
}
}
}
接下来是利用rand函数实现随机填雷。有关rand函数详见【C语言】猜数字游戏-CSDN博客
void SetMine(char board[ROWS][COLS], int row, int col, int a, int b){//生成随机的坐标,布置雷
int count = COUNT;
while (count){
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0' && !(a==x && b==y)){//此处传入开局坐标避免开局暴毙
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');
}
2.3 地雷标记及展开
如果根据已有信息及标记可以判断某些格子没有地雷,需要帮助玩家把这些格子自动展开。
首先判断周围的标记数量:
int GetFlagCount(char show[ROWS][COLS], int x, int y){
int cnt = 0;
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if ((dx == 0 && dy == 0)) continue; // 跳过自身
if(show[x+dx][y+dy]=='#') cnt++;
}
}
return cnt+'0';
}
展开:
int explore(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b){
if(mine[a][b]=='1') return 1;//踩雷返回1
if(show[a][b]=='*'){
int count = GetMineCount(mine, a, b);//该位置不是雷,就统计这个坐标周围有几个雷
show[a][b] = count + '0';
win++;//统计已经展开的格子,用于判断胜利条件
}
if(GetFlagCount(show, a, b)-show[a][b]==0){
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if ((dx == 0 && dy == 0) || (a+dx<1 || a+dx>row) || (b+dy<1 || b+dy>col || show[a+dx][b+dy]!='*')) continue; // 跳过自身、边界及排查过的区域
if(explore(mine, show, row, col, a+dx, b+dy)) return 1;
}
}
}
return 0;
}
2.4 用户界面
扫雷界面展示show数组内容:
void DisplayBoard(char board[ROWS][COLS], int row, int col){
endTime = clock();//更新时间
printf("------扫雷游戏------ 用时:%.2lfs\n", ((double) (endTime - startTime)) / CLOCKS_PER_SEC);
printf(" 0 ");
for (int i = 1; i <= col; i++){
if(i<10) printf(" %d", i);
else if(i==10) printf(" X");
else if(i==20) printf(" D");
else printf(" %d", i%10);
}
printf("\n\n");
for (int i = 1; i <= row; i++){
printf("%2d ", i);
for (int j = 1; j <= col; j++){
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("\n");
}
失败展示mine数组内容:
void DisplayOver(char board[ROWS][COLS], int row, int col){
printf("------扫雷游戏-----\n");
printf(" 0 ");
for (int i = 1; i <= col; i++){
if(i<10) printf(" %d", i);
else if(i==10) printf(" X");
else if(i==20) printf(" D");
else printf(" %d", i%10);
}
printf("\n\n");
for (int i = 1; i <= row; i++){
printf("%2d ", i);
for (int j = 1; j <= col; j++){
if(board[i][j]=='1') printf("* ");
else printf(" ");
}
printf("\n");
}
printf("\n");
}
2.5 游戏循环主体
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b){
win = 0;//记录已展开格数
int x = a;//传入第一次搜索坐标
int y = b;
char f;
int flag = 1;
char line[100];
if(flag) {flag=0;goto here;}
while(win <row*col- COUNT && fgets(line, sizeof(line), stdin)){
if (sscanf(line, "%d%d", &x, &y) == 2){
if (x >= 1 && x <= row && y >= 1 && y <= col){
here: if (explore(mine, show, row, col, x, y)){
printf("很遗憾,你被炸死了\n");
DisplayOver(mine, row, col);
break;
}else{
if(win <row*col- COUNT) DisplayBoard(show, row, col);
}
}else{
printf("坐标非法,重新输入\n");
}
}else if(sscanf(line, "%c%d%d", &f, &x, &y) == 3){
if (x >= 1 && x <= row && y >= 1 && y <= col && f=='f'){
if (show[x][y] == '*'){
show[x][y] = '#';
DisplayBoard(show, row, col);
}else if(show[x][y] == '#'){
show[x][y] = '*';
DisplayBoard(show, row, col);
}else{
printf("非法指令,重新输入\n");
}
}else{
printf("非法指令,重新输入\n");
}
}else{
printf("输入错误,请输入两个整数\n");
}
}
if (win == row * col - COUNT){
endTime = clock();
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
}
}
3 代码实现
代码分为game.h,game.c,test.c三个文件,分别储存声明,函数,主体,在test文件运行。
game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 16
#define COL 30
#define ROWS ROW+2
#define COLS COL+2
int COUNT,row,col,rows,cols,win;
clock_t startTime, endTime;
//初始化难度
void difficulty();
//初始化棋盘
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, int a, int b);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b);
//展开雷
int explore(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b);
//游戏结束
void DisplayOver(char board[ROWS][COLS], int row, int col);
game.c
#include "game.h"
void difficulty(){
int diff;
char l[100];
printf("*******************\n");
printf("**** 1. easy ******\n");
printf("**** 2. medium ****\n");
printf("**** 3. hard ******\n");
printf("*******************\n");
printf("请选择难度:>");
while(fgets(l, sizeof(l),stdin)){
if(sscanf(l,"%d",&diff)==1){
if(diff<=3 && diff>=0){
switch (diff) {
case 1:
COUNT=10;
row=9;
col=9;
break;
case 2:
COUNT=40;
row=16;
col=16;
break;
case 3:
COUNT=99;
row=16;
col=30;
break;
default:
break;
}break;
}else{
printf("输入错误,请重新输入\n");
}
}else{
printf("输入错误,请重新输入\n");
}
}
rows=row+2;
cols=col+2;
}
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){
endTime = clock();
printf("------扫雷游戏------ 用时:%.2lfs\n", ((double) (endTime - startTime)) / CLOCKS_PER_SEC);
printf(" 0 ");
for (int i = 1; i <= col; i++){
if(i<10) printf(" %d", i);
else if(i==10) printf(" X");
else if(i==20) printf(" D");
else printf(" %d", i%10);
}
printf("\n\n");
for (int i = 1; i <= row; i++){
printf("%2d ", i);
for (int j = 1; j <= col; j++){
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("\n");
}
void SetMine(char board[ROWS][COLS], int row, int col, int a, int b){//生成随机的坐标,布置雷
int count = COUNT;
while (count){
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0' && !(a==x && b==y)){
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');
}
int GetFlagCount(char show[ROWS][COLS], int x, int y){
int cnt = 0;
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if ((dx == 0 && dy == 0)) continue; // 跳过自身
if(show[x+dx][y+dy]=='#') cnt++;
}
}
return cnt+'0';
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b){
win = 0;
int x = a;
int y = b;
char f;
int flag = 1;
char line[100];
if(flag) {flag=0;goto here;}
while(win <row*col- COUNT && fgets(line, sizeof(line), stdin)){
if (sscanf(line, "%d%d", &x, &y) == 2){
if (x >= 1 && x <= row && y >= 1 && y <= col){
here: if (explore(mine, show, row, col, x, y)){
printf("很遗憾,你被炸死了\n");
DisplayOver(mine, row, col);
break;
}else{
if(win <row*col- COUNT) DisplayBoard(show, row, col);
}
}else{
printf("坐标非法,重新输入\n");
}
}else if(sscanf(line, "%c%d%d", &f, &x, &y) == 3){
if (x >= 1 && x <= row && y >= 1 && y <= col && f=='f'){
if (show[x][y] == '*'){
show[x][y] = '#';
DisplayBoard(show, row, col);
}else if(show[x][y] == '#'){
show[x][y] = '*';
DisplayBoard(show, row, col);
}else{
printf("非法指令,重新输入\n");
}
}else{
printf("非法指令,重新输入\n");
}
}else{
printf("输入错误,请输入两个整数\n");
}
}
if (win == row * col - COUNT){
endTime = clock();
printf("恭喜你,排雷成功!\n");
DisplayBoard(mine, row, col);
}
}
int explore(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b){
if(mine[a][b]=='1') return 1;
if(show[a][b]=='*'){
int count = GetMineCount(mine, a, b);//该位置不是雷,就统计这个坐标周围有几个雷
show[a][b] = count + '0';
win++;
}
if(GetFlagCount(show, a, b)-show[a][b]==0){
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
if ((dx == 0 && dy == 0) || (a+dx<1 || a+dx>row) || (b+dy<1 || b+dy>col || show[a+dx][b+dy]!='*')) continue; // 跳过自身、边界及排查过的区域
if(explore(mine, show, row, col, a+dx, b+dy)) return 1;
}
}
}
return 0;
}
void DisplayOver(char board[ROWS][COLS], int row, int col){
printf("------扫雷游戏-----\n");
printf(" 0 ");
for (int i = 1; i <= col; i++){
if(i<10) printf(" %d", i);
else if(i==10) printf(" X");
else if(i==20) printf(" D");
else printf(" %d", i%10);
}
printf("\n\n");
for (int i = 1; i <= row; i++){
printf("%2d ", i);
for (int j = 1; j <= col; j++){
if(board[i][j]=='1') printf("* ");
else printf(" ");
}
printf("\n");
}
printf("\n");
}
test.c
#include "game.h"
#include "game.c"
void menu(){
printf("*******************\n");
printf("***** 1. play *****\n");
printf("***** 0. exit *****\n");
printf("*******************\n");
}
void game(){
char mine[ROWS][COLS];//存放布置好的雷
char show[ROWS][COLS];//存放排查出的雷的信息
difficulty();//设置难度 //初始化棋盘
InitBoard(mine, rows, cols, '0');//1. mine数组最开始是全'0'
InitBoard(show, rows, cols, '*');//2. show数组最开始是全'*'
startTime = clock();
DisplayBoard(show, row, col);//打印棋盘
int x,y;//防止开局暴毙
char line[100];
printf("请输入要排查的坐标:>");
while(fgets(line, sizeof(line), stdin)){
if (sscanf(line, "%d%d", &x, &y) == 2){
if (x >= 1 && x <= row && y >= 1 && y <= col){
break;
}else{
printf("坐标非法,重新输入\n");
}
}else{
printf("输入错误,请输入两个整数\n");
}
}
SetMine(mine, row, col, x, y);//1. 布置雷
FindMine(mine, show, row, col, x, y);//2. 排查雷
}
int main(){
int input = 0;
srand((unsigned int)time(NULL));
do{
menu();
printf("请选择:>");
scanf("%d", &input);
getchar();
switch (input){
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
标签:ROWS,游戏,int,COLS,C语言,char,扫雷,printf,row
From: https://blog.csdn.net/Morihiro/article/details/137001807