C语言小游戏扫雷
基于一个游戏的整体逻辑(同前面的三子棋完全一致),所以游戏整体的代码框架如下:
void menu()
{
printf("****************************************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("****************************************\n");
}
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");
return 0;
default :
printf("输入错误!请重新输入\n");
break;
}
}while (input);
}
用一个二维数组来模拟棋盘。
对游戏的整体功能分析发现,在扫雷的时候,我们要对棋盘每个元素的变量产生修改,修改的内容是该位置周围雷的个数,这样不断的修改会破坏原有位置雷的值,让整个游戏一旦开始变无从结束。
所以我们要用两个棋盘来避免以上的问题。
用一个二维数组专门来存放雷阵,后面的操作只是对这个棋盘的判断,将输出结果存放在另一个二维数组中。可以用0来初始化棋盘,用1来表示雷,而某个位置周围元素之和也可以很好的作为返回值输出到状态棋盘。
另一个二维数组便是状态棋盘,如果将棋盘也初始化为0,用雷阵的返回值作为该位置的值会出现一个问题:如果该位置刚好周围没雷,则无法区分没雷和还没有赋值两种情况。
为了解决上面的问题以及便于初始化函数的统一设计,将雷阵初始化为字符0,雷标记为字符1(根据ASCLL码表得知:'0'-0=48 '1'-1=48
)。而将状态棋盘初始化为*
,将返回值count(count+'0'等价于'count'
)以其对应的字符存放。
以下是棋盘的初始化和打印:
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
memset(board, set, rows*cols*sizeof(board[0][0]));
}
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i=0; i<=ROW; i++)
{
printf("%d ",i);
}
printf("\n");
for (i=1; i<=ROW; i++)
{
printf("%d ",i);
for (j=1; j<=COL; j++)
{
printf("%c ",board[i][j]);
}
printf("\n");
}
}
当然棋盘的初始化也可以通过循环遍历来完成,而库函数memset
刚好可以实现此功能,参数分别为:目标数组的起始地址,赋值的内容,赋值的大小(单位为字节)。
在初始化棋盘之前,我们还要考虑数组越界访问的问题,两个棋盘都要对棋盘最外围元素进行操作和判断,如果稍不注意,数组便会越界访问。在这里我们可以初始化两个比所要操作数组外围再大一圈的数组,相应的操作在里面完成,以此来解决数组越界访问问题。
完成上面操作接下来就是置雷和扫雷两个功能了。
置雷比较简单,通过产生1~9
两个随机数,来判断雷阵该位置是否已置雷,没有则将该位置置为字符1,相应的用一个计数器记录置雷的数量。
void SetMine(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int count = 0;
while (count<EASY_COUNT)
{
i = rand()%row+1;
j = rand()%col+1;
if (board[i][j]=='0')
{
board[i][j] = '1';
count++;
}
}
}
接下来用递归实现拓展扫雷。
对其中一个位置进行分析,中间位置如果周围没雷,则先置该对应位置的状态棋盘为字符0,接着对周围8个元素分别再作为中心元素往外拓展,一旦发现某位置周围有雷,则不再拓展。
用循环来生成这九个棋盘位置:
static void extend(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j)
{
int m = 0;
int n = 0;
int count = 0;
if ((count = (mine[i - 1][j]
+ mine[i - 1][j + 1]
+ mine[i][j + 1]
+ mine[i + 1][j + 1]
+ mine[i + 1][j]
+ mine[i + 1][j - 1]
+ mine[i][j - 1]
+ mine[i - 1][j - 1] - 8 * '0')) == 0)
{
show[i][j] = '0';
for (m=-1; m<2; m++)
{
for (n=-1; n<2; n++)
{
if (mine[i+m][j+n]=='0' && show[i+m][j+n]=='*')
{
extend(mine,show,i+m,j+n);
}
}
}
}
else
{
show[i][j] = count + '0';
}
}
这个递归拓展需要注意的是,生成的九个位置中还重复了自身位置,一旦不对此位置进行处理,递归将陷入死循环。所以先置该位置对应状态棋盘为字符0,在递归调用时加上对状态棋盘的判断来避免重复调用中间位置。
最后便是扫雷函数的设计,整个逻辑框架是一个循环,不慎踩中雷跳出循环,游戏结束,或者将棋盘中不是雷的位置排查完,玩家获胜跳出循环,否则一直进行棋盘的排查。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int flag = 1;
while (1)
{
printf("请输入坐标:>\n");
scanf("%d%d", &i, &j);
if (i>0 && i<=ROW && j>0 && j<=COL)
{
//保证第一次不被炸死
if (mine[i][j]=='1')
{
if (flag)
{
do
{
InitBoard(mine,ROWS,COLS,'0');
SetMine(mine,ROW,COL);
}while (mine[i][j]=='1');
flag = 0;
goto flag;
}
printf("很遗憾!你被炸死了\n");
PrintBoard(mine,ROW,COL);
break;
}
flag = 0;
flag:;
extend(mine,show,i,j);
if (count_board_mine(show,ROW,COL)==EASY_COUNT)
{
printf("恭喜你!排雷成功\n");
PrintBoard(mine,ROW,COL);
break;
}
PrintBoard(show,ROW,COL);
}
else
{
printf("输入有误!请重新选择\n");
}
}
}
上面调用的count_board_mine()
是对状态棋盘未排查位置的计数:
static int count_board_mine(char show[ROWS][COLS], int row, int col)
{
int total = 0;
int i = 0;
int j = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] == '*')
{
total++;
}
}
}
return total;
}
由于本函数和上面的拓展函数均只在本源文件中调用,所以可以加上static
进行保护。
在FindMine()
中为了避免玩家一上手就凉凉,所以在碰到第一次选择就中奖的情况对雷阵偷偷进行了重置,在本次判断语句未执行完,通过goto
语句跳过后面不必要的操作,并用状态标记的方法对此次操作作相应记录,保证在偷偷帮你一次下次一次你依然中奖时为你默哀。
完整代码展示:
game.h
#ifndef __GAME_H__
#define __GAME_H__
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.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 SetMine(char board[ROWS][COLS], int row, int col);
void PrintBoard(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
#endif //__GAME_H__
game.c
#include <string.h>
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
memset(board, set, rows*cols*sizeof(board[0][0]));
}
void PrintBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i=0; i<=ROW; i++)
{
printf("%d ",i);
}
printf("\n");
for (i=1; i<=ROW; i++)
{
printf("%d ",i);
for (j=1; j<=COL; j++)
{
printf("%c ",board[i][j]);
}
printf("\n");
}
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int count = 0;
while (count<EASY_COUNT)
{
i = rand()%row+1;
j = rand()%col+1;
if (board[i][j]=='0')
{
board[i][j] = '1';
count++;
}
}
}
//未排出个数
static int count_board_mine(char show[ROWS][COLS], int row, int col)
{
int total = 0;
int i = 0;
int j = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (show[i][j] == '*')
{
total++;
}
}
}
return total;
}
static void extend(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j)
{
int m = 0;
int n = 0;
int count = 0;
if ((count = (mine[i - 1][j]
+ mine[i - 1][j + 1]
+ mine[i][j + 1]
+ mine[i + 1][j + 1]
+ mine[i + 1][j]
+ mine[i + 1][j - 1]
+ mine[i][j - 1]
+ mine[i - 1][j - 1] - 8 * '0')) == 0)
{
show[i][j] = '0';
for (m=-1; m<2; m++)
{
for (n=-1; n<2; n++)
{
if (mine[i+m][j+n]=='0' && show[i+m][j+n]=='*')
{
extend(mine,show,i+m,j+n);
}
}
}
}
else
{
show[i][j] = count + '0';
}
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int flag = 1;
while (1)
{
printf("请输入坐标:>\n");
scanf("%d%d", &i, &j);
if (i>0 && i<=ROW && j>0 && j<=COL)
{
//保证第一次不被炸死
if (mine[i][j]=='1')
{
if (flag)
{
do
{
InitBoard(mine,ROWS,COLS,'0');
SetMine(mine,ROW,COL);
}while (mine[i][j]=='1');
flag = 0;
goto flag;
}
printf("很遗憾!你被炸死了\n");
PrintBoard(mine,ROW,COL);
break;
}
flag = 0;
flag:;
extend(mine,show,i,j);
if (count_board_mine(show,ROW,COL)==EASY_COUNT)
{
printf("恭喜你!排雷成功\n");
PrintBoard(mine,ROW,COL);
break;
}
PrintBoard(show,ROW,COL);
}
else
{
printf("输入有误!请重新选择\n");
}
}
}
test.c
#include <stdio.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,'*');
SetMine(mine,ROW,COL);
PrintBoard(mine,ROW,COL);
PrintBoard(show,ROW,COL);
FindMine(mine,show,ROW,COL);
}
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");
return 0;
default :
printf("输入错误!请重新输入\n");
break;
}
}while (input);
}
效果展示
置雷为80,保证第一下不炸死,利用拓展扫雷的方法一击必胜!