前言:
最近重新拾起了学习C语言的兴趣,在复习到函数递归的时候看见了扫雷这个小游戏,谨以此文纪念自己的程序修改过程。我写的时候参考了系统自带的扫雷玩法,便兴冲冲的开始写代码了,所以写的时候绕了一大圈弯路。我所使用的编程软件是cfree5,因为电脑版本是32位Windows7,实在找不到别的编译器了哈哈哈哈哈哈哈。下面直接上源码,200来行,而且这个扫雷看起来会比较好看。
头文件:game.h
这也算是我第一次尝试用头文件写代码,第一次感觉写代码思路这么清晰。
#include <stdio.h>
#include <stdlib.h> //这个是清屏函数所属函数库
#include <stdarg.h> //这个是可变入参头文件
#include <time.h> //这个函数库有产生时间戳的函数,产生随机数用
#include <windows.h> //延迟时间函数在这里,为了好看
#include <conio.h> //getch函数的头文件
#define heng (20 + 2) // 顾名思义,横着得,这个20可以随意更改代表扫雷界面的长
#define shu (20 + 2) // 顾名思义,竖着的,这个20同样随意更改,表示宽
#define lei 20 // 顾名思义,雷的数量哈哈哈哈
void denglu(); // 登录界面打印
void game(); // 游戏函数
void chushihua(char a[heng][shu]); // 初始化
void dayin(char a[heng][shu]); // 打印
void panduan(char b[heng][shu]); // 判断雷周围的数字
void xunzhao(char a[heng][shu], char b[heng][shu], char c[heng][shu], int i, int j);
// 寻找可以扩散的方块,即空白块和最边上的数字块
int dianji(char a[heng][shu], char b[heng][shu], char c[heng][shu], int i, int j);
// 点击图上的方块(当然是通过写坐标)
void baozha(char a[heng][shu], char b[heng][shu]); // 点到雷了,爆炸了
int shengli(char a[heng][shu], char b[heng][shu], int m); // 胜利!!
void myprintf(boolean b, char *str);
void myprintf2(boolean b, char *str);
void setColor(int fore,int back);
源文件:game.c
这个地方能看出来我英语确实不好,函数名基本都是拼音,一目了然,绝对不会有重复函数(狗头)。
#include "game.h"
void setColor(int fore,int back) {
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), fore+back*0x10);
}
void panduan(char b[heng][shu]) {
int i, j; // 记录每个方块的循环
int x, y; // 记录当前方块的数字
int c = 0; // 记录(雷周围的)数字大小
for (i = 1; i < heng - 1; i++)
for (j = 1; j < shu - 1; j++)
if (b[i][j] != '*') {
c = 0;
for (x = i - 1; x < i + 2; x++)
for (y = j - 1; y < j + 2; y++)
if (b[x][y] == '*')
c++, b[i][j] = c + 48;
}
}
void chushihua(char a[heng][shu]) {
int r = lei;
int i, j;
srand((unsigned int)time(NULL)); // 保证每次产生的雷都不一样
while (r) {
i = rand() % (heng - 2) + 1;
j = rand() % (shu - 2) + 1;
if (a[i][j] != '*') { // 雷可不能放重
a[i][j] = '*';
r--;
}
}
panduan(a);
}
void dayin(char a[heng][shu]) {
int i, j;
printf("\n \t ");
for (i = 1; i < shu - 1; i++)
printf(" %2d ", i); // 这个2d是左对齐还是右对齐来着,忘了
for (i = 1; i < heng - 1; i++) { // 这几步主要是为了好看
printf("\n %2d", i);
for (j = 1; j < shu - 1; j++) {
if(a[i][j]>='0'&&a[i][j]<='9')
setColor(15,3);
else if(a[i][j]=='?')
setColor(12,8);
else
setColor(14,0);
if (j == 1)
printf(" | %c |", a[i][j]);
else
printf(" %c |", a[i][j]);
}
setColor(14,0);
if (i < heng - 2) {
printf("\n\t|");
for (j = 1; j < shu - 1; j++)
printf("---|");
}
setColor(15,3);
}
}
void xunzhao(char a[heng][shu], char b[heng][shu], char c[heng][shu], int i, int j) {
int x, y;
if (i == 0 || j == 0 || i == heng - 1 || j == shu - 1)
; // 把最边上的都给屏蔽掉
else
for (x = i - 1; x < i + 2; x++)
for (y = j - 1; y < j + 2; y++)
if (c[x][y] == 0) { // 每个寻找过得位置都置‘1’,防止浪费栈的空间
c[x][y] = '1';
if (b[x][y] == 0) { // 空白块直接展开,并且寻找下一个
a[x][y] = '0';
xunzhao(a, b, c, x, y);
} else if (b[x][y] > '0' && b[x][y] < '9') // 数字块记录但是不再展开
a[x][y] = b[x][y];
}
}
int dianji(char a[heng][shu], char b[heng][shu], char c[heng][shu], int i, int j) {
if (b[i][j] == '*')
return 0;
else {
xunzhao(a, b, c, i, j);
return 1;
}
}
void baozha(char a[heng][shu], char b[heng][shu]) {
int i, j;
for (i = 0; i < heng; i++)
for (j = 0; j < shu; j++)
if (b[i][j] == '*')
a[i][j] = b[i][j];
system("cls");
dayin(a); // 这一步是为了展现炸弹爆炸,看起来比较厉害
}
int shengli(char a[heng][shu], char b[heng][shu], int m) {
int i, j;
int s = 0;
if (m == 10) // 这个指的是标记的部位等于10个的时候进行胜利判定
for (i = 0; i < heng; i++)
for (j = 0; j < shu; j++)
if (b[i][j] == '*' && a[i][j] == 5) // 雷的位置和标记位置一样
s++;
if (s == 10) {
myprintf(FALSE,"胜利!");
return 1;
} else
return 0;
}
源文件:扫雷.c 头文件在这
#include "game.h"
int lenth, lenth2;
int hight, hight2;
void myprintf(boolean b, char *str) {
COORD coord;
if(!b) {
lenth =lenth2- strlen(str);
}
coord.X = lenth;
coord.Y = hight = (b ? hight+2 : hight2);
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
printf(str);
}
void myprintf2(boolean b, char *str) {
COORD coord;
if(!b) {
lenth =lenth2- strlen(str);
}
coord.X = lenth;
coord.Y = hight = (b ? hight+2 : hight2);
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
void denglu() {
setColor(15,3);
myprintf(FALSE,"1.开始游戏 ");
myprintf(TRUE,"2.退出游戏");
myprintf(TRUE,"欢迎进入扫雷,请选择 ");
}
void game() {
// a是展示给用户的表盘
// b存放雷的位置和雷的数量(数字)
// c是用来存放该位置有没有被点击(或展开)过,为了节省循环的时间
char a[heng][shu] = {0}, b[heng][shu] = {0}, c[heng][shu] = {0};
int i, j;
char l;
int s = 1, m = 0; // 用于判断操作行为
int g = -1; // 控制游戏循环(以及结束)
chushihua(b);
while (g) {
system("cls"); // 每次选择完刷新页面
dayin(a);
myprintf(FALSE,"你可以选择一个坐标:" );
scanf("%d%d", &i, &j);
myprintf(TRUE,"1 点击 \t2 标记" );
myprintf(TRUE,"你选择的操作是:" );
while (s) {
l = getch();// 为了程序的健壮性,允许输错
s = 0;
switch (l) {
case '1':
g = dianji(a, b, c, i, j);
break;
case '2':
a[i][j] = '?'; //这个是标识符
m++; // 标记的数目
break;
default:
myprintf(FALSE,"请重新输入操作!");
s = 1; // 输错了就重新来一次
break;
}
}
s = 1;
if (shengli(a, b, m) == 1)
break;
if (g == 0) {
baozha(a, b);
myprintf(FALSE,"很不幸,你点到了炸弹");
myprintf(TRUE,"游戏结束");
}
}
}
int main(int argc, char *argv[]) {
char a = '0', b; // 从健壮性考虑,这些应该换成char型,从流畅度考虑应该使用getch()获取输入字符;
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);//windows里的类型以及函数
CONSOLE_SCREEN_BUFFER_INFO scr;//windows里的类型
GetConsoleScreenBufferInfo(out, &scr);//windows里的函数
lenth2 = lenth = scr.srWindow.Right / 2;
hight2 = hight = scr.srWindow.Bottom / 3;
system("color 3c");//换个好看的背景色
while (1) {
denglu();
while (a != '2') {
a = getch();
switch (a) {
case '1':
system("cls");
myprintf(FALSE,"开启你的扫雷生涯吧!");
myprintf2(TRUE,"本次游戏共有%个雷,祝你玩得愉快!!");
printf("本次游戏共有%d个雷,祝你玩得愉快!!", lei);
Sleep(1000); // 等待两秒再刷新
system("cls");
game();
a = '2';
break;
case '2':
break;
default:
myprintf(FALSE,"输入错误,请重试");
break;
}
}
myprintf(TRUE,"1 是\t2 否");
myprintf(TRUE,"是否要重新玩一局?");
b = getch();
scanf("%c", &b);
system("cls");
if (b == '2' || a == '2')
break;
}
system("cls");
myprintf(FALSE,"感谢游玩,我们下次再见!!\n\n");
return 0;
}
思路:
最开始的麻烦思路:
最开始的想法就是界面上显示多少方块就初始化多大的数组,比如10*10的方块,需要的横和竖分别为10,10。这样导致的结果就是当你去计算雷周围数字大小时,分九种情况讨论,分类就分了半个多小时,然后因为老打错改了半小时(当然最后全删掉了,啧),下面放一下代码:
void panduan(char b[heng][shu])
{
int i,j;
int x,y;
int c=0;
for(i=1;i<heng-1;i++)
for(j=1;j<shu-1;j++)
if(b[i][j]!='*')
{
c=0;
if(i==0&&j==0)
{
for(x=0;x<2;x++)
for(y=0;y<2;y++)
if(b[x][y]=='*')
c++, b[i][j]=c+48;
}
else if(i==heng-1&&j==0)
{
for(x=heng-1;x>heng-3;x--)
for(y=0;y<2;y++)
if(b[x][y]=='*')
c++, b[i][j]=c+48;
}
else if(i==0&&j==shu-1)
{
for(x=0;x<2;x++)
for(y=shu-1;y>shu-3;y--)
if(b[x][y]=='*')
c++, b[i][j]=c+48;
}
else if(i==heng-1&&j==shu-1)
{
for(x=heng-1;x<heng-3;x--)
for(y=shu-1;y<shu-3;y--)
if(b[x][y]=='*')
c++, b[i][j]=c+48;
}
else if(i==0)
{
for(x=0;x<2;x++)
for(y=j-1;y<j+2;y++)
if(b[x][y]=='*')
c++, b[i][j]=c+48;
}
else if(j==0)
{
for(x=i-1;x<i+2;x++)
for(y=0;y<2;y++)
if(b[x][y]=='*')
c++, b[i][j]=c+48;
}
else if(i==heng-1)
{
for(x=i;x>i-2;x--)
for(y=j-1;y<j+2;y++)
if(b[x][y]=='*')
c++, b[i][j]=c+48;
}
else if(j==shu-1)
{
for(x=i-1;x<i+2;x++)
for(y=shu-1;y>shu-3;y--)
if(b[x][y]=='*')
c++, b[i][j]=c+48;
}
else
{
for(x=i-1;x<i+2;x++)
for(y=j-1;y<j+2;y++)
if(b[x][y]=='*')
c++, b[i][j]=c+48;
}
}
}
这个地方分了四条边+四个角+中间九个方面,某种意义上很锻炼对C语言循环的理解。
更方便的思路:
在你的方块周围再加一层方块,所以头文件define时,heng=20+2,这个2指的就是左右各加了一层,这样好处是不用分类讨论,只要在打印的时候不打印最外层就可以了。其他具体思路都写在注释上了。
附一个随机生成的扫雷地图吧,这个是20*20的地图,有20个雷

1398

被折叠的 条评论
为什么被折叠?



