C语言实现简单推箱子游戏

 更新时间:2021年01月27日 11:13:34   作者:白家名  
这篇文章主要为大家详细介绍了C语言实现简单推箱子游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

使用C语言实现超简单的推箱子游戏,供大家参考,具体内容如下

感谢您打开了这篇文章,下面我将讲述一下推箱子是如何实现的。

另外附赠适配该程序简单好用 专属推箱子地图编辑器 让您在16 * 16大地图的条件下也能轻松编辑地图。

链接:地图编辑器

本程序在没有检测到地图文件的情况下也能独自运行!代码中储存了推箱子游戏第一关的标准地图,让您在没有地图文件的情况下也能熟悉整个程序的流程!

当然,拥有地图文件会也会获得更好的游戏体验,请自行编辑。

废话不多说!

下面进入技术环节:

C语言版 多功能推箱子

编译环境: Windows VS2019
其他编译器,可通过查看下文的“注意事项”将代码更正为其他平台可正常版本

需求:

控制人物将箱子推至目标中,目标全部完成进入下一关。

思路:

使用二维数组储存不同数字,数字包括了地图中所有的元素,通过按键控制人物完成推箱子的操作,达成关卡内的所有目标后,自动进入下一关。

做法:

主要逻辑移动推箱子部分:按下方向键后,双重循环找到人物,根据移动方向储存 人物、人物前面、箱子、箱子前面四大基础信息,并通过判断前方数组值是否是墙壁、目标等,进行人物移动和箱子移动操作。
具体详细做法我已经整理到了代码注释当中,以便一一对应查看。

使用到知识点:

循环、二维数组、读取文件

难点:

在人物和箱子移动的同时,有需要注意当人物移动到了未完成目标或已完成目标、箱子移动到了已完成目标的情况,这种情况需要判断在人物/箱子离开之后,原地又再次变为原元素。

说明:

程序前部分有较多代码用于写出未检测到文件的情况逻辑和关卡选择逻辑,如果要直接查看核心代码请移动到operation();操作人物函数和gbszszhs(char ch);修改二维数组函数。

注意:

由于编译器原因,程序中_kbhit()和_getch()函数可能在其他编译器上编译会出现错误,解决办法是去掉函数前面的“_”。
同时,要将 文件打开函数fopen_s(&fp, FLPA, “r”);更改为fp = fopen(FLPA, “r”);
fcanf_s更改为fcanf scanf_s()更改为scanf

运行效果:

菜单选择:

游戏进行:

代码实现:

#include <stdio.h>
#include <windows.h>
#include <conio.h>


//0代表空地,1代表墙,2代表未达成的目标,3代表箱子,4代表玩家,5代表已放箱子的目标,
//6代表人暂时所在的未达成的目标,7代表人暂时所在的已达成的目标,8代表箱子暂时所在的已达成的目标

#define WH 16  //地图的宽高
#define BYT 529  //一关需要跳过的字数 因为文件指针定位函数的原因,有时定位可能会不准确,可以通过修改BYT进行适配
#define FLPA "C:\\Users\\ASUS\\Desktop\\推箱子地图.txt" //需要读取地图文件的路径 游戏之前需进行设置!!
        //找不到路径将只能进行第一关游戏

//注意:游戏地图边界 不可以 当做墙壁使用!

#define INITMAP      \
 int mapch_init[WH][WH] = {    \
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 1, 1, 1, 3, 0, 3, 2, 1, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 1, 2, 0, 3, 4, 1, 1, 1, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 1, 1, 1, 1, 3, 1, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, \
};

int mapch[WH][WH];

//所有函数之间不是独立和顺序的,会互相调用
void HideCursor(); //隐藏光标
void gotoxy(int x, int y);//光标定位
void scmbxyhs(); //输出地图下方文字信息函数
void wjzdwjzjjrqk(); //未找到文件直接进入第一关情况
int gkxzhs();  //关卡选择函数
void cwjzjrwkdhs(); //从文件中进入关卡的函数
void gnxzjm();  //主菜单选择
void cshhs();  //初始化函数

void tranmap();  //翻译并画出地图
void detection(); //寻找所有该地图中未完成的目标
void gktgszxhs(); //判断关卡是否通过,是进行下一关卡
void operation(); //操作人物主要函数***
void gbszszhs(char ch); //改变数组数值函数
int updatetime(); //获取一次电脑现在的时间
void process();  //主要流程

int main()
{
 cshhs();  //初始化函数

 process();  //主要流程

 return 0;
}

