用C语言写个扫雷

前言:


最近重新拾起了学习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个雷

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值