来点C练习——使用数组函数来制造扫雷游戏,扫雷游戏初阶,分支语句,循环语句,rand函数得到随机值

一、制作游戏的大框架

对于游戏,首先显示在我们面前的是菜单选项,这里我们需要制作一个简单的显示出的菜单界面,还有菜单选项结果。

在源文件创建一个 test.c 文件,作为制作游戏的大框架的文件。

test.c 文件:

void menu()//这里是设计出显示在玩家视野的菜单栏
{
	printf("**********************\n");
	printf("******  1.Play  ******\n");
	printf("******  0.Exit  ******\n");
	printf("**********************\n");
}

int main()
{
	
	int input = 0;//input的值会在循环中使用判断,因此放在循环外

	do//我们会进行多次游戏,所以游戏应该放在循环体中,do while语句
	{
		menu();//这里是显示出的菜单页面
		printf("Please select:>\n");
		scanf("%d", &input);
		switch (input)//switch语句作为游戏初始的开关
		{
			case 1://input的值为1时就进入游戏
				game();//这里是扫雷游戏的主体,在这个函数中设计扫雷游戏
				//printf("game start");//这里打印出游戏开始代替游戏本身进行测试该段代码未出错
				break;
			case 0://input的值为0,则退出游戏
				printf("Exit\n");
				break;
			default://输入除1,0的值,则提示输入错误,循环继续,回到选择输入的代码
				printf("Error,please select\n");
				break;
		}
		printf("\n");
	} while (input);//循环语句时根据input即我们输入值判断是否进入循环的。
//非0为真,进入循环
	return 0;
}

在学习函数时,我们了解到多个文件的使用,为了让代码看起来整齐清晰,我们将接下来写的代码分为三个部分。

首先是main函数及设计游戏的自定义函数在test.c文件中;

其次是库函数和自定义函数的声明在game.h文件中;

最后是自定义函数的定义在game.c文件中。

而在我们创建好这些文件后,会发现在测试上列菜单代码时,程序会报警告,这时我们应在.c 文件中引用我们创建的头文件 game.h ,建立它们之间的联系。

#include "game.h"//在引用自定义头文件时,使用引号

二、扫雷游戏

(一)扫雷游戏的类型

扫雷游戏分为基础,中级,专家,自定义等类型。

难度由它的总格子和雷的数量决定。

(二)扫雷游戏的玩法:

基础的举例,格子是9*9的,左上角是雷数,右上角是秒数(从0计时),中间的笑脸会在触雷和获胜时候变脸。

(三)扫雷游戏的设计分析

从上图中我们可以看到显示在玩家面前的界面时逐渐变化的,因此我们这里制作两个界面。

一个是真实界面mine(玩家在游戏结束前不可见),一个是玩家选择界面show(玩家操作界面)。

那么我们就需要使用二维数组来创造需要的游戏界面,在使用数组时,我们需要考虑到数组的访问越界是否会影响程序。

因为在边界处的格子,我们在判断它周边存在的雷时,是以它为中心扫描他所在九宫格的其他格子,在这个扫描中就存在数组的访问越界。因此我们还需要一个比肉眼可见的界面更大的格子。

三、游戏代码

在我们上面的代码中 game() 是设计游戏主体的函数。接下来结合(一)的分析来设计游戏的界面。

我们假设界面的行为ROW,列为COL。

基础模式中就是设计一个九乘九和十一乘十一的数组。

考虑到后期更有利于改变格子数目,我们可以在game.h中

(一)game()函数的设计

完善game.h

//game.h
//这里放置的是函数的声明,库函数和自定义函数的
//须注意
#pragma once


#include <stdio.h>

#define ROW 9
#define COL 9//显示在玩家面前的界面格子数

#define ROWS ROW+2//为了防止数组越界访问而设计的隐藏界面
#define COLS COL+2

#define EasyCount 10//基础模式下的雷数

test.c

//test.c
//这里是game()函数的设计,也是游戏的主体

