c语言实现扫雷(含循环递归展开)

简介: 本笔记通过c语言实现扫雷小游戏(包含递归展开)游戏实现逻辑位于test.c文件,整个游戏头文件位于game.h,游戏进程的具体操作于game.c中实现。

1.游戏概述


该游戏有9*9个格子(在头文件game.h中玩家可通过改变ROW COL的值来改变棋盘行列数),格子中已随机布置生成10颗雷(玩家也可通过改变EASY_COUNT的值来自行改变雷的数目),test.c中的文件实时记录已经排查过的格子个数,当玩家把雷全部找出且没有选中雷时,游戏胜利,否则,游戏失败!


2.游戏设计


本游戏代码设计通过两个棋盘来完成,在mine棋盘中存储雷的信息,0表示非雷,1表示为类;在show棋盘中初始状态全为’*‘,当玩家开始游戏后,排查出的不是雷的位置有两种情况,1是该位置为数字,即表示该位置周围的雷的数量,2是空格,即表示该位置周围没有雷


3.代码实现


请读者先按照test.c文件来理清游戏逻辑,再去game.c文件中进一步探究步骤的具体实现(game.h只是头文件及函数的声明)


test.c


该文件的阅读请从main函数开始梳理游戏逻辑


#include"game.h"
void menu()
{
  printf("****************\n");
  printf("*****1.play*****\n");
  printf("*****0.exit*****\n");
  printf("****************\n");
}
void game()
{
    //定义两个棋盘
  char mine[ROWS][COLS] = { 0 };//mine棋盘存放雷的信息
  char show[ROWS][COLS] = { 0 };//show棋盘存放排查情况
    //以下五个函数的具体实现在game.c文件中
    //初始化棋盘
  initBoard(mine, ROWS, COLS, '0');//0为非雷,1为雷
  initBoard(show, ROWS, COLS, '*');//‘*’表示未知,‘ ’或‘数字’均表示周围8个格子雷的数量
  displayBoard(show, ROW, COL);//打印棋盘
  setMine(mine, ROW, COL);//随机设置雷
  fineBoard(mine, show, ROW, COL);//玩家扫雷
}
int main()
{
  system("color f4");//实现输出框背景及字体的颜色改变,具体实现见博主博客:https://editor.csdn.net/md/?articleId=121968165
  srand((unsigned int)time(NULL));//此处不做解释,后边解释
  int input = 0;
  do//设置循环,玩家选择是否游戏
  {
  menu();//菜单打印
  printf("请选择>\n");
  scanf("%d", &input);
  switch (input)
  {
  case 1:
    printf("游戏开始\n");
    game();//玩家选择1则进入游戏
    break;
  case 0:
    printf("退出游戏\n");
    break;
  default:
    printf("选择错误,请重新选择\n");
    break;
  }
  } while (input);
}


game.h(该文件只是头文件引用及函数的声明,无需过多阅读)


#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
void initBoard(char board[ROWS][COLS], int rows, int cols, char set);
void displayBoard(char board[ROWS][COLS], int row, int col);
void setMine(char mine[ROWS][COLS], int row, int col);
void fineBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);


game.c


initBoard(初始化棋盘)


两个棋盘传过来棋盘数组及行列数进行初始化,还有一个字符参数用set变量接受,mine棋盘全部初始化为‘0’,show棋盘全部初始化为‘*’


void initBoard(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;
  }
  }
}


displayBoard(棋盘打印)


该部分就是通过循环将棋盘打印,以下为样图


微信图片_20230110150436.png


void displayBoard(char board[ROWS][COLS], int row, int col)
{
  printf("---------------------------\n");
  printf("---------------------------\n");
  int i, j;
  for (j = 0; j <= COL; j++)
  {
  printf("|---");
  }
  printf("|\n");
  for (i = 0; i <= COL; i++)
  {
  printf("| %d ", i);
  }
  printf("|\n");
  for (i = 0; i < ROW; i++) {
  for (j = 0; j <= COL; j++) {
    printf("|---");
  }
  printf("|\n");
  printf("| %d ", i + 1);
  for (j = 0; j < COL; j++) {
    printf("| %c ", board[i+1][j+1]);
  }
  printf("|\n");
  }
  for (j = 0; j <= COL; j++) 
  {
  printf("|---");
  }
  printf("|\n");
}


