게임 개발자인 베로니는 개발 연습을 위해 다음과 같은 간단한 카드 짝맞추기 보드 게임을 개발해 보려고 합니다.
게임이 시작되면 화면에는 카드 16장이 뒷면을 위로하여 4 x 4 크기의 격자 형태로 표시되어 있습니다. 각 카드의 앞면에는 카카오프렌즈 캐릭터 그림이 그려져 있으며, 8가지의 캐릭터 그림이 그려진 카드가 각기 2장씩 화면에 무작위로 배치되어 있습니다.
유저가 카드를 2장 선택하여 앞면으로 뒤집었을 때 같은 그림이 그려진 카드면 해당 카드는 게임 화면에서 사라지며, 같은 그림이 아니라면 원래 상태로 뒷면이 보이도록 뒤집힙니다. 이와 같은 방법으로 모든 카드를 화면에서 사라지게 하면 게임이 종료됩니다.
게임에서 카드를 선택하는 방법은 다음과 같습니다.
"베로니"는 게임 진행 중 카드의 짝을 맞춰 몇 장 제거된 상태에서 카드 앞면의 그림을 알고 있다면, 남은 카드를 모두 제거하는데 필요한 키 조작 횟수의 최솟값을 구해 보려고 합니다. 키 조작 횟수는 방향키와 [Enter] 키를 누르는 동작을 각각 조작 횟수 1로 계산하며, [Ctrl] 키와 방향키를 함께 누르는 동작 또한 조작 횟수 1로 계산합니다.
다음은 카드가 몇 장 제거된 상태의 게임 화면에서 커서를 이동하는 예시입니다.
아래 그림에서 빈 칸은 이미 카드가 제거되어 없어진 칸을 의미하며, 그림이 그려진 칸은 카드 앞 면에 그려진 그림을 나타냅니다.
예시에서 커서는 두번째 행, 첫번째 열 위치에서 시작하였습니다.
[Enter] 입력, ↓ 이동, [Ctrl]+→ 이동, [Enter] 입력 = 키 조작 4회
[Ctrl]+↑ 이동, [Enter] 입력, [Ctrl]+← 이동, [Ctrl]+↓ 이동, [Enter] 입력 = 키 조작 5회
[Ctrl]+→ 이동, [Enter] 입력, [Ctrl]+↑ 이동, [Ctrl]+← 이동, [Enter] 입력 = 키 조작 5회
위와 같은 방법으로 커서를 이동하여 카드를 선택하고 그림을 맞추어 카드를 모두 제거하기 위해서는 총 14번(방향 이동 8번, [Enter] 키 입력 6번)의 키 조작 횟수가 필요합니다.
현재 카드가 놓인 상태를 나타내는 2차원 배열 board와 커서의 처음 위치 r, c가 매개변수로 주어질 때, 모든 카드를 제거하기 위한 키 조작 횟수의 최솟값을 return 하도록 solution 함수를 완성해 주세요.
board | r | c | result |
---|---|---|---|
[[1,0,0,3],[2,0,0,0],[0,0,0,2],[3,0,1,0]] | 1 | 0 | 14 |
[[3,0,0,2],[0,0,1,0],[0,1,0,0],[2,0,0,3]] | 0 | 1 | 16 |
# 코드
from collections import deque
from itertools import permutations
from copy import deepcopy
def move_count(board, start, end):
if start==end: return 0
queue, visit = deque([[start[0], start[1], 0]]), {start}
while queue: # BFS
r, c, cnt = queue.popleft()
for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)]:
nr, nc = r+dr, c+dc # Normal move
cr, cc = r, c
while True: # Ctrl + move
cr, cc = cr+dr, cc+dc
if not (0 <= cr <= 3 and 0 <= cc <= 3):
cr, cc = cr-dr, cc-dc
break
elif board[cr][cc] != 0:
break
if (nr, nc) == end or (cr, cc) == end: # 도착 최단 경로
return cnt + 1
if (0 <= nr <= 3 and 0 <= nc <= 3) and (nr, nc) not in visit:
queue.append((nr, nc, cnt+1))
visit.add((nr, nc))
if (cr, cc) not in visit:
queue.append((cr, cc, cnt+1))
visit.add((cr, cc))
def backtracking(board, card_dict, start, order, cost):
if len(order) == 0 :
return cost
card = order[0]
# 현재 위치에서 card 삭제시 횟수 확인
del_cnt1 = move_count(board, start, card_dict[card][0]) + move_count(board, card_dict[card][0], card_dict[card][1])
del_cnt2 = move_count(board, start, card_dict[card][1]) + move_count(board, card_dict[card][1], card_dict[card][0])
# 새로운 board 생성
new_board = deepcopy(board)
new_board[card_dict[card][0][0]][card_dict[card][0][1]] = 0
new_board[card_dict[card][1][0]][card_dict[card][1][1]] = 0
# 적은 조작 횟수를 한 경우를 따라 재귀
if del_cnt1 < del_cnt2:
return backtracking(new_board, card_dict, card_dict[card][1], order[1:], cost + del_cnt1)
else:
return backtracking(new_board, card_dict, card_dict[card][0], order[1:], cost + del_cnt2)
def solution(board, r, c):
answer = int(1E10)
card_dict = {}
card_cnt = 0
for i in range(4):
for j in range(4):
if board[i][j] != 0:
if board[i][j] not in card_dict:
card_dict[board[i][j]] = [(i, j)]
else:
card_dict[board[i][j]].append((i, j))
card_cnt = len(card_dict.keys())
for case in permutations(range(1, card_cnt+1)):
answer = min(answer, backtracking(board, card_dict, (r, c), case, 0))
answer += (card_cnt * 2)
return answer