//游戏开始初始化部分
void scmbxyhs()  //输出地图提示信息函数
{
 gotoxy(34, 17);  
 printf("本关剩余目标数:");
 gotoxy(34, 19);
 printf("本关已走步数:");
 gotoxy(32, 21);
 printf("您使用 秒完成了本关!");
 gotoxy(49, 19);
 printf("0"); //输出初始的步数0
}

void wjzdwjzjjrqk() //未找到文件直接进入第一关情况
{
 system("cls");
 printf("地图文件不存在,\n直接进入第一关");

 INITMAP    //初始地图数组

 for (int i = 0; i < WH; i ) //如果地图文件不存在则将初始地图数组的值赋给需要使用的地图
 for (int j = 0; j < WH; j ) //将初始地图数组的值复制给当前地图数组
  mapch[i][j] = mapch_init[i][j];

 Sleep(2000);  //等待两秒进入第一关
 system("cls");  //清屏
 tranmap();  //画出初始地图
 detection();  //目标信息
 scmbxyhs();  //输出地图下方文字信息
}


int n = 1;   //输入关卡变量**
int maxn = 0;  //最大关数

int dczdgshs()  //最大关数
{
 FILE* fp = NULL; //因为用于提示和限制输入情况,所以需要得到最大关卡数maxn
 fopen_s(&fp, FLPA, "r");

 if (fp == NULL)
 {
 wjzdwjzjjrqk(); //未找到文件直接进入第一关情况
 return 0;
 }
 int temp = 0;  //临时变量用来统计地图文件全字节数

 rewind(fp);  //文件指针移动到文件首部

 while (!feof(fp)) //文件指针还没有到文件尾进入循环
 {
 fgetc(fp);  //读字符函数从文件开头读,向后移动文件指针
 temp ;  //每读一个字符则临时变量自增,统计出全文件有几个字符
 }
 fclose(fp);  //文件使用完成关闭文件
 maxn = temp / BYT 1; //最大关数就是所有字符的数量除以一关的字符数
 return 1;
}



int gkxzhs()   //选择关卡函数
{
 system("cls");  //输出关卡选择提示
 gotoxy(26, 8);
 printf("请输入想要挑战的关卡:(回车确认)");

 if (!dczdgshs()) //得出最大关数函数,返回值为0代表未找到地图文件,直接进入流程
 return 0;

 gotoxy(36, 10);
 printf("请输入1- %d ", maxn);//输出提示最大关数信息

 srgk:  //重新选择关卡

 gotoxy(41, 12);
 scanf_s("%d", &n);

 if (n<1 || n>maxn) //对输入的错误关卡信息加以限制
 {
 gotoxy(36, 12);
 printf("  ");
 gotoxy(33, 11);
 printf("请输入正确的关数");
 goto srgk;  //如果输入错误的关卡输出提示信息并返回到输入的地方重新输入
 }
 return 1;  //如果找到了文件就返回1
}

void cwjzjrwkdhs()  //从文件中进入关卡,适用于可以找到地图文件的情况
{
 //关数变量n的默认初始化值为1
 FILE* fp = NULL;
 fopen_s(&fp, FLPA, "rb");

 if (fp == NULL)
 {
 wjzdwjzjjrqk();  //直接进入第一关函数
 return;   //地图文件不存在则直接进入第一关
 }

 //读文件进入关卡的代码,从第一关进入和选择特定关从n关进入两种情况都会执行
  //流程如果用到该函数,检测已达成的目标和未达成目标的个数一直则调用函数,n关数自增1
 int skip = (n - 1) * BYT; //到n关需要跳过的字数 跳过一关需要的字数为512

 fseek(fp, skip, 0);  //定位到地图文件第skip个字节处开始读取
 int i, j;
 for (i = 0; i < WH; i ) //读取512个字符
 {
 for (j = 0; j < WH; j )
 {
  fscanf_s(fp, "%d ", &mapch[i][j]);//格式读函数,直接以整数格式读取数值存入地图数组mapch中
  printf("%d ", mapch[i][j]);
 }
 printf("\n");
 }
 fclose(fp);   //读取文件完毕,将文件关闭
 system("cls");
}

