描述
用C语言实现扫雷小游戏。
要求
1、第一步不能被炸死。
2、输入一个坐标,可以展开一片。
思路
先给出一个头文件game.c,是我们自定义的一个头文件,里面有我们在写扫雷程序里面所需要的所有头文件、常量还有所有函数的声明。
game.h
#define _CRT_SECURE_NO_WARNINGS 1
#ifndef __GAME_H__
#define __GAME_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS (ROW+2)
#define COLS (COL+2)
#define EASY_MODE 10
int book[ROWS][COLS];
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void Init(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void setMine(char mine[ROWS][COLS], int row, int col);
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int GetCount(char mine[ROWS][COLS], int x, int y);
void SafeMove(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void open(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
int CountShow(char show[ROWS][COLS], int row, int col);
#endif //__GAME_H__
接下来就是我整个程序的设计思路!!!!
1、首先最重要的是先写主函数(main()),然后根据主函数的需要来设计自定义函数。
int main(void)
{
int i = 0;
int j = 0;
int input = 0;
srand((unsigned int)time(NULL));
//调用菜单函数,打印游戏菜单
menu();
do
{
printf("请输入您要选的选项:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("输入错误!请重新输入!\n");
break;
}
} while (input);
system("pause");
return 0;
}
因为这个游戏不能只玩一次就退出了,选择权在玩家手里,所以我们需要一个循环来判断玩家还想不想玩。
2、然后根据主函数的需要,我们先写一个菜单函数,这个函数很简单,只需要输出就好,菜单的样子也是根据你的喜好来设计。
void menu()
{
printf("***********************************\n");
printf("******* 1. play *******\n");
printf("******* 0. exit *******\n");
printf("***********************************\n");
}
3、接下来我们需要game()函数啦,在这个函数里我们将要实现扫雷游戏的全部内容,也是一样,我们在这个函数里先写一个大的框架,需要什么函数我们先写下名字,然后再根据我们的需要去在game.c里设计所有我们需要的自定义函数。首先需要两个数组,分别为mine[][] 和 show[][] 用来记录扫雷盘,mine数组用来存哪里是地雷,哪里不是地雷。show数组用来给玩家显示玩游戏的过程。
扫雷的面板是9X9,但是我们要给数组11X11,因为在算某个坐标的周围有多少个地雷的时候我们需要扫描他周围8个格子,目前我的水平,还做不到让它根据实际情况去扫描,所以我给二维数组多定义一圈,让它们初始化跟内圈一样为‘0’,这样扫描的时候就不算那些格子了。所以,我们需要初始化函数,还有打印的函数,不打印怎么玩!!!!!
其次,我们要一个函数来随机摆放地雷的位置,那么就有了setMine函数。这里说一下,因为随机所以需要rand()库函数,但是调用rand之前,要在main里面写一句
srand((unsigned int)time(NULL));
要不然rand的随机值每次开始都一样。
因为第一步不能炸死,我们就需要设计SaveMove函数,第一步走完,要判断一下玩家赢了没有,说不定就一个地雷呢!随便走一步,多数情况就赢了!所以需要一个CountShow函数来算现在show数组里还有多少个‘*’,如果‘*’跟地雷的数目一样,那么玩家就赢了呀!
如果第一步没有赢,那就往下走,因为往往需要多次才能赢,我们就设计一个循环实现,先来个死循环while(1),赢了或者输了跳出去就行了嘛!
再来一个FindMine函数,来判断玩家踩到地雷了没有,踩到了就返回1,没踩到就返回0 。
如果 没踩到就继续判断一个赢了没有呀!
那么如果没有赢也没有输,那就再打印一遍show 数组,让玩家继续玩下去,当然是修改后的show数组!
void game()
{
int is = 0;
//mine 数组存储雷的位置 0 为空,1 为雷
char mine[ROWS][COLS] = { 0 };
//show 数组存储游戏盘,并且在每次点击后显示周围雷的个数
char show[ROWS][COLS] = { 0 };
//初始化mine 数组所有元素为'0',show 数组所有元素为'*'
Init(mine, show, ROWS, COLS);
//设置地雷
setMine(mine, ROWS, COLS);
//打印mine 数组
DisplayBoard(mine, ROWS, COLS);
printf("\n");
printf("-------------------------\n");
printf("\n");
//打印游戏面板
DisplayBoard(show, ROWS, COLS);
//第一步不被炸死
SafeMove(mine, show, ROWS, COLS);
//如果面板上剩下的'*' 与地雷的总数相同时,玩家赢
if (CountShow(show, ROWS, COLS) == EASY_MODE)
{
printf("-------------------------\n");
DisplayBoard(mine, ROWS, COLS);
printf("恭喜你!你赢了!\n");
return;
}
/*DisplayBoard(mine, ROWS, COLS);
printf("\n");
printf("-------------------------\n");
printf("\n");*/
DisplayBoard(show, ROWS, COLS);
while (1)
{
//判断玩家选择的坐标是不是地雷,若是则返回1,若不是则展开附近地雷信息
is = FindMine(mine, show, ROWS, COLS);
//如果面板上剩下的'*' 与地雷的总数相同时,玩家赢
if (CountShow(show, ROWS, COLS) == EASY_MODE)
{
printf("-------------------------\n");
DisplayBoard(mine, ROWS, COLS);
printf("恭喜你!你赢了!\n");
break;
}
if (is == 1)
{
printf("BOOM!!!!!!!!!!\n");
printf("很遗憾,你被炸死了!\n");
DisplayBoard(mine, ROWS, COLS);
break;
}
/* DisplayBoard(mine, ROWS, COLS);
printf("\n");
printf("-------------------------\n");
printf("\n");*/
DisplayBoard(show, ROWS, COLS);
}
}
在注释掉的显示mine数组的部分,是调试的时候我们需要知道地雷在哪里,我们才能实现我们想要的结果,当然,如果你聪明绝顶,那就当我刚才放了个屁。
4、接下来我们要开始写game()函数里面所需要的函数了,就像刚才所说的,最基本的就是初始化和打印函数了,这个没有什么难的,不过初始化函数里用到了memset()库函数,这个函数参数依次是,(需要修改的内存起始地址, 需要修改为的字符 , 字符的个数是多少)
void Init(char board[ROWS][COLS], int row, int col, char ch)
{
memset(&board[0][0], ch, row*col*sizeof(board[0][0]));
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//打印横坐标
printf("0 ");
for (i = 1; i < row - 1; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
5、下来就是设置地雷的函数。需要注意的点已经在第3点里面提到了!
void setMine(char mine[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = EASY_MODE;
while (count)
{
//我们需要9个数字,如果%10产生的是0~8, 9个数字
//但是我们需要1~9,所以先%9产生8个数字,再加1就好
x = rand() % 9 + 1;
y = rand() % 9 + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
6、下来就是第一步不能被炸死这个函数SaveMove。
void SafeMove(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int flag = 1;
int count = 0;
char ch = 0;
while (1)
{
printf("请输入您要选择的坐标:");
scanf("%d%d", &x, &y);
if (x < 1 && x > COL && y < 1 && y > ROW)
{
printf("您输入的坐标超过当前地图,请重新输入!\n");
continue;
}
if (mine[x][y] == '1')
{
//如果玩家输入的坐标是1,则强行把这个坐标改为0
mine[x][y] = '0';
char ch = GetCount(mine, x, y);
show[x][y] = ch + '0';
book[x][y] = 1;
open(mine, show, x, y);
//将一个地雷改为了空地,则需要再随机填一个雷
while (flag)
{
x = rand() % 9 + 1;
y = rand() % 9 + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
flag--;
break;
}
}break;
}
else if (mine[x][y] == '0')
{
char ch = GetCount(mine, x, y);
show[x][y] = ch + '0';
book[x][y] = 1;
open(mine, show, x, y);
break;
}
}
}
7、第一步走完,需要判断一下玩家赢了没有,说不定就是那么腻害!怎么判断赢了没有呀!往前翻!!!不认真看!
所以需要一个CountShow函数来算现在show数组里还有多少个‘*’,如果‘*’跟地雷的数目一样,那么玩家就赢了呀!
int CountShow(char show[ROWS][COLS], int row, int col)//判断剩余未知区域的个数,个数为雷数时玩家赢
{
int count = 0;
int i = 0;
int j = 0;
for (i = 1; i < row - 1; i++)
{
for (j = 1; j < col - 1; j++)
{
if (show[i][j] == '*')
{
count++;
}
}
}
return count;
}
8、第一步走完了,那么就要走下一步了,我们就有了FindMine函数,用来判断玩家走的是不是地雷所在的位置。
//踩到雷返回1,没有踩到雷返回0
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
printf("请输入您选择的坐标:");
scanf("%d%d", &x, &y);
if (x < 1 && x > COL && y < 1 && y > ROW)
{
printf("您输入的坐标超过当前地图,请重新输入!\n");
}
else
{
if (mine[x][y] == '0')
{
char ch = GetCount(mine, x, y);
show[x][y] = ch + '0';
book[x][y] = 1;
open(mine, show, x, y);
if (CountShow(show, row, col) == EASY_MODE)
{
return 0;
}
}
else if (mine[x][y] == '1')
{
return 1;
}
}
return 0;
}
9、接下来,最重要的一步!!!!!!!!
如果我们选择一个坐标,那个坐标不是地雷,我们就要像别人家的扫雷一样,去展开一片没有雷的区域。那么这个区域什么时候是个头呢,那就是要被提示附近有地雷的数字包围起来!
这个函数用到了深度优先算法,里面有递归所以要好好看。
注意:next数组用来存放一组上下左右移动的方向。
tx和ty用来存放下一步的坐标
而且这个函数里用到了算周围有几个地雷的函数,GetCount函数一并附在下面!
//用深度优先搜索来对面板展开
void open(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int tx = 0;
int ty = 0;
int k = 0;
//定义next数组,用来对四个方向进行搜索
int next[4][2] = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
//在展开的时候如果GetCount函数返回的不是0,则给show数组赋返回的值
if (GetCount(mine, x, y) != 0)
{
show[x][y] = GetCount(mine, x, y) + '0';
}
//如果是0,给show数组那个位置赋空格
else if (GetCount(mine, x, y) == 0)
{
show[x][y] = ' ';
}
for (k = 0; k <= 3; k++)
{
tx = x + next[k][0];
ty = y + next[k][1];
if (tx < 1 || tx > ROW || ty < 1 || ty > COL)
{
continue;
}
if (mine[tx][ty] == '0' && book[tx][ty] == 0 && GetCount(mine, tx, ty) < 1)
{
book[tx][ty] = 1;
open(mine, show, tx, ty);
}
//这一步很关键,是展开时遇到数字停下来的一步
if (mine[tx][ty] == '0' && book[tx][ty] == 0 && GetCount(mine, tx, ty) > 0)
{
show[tx][ty] = GetCount(mine, tx, ty) + '0';
continue;
}
}
return;
}
//统计面板上剩下的'*'
int CountShow(char show[ROWS][COLS], int row, int col)//判断剩余未知区域的个数,个数为雷数时玩家赢
{
int count = 0;
int i = 0;
int j = 0;
for (i = 1; i < row - 1; i++)
{
for (j = 1; j < col - 1; j++)
{
if (show[i][j] == '*')
{
count++;
}
}
}
return count;
}
int GetCount(char mine[ROWS][COLS], int x, int y)
{
int count = 0;
//分别对(x,y)坐标周围8个格子进行判断,如果是雷则count + 1
//最后返回count就是(x,y)周围的地雷个数
if (mine[x - 1][y] == '1')
count++;
if (mine[x - 1][y - 1] == '1')
count++;
if (mine[x][y - 1] == '1')
count++;
if (mine[x + 1][y - 1] == '1')
count++;
if (mine[x + 1][y] == '1')
count++;
if (mine[x + 1][y + 1] == '1')
count++;
if (mine[x][y + 1] == '1')
count++;
if (mine[x - 1][y + 1] == '1')
count++;
return count;
}
源代码
game.h
#define _CRT_SECURE_NO_WARNINGS 1
#ifndef __GAME_H__
#define __GAME_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS (ROW+2)
#define COLS (COL+2)
#define EASY_MODE 10
int book[ROWS][COLS];
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void Init(char board[ROWS][COLS], int row, int col, char ch);
void setMine(char mine[ROWS][COLS], int row, int col);
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int GetCount(char mine[ROWS][COLS], int x, int y);
void SafeMove(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void open(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
int CountShow(char show[ROWS][COLS], int row, int col);
#endif //__GAME_H_
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
/*
* 实现扫雷游戏的测试
* 要求:
* 1、第一步不能炸死
* 2、点开一个若不是雷,则可以展开一片
* 郭文峰
* 2018/10/19
*/
void menu()
{
printf("***********************************\n");
printf("******* 1. play *******\n");
printf("******* 0. exit *******\n");
printf("***********************************\n");
}
void game()
{
int is = 0;
//mine 数组存储雷的位置 0 为空,1 为雷
char mine[ROWS][COLS] = { 0 };
//show 数组存储游戏盘,并且在每次点击后显示周围雷的个数
char show[ROWS][COLS] = { 0 };
//初始化mine 数组所有元素为'0',show 数组所有元素为'*'
//Init(mine, show, ROWS, COLS);
Init(mine, ROWS, COLS, '0');
Init(show, ROWS, COLS, '*');
//设置地雷
setMine(mine, ROWS, COLS);
//打印mine 数组
DisplayBoard(mine, ROWS, COLS);
printf("\n");
printf("-------------------------\n");
printf("\n");
//打印游戏面板
DisplayBoard(show, ROWS, COLS);
//第一步不被炸死
SafeMove(mine, show, ROWS, COLS);
//如果面板上剩下的'*' 与地雷的总数相同时,玩家赢
if (CountShow(show, ROWS, COLS) == EASY_MODE)
{
printf("-------------------------\n");
DisplayBoard(mine, ROWS, COLS);
printf("恭喜你!你赢了!\n");
return;
}
/*DisplayBoard(mine, ROWS, COLS);
printf("\n");
printf("-------------------------\n");
printf("\n");*/
DisplayBoard(show, ROWS, COLS);
while (1)
{
//判断玩家选择的坐标是不是地雷,若是则返回1,若不是则展开附近地雷信息
is = FindMine(mine, show, ROWS, COLS);
//如果面板上剩下的'*' 与地雷的总数相同时,玩家赢
if (CountShow(show, ROWS, COLS) == EASY_MODE)
{
printf("-------------------------\n");
DisplayBoard(mine, ROWS, COLS);
printf("恭喜你!你赢了!\n");
break;
}
if (is == 1)
{
printf("BOOM!!!!!!!!!!\n");
printf("很遗憾,你被炸死了!\n");
DisplayBoard(mine, ROWS, COLS);
break;
}
/* DisplayBoard(mine, ROWS, COLS);
printf("\n");
printf("-------------------------\n");
printf("\n");*/
DisplayBoard(show, ROWS, COLS);
}
}
int main(void)
{
int i = 0;
int j = 0;
int input = 0;
srand((unsigned int)time(NULL));
//调用菜单函数,打印游戏菜单
menu();
do
{
printf("请输入您要选的选项:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("输入错误!请重新输入!\n");
break;
}
} while (input);
system("pause");
return 0;
}
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void Init(char board[ROWS][COLS], int row, int col, char ch)
{
memset(&board[0][0], ch, row*col*sizeof(board[0][0]));
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//打印横坐标
printf("0 ");
for (i = 1; i < row - 1; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void setMine(char mine[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = EASY_MODE;
while (count)
{
//我们需要9个数字,如果%10产生的是0~8, 9个数字
//但是我们需要1~9,所以先%9产生8个数字,再加1就好
x = rand() % 9 + 1;
y = rand() % 9 + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//踩到雷返回1,没有踩到雷返回0
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
printf("请输入您选择的坐标:");
scanf("%d%d", &x, &y);
if (x < 1 && x > COL && y < 1 && y > ROW)
{
printf("您输入的坐标超过当前地图,请重新输入!\n");
}
else
{
if (mine[x][y] == '0')
{
char ch = GetCount(mine, x, y);
show[x][y] = ch + '0';
book[x][y] = 1;
open(mine, show, x, y);
if (CountShow(show, row, col) == EASY_MODE)
{
return 0;
}
}
else if (mine[x][y] == '1')
{
return 1;
}
}
return 0;
}
int GetCount(char mine[ROWS][COLS], int x, int y)
{
int count = 0;
//分别对(x,y)坐标周围8个格子进行判断,如果是雷则count + 1
//最后返回count就是(x,y)周围的地雷个数
if (mine[x - 1][y] == '1')
count++;
if (mine[x - 1][y - 1] == '1')
count++;
if (mine[x][y - 1] == '1')
count++;
if (mine[x + 1][y - 1] == '1')
count++;
if (mine[x + 1][y] == '1')
count++;
if (mine[x + 1][y + 1] == '1')
count++;
if (mine[x][y + 1] == '1')
count++;
if (mine[x - 1][y + 1] == '1')
count++;
return count;
}
//SafeMove函数是保证玩家输入第一个坐标时不被炸死
void SafeMove(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int flag = 1;
int count = 0;
char ch = 0;
while (1)
{
printf("请输入您要选择的坐标:");
scanf("%d%d", &x, &y);
if (x < 1 && x > COL && y < 1 && y > ROW)
{
printf("您输入的坐标超过当前地图,请重新输入!\n");
continue;
}
if (mine[x][y] == '1')
{
//如果玩家输入的坐标是1,则强行把这个坐标改为0
mine[x][y] = '0';
char ch = GetCount(mine, x, y);
show[x][y] = ch + '0';
book[x][y] = 1;
open(mine, show, x, y);
//将一个地雷改为了空地,则需要再随机填一个雷
while (flag)
{
x = rand() % 9 + 1;
y = rand() % 9 + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
flag--;
break;
}
}break;
}
else if (mine[x][y] == '0')
{
char ch = GetCount(mine, x, y);
show[x][y] = ch + '0';
book[x][y] = 1;
open(mine, show, x, y);
break;
}
}
}
//用深度优先搜索来对面板展开
void open(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int tx = 0;
int ty = 0;
int k = 0;
//定义next数组,用来对四个方向进行搜索
int next[4][2] = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
//在展开的时候如果GetCount函数返回的不是0,则给show数组赋返回的值
if (GetCount(mine, x, y) != 0)
{
show[x][y] = GetCount(mine, x, y) + '0';
}
//如果是0,给show数组那个位置赋空格
else if (GetCount(mine, x, y) == 0)
{
show[x][y] = ' ';
}
for (k = 0; k <= 3; k++)
{
tx = x + next[k][0];
ty = y + next[k][1];
if (tx < 1 || tx > ROW || ty < 1 || ty > COL)
{
continue;
}
if (mine[tx][ty] == '0' && book[tx][ty] == 0 && GetCount(mine, tx, ty) < 1)
{
book[tx][ty] = 1;
open(mine, show, tx, ty);
}
//这一步很关键,是展开时遇到数字停下来的一步
if (mine[tx][ty] == '0' && book[tx][ty] == 0 && GetCount(mine, tx, ty) > 0)
{
show[tx][ty] = GetCount(mine, tx, ty) + '0';
continue;
}
}
return;
}
//统计面板上剩下的'*'
int CountShow(char show[ROWS][COLS], int row, int col)//判断剩余未知区域的个数,个数为雷数时玩家赢
{
int count = 0;
int i = 0;
int j = 0;
for (i = 1; i < row - 1; i++)
{
for (j = 1; j < col - 1; j++)
{
if (show[i][j] == '*')
{
count++;
}
}
}
return count;
}
总结:
这个程序难的地方就是你要有一个清晰的思路,知道你需要干什么,所以这里我是先写大的框架,然后我需要用到什么,我再去写什么,一点一点的去完成。
还有写程序的时候遇到难题就是再展开这一块,还好之前看的算法书起了作用,这里安利一本叫《啊哈!算法》的书,里面很可爱的讲了各种算法,推荐新手去看,绝对能看懂!