void game()
{
	//创建需要的数组
  //扫雷游戏的实现
//mine数组是用来存放布置好的雷的信息,玩家看不到的信息
char mine[ROWS][COLS] = { 0 };    //'0'放置的是字符
//show数组是用来存放排查出的雷的信息,玩家游玩的界面
char show[ROWS][COLS] = { 0 };   //'*'放置的是字符

//函数初始化
	InitBoard(mine,ROW,COL,'0');//放置‘0’
	InitBoard(show,ROW,COL,'*');//放置‘*’
  
  //放置雷
	SetMine(mine,ROW,COL);

  //打印界面
  //DisPlayBoard(mine,ROW,COL);//这里可以作为测试放置雷是否正确,
  DisPlayBoard(show,ROW,COL);

  //探查雷
  FindMine(mine,show,ROW,COL);
}

(二)设计源文件game.c

我们在上列的game()函数中设计好了游戏的主体,现在开始完善函数内部的自定义函数。

在使用我们的自定义函数时,需要注意在头文件game.h中,我们要进行声明

完善game.h

//game.h
//这里放置的是函数的声明,库函数和自定义函数的
//须注意
#pragma once


#include <stdio.h>

#define ROW 9
#define COL 9//显示在玩家面前的界面格子数

#define ROWS ROW+2//为了防止数组越界访问而设计的隐藏界面
#define COLS COL+2

#define EasyCount 10//基础模式下的雷数

//下列是自定义函数的声明
//函数初始化,为了现目,将对应的自定义函数写上作为对比
	//InitBoard(mine,ROW,COL,'0');//放置‘0’
	//InitBoard(show,ROW,COL,'*');//放置‘*’

	void InitBoard(char arr[ROWS][COLS],int row,int col,char set);
  //因为数组mine和show都需要进行初始化,且均为char类型,因此都用arr数组来定义
  //‘0’,‘*’,是字符,均为初始化,因此使用char set 来概括

  
  //放置雷,是在玩家玩游戏时看不到的界面
	//SetMine(mine,ROW,COL);
	void SetMine(char mine[ROWS][COLS],int row, int col);
  

  //打印界面
  //DisPlayBoard(mine,ROW,COL);//这里可以作为测试放置雷是否正确,
  //DisPlayBoard(show,ROW,COL);
  //我们上列打印的设计也是含有mine和show数组,写作为一个函数即可
	void DisPlayBoard(char arr[ROWS][COLS],int row,int col);

  //探查雷
  //FindMine(mine,show,ROW,COL);
  void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col);

接下来是根据我们game()中的设计来自定义函数的功能

自定义函数的定义放置在game.c的文件中.

初始化函数_game.c

//下列是自定义函数的定义
//函数初始化,为了现目,将对应的自定义函数写上作为对比
void InitBoard(char arr[ROWS][COLS],int rows,int cols,char set)
{
    int i = 0;
    for(i=0; i<=rows; i++)
    {
        int j = 0;
        for(j=0; j<=cols; j++)
        {
            arr[i][j] = set;//set就代表'0'.'*'
        }
    }
}

放置雷

注意rand函数的使用要包括的头文件放置在game.h中,这时我们主要补充完善头文件

game.h
#include <stdlib.h>
#include <time.h>
game.c
//game.c

//放置雷,是在玩家玩游戏时看不到的界面
//SetMine(mine,ROW,COL);
//放置雷就是随机放置,这时就需要我们的rand函数,
//使用rand函数,需要通过srand函数的参数seed来设置rand函数生成随机数的时候的种子
void SetMine(char mine[ROWS][COLS],int row, int col)
{
    //雷放置在mine数组中,数量为EasyCount个,
    int count = EasyCount;
    while(count)//当count为0时为假,循环停止
    {
        int x = rand()%row + 1;
        //注意:取模row,的值为0~row-1,我们设置的row是9,所以是0~8,
        //加上一就是1~9,正好对应数组的下标
        int y = rand()%col + 1;
        if(mine[x][y] == '0') //注意我们的随机值可能出现重复的情况,
        {//如果不对数组中的数进行判断可能出现重复设置的情况,导致我们设置的10个雷不准确
            mine[x][y] = '1';//数组时char类型的,不要手误,打成了1
            count--;//这是判断10个雷的条件,以及循环结束的条件
        }
    }
}
test.c文件

完善test.c文件,使用rand函数,需要通过srand函数的参数seed来设置rand函数生成随机数的时候的种子

//test.c文件

