과제로 간단한 소코반 게임을 만들어 보았다.
각 스테이지의 모든 박스(공)를 정해진 위치(구멍)에 올려놓는(넣는) 것을 목표로 하는 게임이다. 링크를 따라가거나 검색해보면 아마 언젠가 한번 정도는 해봤을 게임일거라 생각한다.
처음엔 '금방 만들지 않을까?'하는 마음이었다. 로직 자체가 단순했다고 생각했기 때문이다. 하지만 웬걸, 생각보다 만들기 쉽지 않았다. (아니면 내 실력이 부족한걸지도 🥲)
자바와 파이썬 중에 고민했다. 각 언어에 대한 실력은 고만고만하지만 그나마 파이썬이 낫겠다 싶어 파이썬으로 만들기로 했다. 자바를 좀 더 공부하고 자바로도 만들어 보자고 스스로 다짐했다.
https://gist.github.com/ivorrr987/43694b65e390d8bfb202ea9f9a7fa809
(과제를 수행하면서 gist를 이용하여 버전 관리를 했다. 추후에 좀 더 정리하여 github repository에 올리고자 한다.)
https://github.com/ivorrr987/sokoban
본격적인 구현에 앞서 코드를 어떻게 짤지 생각해보았다.
기본적으로 반드시 필요하다 생각한 것들은 다음과 같다.
심벌들을 설정하고 그걸로 맵을 구성해야 했다. 그래야 사용자 화면에 출력하고 사용자가 명령어를 통해 게임을 할 수 있을테니까.
심벌은 다음처럼 정했다.
수정 및 관리가 용이하도록 txt파일에 각 스테이지의 맵을 저장하고 그 정보를 가져와 사용하기로 했다.
maps.txt
Stage 1
#####
#OoP#
#####
---
Stage 2
#######
### O ###
# o #
# OoPoO #
# o #
### O ###
#######
---
Stage 3
####
### #
#P Oo##
# o #
# #O #
# #
#######
---
Stage 4
######
## ##
# OO #
# # ooo#
# #PO#
########
---
Stage 5
######
#P ##
# Oo0 #
# #oO #
# #
########
각 스테이지에 대한 정보를
스테이지명
맵 정보
로 나누고, 각 스테이지는 ---
로 나누었다.
maps.txt
에는 모든 스테이지의 정보가 한 데 뭉쳐 저장되어 있다. 게임 시 사용자는 자신이 플레이 중인 스테이지의 정보만 보면 되므로 각 스테이지 별로 데이터를 분리하고자 했다.
즉, 전체 데이터 추출 → 스테이지 별로 데이터 분리
def open_file(filename):
with open(filename) as maps_file:
origin = maps_file.read()
return origin
def divide_stages(origin)
return origin.split("---\n")
maps.txt 파일을 불러와 읽고 그 내용을 origin 함수에 담아 전달하도록 했다. 변수명 짓는 게 어려웠다. '더 적절한 단어가 있을 것 같은데'하는 생각이 코딩하는 내내 들었다.
divide_stages
함수는 전체 데이터를 가져와 '---\n'를 기준으로 나누는 역할을 하게 했다.
origin = open_file("maps.txt")
divided = divide_stages(origin)
print(divided)
위의 코드를 실행한 결과는 다음과 같다.
['Stage 1\n#####\n#OoP#\n#####\n', 'Stage 2\n ####### \n### O ###\n# o #\n# OoPoO #\n# o #\n### O ###\n ####### \n', 'Stage 3\n #### \n### # \n#P Oo##\n# o #\n# #O #\n# #\n#######\n', 'Stage 4\n ###### \n## ##\n# OO #\n# # ooo#\n# #PO#\n########\n', 'Stage 5\n###### \n#P ## \n# Oo0 #\n# #oO #\n# #\n########']
보다시피 각 스테이지 별 데이터가 문자열로 저장되어 있다. 나중에 명령어에 따라 P나 o 등을 옮겨야 할텐데 각 스테이지 별 데이터가 문자열로 저장되어 있으면 다루기 힘들거라 판단했다. 명령어에 대한 동작을 구현하기 위해 각 심벌을 하나씩 따로 떼서 list에 담아야 했다.
def save(map_str): # 문자열 'stage_info'를 list로 변환하여 필요한 값만 저장
map_str= map_str.split("\n")
map_list= []
for info in map_str:
if 'Stage' not in info and info!='':
map_list.append(list(info))
return map_list
# 문자열로 저장된 각 스테이지 정보를 2차원 배열로 변환.
def set_stages(maps_str):
stages=[]
for i in range(len(maps_str)):
stages.append(save(maps_str[i]))
return stages
maps_list = set_stages(divided)
print(maps_list)
divided의 각 원소, 즉 문자열 타입의 각 스테이지 정보는 set_stages
함수에서 분리되고 save
함수를 통해 list로 변환되어 2차원 배열이 된다. set_stages
함수는 2차원 배열(각 스테이지 데이터)의 list를 반환하고, 그 반환값에 해당하는maps_list
를 출력하면 다음의 결과를 얻을 수 있다.
[[['#', '#', '#', '#', '#'], ['#', 'O', 'o', 'P', '#'], ['#', '#', '#', '#', '#']], [[' ', ' ', '#', '#', '#', '#', '#', '#', '#', ' ', ' '], ['#', '#', '#', ' ', ' ', 'O', ' ', ' ', '#', '#', '#'], ['#', ' ', ' ', ' ', ' ', 'o', ' ', ' ', ' ', ' ', '#'], ['#', ' ', ' ', 'O', 'o', 'P', 'o', 'O', ' ', ' ', '#'], ['#', ' ', ' ', ' ', ' ', 'o', ' ', ' ', ' ', ' ', '#'], ['#', '#', '#', ' ', ' ', 'O', ' ', ' ', '#', '#', '#'], [' ', ' ', '#', '#', '#', '#', '#', '#', '#', ' ', ' ', ' ', ' ']], [[' ', ' ', '#', '#', '#', '#', ' '], ['#', '#', '#', ' ', ' ', '#', ' '], ['#', 'P', ' ', 'O', 'o', '#', '#'], ['#', ' ', ' ', ' ', 'o', ' ', '#'], ['#', ' ', '#', 'O', ' ', ' ', '#'], ['#', ' ', ' ', ' ', ' ', ' ', '#'], ['#', '#', '#', '#', '#', '#', '#']], [[' ', '#', '#', '#', '#', '#', '#', ' '], ['#', '#', ' ', ' ', ' ', ' ', '#', '#'], ['#', ' ', 'O', 'O', ' ', ' ', ' ', '#'], ['#', ' ', '#', ' ', 'o', 'o', 'o', '#'], ['#', ' ', ' ', ' ', '#', 'P', 'O', '#'], ['#', '#', '#', '#', '#', '#', '#', '#']], [['#', '#', '#', '#', '#', '#', ' ', ' '], ['#', 'P', ' ', ' ', ' ', '#', '#', ' '], ['#', ' ', 'O', 'o', '0', ' ', ' ', '#'], ['#', ' ', '#', 'o', 'O', ' ', ' ', '#'], ['#', ' ', ' ', ' ', ' ', ' ', ' ', '#'], ['#', '#', '#', '#', '#', '#', '#', '#']]]
굵게 표시한 부분은 stage1에 해당하는 데이터이다. 행렬로 구성해보면 maps.txt 속 stage1 정보와 일치함을 알 수 있다.
이렇게 소코반 게임 구현에 이용할 데이터 전처리 과정을 마쳤다.