setMine(随机安放雷在mine棋盘上)


count为雷的数量,玩家可通过更改game.h文件中EASY_COUNT的值来自行改变雷的数目


void setMine(char mine[ROWS][COLS], int row, int col)
{
  int x = 0, y = 0, count = EASY_COUNT;
  while (count)
  {//此处解释srand((unsigned int)time(NULL)),是为了返回时间戳以随机生成坐标,将该位置设置为雷
  x = rand() % row + 1;
  y = rand() % col + 1;
  if (mine[x][y] == '0')
  {//如果该位置为0,将该位置置为1,count-1,因为可能随机生成同样的数字,所以,循环次数>=count
    mine[x][y] = '1';
    count--;
  }
  }
}


fineBoard(玩家扫雷)


实现这一步需要三个函数fineBoard,expandBoard,getCount来共同完成


fineBoard


void fineBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
  printf("请输入你要排查的位置>\n");
  int x, y;
  int win = 0;//win的值实时记录已知非雷格子数,当win的值与row*col-EASY_COUNT值相等时,雷被排完,游戏结束
  while (win<row*col-EASY_COUNT)//设置循环让玩家进行排雷,不断增加win的值
  {
  scanf("%d %d", &x, &y);
  if (x >= 1 && x <= row && y >= 1 && y <= col)//判断输入坐标合法性
  {
    if (mine[x][y] == '1')//若为1,即被炸
    {
    printf("很遗憾,你被炸死了!\n");
    displayBoard(mine, ROW, COL);
    break;
    }
    else
    {
    expandBoard(mine, show, x, y,&win);//该函数下边解释
    displayBoard(show, ROW, COL);
    }
  }
  else
  {
    printf("你输入的坐标非法,请重新输入\n");
  }
  }
  if (win ==  row * col - EASY_COUNT)
  {
  printf("恭喜你获得胜利!\n");
  displayBoard(mine, ROW, COL);
  }
}


expandBoard(设置循环递归展开)


进入该函数是因为该位置不是雷才会进入,如果该位置周围有雷则该位置对应show棋盘变为数字,不会进入递归,若周围无雷则才会经过判断后进行递归展开


static void expandBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,int*win)
{//因为需要通过递归展开,所以递归的位置每次进入时先判断位置的合法性
  if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
  {
  if (show[x][y] == ' ' || show[x][y] != '*')
    return;//排除已经检查过的点,避免形成死递归
  else if (getCount(mine, x, y) != 0)//getCount()函数下边解释
  {//若该位置周围有雷也不会进行递归,该位置对应show棋盘变为数字,win的值+1(也是排查了一个位置的情况)
    show[x][y] = getCount(mine, x, y) + '0';
    (*win)++;
    return;
  }
  else//该位置周围雷数量为0时才会进入这一步
  {
    show[x][y] = ' ';//将该位置置为‘ ’,并让win+1
    (*win)++;
    for (int i = -1; i <= 1; i++)//设置循环将该位置所在九宫格的所有格子进行递归
    {
    for (int j = -1; j <= 1; j++)
    {//当i=0,j=0时不会进入递归,因为此时该位置在循环上边已经将其置为‘ ’,避免形成死递归
      expandBoard(mine, show, x + i, y + j, win);
    }
    }
  }
  }
}


getCount(计算当前位置周围雷的数量)


通过设置循环将该位置的周围八个位置计算,因为棋盘数组为字符,则需要通过-‘0’将其转变为int型


static int getCount(char mine[ROWS][COLS], int x, int y)
{
  int i, j;
  int count = 0;
  for (i = -1; i <= 1; i++)//该循环将该位置所在九宫格进行排查
  {
  for (j = -1; j <= 1; j++)
  {
    count = count+mine[x + i][y + j]-'0';
  }
  }
  return count;
}


以下为game.c文件全部源码