void gnxzjm()   //主菜单页面选择
{
 system("cls");
 gotoxy(39, 8);
 printf("推箱子");
 gotoxy(34, 10);
 printf("输入1 开始新游戏");
 gotoxy(34, 12);
 printf("输入2 选择关卡");
 gotoxy(34, 14);
 printf("输入3 退出游戏");

 Head:   //用于返回的标签
 gotoxy(34, 17);
 char chn=_getch();  //选择游戏模式

 if (chn == '3')
 {
 system("cls");  //退出游戏则输出提示信息
 gotoxy(34, 12);
 printf("欢迎下次光临");
 Sleep(2000);
 gotoxy(0, 24);
 exit(0); //退出游戏
 }
 
 else if (chn == '2')
 {
 if (!gkxzhs())  //进入关卡选择
  return;  //如果关卡选择函数返回值为0则代表未找到文件,直接跳出初始化函数
 //如果返回1找到文件则继续执行该函数下面的内容
 }

 else if (chn == '1')
 n = 1;   //如果选择新游戏,直接从第一关开始
 else
 {
 gotoxy(34, 16);
 printf("请输入正确的选择:");
 goto Head;  //选择错误的菜单,则重新返回选择菜单的地方
 }

 //选择1的情况:

 cwjzjrwkdhs();  //从文件读取关卡的函数
 detection();  //统计当前关卡的目标数
 tranmap();   //画出地图
 scmbxyhs();   //输出地图下方文字信息
}

int detunf;//检测未完成目标的变量,初始为一个关卡中未完成目标的个数,箱子碰到未完成目标时,自减

void detection()  //检测当前关中有多少个目标
{
 //detunf
 detunf = 0;  //从0开始统计
 int i, j;
 for (i = 0; i < WH; i )
 for (j = 0; j < WH; j )
  if (mapch[i][j] == 2)
  detunf ;
 gotoxy(50,17);  //在提示信息的位置输出剩余目标信息
 printf("%d", detunf); 
}

void cshhs()  //总初始化函数
{
 system("title 推箱子");//控制台标题
 system("mode con cols=84 lines=26");//设置控制台大小,第一个参数为横轴,地图参数32 16
 gnxzjm();  //主菜单页面选择
 HideCursor(); //隐藏光标函数
 dczdgshs();  //找到地图文件的情况下得出最大关数
}



//游戏流程部分
void tranmap()  //翻译地图
{
 gotoxy(26, 1); //输出地图时在控制台中间输出,地图最上方空一行
 int i, j;   
 for (i = 0; i < WH; i )
 {
 for (j = 0; j < WH; j )
 {
  if (mapch[i][j] == 1)
  printf("■");
  else if (mapch[i][j] == 2)
  printf("★");
  else if (mapch[i][j] == 3 || mapch[i][j] == 8)
  printf("●");
  else if (mapch[i][j] == 4 || mapch[i][j] == 6 || mapch[i][j] == 7)
  printf("♀");
  else if (mapch[i][j] == 5)
  printf("--");
  else
  printf(" ");
 }
 gotoxy(26, i 1); //根据数组的y轴更改地图输出的y轴,地图最上方空一行
 }
}

int opnum;  //每一关行走的步数
int time;
int time_2;

void gktgszxhs()  //判断当前关卡是否通过,是进入下一关
{
 if (detunf == 0) //当前关卡目标为0时
 {
 n ;  //关卡变量自增
 if (n > maxn) //如果关数n大于了最大关卡数则返回主菜单
 {
  tranmap(); //画出地图
  gotoxy(26, 22);
  printf("您已通关所有关卡,3秒后返回主菜单!");
  Sleep(3000); //等待三秒
  opnum = 0, time = updatetime(),time_2 = 0;//行走的步数,本关时间清0,重新获取当前时间,\
       因为需要输出的time_2是用当前时间-刚开始的时间
  gnxzjm(); //主菜单页面选择函数
 }
 else
 {
  tranmap(); //画出地图
  Sleep(2000); //等候两秒
  cwjzjrwkdhs(); //当前关卡目标为0时,从文件中向地图数组中读入下一关卡的信息
  detection(); //统计当前关卡目标个数
  scmbxyhs(); //输出地图下方的信息
  opnum = 0, time = updatetime(),time_2 = 0;//注释见297行
    //按键结束之后操控函数会调用画出地图函数
 }
 }
}

int i, j;   //找到后的人的坐标

int box_x, box_y;  //箱子的坐标
int boxnext_x, boxnext_y; //箱子前面的坐标
int peonext_x, peonext_y; //人前面的坐标

