文章目录
1.前言
前段时间写了一个推箱子小游戏,只是实现了几个基本的功能,这次在上次的基础上添加了一些功能;
这次代码的代码量稍微大一点,可读性没有那么强,如果看起来比较难理解,可以先看一下上次我写的比较简单的推箱子博客铺垫一下; 较简单推箱子博客链接,点击文字即可进入.
增加图形界面的推箱子链接,点击文字即可进入;
2.效果展示
新添地图选择功能,返回一步功能,人物可以进入目标点,修复箱子从目标点出来后目标点消失问题;
3.地图功能的构建
3.1地图构建
上次只有一个地图,因此用一个二维数组保存地图就够了。这次添加了地图选择功能,因此采用了三维数组,这样第一个下标就可以用来选择地图;
char map[2][10][10]={ //0位墙壁■,1位空白,2为箱子●,3为目的点¤,4为人♀,5为小人儿与目标点重合★,6为箱子进入了目标点☆
{
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 3, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 1, 1, 1, 1, 3, 1, 0 },
{ 0, 1, 1, 1, 2, 1, 1, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
{ 0, 1, 2, 1, 0, 1, 2, 0, 0, 0 },
{ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0 },
{ 0, 0, 0, 1, 1, 1, 1, 1, 3, 0 },
{ 0, 3, 1, 2, 1, 4, 1, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
},//地图1
{
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 3, 0, 0, 0, 0, 0, 1, 3, 0 },
{ 0, 3, 0, 1, 1, 1, 1, 3, 1, 0 },
{ 0, 1, 1, 1, 2, 1, 2, 1, 1, 0 },
{ 0, 1, 2, 1, 1, 1, 1, 0, 0, 0 },
{ 0, 1, 1, 2, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 1, 1, 1, 2, 1, 1, 0 },
{ 0, 3, 0, 2, 1, 0, 1, 1, 0, 0 },
{ 0, 3, 1, 1, 1, 4, 1, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
},
};
3.2选择功能的构建
我们依然可以将三维数组的数据读入到二维数组之中完成地图的选择,同时每个地图小人的起始位置都是一样的,即可以直接设定,三维数组的第一个下标就可以当做选择地图的标号;选定好地图之后调用函数将三维数组的数据拷贝至二维数组内
char arr[ROW][LINE];
int select = 0;
int quit = 0;
while (!quit)
{
printf("请选择你的关卡\n");
printf("###########################\n");
printf("1.第一关##########2.第二关###\n");
printf("###########################\n");
scanf("%d", &select);
switch (select)
{
case 1:
select = 0;//数组下标从0开始
SlectMap(arr, map, select);
quit = 1;
break;
case 2:
select = 1;
SlectMap(arr, map, select);
quit = 1;
break;
default :
printf("输入有误,请从新输入\n");
}
}
void SlectMap(char arr[][LINE], char map[][ROW][LINE],int select)//选择地图进行数据拷贝
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LINE; j++)
{
arr[i][j] = map[select][i][j];
}
}
}
3.3地图的打印
地图打印直接构建一个循环函数即可
void ShowMap(char arr[][LINE])//打印地图
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LINE; j++)
{
if (arr[i][j] == 0)
printf("■");//打印墙壁
else if (arr[i][j] == 1)//打印空白
printf(" ");
else if (arr[i][j] == 2)//打印箱子
printf("●");
else if (arr[i][j] == 3)//打印目的点
printf("¤");
else if (arr[i][j] == 4)//打印人物
printf("♀");
else if (arr[i][j] == 5)//打印人物和目标点重叠后的图案
printf("★");
else if (arr[i][j] == 6)//箱子进入目标点后的图案
printf("☆");
}
printf("\n");
}
}
4.返回功能的实现
按照顺序应该是先实现移动功能的,由于代码量的增加,不先提返回功能代码的可读性没有那么好;当然我还是建议先看一下我先前的那篇只有基本功能的博客,比较清晰易懂;
返回功能说白了就是保存上一步的数据,那么我们可以构建一个函数进行保存,即在移动后数据更新之前完成数据的存储,在下次移动之前进行判断是否需要退一步,这样就完成了返回的功能;需要注意的是我们不仅仅需要保存地图的数据还需要保存小人的位置,因为我们按了返回键呈现出来的地图实际上是上一次展现的地图,如果不将小人儿的位置重新回归,那么实际上是对不上号的;
这里完成的是数据的存储
void Retain(char retarr[][LINE],char arr[][LINE])//保存旧地图
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LINE; j++)
{
retarr[i][j] = arr[i][j];
}
}
}
void Save(char retarr[][LINE], char arr[][LINE], int *row, int *line, int *retrow, int *retline)//更新前内容保存
{
Retain(retarr, arr);//内容新前将其保存一份;
//小人坐标更新前保存一份
*retrow = *row;
*retline = *line;
}
完成了数据的存储,当我们需要后退一步怎么办呢,我们读取到的数据为r时就将存储的内容打印出来,结束这一次循环;
if (move == 'r')//返回
{
system("cls");
ShowMap(retarr);//打印旧地图
Retain(arr, retarr);//地图内容更换为上一步的内容
//将小人坐标替换为上一步的小人坐标
*row = *retrow;
*line = *retline;
break;
}
5.人物可以到达目标点的实现
刚开始我的代码之中是禁止人物到达目标点的,在这里做出了修改,增加了人物可以进出目标点;
增加这个功能首先要进行考虑的是人物从目标点出来之后,目标点怎么恢复原样。我将人物和目标点重叠设计为了另外一种图案,那么当人物进行移动位置时,判断人物的当前坐标的内容是否为重叠图案,如果是移动后就替换成目标点,否则替换为空白,因为人物只可能处于空白处和目标点处,不可能处于墙壁和箱子之中;
下面这段代码的后半部分可以封装成函数,因为在其它的情况下也需要判定小人是不是从目标点出来,但是我为了代码的可读性就没有将其封装起来;
else if (arr[newrow][newline] == 3)//为目标点
{
arr[newrow][newline]=5;//小人儿和目标点重合的图像
if (arr[*row][*line] == 5)//此时小人与目标重合,
{
arr[*row][*line] = 3;//那么出来后就变成目标点了;
}
else//小人儿没有在目标点中
{
arr[*row][*line] = 1;//小人的地方变为空白
}
//坐标更新
*row = newrow;
*line = newline;
6.箱子到达目的点后出来不会数据丢失
先前我们只进行了判定箱子是否可以移动到下一步,因此当箱子进入目标点再出来的时候,目标点的数据就丢失了;
这里同样可以借用人物到达目标点的方法进行实现。我们将箱子和目标点设计为一种图案,当人推箱子的时候,下一步小人儿肯定在箱子的位置,因此下一步小人儿的位置就变成了小人和目标点的结合图案。而箱子下一步的情况要进行判定,箱子下一步的情况有可能是目标点有可能是空白;
else //箱子前面不是墙壁
{
if (arr[nextrow][nextline] == 3)//箱子前面是目标点
{
arr[nextrow][nextline] = 6;//箱子前挪,图案变成箱子和目标点重合的图案
}
else//箱子前面是空白
{
arr[nextrow][nextline] = 2;//空白处变成箱子
}
if (arr[newrow][newline] == 6)//原箱子位置是目标点
{
arr[newrow][newline] = 5;//变成目标点和小人儿的结合
}
else
{
arr[newrow][newline] = 4;//箱子位置变为人
}
if (arr[*row][*line] == 5)//此时小人与目标点重合,
{
arr[*row][*line] = 3;//那么出来后就变成目标点了;
}
else
{
arr[*row][*line] = 1;//小人的地方变为空白
}
//坐标更新
*row = newrow;
*line = newline;
}
7.判断函数
我们可以通过统计目标点的当前剩余个数来实现这个功能。需要注意的是,小人进入目标点后目标点的个数也是减少的,因此还需要加上一个小人儿和目标点的重叠图案个数;
void Judge(char arr[][LINE],int *boxs)
{
int temp = 0;
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LINE; j++)
{
if (arr[i][j] == 3||arr[i][j]==5)//剩余目标点数以及人物进入了目标点
temp++;
}
}
*boxs = temp;
}
8.小人移动的实现
9.完整代码
#include "game.h"
void ShowMap(char arr[][LINE])//打印地图
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LINE; j++)
{
if (arr[i][j] == 0)
printf("■");//打印墙壁
else if (arr[i][j] == 1)//打印空白
printf(" ");
else if (arr[i][j] == 2)//打印箱子
printf("●");
else if (arr[i][j] == 3)//打印目的点
printf("¤");
else if (arr[i][j] == 4)//打印人物
printf("♀");
else if (arr[i][j] == 5)//打印人物和目标点重叠后的图案
printf("★");
else if (arr[i][j] == 6)//箱子进入目标点后的图案
printf("☆");
}
printf("\n");
}
}
void Move(char arr[][LINE], int *row, int *line, char retarr[][LINE],int *retrow, int *retline)//移动小人儿
{
while (1)
{
int newrow = *row;
int newline = *line;
int quit = 0;
printf("请通过w,s,a,d,控制上下左右,输入r返回\n");
int move=0;
int c = 0;
while (!quit)
{
move = getchar();//从标准输入读取字符
while((c=getchar())!='\n');//吃掉多余的字符以及空格
switch (move)
{
case 'w':
newrow--;//向上移动
quit = 1;
break;
case 's':
newrow++;//向下移动
quit = 1;
break;
case 'a':
newline--;//向左移动
quit = 1;
break;
case 'd':
newline++;//向右移动
quit = 1;
break;
case 'r':
quit = 1;
break;
default:
printf("输入有误,请从新输入\n");
break;
}
}
if (move == 'r')//返回
{
system("cls");
ShowMap(retarr);//打印旧地图
Retain(arr, retarr);//地图内容更换为上一步的内容
//将小人坐标替换为上一步的小人坐标
*row = *retrow;
*line = *retline;
break;
}
Save(retarr, arr, row, line, retrow, retline);//此时已经不需要返回,坐标更新前保存一份
if (arr[newrow][newline] == 0 )//下一个坐标为墙壁
{
printf("禁止前行\n");
break;
}
else//前面为空白,箱子,或者目标点,或者箱子到目标点后的结合图案(也是箱子)
{
if (arr[newrow][newline] == 1)//为空白
{
arr[newrow][newline]= 4;//将小人移动到坐标点
if (arr[*row][*line] == 5)//此时小人与目标重合,
{
arr[*row][*line] = 3;//那么出来后就变成目标点了;
}
else
{
arr[*row][*line] = 1;//小人的地方变为空白
}
//坐标更新
*row = newrow;
*line = newline;
}
else if (arr[newrow][newline] == 3)//为目标点
{
arr[newrow][newline]=5;//小人儿和目标点重合的图像
if (arr[*row][*line] == 5)//此时小人与目标重合,
{
arr[*row][*line] = 3;//那么出来后就变成目标点了;
}
else
{
arr[*row][*line] = 1;//小人的地方变为空白
}
//坐标更新
*row = newrow;
*line = newline;
}
else if (arr[newrow][newline] == 2||arr[newrow][newline]==6)//下一个点为箱子或者箱子和目标点的结合图案
{
int nextrow = newrow;
int nextline = newline;
if (move == 'w')//上
nextrow = newrow - 1;
else if (move == 's')//下
nextrow = newrow + 1;
else if (move == 'a')//左
nextline = newline - 1;
else//右
nextline = newline + 1;
if (arr[nextrow][nextline] == 0 || arr[nextrow][nextline] == 2||arr[nextrow][nextline]==6)
//箱子前面是墙壁或者箱子或者箱子和目标点的结合
{
printf("移动不了,箱子前方有障碍\n");
break;
}
else //箱子前面不是墙壁
{
if (arr[nextrow][nextline] == 3)//箱子前面是目标点
{
arr[nextrow][nextline] = 6;//箱子前挪,图案变成箱子和目标点重合的图案
}
else//箱子前面是空白
{
arr[nextrow][nextline] = 2;//空白处变成箱子
}
if (arr[newrow][newline] == 6)//原箱子位置是目标点
{
arr[newrow][newline] = 5;//变成目标点和小人儿的结合
}
else
{
arr[newrow][newline] = 4;//箱子位置变为人
}
if (arr[*row][*line] == 5)//此时小人与目标点重合,
{
arr[*row][*line] = 3;//那么出来后就变成目标点了;
}
else
{
arr[*row][*line] = 1;//小人的地方变为空白
}
//坐标更新
*row = newrow;
*line = newline;
}
}
}
//进行移动之后的地图打印;
system("cls");
ShowMap(arr);
break;
}
}
void Judge(char arr[][LINE],int *boxs)
{
int temp = 0;
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LINE; j++)
{
if (arr[i][j] == 3||arr[i][j]==5)//剩余目标点数以及人物进入了目标点
temp++;
}
}
*boxs = temp;
}
void Retain(char retarr[][LINE],char arr[][LINE])//保存旧地图
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LINE; j++)
{
retarr[i][j] = arr[i][j];
}
}
}
void Save(char retarr[][LINE], char arr[][LINE], int *row, int *line, int *retrow, int *retline)//更新前内容保存
{
Retain(retarr, arr);//内容新前将其保存一份;
//小人坐标更新前保存一份
*retrow = *row;
*retline = *line;
}
void SlectMap(char arr[][LINE], char map[][ROW][LINE],int select)//选择地图
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < LINE; j++)
{
arr[i][j] = map[select][i][j];
}
}
}
char map[2][10][10]={ //0位墙壁■,1位空白,2为箱子●,3为目的点¤,4为人♀,5为小人儿与目标点重合★,6为箱子进入了目标点☆
{
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 3, 1, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 1, 1, 1, 1, 1, 3, 1, 0 },
{ 0, 1, 1, 1, 2, 1, 1, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 },
{ 0, 1, 2, 1, 0, 1, 2, 0, 0, 0 },
{ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0 },
{ 0, 0, 0, 1, 1, 1, 1, 1, 3, 0 },
{ 0, 3, 1, 2, 1, 4, 1, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
},//地图1
{
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 3, 0, 0, 0, 0, 0, 1, 3, 0 },
{ 0, 3, 0, 1, 1, 1, 1, 3, 1, 0 },
{ 0, 1, 1, 1, 2, 1, 2, 1, 1, 0 },
{ 0, 1, 2, 1, 1, 1, 1, 0, 0, 0 },
{ 0, 1, 1, 2, 0, 1, 1, 0, 0, 0 },
{ 0, 1, 0, 1, 1, 1, 2, 1, 1, 0 },
{ 0, 3, 0, 2, 1, 0, 1, 1, 0, 0 },
{ 0, 3, 1, 1, 1, 4, 1, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
},
};
void Game()
{
char arr[ROW][LINE];
int select = 0;
int quit = 0;
while (!quit)
{
printf("请选择你的关卡\n");
printf("###########################\n");
printf("1.第一关##########2.第二关###\n");
printf("###########################\n");
scanf("%d", &select);
switch (select)
{
case 1:
select = 0;//数组下标从0开始
SlectMap(arr, map, select);
quit = 1;
break;
case 2:
select = 1;
SlectMap(arr, map, select);
quit = 1;
break;
default :
printf("输入有误,请从新输入\n");
}
}
ShowMap(arr);
int row = 8, line = 5;//小人初始横纵坐标
int boxs= 4;//还未到达目标点的盒子数
char retarr[ROW][LINE];
Retain(retarr, arr);//先保存一份,直接按返回不会出错
int retrow=row, retline=line;//旧的小人地址
while (boxs)
{
Move(arr, &row, &line,retarr,&retrow,&retline);//移动小人
Judge(arr, &boxs);//剩余目标点数
if (boxs == 0)
printf("恭喜你,通关成功\n");
}
}
#ifndef _GAME_H_
#define _GAME_H_
#include <stdio.h>
#include <windows.h>
#pragma warning (disable :4996)
#define ROW 10
#define LINE 10
void SlectMap(char arr[][LINE], char[][ROW][LINE],int select);
void Game();
void Retain(char retarr[][LINE], char arr[][LINE]);
void Save(char retarr[][LINE], char arr[][LINE], int *row, int *line, int *retrow, int *retline);
#endif
#include "game.h"
int main()
{
Game();
system("pause");
return 0;
}