https://www.acmicpc.net/problem/4577
문제
소코반은 1982년에 일본에서 만들어진 게임으로, 일본어로 창고지기라는 뜻이다. 이 게임은 캐릭터를 이용해 창고 안에 있는 박스를 모두 목표점으로 옮기는 게임이다. 목표점의 수와 박스의 수는 같다. 플레이어는 화살표(위, 아래, 왼쪽, 오른쪽)를 이용해 캐릭터를 아래와 같은 규칙으로 조정할 수 있다.
- 캐릭터에게 지시한 방향이 빈 칸(박스나 벽이 아닌 곳)인 경우에는 그 칸으로 이동한다.
- 지시한 방향에 박스가 있는 경우에는, 박스를 민다. 이 경우에는 박스가 이동할 칸도 비어있어야 한다.
- 지시한 방향이 벽인 경우, 또는 박스가 있는데, 박스가 이동할 칸에 다른 박스나 벽이 있는 경우에는 키를 눌러도 캐릭터는 이동하지 않는다.
모든 박스를 목표점으로 이동시킨 경우에 게임은 끝난다. 게임이 끝난 후에 입력하는 키는 모두 무시된다.
준규는 소코반으로 고통받는 친구들을 위해서 소코반의 해를 찾는 프로그램을 작성하려고 한다. 하지만, 소코반의 해를 찾는 문제는 NP-hard와 PSPACE-complete에 속하고, 매우 어려운 문제이다. 따라서, 간단한 소코반 프로그램을 작성해보려고 한다.
사용자가 입력한 키가 순서대로 주어졌을 때, 게임이 어떻게 진행되는지를 구하는 프로그램을 작성하시오.
게임의 상태는 아래와 같은 문자로 나타낼 수 있다.
문자 | 뜻 |
. | 빈 공간 |
# | 벽 |
+ | 비어 있는 목표점 |
b | 박스 |
B | 목표점 위에 있는 박스 |
w | 캐릭터 |
W | 목표점 위에 있는 캐릭터 |
첫 번째 입력은 문제의 그림과 같다.
입력
입력은 여러 개의 테스트 케이스로 이루어져 있다.
각 테스트 케이스의 첫째 줄에는 행과 열의 수 R, C가 주어진다. (4 ≤ R ≤ 15, 4 ≤ C ≤ 15) 다음 R개 줄에는 현재 게임의 상태가 주어진다. 모든 줄은 C개의 문자로 이루어져 있다. 마지막 줄에는 플레이어가 입력한 키가 순서대로 주어지며 길이는 최대 50이다. 위, 아래, 왼쪽, 오른쪽은 U, D, L, R로 나타낸다.
입력의 마지막 줄에는 0 0이 주어진다.
입력으로 주어지는 모든 데이터는 항상 캐릭터가 한 명이고, 박스의 수와 목표점의 수는 같다. 또, 목표점 위에 올라가 있지 않은 박스는 적어도 한 개 이며, 가장 바깥쪽 칸은 항상 벽이다.
출력
각각의 게임에 대해서, 게임 번호를 출력한 다음에 게임이 끝났으면 complete를, 아니면 incomplete를 출력한다. 그 다음 줄부터 R개 줄에는 게임의 상태를 출력한다.
예제 입력 1
8 9
#########
#...#...#
#..bb.b.#
#...#w#.#
#...#b#.#
#...++++#
#...#..##
#########
ULRURDDDUULLDDD
6 7
#######
#..####
#.+.+.#
#.bb#w#
##....#
#######
DLLUDLULUURDRDDLUDRR
0 0
예제 출력 1
Game 1: incomplete
#########
#...#...#
#..bb...#
#...#.#.#
#...#.#.#
#...+W+B#
#...#b.##
#########
Game 2: complete
#######
#..####
#.B.B.#
#.w.#.#
##....#
#######
풀이과정
풀이
시뮬레이션 문제이다. 주의할 점은 캐릭터나 박스가 목표점에 있을 수 있다는 점이다. 타겟에 올라갈때마다 대문자로 바꾸고 하는게 번거로워서, 모든 계산을 다하고 한꺼번에 처리해주었다.
import sys
input = sys.stdin.readline
game = 0
while 1:
game += 1
# 가로, 세로
R, C = map(int, input().split())
if R == 0 and C == 0:
break
# 초기화
sokoban = [list(input().rstrip()) for _ in range(R)]
# 캐릭터 위치
cx, cy = 0, 0
# 현재 박스 상태 & 박스 목표
box_state, box_target = 0, 0
targets = []
# 현재 상태로 초기화
for a in range(R):
for b in range(C):
if sokoban[a][b] == 'w':
cx, cy = a, b
elif sokoban[a][b] == 'W':
targets.append((a, b))
cx, cy = a, b
box_target += 1
elif sokoban[a][b] == 'B':
targets.append((a, b))
box_state += 1
box_target += 1
elif sokoban[a][b] == '+':
targets.append((a, b))
box_target += 1
sokoban[a][b] = '.'
# 입력 키
keys = list(input().rstrip())
# 방향 설정
direction = {"U": (-1, 0), "D": (1, 0), "L": (0, -1), "R": (0, 1)}
# for 문 돌리기
for key in keys:
add_x, add_y = direction[key]
ncx, ncy = cx+add_x, cy+add_y
# 빈칸이라면 그 칸으로 이동
if sokoban[ncx][ncy] == '.':
sokoban[cx][cy] = '.'
sokoban[ncx][ncy] = 'w'
cx, cy = ncx, ncy
# 지시한 방향에 박스가 있다면
elif sokoban[ncx][ncy].lower() == 'b':
# 비어있는 경우 -> 이동 u, b, .
if sokoban[ncx+add_x][ncy+add_y] == '.':
sokoban[cx][cy] = '.'
sokoban[ncx][ncy] = 'w'
sokoban[ncx+add_x][ncy+add_y] = 'b'
cx, cy = ncx, ncy
# 타겟 처리
if (ncx, ncy) in targets:
box_state -= 1
if (ncx+add_x, ncy+add_y) in targets:
box_state += 1
# 막혀있는 경우 -> 이동 x
else:
pass
# 지시한 방향이 벽 -> 이동 x
elif sokoban[ncx][ncy] == '#':
pass
# 모든 박스가 목표점 이동하면, break
if box_state == box_target:
# 타겟 처리.
for x, y in targets:
sokoban[x][y] = 'B'
print("Game "+str(game)+": complete")
for soko in sokoban:
print("".join(soko))
break
else:
# 타겟 처리.
for x, y in targets:
if sokoban[x][y] == '.':
sokoban[x][y] = '+'
elif sokoban[x][y] == 'b':
sokoban[x][y] = 'B'
elif sokoban[x][y] == 'w':
sokoban[x][y] = 'W'
print("Game "+str(game)+": incomplete")
for soko in sokoban:
print("".join(soko))
'Problem Solving > 백준' 카테고리의 다른 글
[백준] 외판원 순회 2(10971번) - 파이썬(Python) (0) | 2022.07.17 |
---|---|
[백준] 다음 순열(10972번) - 파이썬(Python) (0) | 2022.07.17 |
[백준] 이전 순열(10973번) - 파이썬(Python) (0) | 2022.06.05 |
[백준] 집합(11723번) - 파이썬(Python) (0) | 2022.06.05 |
[백준] 종이 조각(14391번) - 파이썬(Python) (0) | 2022.06.05 |