小游戏:扫雷 (C语言实现扫雷的基本功能)
来源:互联网 发布:金庸小说排名 知乎 编辑:程序博客网 时间:2024/05/16 14:38
该程序分为三个文件:
1.game.h :包含头文件的引用、函数的声明和宏定义
2.game.c :包含各功能函数的具体实现
3.test.c :各功能函数的调用(程序的流程)
功能介绍:
1.初始化雷盘
2.打印雷盘
3.随机设置雷的分布
4.统计坐标位置周围的雷数
5.扩展式排雷
6.给所选坐标位置做标记
7.取消标记
8.第一次排雷不会被炸死
读者可以自己额外增加新功能,比如计时功能等。
作者提醒:
具体的内容都在代码注释里详细解释(我觉得是非常详细了)
当然,本人并不推荐这个程序中的注释风格,以后编代码千万别这么写注释
这样注释纯粹是为了初学者能轻松理解所有代码的作用
game.h
#ifndef __GAME_H__#define __GAME_H__#include <stdio.h>#include <stdlib.h>#include <string.h>#include <time.h>#define ROW 10//雷盘行数#define COL 10//雷盘列数#define ROWS ROW+2//数组(实际)行数#define COLS COL+2//数组(实际)列数#define EASY_COUNT 10//初级难度的雷数void InitBoard(char board[ROWS][COLS], int row, int col, char set);//初始化雷盘void DisplayBoard(char board[ROWS][COLS], int row, int col);//打印雷盘void SetBoard(char board[ROWS][COLS], int row, int col, int count);//布雷int GetCount(char board[ROWS][COLS], int x, int y);//统计雷数void expend(char board1[ROWS][COLS], char board[ROWS][COLS], int x, int y,int *num);//扩展式排雷void sign(char board[ROWS][COLS], int x, int y);//标记猴头(雷)void unsign(char board[ROWS][COLS], int x, int y);//取消标记#endif//__GAME_H__
game.c
#include "game.h"void InitBoard(char board[ROWS][COLS], int row, int col, char set)//初始化雷盘{ memset(board,set,row*col*sizeof(board[0][0]));//利用memset初始化,memset用法自行看书学习}void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印雷盘{ system("CLS");//每次打印雷盘之前清屏一次 int i = 0; int j = 0; printf(" ");//为了打印列坐标时对齐 for (i = 1; i <= row; i++) printf("%d ", i);//打印列坐标1 2 3 4 5 6 7 8 9 10 printf("\n"); for (i = 1; i <=row; i++) { printf("%2d ", i);//打印行坐标1 2 3 4 5 6 7 8 9 10 for (j = 1; j <=row; j++) { printf("%c ", board[i][j]); } printf("\n"); }}void SetBoard(char board[ROWS][COLS], int row, int col,int count)//布雷{ int x = 0; int y = 0; while (count) { x = rand() % row + 1;//保证行坐标在1到10之间 y = rand() % col + 1;//保证列坐标在1到10之间 if (board[x][y] == '0')//判断该位置是否布过雷 { board[x][y] = '1';//‘1’代表有雷 count--;//布一次雷,雷数减一 } }}int GetCount(char board[ROWS][COLS], int x, int y)//统计雷数{ return board[x - 1][y] + board[x - 1][y - 1] + board[x][y - 1] + board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] + board[x][y + 1] + board[x - 1][y + 1] - 8 * '0';//将该位置周围八个位置的情况(是否有雷)计数}void expend(char board1[ROWS][COLS], char board2[ROWS][COLS], int x, int y, int *num)//扩展式排雷(递归){ int i = 0; int j = 0; if (board2[x][y]=='*')//如果该位置字符为'*',则该位置是未排过的,进行排雷 { (*num)++;//排雷次数加一 int count = GetCount(board1,x,y);//统计该位置周围的雷数 if (count != 0)//如果该位置周围的雷数不为0 { board2[x][y] = count + '0';//显示雷数,则该位置的字符不为'*',可避免下次排雷重复排到该位置 } else//如果该位置的雷数为0,则向它周围八个位置扩展排雷 { board2[x][y] = '0';//该位置的字符显示为‘0’ for (i = -1; i <= 1; i++) { for (j = -1; j <= 1; j++) { if (x + i >= 1 && x + i <=ROW && y + j >= 1 && y + j <=COL)//耗费我3个小时调试时间的不起眼的小错误!!!!!!! { if (i != 0 || j != 0)//避免重复排到自己 expend(board1, board2, x + i, y + j, num); } } } } }}void sign(char board[ROWS][COLS], int x, int y)//用‘@’标记雷{ if (board[x][y] == '*') { board[x][y] = '@'; }}void unsign(char board[ROWS][COLS], int x, int y)//取消标记{ if (board[x][y] == '@') { board[x][y] = '*'; }}
test.c
#include "game.h"void game(){ int x = 0; int y = 0; int win = 0;//判定输赢的排雷次数 int select = 0;//决定游戏功能的变量 char mine[ROWS][COLS] = { 0 };//存雷的数组(雷盘) char show[ROWS][COLS] = { 0 };//展示的数组(雷盘) InitBoard(show, ROWS, COLS, '*');//展示的数组(雷盘)初始化为'*' InitBoard(mine, ROWS, COLS, '0');//存雷的数字(雷盘)初始化为'0' SetBoard(mine, ROW, COL, EASY_COUNT);//布雷 DisplayBoard(show, ROW, COL);//打印雷盘 while (win<(ROW*COL-EASY_COUNT))//当排雷的次数不少于无雷格数(雷盘格数 减 雷数)时,停止排雷 { printf("请选择功能:\n1 for 排雷;2 for 标记 3 for 取消标记 4 for 再来一局\n"); scanf("%d", &select); fflush(stdin);//清空输入缓冲区,避免多输造成的影响 if (select == 1)//1 for 排雷 { printf("请输入坐标:\n"); scanf("%d%d", &x, &y); fflush(stdin);//清空输入缓冲区,避免多输造成的影响 if (x >= 1 && x <= ROW&&y >= 1 && y <= COL)//检验坐标是否合法 { if (mine[x][y] == '1')//如果所选位置有雷,判断是否为第一次排雷 { if (win != 0)//如果不是第一次排雷,宣布游戏结束 { DisplayBoard(mine, ROW, COL);//排雷失败后打印一下雷的分布 printf("你被炸死了!\n"); return; } else//如果是第一次排雷,将这颗雷转移到其他位置,保证第一次不会排到雷 { mine[x][y] = '0'; SetBoard(mine, ROW, COL, 1); expend(mine, show, x, y, &win); } } else//如果所选位置没有雷,进行扩展式排雷 { expend(mine, show, x, y, &win); } DisplayBoard(show, ROW, COL);//打印排雷后的雷盘 } else { printf("错误坐标:\n"); } } else if (select==2)//2 for 标记 { printf("请输入坐标:\n"); scanf("%d%d", &x, &y); fflush(stdin);//清空输入缓冲区,避免多输造成的影响 sign(show,x,y); DisplayBoard(show,ROW,COL); } else if (select==3)//3 for 取消标记 { printf("请输入坐标:\n"); scanf("%d%d", &x, &y); fflush(stdin);//清空输入缓冲区,避免多输造成的影响 unsign(show, x, y); DisplayBoard(show, ROW, COL); } else if (select==4)//4 for 结束游戏 { return; } } printf("排雷成功!\n");}void menu(){ int num = 0; srand((unsigned int)time(NULL));//产生随机种子,用于随机布雷 do { printf("*********1.play 0.exit**********"); printf("请选择:\n"); scanf("%d", &num); switch (num) { case 1: game(); break; case 0: break; default: printf("选择错误!\n"); break; } } while (num);}void test(){ menu();}int main(){ test(); return 0;}
截图:
程序开始运行
排雷过程
标记雷
取消标记
扫雷失败
重新开始游戏
扫雷成功
心得体会:
这个扫雷可以算是个小小小小的项目,对现阶段的我来说还不算难,但过程并不轻松。
我针对其中一个bug进行解释说明,为什么说过程并不轻松?
该程序判定扫雷成功的方式是:
在expend函数中传形参win计数(每排一次雷,win自加一次,当win不小于无雷格数时游戏结束)
代码如下:
//调用排雷的功能函数,将win传给形参numexpend(mine, show, x, y, &win);//功能函数的具体内容void expend(char board1[ROWS][COLS], char board2[ROWS][COLS], int x, int y, int *num)//扩展式排雷(递归){ int i = 0; int j = 0; if (board2[x][y]=='*')//如果该位置字符为'*',则该位置是未排过的,进行排雷 { (*num)++;//排雷次数加一 int count = GetCount(board1,x,y);//统计该位置周围的雷数 if (count != 0)//如果该位置周围的雷数不为0 { board2[x][y] = count + '0';//显示雷数,则该位置的字符不为'*',可避免下次排雷重复排到该位置 } else//如果该位置的雷数为0,则向它周围八个位置扩展排雷 { board2[x][y] = '0';//该位置的字符显示为‘0’ for (i = -1; i <= 1; i++) { for (j = -1; j <= 1; j++) { if (x + i >= 1 && x + i <=ROW && y + j >= 1 && y + j <=COL)//耗费我3个小时调试时间的不起眼的小错误!!!!!!! { if (i != 0 || j != 0)//避免重复排到自己 expend(board1, board2, x + i, y + j, num); } } } } }}
我程序中出现的bug是win计数出错(win总是大于实际排雷次数),
这个bug的具体错误耗费了我3个小时才找到,其实就是我把
if (x + i >= 1 && x + i <=ROW && y + j >= 1 && y + j <=COL)
写成了:
if (x + i >= 1 && x + i <ROWS && y + j >= 1 && y + j <COLS)
这样就导致排雷排到雷盘行和列的最边缘的位置时会出错
如图:
排雷应该在1到10坐标之间(即黑框里)进行,当我们定义i和j小于ROWS的时候,在这个程序中就是小于12,则坐标为0到11(即红框里).
简单来说,就是本应该只在黑框内排雷,但是却把最外层(非雷区)也排了,这样就会增加排雷次数,从而导致win的数值不正常,总是大于90(雷格数10x10-雷数10=90)
在这里看来,您可能会想,“这么简单的bug你竟然花了3个小时!啧啧啧。。。”
其次,我觉得,真的是“当局者迷”,有时候自己真的很难发现自己的微不足道的错误=》小bug
唉。。。
正应了那句话,“程序员20%的时间花在编代码上,80%的时间花在调试上”。
OK!在此,我建议和我一样初学者们,在对一个程序编写之前,首先分析整个程序的流程和功能, 做到有模有块,如果脑海里实在理不出来步骤,不妨在纸上慢慢画出来或者写出来,
莫急于上手编代码!
最后,送给大家一句陈正冲先生的话,“调试代码才是最长水平的!”
- 小游戏:扫雷 (C语言实现扫雷的基本功能)
- 【扫雷】编写一个小游戏--扫雷的c语言实现
- C语言实现小游戏--扫雷
- C语言小游戏“扫雷”
- 扫雷小游戏【C语言】
- c语言小游戏---扫雷
- c语言小游戏扫雷
- C语言小游戏---扫雷
- C语言 — 实现扫雷小游戏
- C语言简单实现扫雷小游戏~~~
- Pace 8 (C语言实现扫雷小游戏)
- 用C实现的扫雷小游戏
- c语言模拟扫雷小游戏
- C语言编写扫雷小游戏
- C语言实现扫雷
- c语言实现扫雷
- C语言实现扫雷
- C语言*扫雷实现
- java 常用集合list与Set、Map区别及适用场景总结
- UVA 136 丑数
- 让移动端页面滚动后不触发touchend事件的方法
- jq 城市列表
- jquery ajax的简单使用
- 小游戏:扫雷 (C语言实现扫雷的基本功能)
- Delphi FrieDAC 大数据处理
- python学习--list
- 文章标题
- 类初始化步骤
- 数据结构与算法(Java描述)-11、串的基本概念以及串存储结构
- 如何显示遮罩层时禁止底层页面滑动
- TOP100summit:【分享实录】链家网大数据平台体系构建历程
- 前后台编码问题