void gbszszhs(char ch) //修改地图数组值主要函数**
{
 for (i = 0; i < WH; i ) //遍历 i是y轴,j是x轴
 {
 for (j = 0; j < WH; j )
 {
  if (mapch[i][j] == 4 || mapch[i][j] == 6 || mapch[i][j] == 7)  //找到人的位置
  {
  if (ch == 'w') // 用于确定不同方向上 箱子、箱子前面、人前面的坐标
  {
   box_y = i - 1; box_x = j;  //箱子当前
   boxnext_y = i - 2; boxnext_x = j; //箱子前面
   peonext_y = i - 1; peonext_x = j; //人前面
  }
  else if (ch == 'a')
  {
   box_y = i; box_x = j - 1;
   boxnext_y = i; boxnext_x = j - 2;
   peonext_y = i; peonext_x = j - 1;
  }
  else if (ch == 's')
  {
   box_y = i 1; box_x = j;
   boxnext_y = i 2; boxnext_x = j;
   peonext_y = i 1; peonext_x = j;
  }
  else if (ch == 'd')
  {
   box_y = i; box_x = j 1;
   boxnext_y = i; boxnext_x = j 2;
   peonext_y = i; peonext_x = j 1;
  }

  //排除大的错误
  if (mapch[box_y][box_x] == 3 || mapch[box_y][box_x] == 8) //如果人的前边是箱子
   if (mapch[boxnext_y][boxnext_x] == 1 || mapch[boxnext_y][boxnext_x] == 3 || mapch[boxnext_y][boxnext_x] == 8)
   return; //如果箱子前边是墙或者是箱子直接结束修改函数
  
  if (mapch[box_y][box_x] == 1) //人的前边不能是墙
   return;  //如果人的前边是墙直接结束修改

  opnum ;  //每次有效操作步数自增一次,并输出
  gotoxy(49, 19);
  printf("%d", opnum);//步数

  //箱子改变
  if (mapch[box_y][box_x] == 3 || mapch[box_y][box_x] == 8) //如果人的前边是箱子
  {
   //箱子原地改变:
   if (mapch[box_y][box_x] == 3) //如果箱子所在的位置是 一般箱子 
   mapch[box_y][box_x] = 0; //改变为空地
   else if (mapch[box_y][box_x] == 8) //如果箱子所在的位置是 箱子暂时所在已达成的目标
   mapch[box_y][box_x] = 5; //改变为已达成的目标

   //箱子的下一步改变:

   if (mapch[boxnext_y][boxnext_x] == 2) //如果箱子前面的格子是未放箱子的目标
   {
   mapch[boxnext_y][boxnext_x] = 5; //则该格子变为已放箱子的目标
   detunf--;    //本关目标减1
   gotoxy(50, 17);    //输出本关剩余目标个数
   printf("%d", detunf);
   }
   else if (mapch[boxnext_y][boxnext_x] == 0) //如果箱子前面是空地   
   mapch[boxnext_y][boxnext_x] = 3; //则变为普通箱子
   else if (mapch[boxnext_y][boxnext_x] == 5) //如果箱子前面是已经放过箱子的目标
   mapch[boxnext_y][boxnext_x] = 8; //则变为 箱子暂时所在已达成的目标
  }

  //原地改变
  if (mapch[i][j] == 6)
   mapch[i][j] = 2; //走出去之后原地又变回2 未达成的目标
  else if (mapch[i][j] == 7)
   mapch[i][j] = 5; //走出去之后原地又变回5 已达成的目标
  else if (mapch[i][j] == 4)
   mapch[i][j] = 0; //走出去之后原地变回4 人的原型


  //人下一步改变
  if (mapch[peonext_y][peonext_x] == 0 || mapch[peonext_y][peonext_y] == 3)//如果他的下一步是普通箱子或者空地
   mapch[peonext_y][peonext_x] = 4; //人是 普通的人

  if (mapch[peonext_y][peonext_x] == 2) //如果他的下一步还是未达成的目标
   mapch[peonext_y][peonext_x] = 6; //人是 人暂时所在未达成的目标

  if (mapch[peonext_y][peonext_x] == 5) //如果人的下一步是已经达成的目标
   mapch[peonext_y][peonext_x] = 7; //人是 人暂时所在已经达成的目标

  if (mapch[peonext_y][peonext_x] == 8) //如果人的下一步是 箱子暂时所在已达成的目标
   mapch[peonext_y][peonext_x] = 7; //人还是 暂时所在已达成的目标

  goto L1; //修改完成后不需要遍历后面的数组,直接跳出所有循环
  }
 }
 }

 L1:   //用于跳出的标签
 gktgszxhs(); //关卡通过则进入下一关
}