//srand((unsigned int)time(NULL));
//放在do while循环前
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));//注意头文件
	do
	{
		menu();
		printf("Please select:>\n");
		scanf("%d", &input);
		switch (input)
		{
			case 1:
				game();//游戏入口
				//printf("game start");
				break;
			case 0:
				printf("Exit\n");
				break;
			default:
				printf("Error,please select\n");
				break;
		}
		printf("\n");
	} while (input);

	return 0;
}

打印界面_game.c

//打印界面
//DisPlayBoard(mine,ROW,COL);//这里可以作为测试放置雷是否正确,
//DisPlayBoard(show,ROW,COL);
//我们上列打印的设计也是含有mine和show数组,写作为一个函数即可
void DisPlayBoard(char arr[ROWS][COLS],int row,int col)
{
    int i = 0;
    int j = 0;
    printf("-----Game Interface-----\n");
    
    //为了玩家方便找到对应的坐标,将行列标上数字
    //列  从0开始是为了对齐
    for(i=0; i<col; i++)
    {
        printf("%d ",i);//i = 0,1,2,3,4,…
    }
    printf("\n");

        for(i=1; i<row; i++)//注意,这个函数是打印函数,是会出现在玩家视野中的游戏界面
    {   //而出现在玩家界面的格子是row*col的
        {
            printf("%d ",i);//行  搭配后一个for循环后的换行代码,变为每行的标注
            for(j=1; j<col; j++)
            {
                printf("%c ",arr[i][j]);//数组时char类型的,打印出row*col的格子
            }
            printf("\n");//换行
        }
    }
}

探查雷_game.c

玩家输入坐标,判断该坐标是否为雷,如果是雷,则游戏结束,玩家失败;如果不是雷,需要判断这个坐标周围有多少雷,为玩家提供提示。

选择的坐标是九宫格的中心,探查的范围是以这个坐标为中心的九宫格范围,所以处于边界的坐标在探查中会出现访问越界状况,但是我们创建的数组arr[ROWS][COLS]可以防止访问越界。

玩家输入坐标,我们需要进行下列判断:

  • 是否是合法坐标,有无超过我们固定的数组arr[row][col]的范围;
  • 是合法坐标,这个坐标是否是未选择过的坐标'*'就是未选择过的坐标;
  • 是合法坐标,但是重复选择坐标,坐标显示已不是'*';
  • 是合法坐标,且未被选择过,但是是雷,则游戏结束,玩家失败;
  • 是合法坐标,且未被选择过,不是雷,玩家接着往下选择,直到所有的雷被避开,玩家这时获胜,游戏结束
  • 玩家输入坐标是个循环,直到胜利都是需要在循环体内输入坐标
  • 获胜后,判断走的步数和无雷位置数量一致,则获胜,因为在获胜前一旦触雷,游戏就结束了
//使用static修饰,只能在game.c 文件中使用
//放置在需要使用的函数前,即完成了函数的声明和定义,无须在game.h中声明
static int JudgeSteps(char mine[ROWS][COLS],int x,int y)
{
    int i = 0;
    int j = 0;
    int count = 0;
    for(i=x-1; i<=x+1; i++)
    {
        for(j=y-1; j<=y+1; j++)
        {
            count += (mine[i][j] - '0');
        }
    }
    return count;
}


//探查雷
//FindMine(mine,show,ROW,COL);
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col)
{
    int x = 0;
    int y = 0;
    int steps = 0;
    while(steps < row*col-EasyCount)
    {
        printf("Please select:>");
        scanf("%d %d",&x,&y);
        if(x>=1 && x<=row && y>=1 && y<=col)
        {
            if(show[x][y] == '*')
            {
                if(mine[x][y] == '1')
                {
                    printf("Unfortunately, you lost\n");
                    DisPlayBoard(mine, ROW, COL);//打印真实游戏界面
                    break;//游戏结束,跳出循环
                }
                else//不是雷,查它的周边有多少雷
                {
                    int count = JudgeSteps(mine,x,y);
                    show[x][y] = count + '0';
                    DisPlayBoard(show, ROW, COL);

                    steps++;//避开了一个雷,++才能达到跳出循环的条件
                }
            }
            else
            {
                printf("Repeated input.Please reselect\n");
            }
        }
        else
        {
            printf("Error.Please reselect\n");
        }
    }
    if(steps == row * col - EasyCount)
    {
        printf("Congratulations.YOU WIN.\n");
        DisPlayBoard(mine, ROW, COL);
    }
}

