这两天学习了如何用C语言做扫雷小游戏,具体过程及实现思路请看下方代码。
(已完成递归排查雷功能,但标记雷和取消标记雷、标记后显示雷的个数 这两个功能还没做,到时候做完再进行更新。)
递归思路: 首先将单个坐标的周围地雷的地雷信息传给一个3*3的数组,我们先称其为方阵, 方阵中心的元素不需要用到,所以是空符号。
扫雷有一个规则,当这个坐标的正上方和正左方坐标的地雷个数信息都不为’0’时,
则本坐标左上方的坐标将不会被玩家得知,如果至少有一方的地雷个数信息是’0’时,
则将左上方的坐标信息告知玩家,右上方、左下方、右下方的坐标亦是如此。
所以我们可以先判断方阵中的元素满不满足上述条件,满足则给玩家提供(左上。。。右下)坐标信息, 然后再以左上。。。右下坐标为起点,进行递归。
其中递归的条件是该坐标周围没有地雷。而起点坐标的正上、下、左、右的坐标不用通过数组判断,只要 满足递归条件就进行递归。
practice.c
#define _CRT_SECURE_NO_WARNINGS
# include"game.h"
void menu() //菜单
{
printf("--------------------------\n");
printf("--------- 1.Play ---------\n");
printf("--------- 0.Exit ---------\n");
printf("--------------------------\n");
}
void game()
{
srand((unsigned int)time(NULL)); //time函数返回类型是time_t,所以要将类型强制转换为srand函数的形参所需要的类型unsigned int
char board_mine[ROWS][COLS]; //这里要做两个棋盘,一个用于放置地雷,一个用于显示地雷信息
char board_show[ROWS][COLS]; //通过将两个棋盘的比对来进行扫雷
//初始化棋盘
init_board(board_mine, ROWS, COLS, '0'); //在棋盘外围加上一圈以放置排查地雷的时候数组访问越界
init_board(board_show, ROWS, COLS, '*');
//打印棋盘
//display_board(board_mine, ROW, COL);
display_board(board_show, ROW, COL);
//布置雷
set_mine(board_mine, ROW, COL);
//display_board(board_mine, ROW, COL); //这里用于开金手指进行程序测试
//排查雷
find_mine(board_mine, board_show, ROW, COL);
}
void test()
{
int input = 0;
do
{
menu();
printf("请输入你的选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请从新选择!\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
/*
拓展:
1.对安全坐标周围的8个坐标做递归操作
条件:1.该坐标不是雷;2.该坐标周围没有雷;3.该坐标没有被排查过
这个目标完成了,并且进行递归思路升级,详见game.c的
void recursion_check(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
2.标记雷和取消标记雷,标记后显示雷的个数
这个目标还没有完成
*/
game.h
#pragma once
#define ROW 9 //扫雷区域大小
#define COL 9
#define ROWS ROW+2 //加宽棋盘
#define COLS COL+2
#define EASY_COUNT 10 //设置地雷个数
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//初始化棋盘
void init_board(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
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[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//递归检查周围情况
void recursion_check(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
//输赢判断
int is_win(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//废弃函数,记录自己行不通的思路
void recursion_check_complement(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
game.c
#define _CRT_SECURE_NO_WARNINGS
# include"game.h"
void init_board(char board[ROWS][COLS], int rows, int cols, char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void display_board(char board[ROWS][COLS], int row, int col)
{
printf("------------------------------\n");
printf(" ");
for (int c = 1; c <= col; c++)
{
printf("%d ", c);
}
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 count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1; //坐标,范围1-9,使地雷都落在棋盘内
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
//地雷信息个数传递
static int count_mines(char mine[ROWS][COLS],int x,int y) //坐标周围地雷个数检查
{
char count = 0;
//printf("%d\n", count);
for (int i = -1; i < 2; i++)
{
for (int j = -1; j < 2; j++)
{
if (i == 0 && j == 0)
continue;
count += mine[x + i][y + j]-'0';
//printf("%d\n", count);
}
}
//将坐标(x,y)周围的元素加起来后减去八个字符0,得到的是周围地雷的数量,字符0的ASCII是48
return count;
}
//将坐标周围地雷个数信息传给数组arr
static void get_info_around(char mine[ROWS][COLS], char arr[3][3], int x, int y)
{
for (int i = -1; i < 2; i++)
{
if (x + i >= 1 && x + i <= ROW) //防止访问数组超出行范围
{
for (int j = -1; j < 2; j++)
{
if (i == 0 && j == 0) //避免排查本身坐标
continue;
if (y + j >= 1 && y + j <= COL) //防止访问数组超出列范围
{
arr[i + 1][j + 1] = count_mines(mine, x + i, y + j) + '0';
}
}
}
}
}
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int step = 0;
int flag = 0;
while (1)
{
flag = is_win(mine, show, row, col);
printf("flag = %d\n", flag);
if (flag) //排查够71次后就说明排查完了
{
printf("恭喜你,排雷成功!\n");
break;
}
printf("请输入排查坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] != '*')
{
printf("该坐标已被排查过,请重新选择。\n");
continue;
}
else if (mine[x][y] == '1')
{
printf("你踩到雷了,游戏结束。\n");
display_board(mine, ROW, COL);
break;
}
else
{
show[x][y] = count_mines(mine,x,y)+'0'; //此处的0不是数字0,而是字符0,ASCII码为48
if(show[x][y] == '0')
recursion_check(mine, show, x, y);
display_board(show, ROW, COL);
step++;
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
}
/*
递归思路:
首先将单个坐标的周围地雷的地雷信息传给一个3*3的数组,我们先称其为方阵,
方阵中心的元素不需要用到,所以是空符号。
扫雷有一个规则,当这个坐标的正上方和正左方坐标的地雷个数信息都不为'0'时,
则本坐标左上方的坐标将不会被玩家得知,如果至少有一方的地雷个数信息是'0'时,
则将左上方的坐标信息告知玩家,右上方、左下方、右下方的坐标亦是如此。
所以我们可以先判断方阵中的元素满不满足上述条件,满足则给玩家提供(左上。。。右下)坐标信息,
然后再以左上。。。右下坐标为起点,进行递归。
其中递归的条件是该坐标周围没有地雷。而起点坐标的正上、下、左、右的坐标不用通过数组判断,只要
满足递归条件就进行递归。
*/
void recursion_check(char mine[ROWS][COLS],char show[ROWS][COLS], int x, int y)
{
char arr[3][3] = { ' ' };
get_info_around(mine, arr, x, y); //将坐标周围地雷信息传给数组arr
for (int i = -1; i < 2; i++)
{
if (x + i >= 1 && x + i <= ROW) //防止访问数组超出行范围
{
for (int j = -1; j < 2; j++)
{
if (i == 0 && j == 0) //避免排查本身坐标
continue;
if (y + j >= 1 && y + j <= COL) //防止访问数组超出列范围
{
if (mine[x + i][y + j] == '0')
{
if (show[x + i][y + j] == '*') //当这个坐标没被排查过时才进行递归检查
{
if (i == -1 && j == -1) //坐标左上检查
{
if (arr[0][1] == '0' || arr[1][0] == '0')
{
//show[x + i][y + j] = count_mines(mine, x + i, y + j) + '0';
show[x + i][y + j] = arr[0][0]; //将数组左上方的元素传给show棋盘
if (show[x + i][y + j] == '0') //如果该坐标不是字符0则停止进入递归
recursion_check(mine, show, x + i, y + j);
}
}
if (i == -1 && j == 1) //坐标右上检查
{
if (arr[0][1] == '0' || arr[1][2] == '0')
{
//show[x + i][y + j] = count_mines(mine, x + i, y + j) + '0';
show[x + i][y + j] = arr[0][2];
if (show[x + i][y + j] == '0')
recursion_check(mine, show, x + i, y + j);
}
}
if (i == 1 && j == -1) //坐标左下检查
{
if (arr[1][0] == '0' || arr[2][1] == '0')
{
//show[x + i][y + j] = count_mines(mine, x + i, y + j) + '0';
show[x + i][y + j] = arr[2][0];
if (show[x + i][y + j] == '0')
recursion_check(mine, show, x + i, y + j);
}
}
if (i == 1 && j == 1) //坐标右下检查
{
if (arr[1][2] == '0' || arr[2][1] == '0')
{
//show[x + i][y + j] = count_mines(mine, x + i, y + j) + '0';
show[x + i][y + j] = arr[2][2];
if (show[x + i][y + j] == '0')
recursion_check(mine, show, x + i, y + j);
}
}
else
{
show[x + i][y + j] = count_mines(mine, x + i, y + j) + '0';
if (show[x + i][y + j] == '0')
recursion_check(mine, show, x + i, y + j);
}
}
}
}
}
}
}
}
int is_win(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int count_show = 0;
int count_mine = 0;
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (show[i][j] == '*')
count_show++;
}
}
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (mine[i][j] == '1')
count_mine++;
}
}
printf("count_show = %d\n", count_show);
printf("count_mines = %d\n", count_mine);
if (count_show == count_mine)
{
return 1;
}
return 0;
}
//废弃函数,记录自己行不通的递归思路(栈溢出,应该是访问数组时越界了)
void recursion_check_complement(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x - 1 >= 1 && x - 1 <= ROW && x + 1 <= ROW && x + 1 >= 1 && y - 1 >= 1 && y - 1 <= ROW && y + 1 <= COL && y + 1 >= 1)
{
if (show[x - 1][y] == '*') //坐标未排查过才进行排查
show[x - 1][y] = count_mines(mine, x - 1, y) + '0'; //正上坐标排查
if (show[x][y - 1] == '*')
show[x][y - 1] = count_mines(mine, x, y - 1) + '0'; //正左坐标排查
if (show[x][y + 1] == '*')
show[x][y + 1] = count_mines(mine, x, y + 1) + '0'; //正右坐标排查
if (show[x + 1][y] == '*')
show[x + 1][y] = count_mines(mine, x - 1, y) + '0'; //正下坐标排查
if (show[x - 1][y] == '0' || show[x][y - 1] == '0') //左上方坐标排查条件
{
if (show[x - 1][y - 1] == '*') //坐标未排查过才进行排查
show[x - 1][y - 1] = count_mines(mine, x - 1, y - 1) + '0';
}
if (show[x - 1][y] == '0' || show[x][y + 1] == '0') //右上方坐标排查条件
{
if (show[x - 1][y + 1] == '*') //坐标未排查过才进行排查
show[x - 1][y + 1] = count_mines(mine, x - 1, y + 1) + '0';
}
if (show[x + 1][y] == '0' || show[x][y - 1] == '0') //左下方坐标排查条件
{
if (show[x + 1][y - 1] == '*') //坐标未排查过才进行排查
show[x + 1][y - 1] = count_mines(mine, x + 1, y - 1) + '0';
}
if (show[x + 1][y] == '0' || show[x][y + 1] == '0') //右下方坐标排查条件
{
if (show[x + 1][y - 1] == '*') //坐标未排查过才进行排查
show[x + 1][y - 1] = count_mines(mine, x + 1, y + 1) + '0';
}
//如果满足条件后,进行递归
//1. 左上坐标进行递归判断
if (show[x - 1][y - 1] == '0')
{
recursion_check_complement(mine, show, x - 1, y - 1);
}
//2. 正上坐标进行递归判断
if (show[x - 1][y] == '0')
{
recursion_check_complement(mine, show, x - 1, y);
}
//3. 右上坐标进行递归判断
if (show[x - 1][y + 1] == '0')
{
recursion_check_complement(mine, show, x - 1, y + 1);
}
//4. 左正坐标进行递归判断
if (show[x][y - 1] == '0')
{
recursion_check_complement(mine, show, x, y - 1);
}
//5. 右正坐标进行递归判断
if (show[x][y + 1] == '0')
{
recursion_check_complement(mine, show, x, y + 1);
}
//6. 左下坐标进行递归判断
if (show[x + 1][y - 1] == '0')
{
recursion_check_complement(mine, show, x + 1, y - 1);
}
//7. 左下坐标进行递归判断
if (show[x + 1][y - 1] == '0')
{
recursion_check_complement(mine, show, x + 1, y - 1);
}
//8. 正下坐标进行递归判断
if (show[x + 1][y] == '0')
{
recursion_check_complement(mine, show, x + 1, y);
}
//9. 正下坐标进行递归判断
if (show[x + 1][y + 1] == '0')
{
recursion_check_complement(mine, show, x + 1, y + 1);
}
}
}
程序截图:
更改下雷的数量,快速看结果