赞
踩
今天给大家详细介绍一个C语言入门小游戏——扫雷。
鉴于论坛内同样的文章过多且大多都是简化版的扫雷游戏,本文将使用递归的方式实现“一键清空”版本的扫雷游戏。
废话不多说,开整!
为了实现三子棋游戏,并且有一个良好的编程习惯,我们需要三个文件分辨存储我们需要的代码:
这里我们先给出游戏整体的代码,大家可以先看一看,我们在下面一一分解做出详细介绍:
主函数(main.c)代码:
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //菜单 void menu() { printf("------------------------------------------\n"); printf("----------- 1.game 0.exit ---------------\n"); printf("------------------------------------------\n"); } void game() { char mine_board[ROWS][COLS] = { 0 }; char show_board[ROWS][COLS] = { 0 };//这两个数组此时都只是不完全初始化 inite_board(mine_board, ROWS, COLS, '0'); inite_board(show_board, ROWS, COLS, '*'); set_mine(mine_board, ROW, COL); int ret = 0; while (1) { display_board(show_board, ROW, COL); ret = find_mine(mine_board, show_board, ROW, COL); if (ret == EASY_METHOD) //和玩家输入得雷数要保持一致 { printf("游戏结束,玩家胜利!\n"); display_board(mine_board, ROW, COL); break; } if (ret == 1) //如果为1,表示被炸到了 { break; } } } int main() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("要来一局扫雷游戏吗?\n"); scanf("%d", &input); printf("\n"); switch (input) { case 1: game(); break; case 0: printf("退出程序\n"); break; default: printf("选择错误,请重新选择\n"); break; } } while (input); }
功能函数(function.c)代码
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void inite_board(char board[ROWS][COLS], int rows, int cols, char c) { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { board[i][j] = c; } } } void display_board(char board[ROWS][COLS], int row, int col) { printf("----------------扫雷游戏-----------------\n"); for (int i = 0; i <= row; i++) { printf("%d ", i); } printf("\n"); for (int i = 1; i <= row; i++) { printf("%d ", i); for (int j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("----------------扫雷游戏-----------------\n"); } void set_mine(char board[ROWS][COLS], int row, int col) { int mines = EASY_METHOD; while (mines) { int x = rand() % row + 1; int y = rand() % col + 1; if (x >= 1 && x <= row && y >= 1 && y <= col) { if (board[x][y] != '1') { board[x][y] = '1'; mines--; } } } } int round_mines(char mine_board[ROWS][COLS],int x,int y) { return ( mine_board[x - 1][ y - 1] + mine_board[x - 1][y] + mine_board[x - 1][y + 1] + mine_board[x][y - 1] + mine_board[x][y + 1] + mine_board[x + 1][y - 1] + mine_board[x + 1][y] + mine_board[x + 1][y + 1] - 8 * '0' ); } void remove_board(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col, int x, int y) { if (round_mines(mine_board, x, y) == 0) { //如果要位置在棋盘的四周就不递归了 if ((x == 9 && y >= 1 && y <= 9) || (x == 1 && y >= 1 && y <= 9) || (y == 1 && x >= 1 && x <= 9) || (y == 9 && x >= 1 && x <= 9)) { show_board[x][y] = ' '; } else { show_board[x][y] = ' '; //周围没有雷,就把该位置设为空 for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (show_board[x + i][y + j] == '*')//找到没有被开的位置,进入递归,继续设置空 { remove_board(mine_board, show_board, row, col, x + i, y + j); } } } } } else { show_board[x][y] = round_mines(mine_board, x, y)+'0';//周围有雷的位置返回标记为雷的数量 } } int is_win(char board[ROWS][COLS]) { int count = 0; int i = 0, j = 0; for (i = 1; i <= ROW; i++) { for (j = 1; j <= COL; j++) { if ('*' == board[i][j]) { count++; } } } return count; } int find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col) { int x = 0; int y = 0; while (1) { printf("请输入你要排查的坐标:\n"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show_board[x][y] != '*') { printf("此位置已经排除,请输入其他位置\n"); } else { //如果是雷 if (mine_board[x][y] == '1') { printf("很遗憾,你被炸到了\n"); display_board(mine_board, ROW, COL); return 1; } else//如果不是雷 { //递归实现如果周围没有雷就消失一片 remove_board(mine_board, show_board, row, col, x, y); 统计mine_board数组x,y坐标周围有多少个雷 //int count = round_mines(mine_board, x, y); //show_board[x][y] = count + '0'; } } } else { printf("输入的坐标非法,请重新输入\n"); } break; } return is_win(show_board); }
头文件(game.h)代码:
#pragma once #include<stdio.h> #include<stdlib.h> #include<time.h> #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 #define EASY_METHOD 10 //初始化棋盘 void inite_board(char board[ROWS][COLS], int rows, int cols, char c); //显示棋盘,显示数据内容,所以传进来row和col void display_board(char board[ROWS][COLS], int row, int col); //设置雷 void set_mine(char board[ROWS][COLS], int row, int col); //排查雷 void find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col);
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //菜单 void menu() { printf("------------------------------------------\n"); printf("----------- 1.game 0.exit ---------------\n"); printf("------------------------------------------\n"); } void game() { char mine_board[ROWS][COLS] = { 0 }; char show_board[ROWS][COLS] = { 0 };//这两个数组此时都只是不完全初始化 inite_board(mine_board, ROWS, COLS, '0'); inite_board(show_board, ROWS, COLS, '*'); set_mine(mine_board, ROW, COL); int ret = 0; while (1) { display_board(show_board, ROW, COL); ret = find_mine(mine_board, show_board, ROW, COL); if (ret == EASY_METHOD) //和玩家输入得雷数要保持一致 { printf("游戏结束,玩家胜利!\n"); display_board(mine_board, ROW, COL); break; } if (ret == 1) //如果为1,表示被炸到了 { break; } } } int main() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("要来一局扫雷游戏吗?\n"); scanf("%d", &input); printf("\n"); switch (input) { case 1: game(); break; case 0: printf("退出程序\n"); break; default: printf("选择错误,请重新选择\n"); break; } } while (input); }
当我们开始一局游戏时,首先出现在界面上的应该是游戏菜单,在这里我们使用menu()
函数实现该功能。
当程序开始运行后,先打印出菜单界面:
输入我们的选择(1 or 0)进入switch
语句,如果选择为0则退出游戏程序,如果选择为1则进入case 1
开始运行游戏程序。如果输入其他的字符,则会提示重新输入!
在game()函数里,我们需要创建两个二维数组来保存数据,这是因为一个9*9的二位数组在逻辑结构上和我们的扫雷棋盘形状是一样的,方便我们进行代码的编写。
为了防止越界,我们数组创建为11*11,相对于9*9的棋盘多了两行两列,但数据放在数组中间9*9的位置里面。
一个数组mine_board放布置好的雷的信息,先都初始化为字符0,然后随机放置雷,有雷的地方布置为字符1。雷的地方布置为字符1是为了方便我们后续统计每个位置附近雷的数量有多少。
另一个数组show_board放排查出的雷的信息,先都初始化为“*”,表示没有被排查的地方,当玩家选择要排查的位置坐标后,显示该位置坐标的信息:
当我们的选择为1(来一局游戏)后,程序就会进入到game函数里。
头文件(game.h)代码:
#pragma once #include<stdio.h> #include<stdlib.h> #include<time.h> #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 #define EASY_METHOD 10 //初始化棋盘 void inite_board(char board[ROWS][COLS], int rows, int cols, char c); //显示棋盘,显示数据内容,所以传进来row和col void display_board(char board[ROWS][COLS], int row, int col); //设置雷 void set_mine(char board[ROWS][COLS], int row, int col); //排查雷 void find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col);
在头文件里,我们不仅要对函数进行声明,还要进行宏定义的设置,这样方便我们在日后将代码更改为其他规格的棋盘。头文件里,ROW和COL代表扫雷数组的数据范围,EASY_METHOD代表简单模式下一局游戏中雷的数量。
然后我们来看一看game函数的实现思路:
void game() { char mine_board[ROWS][COLS] = { 0 }; char show_board[ROWS][COLS] = { 0 };//这两个数组此时都只是不完全初始化 inite_board(mine_board, ROWS, COLS, '0'); inite_board(show_board, ROWS, COLS, '*'); set_mine(mine_board, ROW, COL); int ret = 0; while (1) { display_board(show_board, ROW, COL); ret = find_mine(mine_board, show_board, ROW, COL); if (ret == EASY_METHOD) //和玩家输入得雷数要保持一致 { printf("游戏结束,玩家胜利!\n"); display_board(mine_board, ROW, COL); break; } if (ret == 1) //如果为1,表示被炸到了 { break; } } }
我们的数组在创建好后,需要先对两个数组棋盘进行初始化inite_board(mine_board, ROWS, COLS, '0');
,该函数的最后一个参数代表被传进数组的值将被初始化为该参数。初始化之后,我们先在mine_board数组里面设置雷的信息set_mine(mine_board, ROW, COL);
,然后我们将show_board棋盘打印出来 display_board(show_board, ROW, COL);
,display_board
函数的第一个参数也可以传入mine_board数组,这样就能展示游戏中雷的分布了。此时游戏运行状况为:
然后我们将两个数组传入find_mine(mine_board, show_board, ROW, COL)
函数内,进行扫雷排查,当show_board棋盘内所有没有雷的位置都被找到时,游戏结束,玩家获胜。
void inite_board(char board[ROWS][COLS], int rows, int cols, char c)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = c;
}
}
}
这里较为容易,只需要传参,使用两层循环,将数组内的值都赋值为所传入的参数c
即可。
void display_board(char board[ROWS][COLS], int row, int col) { printf("----------------扫雷游戏-----------------\n"); for (int i = 0; i <= row; i++) { printf("%d ", i); } printf("\n"); for (int i = 1; i <= row; i++) { printf("%d ", i); for (int j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("----------------扫雷游戏-----------------\n"); }
为了方便玩家进行位置的选择,我们要在棋盘的最上方和最左列打印对印的序号位置,然后再其他位置打印我们所保存的数组数据。效果如下:
void set_mine(char board[ROWS][COLS], int row, int col) { int mines = EASY_METHOD;//mines为雷的数量,EASY_METHOD来源于先前的宏定义 while (mines) { int x = rand() % row + 1;//x的取值为1-9 int y = rand() % col + 1;//y的取值为1-9 if (x >= 1 && x <= row && y >= 1 && y <= col) { if (board[x][y] != '1') { board[x][y] = '1'; mines--; } } } }
随机取的x和y的坐标,然后在不是雷的位置布下雷,用字符1代表该位置有雷是为了方便后续统计某个位置周围雷的数量。
ps:这就是所谓的作弊模式
int is_win(char board[ROWS][COLS]) { int count = 0; int i = 0, j = 0; for (i = 1; i <= ROW; i++) { for (j = 1; j <= COL; j++) { if ('*' == board[i][j]) { count++; } } } return count; } int find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col) { int x = 0; int y = 0; while (1) { printf("请输入你要排查的坐标:\n"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show_board[x][y] != '*') { printf("此位置已经排除,请输入其他位置\n"); } else { //如果是雷 if (mine_board[x][y] == '1') { printf("很遗憾,你被炸到了\n"); display_board(mine_board, ROW, COL); return 1; } else//如果不是雷 { //递归实现如果周围没有雷就消失一片 remove_board(mine_board, show_board, row, col, x, y); 统计mine_board数组x,y坐标周围有多少个雷 //int count = round_mines(mine_board, x, y); //show_board[x][y] = count + '0'; } } } else { printf("输入的坐标非法,请重新输入\n"); } break; } return is_win(show_board); }
该函数的设计思路较为简单,大家阅读便知,核心代码在于其中的递归实现一键清空周围不是雷的位置。
int round_mines(char mine_board[ROWS][COLS],int x,int y) { return ( mine_board[x - 1][ y - 1] + mine_board[x - 1][y] + mine_board[x - 1][y + 1] + mine_board[x][y - 1] + mine_board[x][y + 1] + mine_board[x + 1][y - 1] + mine_board[x + 1][y] + mine_board[x + 1][y + 1] - 8 * '0' ); } void remove_board(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col, int x, int y) { if (round_mines(mine_board, x, y) == 0) { //如果要位置在棋盘的四周就不递归了 if ((x == 9 && y >= 1 && y <= 9) || (x == 1 && y >= 1 && y <= 9) || (y == 1 && x >= 1 && x <= 9) || (y == 9 && x >= 1 && x <= 9)) { show_board[x][y] = ' '; } else { show_board[x][y] = ' '; //周围没有雷,就把该位置设为空 for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (show_board[x + i][y + j] == '*')//找到没有被开的位置,进入递归,继续设置空 { remove_board(mine_board, show_board, row, col, x + i, y + j); } } } } } else { show_board[x][y] = round_mines(mine_board, x, y)+'0';//周围有雷的位置返回标记为雷的数量 } }
逐层递归,当该位置没有雷的时候,继续递归附近的位置直到遇到附近有雷的位置,然后将该位置的数据保存为周围雷的数量,然后返回该层递归,当递归到棋盘的四周时同样也要结束递归,防止出现探查一个位置就几乎将整个棋盘都递归完了的情况。
游戏运行状况:
以上就是扫雷游戏的全部内容,相信大家看到这里应该也能够独立的完成属于自己专有的“扫雷游戏”,也相信大家在这过程中对于C语言,对于编程有了更进一步的看法。
很高兴能够为大家带来帮助,码字不易,如果大家觉得我这篇文章有帮助的话,可以
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。