四、整理代码

(一)game.h

//game.h
//这里放置的是函数的声明,库函数和自定义函数的
//须注意

#pragma once

#include <stdio.h>
#include <stdlib.h>//rand函数的使用声明
#include <time.h>//time函数的使用声明


#define ROW 9
#define COL 9//显示在玩家面前的界面格子数

#define ROWS ROW+2//为了防止数组越界访问而设计的隐藏界面
#define COLS COL+2

#define EasyCount 10//基础模式下的雷数

//下列是自定义函数的声明
//函数初始化,为了现目,将对应的自定义函数写上作为对比
	//InitBoard(mine,ROW,COL,'0');//放置‘0’
	//InitBoard(show,ROW,COL,'*');//放置‘*’

	void InitBoard(char arr[ROWS][COLS],int row,int col,char set);
  //因为数组mine和show都需要进行初始化,且均为char类型,因此都用arr数组来定义
  //‘0’,‘*’,是字符,均为初始化,因此使用char set 来概括

  
  //放置雷,是在玩家玩游戏时看不到的界面
	//SetMine(mine,ROW,COL);
	void SetMine(char mine[ROWS][COLS],int row, int col);
  

  //打印界面
  //DisPlayBoard(mine,ROW,COL);//这里可以作为测试放置雷是否正确,
  //DisPlayBoard(show,ROW,COL);
  //我们上列打印的设计也是含有mine和show数组,写作为一个函数即可
	void DisPlayBoard(char arr[ROWS][COLS],int row,int col);

  //探查雷
  //FindMine(mine,show,ROW,COL);
  void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col);

(二)test.c

//test.c

#include "game.h"

void menu()//这里是设计出显示在玩家视野的菜单栏
{
	printf("**********************\n");
	printf("******  1.Play  ******\n");
	printf("******  0.Exit  ******\n");
	printf("**********************\n");
}

void game()
{
	//创建需要的数组
  //扫雷游戏的实现
//mine数组是用来存放布置好的雷的信息,玩家看不到的信息
char mine[ROWS][COLS] = { 0 };    //'0'放置的是字符
//show数组是用来存放排查出的雷的信息,玩家游玩的界面
char show[ROWS][COLS] = { 0 };   //'*'放置的是字符

//函数初始化
	InitBoard(mine,ROW,COL,'0');//放置‘0’
	InitBoard(show,ROW,COL,'*');//放置‘*’
  
  //放置雷
	SetMine(mine,ROW,COL);

  //打印界面
  //DisPlayBoard(mine,ROW,COL);//这里可以作为测试放置雷是否正确,
  DisPlayBoard(show,ROW,COL);

  //探查雷
  FindMine(mine,show,ROW,COL);
}

int main()
{
	int input = 0;//input的值会在循环中使用判断,因此放在循环外

	do//我们会进行多次游戏,所以游戏应该放在循环体中,do while语句
	{
		menu();//这里是显示出的菜单页面
		printf("Please select:>\n");
		scanf("%d", &input);
		switch (input)//switch语句作为游戏初始的开关
		{
			case 1://input的值为1时就进入游戏
				game();//这里是扫雷游戏的主体,在这个函数中设计扫雷游戏
				//printf("game start");//这里打印出游戏开始代替游戏本身进行测试该段代码未出错
				break;
			case 0://input的值为0,则退出游戏
				printf("Exit\n");
				break;
			default://输入除1,0的值,则提示输入错误,循环继续,回到选择输入的代码
				printf("Error,please select\n");
				break;
		}
		printf("\n");
	} while (input);//循环语句时根据input即我们输入值判断是否进入循环的。
//非0为真,进入循环
	return 0;
}

(三)game.c

#define _CRT_SECURE_NO_WARNINGS


#include "game.h"

//下列是自定义函数的定义
//函数初始化,为了现目,将对应的自定义函数写上作为对比
void InitBoard(char arr[ROWS][COLS], int rows, int cols, char set)
{
    int i = 0;
    for (i = 0; i <= rows; i++)
    {
        int j = 0;
        for (j = 0; j <= cols; j++)
        {
            arr[i][j] = set;//set就代表'0'.'*'
        }
    }
}