void operation() //操作人物函数
{
 char ch = _getch(); //接收输入的方向 _getch()即使接收
 switch (ch)
 {
 case 'w':  //向不同方向移动
 gbszszhs(ch); //传递参数,修改二维数组
 break;
 case 'a':
 gbszszhs(ch);
 break;
 case 's':
 gbszszhs(ch);
 break;
 case 'd':
 gbszszhs(ch);
 break;
 }
 tranmap();  //重新画出地图
}

int updatetime()   //获取一次电脑现在的时间
{
 int now;
 SYSTEMTIME system_time;
 GetLocalTime(&system_time);
 now = system_time.wMinute * 60 system_time.wSecond;
 return now;
}

void process() //主要流程
{
 time = updatetime(); //初始时间
 while (1)
 {
 time_2 = updatetime() - time; //每关的时间time_2,值为当前时间减去当前关卡开始的时间
 gotoxy(39, 21);
 printf("%d s", time_2);  //输出

 if(_kbhit())
  operation(); //操作人物、修改数值主要函数
 Sleep(20);  //游戏帧率和手感 休眠时间
 }
 
}

void gotoxy(int x, int y) //光标定位
{
 COORD pos = { x,y };
 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

void HideCursor()  //光标隐藏
{
 CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
 SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}

主要函数:gbszszhs()里面的逻辑是比较复杂的,我当时写这段代码的时候也是反反复复修改好多次甚至推翻重做才理通顺这些逻辑的。

如果对于程序代码注释有我没写明白的地方,欢迎在评论区下方留言询问,如果我看到会尽最大的努力为您解惑。

不足之处:

地图在屏幕上显示时容易出错,需要调整每关字数。原因并不明确。

因为作者对C语言的学习还比较浅薄,代码写到初始化游戏的两种模式(有文件和无文件)时思维有些混乱,导致代码在这一部分有很多的缺陷,但最终程序的效果还是出来了。
但其实对整篇所有代码而言最重要的部分还是gbszszhs()函数,只要将这个函数完全理解并熟练掌握了,那么整个“推箱子”游戏也就非常简单了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • C++小知识:C/C++中不要按值传递数组

    C++小知识:C/C++中不要按值传递数组

    今天小编就为大家分享一篇关于C++小知识:C/C++中不要按值传递数组,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • C++ deque与vector对比的优缺点

    C++ deque与vector对比的优缺点

    这篇文章主要介绍了C++中deque与vector相比的优势与劣势,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-01-01
  • C++11 移动构造函数的使用

    C++11 移动构造函数的使用

    本文主要介绍了C++11 移动构造函数的使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • C 语言常用方法技巧

    C 语言常用方法技巧

    本文主要介绍了C语言常用方法技巧。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-03-03
  • 浅谈C++空间配置器allocator

    浅谈C++空间配置器allocator

    在STL中,Memory Allocator处于最底层的位置,为一切的Container提供存储服务,是一切其他组件的基石。对于一般使用 STL 的用户而言,Allocator是不可见的。本文将主要介绍C++空间配置器allocator
    2021-06-06
  • C/C++ int数与多枚举值互转的实现

    C/C++ int数与多枚举值互转的实现

    在C/C++在C/C++的开发中经常会遇到各种数据类型互转的情况,本文主要介绍了C/C++ int数与多枚举值互转的实现,具有一定的参考价值,感兴趣的可以了解一下
    2021-08-08
  • C语言计算连续无序数组中缺省数字方法详解

    C语言计算连续无序数组中缺省数字方法详解

    这篇文章主要介绍了C语言计算连续无序数组中缺省数字方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-02-02
  • c++命名对象和匿名对象的解析

    c++命名对象和匿名对象的解析

    像按值传递的对象(函数入参,函数返回值)都是匿名对象,那匿名对象的特点是什么呢?下面通过实例代码给大家解析c++命名对象和匿名对象的相关知识,感兴趣的朋友一起看看吧
    2021-10-10
  • Qt数据库应用之实现通用数据库请求

    Qt数据库应用之实现通用数据库请求

    这篇文章主要为大家介绍了Qt中是如何实现通用数据库请求的,文中的示例代码讲解详细,对我们学习Qt有一定帮助,感兴趣的小伙伴可以了解一下
    2022-03-03
  • c++利用windows函数实现计时示例

    c++利用windows函数实现计时示例

    这篇文章主要介绍了c++利用windows函数实现计时示例,需要的朋友可以参考下
    2014-05-05

最新评论