#include"game.h"
void initBoard(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 displayBoard(char board[ROWS][COLS], int row, int col)
{
  printf("---------------------------\n");
  printf("---------------------------\n");
  int i, j;
  for (j = 0; j <= COL; j++)
  {
  printf("|---");
  }
  printf("|\n");
  for (i = 0; i <= COL; i++)
  {
  printf("| %d ", i);
  }
  printf("|\n");
  for (i = 0; i < ROW; i++) {
  for (j = 0; j <= COL; j++) {
    printf("|---");
  }
  printf("|\n");
  printf("| %d ", i + 1);
  for (j = 0; j < COL; j++) {
    printf("| %c ", board[i+1][j+1]);
  }
  printf("|\n");
  }
  for (j = 0; j <= COL; j++) 
  {
  printf("|---");
  }
  printf("|\n");
}
void setMine(char mine[ROWS][COLS], int row, int col)
{
  int x = 0, y = 0, count = EASY_COUNT;
  while (count)
  {
  x = rand() % row + 1;
  y = rand() % col + 1;
  if (mine[x][y] == '0')
  {
    mine[x][y] = '1';
    count--;
  }
  }
}
static int getCount(char mine[ROWS][COLS], int x, int y)
{
  int i, j;
  int count = 0;
  for (i = -1; i <= 1; i++)
  {
  for (j = -1; j <= 1; j++)
  {
    count = count+mine[x + i][y + j]-'0';
  }
  }
  return count;
}
static void expandBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,int*win)
{
  if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
  {
  if (show[x][y] == ' ' || show[x][y] != '*')//排除已经检查过的点
    return;
  else if (getCount(mine, x, y) != 0)
  {
    show[x][y] = getCount(mine, x, y) + '0';
    (*win)++;
    return;
  }
  else
  {
    show[x][y] = ' ';
    (*win)++;
    for (int i = -1; i <= 1; i++)
    {
    for (int j = -1; j <= 1; j++)
    {
      expandBoard(mine, show, x + i, y + j, win);
    }
    }
  }
  }
}
void fineBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
  printf("请输入你要排查的位置>\n");
  int x, y;
  int win = 0;
  while (win<row*col-EASY_COUNT)
  {
  scanf("%d %d", &x, &y);
  if (x >= 1 && x <= row && y >= 1 && y <= col)
  {
    if (mine[x][y] == '1')
    {
    printf("很遗憾,你被炸死了!\n");
    displayBoard(mine, ROW, COL);
    break;
    }
    else
    {
    expandBoard(mine, show, x, y,&win);
    displayBoard(show, ROW, COL);
    }
  }
  else
  {
    printf("你输入的坐标非法,请重新输入\n");
  }
  }
  if (win ==  row * col - EASY_COUNT)
  {
  printf("恭喜你获得胜利!\n");
  displayBoard(mine, ROW, COL);
  }
}
相关文章
|
2月前
|
C语言
初识C语言2——分支语句和循环语句
初识C语言2——分支语句和循环语句
78 5
|
2月前
|
C语言
扫雷游戏(用C语言实现)
扫雷游戏(用C语言实现)
125 0
|
2月前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
71 7
|
2月前
|
C语言
【c语言】循环语句
循环结构是C语言中用于简化重复操作的重要工具,主要包括while循环、do-while循环和for循环。while循环是最基本的形式,通过不断检查条件来决定是否继续执行循环体。do-while循环则先执行循环体,再检查条件,至少执行一次。for循环逻辑更复杂,但使用频率最高,适合初始化、条件判断和更新变量的集中管理。此外,循环中还可以使用break和continue语句来控制循环的提前终止或跳过当前迭代。最后,循环可以嵌套使用,解决更复杂的问题,如查找特定范围内的素数。
51 6
|
2月前
|
C语言
c语言回顾-函数递归(上)
c语言回顾-函数递归(上)
44 2
|
2月前
|
Serverless C语言
C语言控制语句:分支、循环和转向
C语言控制语句:分支、循环和转向
|
2月前
|
存储 算法 安全
C语言实现扫雷游戏
C语言实现扫雷游戏
|
2月前
|
C语言
初学者指南:使用C语言实现简易版扫雷游戏
初学者指南:使用C语言实现简易版扫雷游戏
44 0
|
2月前
|
C语言
教你快速理解学习C语言的循环与分支
教你快速理解学习C语言的循环与分支
19 0
|
2月前
|
C语言
c语言回顾-函数递归(下)
c语言回顾-函数递归(下)
42 0