//放置雷,是在玩家玩游戏时看不到的界面
//SetMine(mine,ROW,COL);
//放置雷就是随机放置,这时就需要我们的rand函数,
//使用rand函数,需要通过srand函数的参数seed来设置rand函数生成随机数的时候的种子
void SetMine(char mine[ROWS][COLS], int row, int col)
{
    //雷放置在mine数组中,数量为EasyCount个,
    int count = EasyCount;
    while (count)//当count为0时为假,循环停止
    {
        int x = rand() % row + 1;
        //注意:取模row,的值为0~row-1,我们设置的row是9,所以是0~8,
        //加上一就是1~9,正好对应数组的下标
        int y = rand() % col + 1;
        if (mine[x][y] == '0')//注意我们的随机值可能出现重复的情况,
        {//如果不对数组中的数进行判断可能出现重复设置的情况,导致我们设置的10个雷不准确
            mine[x][y] = '1';//数组时char类型的,不要手误,打成了1
            count--;//这是判断10个雷的条件,以及循环结束的条件
        }
    }
}

//打印界面
//DisPlayBoard(mine,ROW,COL);//这里可以作为测试放置雷是否正确,
//DisPlayBoard(show,ROW,COL);
//我们上列打印的设计也是含有mine和show数组,写作为一个函数即可
void DisPlayBoard(char arr[ROWS][COLS], int row, int col)
{
    int i = 0;
    int j = 0;
    printf("-----Game Interface-----\n");

    //为了玩家方便找到对应的坐标,将行列标上数字
    //列  从0开始是为了对齐
    for (i = 0; i < col; i++)
    {
        printf("%d ", i);//i = 0,1,2,3,4,…
    }
    printf("\n");

    for (i = 1; i < row; i++)//注意,这个函数是打印函数,是会出现在玩家视野中的游戏界面
    {   //而出现在玩家界面的格子是row*col的
        {
            printf("%d ", i);//行  搭配后一个for循环后的换行代码,变为每行的标注
            for (j = 1; j < col; j++)
            {
                printf("%c ", arr[i][j]);//数组时char类型的,打印出row*col的格子
            }
            printf("\n");//换行
        }
    }
}


//使用static修饰,只能在game.c 文件中使用
//放置在需要使用的函数前,即完成了函数的声明和定义,无须在game.h中声明
static int JudgeSteps(char mine[ROWS][COLS], int x, int y)
{
    int i = 0;
    int j = 0;
    int count = 0;
    for (i = x - 1; i <= x + 1; i++)
    {
        for (j = y - 1; j <= y + 1; j++)
        {
            count += (mine[i][j] - '0');
        }
    }
    return count;
}


//探查雷
//FindMine(mine,show,ROW,COL);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int x = 0;
    int y = 0;
    int steps = 0;
    while (steps < row * col - EasyCount)
    {
        printf("Please select:>");
        scanf("%d %d", &x, &y);

        //判断坐标的有效性
        if (x >= 1 && x <= row && y >= 1 && y <= col)
        {
            if (show[x][y] == '*')
            {
                if (mine[x][y] == '1')
                {
                    printf("Unfortunately, you lost\n");
                    DisPlayBoard(mine, ROW, COL);//打印真实游戏界面
                    break;//游戏结束,跳出循环
                }
                else//不是雷,查它的周边有多少雷
                {
                    int count = JudgeSteps(mine, x, y);
                    show[x][y] = count + '0';
                    DisPlayBoard(show, ROW, COL);

                    steps++;//避开了一个雷,++才能达到跳出循环的条件
                }
            }
            else
            {
                printf("Repeated input.Please reselect\n");
            }
        }
        else
        {
            printf("Error.Please reselect\n");
        }
    }
    if (steps == row * col - EasyCount)
    {
        printf("Congratulations.YOU WIN.\n");
        DisPlayBoard(mine, ROW, COL);
    }
}

五、运行代码

我们可以将雷的值设置大一些,接近80个来检查代码的可用性。

将mine数组的界面打印出来,以便我们避开雷。否则检查太过复杂。

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值