172.+立体推箱子

一. 题目

立体推箱子是一个风靡世界的小游戏。

游戏地图是一个 N 行 M 列的矩阵,每个位置可能是硬地(用 . 表示)、易碎地面(用 E 表示)、禁地(用 # 表示)、起点(用 X 表示)或终点(用 O 表示)。

你的任务是操作一个 1×1×2 的长方体。

这个长方体在地面上有两种放置形式,“立”在地面上(1×1 的面接触地面)或者“躺”在地面上(1×2 的面接触地面)。

在每一步操作中,可以按上下左右四个键之一。

按下按键之后,长方体向对应的方向沿着棱滚动 90 度。

任意时刻,长方体不能有任何部位接触禁地,并且不能立在易碎地面上。

字符 X 标识长方体的起始位置,地图上可能有一个 X 或者两个相邻的 X

地图上唯一的一个字符 O 标识目标位置。

求把长方体移动到目标位置(即立在 O 上)所需要的最少步数。

在移动过程中,XO 标识的位置都可以看作是硬地被利用。

输入格式

输入包含多组测试用例。

对于每个测试用例,第一行包括两个整数 N 和 M。

接下来 N 行用来描述地图,每行包括 M 个字符,每个字符表示一块地面的具体状态。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需考虑。

输出格式

每个用例输出一个整数表示所需的最少步数,如果无解则输出 Impossible

每个结果占一行。

数据范围

3≤N,M≤500

在这里插入图片描述

输入样例

输出样例

7 7
#######
#..X###
#..##O#
#....E#
#....E#
#.....#
#######
0 0
10

二. 题目分析

本题是迷宫问题的变形, 要找到"最少移动次数", 使用BFS算法, 遍历所有路径.

难点不在BFS的思路, 而在于状态的处理. 在本题中, 箱子有三种状态, 立着放, 竖着放和横着放. 每种状态要移动的方式不同, 所以要区分开来.

在这里插入图片描述

鉴于横着放和竖着放时, 有两个方格在地图上, 为了简化解题过程, 我们设置一个"核心", 配合箱子的"状态"我们就能确定方块的位置. 立着放时, 显然箱子"核心"标记在下面这个箱子; 竖着放时, 我们将"核心"标记在上面的方格上; 横着放时, 我们将"核心"标记在左边的方格上.("核心"的标注可以自定义, 但是注意在移动后按照自己的定义标注方式更新核心的位置).

这里我们按照上图从左到右的顺序, 用0, 1, 2三个数字来表示三种状态.

对应三种状态, 我们还需要定义三种状态下向四个方向移动的方式:

//状态0标志立起来, 状态1表示竖着放, 状态2标志横着放
//nextx[i][j]表示在状态i下向j方向移动时坐标x应该如何改变
//nextstat[i][j]表示在状态i下向j方向移动后,箱子的状态
//以下三个状态都按照上,下,左,右的移动顺序
int nextx[3][4] = { {-2,1,0,0},{-1,2,0,0},{-1,1,0,0} }, nexty[3][4] = { {0,0,-2,1},{0,0,-1,1},{0,0,-1,2} };
int nextstat[3][4] = { {1,1,2,2},{0,0,1,1},{2,2,0,0} };

和经典的迷宫问题相同, 我们需要记录一下某个位置是否已经"走过"了. 但是与迷宫问题不同的是, 我们定义一个位置已经"走过"了需要注意两个点, 即核心位置和状态都要相同. 如下, 虽然核心位置相同, 但是两个箱子的状态不相同, 我们也不能定义为已经走过了.

在这里插入图片描述

最后一个需要注意的点是, 当箱子的状态为1或2时, 我们要检查箱子的两个方格是否都在合法位置而不仅是"核心".

三. 代码实现

#include<iostream>
#include<cstring>
#include<queue>
#define Max 505
using namespace std;
char map[Max][Max];
bool flag[3][Max][Max]={false};
pair<int, int>start, End;
int estat=0;
int n, m;
//状态0标志立起来, 状态1表示竖着放, 状态2标志横着放
//nextx[i][j]表示在状态i下向j方向移动时坐标x应该如何改变
//nextstat[i][j]表示在状态i下向j方向移动后,箱子的状态
//以下三个状态都按照上,下,左,右的移动顺序
int nextx[3][4] = { {-2,1,0,0},{-1,2,0,0},{-1,1,0,0} }, nexty[3][4] = { {0,0,-2,1},{0,0,-1,1},{0,0,-1,2} };
int nextstat[3][4] = { {1,1,2,2},{0,0,1,1},{2,2,0,0} };
struct S {
	int x, y;
	int stat;
	int step;
};
bool check(struct S t) {
	if (t.stat == 0) {
		if (map[t.x][t.y] == '#'|| map[t.x][t.y] == 'E')//立起来只能在坚实的地面上
			return false;
	}
	else if (t.stat == 1) {
		if (map[t.x][t.y] == '#' || map[t.x + 1][t.y] == '#')//竖着放和横着放要检查核心和另一个方格是否都在合法的地面上
			return false;
	}
	else {
		if (map[t.x][t.y] == '#' || map[t.x][t.y + 1] == '#')
			return false;
	}
	return true;
}
int BFS(pair<int, int>pp, int stat) {
	S p;
	p.x = pp.first, p.y = pp.second, p.stat = stat;
	p.step = 0;
	queue<S>q;
	q.push(p);
	int i;
	flag[p.stat][p.x][p.y] = true;
	while (!q.empty()) {
		S t = q.front();
		q.pop();
		if (t.stat == estat && t.x == End.first && t.y == End.second)
			return t.step;
		for (i = 0; i < 4; i++) {
			S temp;
			temp.x = t.x + nextx[t.stat][i];
			temp.y = t.y + nexty[t.stat][i];
			temp.stat = nextstat[t.stat][i];
			temp.step = t.step + 1;
			if (!flag[temp.stat][temp.x][temp.y] && check(temp)) {
				flag[temp.stat][temp.x][temp.y] = true;
				q.push(temp);
			}
		}
	}
	return -1;
}
int main() {
	int i,j;
	while (cin >> n >> m && n != 0 && m != 0) {
		for (i = 0; i < n; i++) {
			for (j = 0; j < m; j++)
			{
				cin >> map[i][j];
				if (map[i][j] == 'X')
					start.first = i, start.second = j;
				else if (map[i][j] == 'O')
					End.first = i, End.second = j;
			}
		}
		int sstat=0;
		int dx[] = { 0,0,1,-1 },dy[]={1,-1,0,0};
		for (i = 0; i < 4; i++) {//修改起点和终点的标志位
			int sx = start.first + dx[i], sy = start.second+dy[i];
			int ex = End.first + dx[i], ey = End.second + dy[i];
			if (map[sx][sy] == 'X') {
				if (i == 3)//方块是竖的,标志位打在上面的方格
				{
					start.first -= 1;
					sstat = 1;
				}
				if (i == 1)//方块是横的标志位打在左边的方格
				{
					start.second -= 1;
					sstat = 2;
				}
			}
			if (map[ex][ey] == 'O') {
				if (i == 2)//方块是竖的,标志位打在上面的方格
				{
					End.first -= 1;
					estat = 1;
				}
				if (i == 1)//方块是横的标志位打在左边的方格
				{
					start.second -= 1;
					estat = 2;
				}
			}

		}
		memset(flag, false, sizeof(flag));
		int t= BFS(start, sstat);
		if (t == -1)
			cout << "Impossible" << endl;
		else
			cout << t<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
基于Java Swing的箱子游戏设计与实现主要包括以下几个步骤: 1. 游戏界面设计:利用Swing提供的组件进行界面设计,包括游戏地图、箱子、目标点、人物等元素的图形化展示,并添加按钮以及文本框等交互组件。 2. 游戏逻辑设计:设计游戏规则和逻辑,包括人物移动、箱子动、胜利判断等核心功能。通过监听按钮事件以及键盘事件来实现玩家的操作,并根据操作进行游戏状态的更新。 3. 地图设计与关卡:设计游戏地图以及关卡,可以通过二维数组或者读取外部文件的方式来表示地图。每个关卡的地图布局和目标点位置可以通过代码或者配置文件进行定义。 4. 碰撞检测与移动处理:在游戏中需要处理地图、箱子、目标点以及人物之间的碰撞关系,通过判断下一步的位置是否可行来确定是否进行移动操作,避免发生错误的移动。 5. 游戏状态管理:设计游戏的状态管理机制,包括游戏开始、暂停、重新开始、胜利、失败等状态的切换和更新。同时需要记录游戏的当前状态,并在界面上进行相应的显示和提示。 6. 游戏界面布局和美化:通过合理的布局和设计来提高游戏的美观度和可操作性,可以考虑添加音效、动画效果等来增加游戏的趣味性。 7. 优化和测试:在开发完成后进行测试,包括对游戏逻辑和界面的测试、异常处理的测试以及性能优化的测试,保证游戏的稳定性和流畅性。 通过以上步骤的设计和实现,基于Java Swing的箱子游戏可以有效地展示出游戏的功能和特色,提供给用户一个良好的游戏体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值