https://github.com/ivorrr987/sokoban
sokoban.py
import processing
import main
origin = processing.open_file("maps.txt") # maps.txt에 저장된 정보를 가져옴.
maps_str = processing.divide_stages(origin) # '====='를 기준으로 스테이지 별로 분할.
stages = processing.set_stages(maps_str) # 각 스테이지 지도 정보를 2차원 배열 형태로 저장
main.start_game(stages)
sokoban.py 파일로 게임을 실행할 수 있다.
데이터를 처리하여 사용하기 편한 상태로 가공하고
main.py의 start_game
함수를 이용하여 게임을 시작한다.
main.py
import copy
import move
import show
import check
import processing
import slot
# 명령어에 따라 동작 수행
def exec_commands(map_list, map_list_init, commands, hole_loc, log, slots, i):
move_commands = ['w', 'W', 'a', 'A', 's', 'd', 'D'] # 이동 관련된 명령어들. 'S'는 저장용으로 써야 하기 때문에 제외.
while len(commands)>0: # 입력받은 명령어들을 순차적으로 처리
if check.is_all_in(map_list): # 대기 중인 명령어가 있더라도 현재 스테이지를 깰 수 있는 상태면 바로 완료.
return map_list, log, False, i
if commands[0] in move_commands:
map_list, success = move.update_map(map_list, commands[0], hole_loc)
if success==True:
print(f"(턴수: {len(log)})") # 턴수 출력
log[len(log)] = map_list # 로그 생성
commands.pop(0)
elif commands[0] == 'r' or commands[0] == 'R': # 매 스테이지 시작마다 복사해두었던 지도 정보를 이용해 초기화 명령 실행.
map_list= copy.deepcopy(map_list_init)
show.show_basic_info(map_list)
log.clear() # log를 초기화 (+턴수 초기화 효과)
log = {0 : map_list}
print("r: 지도를 리셋합니다")
commands.pop(0)
elif commands[0] == 'c':
if check.check_possible(map_list)[0]:
print("이동이 불가능한 공이 없습니다.")
else:
print("이동이 불가능한 공이 있습니다. 리셋 혹은 종료를 권합니다")
print("해당 공의 좌표는 다음과 같습니다")
print(check.check_possible(map_list)[1])
commands.pop(0)
elif commands[0] == 'q' or commands[0] == 'Q': # 'quit' 명령어 입력 시 게임 종료.
return map_list, log, True, i
elif 49<= ord(commands[0]) and ord(commands[0])<=53: # 1~5의 숫자가 입력되었을 때
slot.show_slot(slots, int(commands[0])-1)
command = input()
if command == 'S':
slots[int(commands[0])-1] = copy.deepcopy(slot.save_slot(map_list, slots, int(commands[0])-1, log, i))
elif command == 'L':
map_list, log, i = slot.load_slot(slots, int(commands[0])-1)
else:
print("잘못된 입력입니다")
commands.pop(0)
else : # 올바른 명령이 아닐 시, 아무 동작 없이 command만 지움.
print("올바른 명령이 아닙니다")
commands.pop(0)
return map_list, log, False, i
def input_command(): # 커맨드를 입력받아 알파벳만 list 변수에 저장 후 return
print("SOKOBAN>", end=' ')
commands = list(input().replace(" ",''))
return commands
def finish_game():
print("축하합니다. 모든 스테이지를 완료하셨습니다")
# 게임 시작
def start_game(stages):
i=0
slots = [[stages[0], {} , 0]]*5
while i<len(stages):
# for i in range(len(stages)): # 스테이지1부터 마지막 스테이지까지. 스테이지 지도 정보는 map.txt 내용 수정하여 설정할 수 있음.
show.show_stage_info(stages[i], i+1) # 각 스테이지 정보 표시
stage_init = copy.deepcopy(stages[i])
hole_loc = processing.save_hole_loc(stage_init)
log = {0 : stages[i]} # 턴수 출력과 추후 'undo' 명령을 위해
quit = False
while check.is_all_in(stages[i])==False and quit==False: ## 스테이지를 깰 때까지 반복. 'quit' 명령어 입력 시 스테이지 및 게임 종료.
commands = input_command()
stages[i], log, quit, i = exec_commands(stages[i], stage_init, commands, hole_loc, log, slots, i)
if quit:
print("게임을 종료합니다")
break
else:
print(f"\n<Stage {i+1} Clear>\n")
i+=1
if quit==False:
finish_game()
move.py
import show
def locate_player(map_list): ## 플레이어 위치 확인
for i in range(len(map_list)):
if 'P' in map_list[i]:
row = i
col = map_list[i].index('P')
return [row, col]
#이동하고자 하는 위치가 공백이거나 빈 구멍일 경우
def move_up(map_list, current_row, current_col, hole_loc):
map_list[current_row-1][current_col] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'w')
return map_list
def move_left(map_list, current_row, current_col, hole_loc):
map_list[current_row][current_col-1] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'a')
return map_list
def move_down(map_list, current_row, current_col, hole_loc):
map_list[current_row+1][current_col] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 's')
return map_list
def move_right(map_list, current_row, current_col, hole_loc):
map_list[current_row][current_col+1] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'd')
return map_list
# 플레이어가 이동하고자 하는 위치에 공이 놓여있어서 공을 밀어야 할 경우
def push_up(map_list, current_row, current_col, hole_loc):
success = True
if map_list[current_row-2][current_col] == ' ':
map_list[current_row-2][current_col] = 'o'
map_list[current_row-1][current_col] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'w')
elif map_list[current_row-2][current_col]== 'O':
map_list[current_row-2][current_col] = '0'
map_list[current_row-1][current_col] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'w')
else:
success = show.show_fail(map_list)
return map_list, success
def push_left(map_list, current_row, current_col, hole_loc):
success = True
if map_list[current_row][current_col-2] == ' ':
map_list[current_row][current_col-2] = 'o'
map_list[current_row][current_col-1] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'a')
elif map_list[current_row][current_col-2]== 'O':
map_list[current_row][current_col-2] = '0'
map_list[current_row][current_col-1] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'a')
else:
success = show.show_fail(map_list)
return map_list, success
def push_down(map_list, current_row, current_col, hole_loc):
success = True
if map_list[current_row+2][current_col] == ' ':
map_list[current_row+2][current_col] = 'o'
map_list[current_row+1][current_col] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list,'s')
elif map_list[current_row+2][current_col]== 'O':
map_list[current_row+2][current_col] = '0'
map_list[current_row+1][current_col] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list,'s')
else:
success=show.show_fail(map_list)
return map_list, success
def push_right(map_list, current_row, current_col, hole_loc):
success = True
if map_list[current_row][current_col+2] == ' ':
map_list[current_row][current_col+2] = 'o'
map_list[current_row][current_col+1] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list,'d')
elif map_list[current_row][current_col+2]== 'O':
map_list[current_row][current_col+2] = '0'
map_list[current_row][current_col+1] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list,'d')
else:
success = show.show_fail(map_list)
return map_list, success
# 0을 o과 O으로 분리하는 움직임일 때.
def take_out_up(map_list, current_row, current_col, hole_loc):
success = True
if map_list[current_row-2][current_col] == ' ':
map_list[current_row-2][current_col]='o'
map_list[current_row-1][current_col]='P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'w')
elif map_list[current_row-2][current_col] == 'O':
map_list[current_row-2][current_col] = '0'
map_list[current_row-1][current_col] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'w')
else:
success = show.show_fail(map_list)
return map_list, success
def take_out_left(map_list, current_row, current_col, hole_loc):
success = True
if map_list[current_row][current_col-2] == ' ':
map_list[current_row][current_col-2]='o'
map_list[current_row][current_col-1]='P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'a')
elif map_list[current_row][current_col-2] == 'O':
map_list[current_row][current_col-2] = '0'
map_list[current_row][current_col-1] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'a')
else:
success = show.show_fail(map_list)
return map_list, success
def take_out_down(map_list, current_row, current_col, hole_loc):
success = True
if map_list[current_row+2][current_col] == ' ':
map_list[current_row+2][current_col]='o'
map_list[current_row+1][current_col]='P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 's')
elif map_list[current_row+2][current_col] == 'O':
map_list[current_row+2][current_col] = '0'
map_list[current_row+1][current_col] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 's')
else:
success = show.show_fail(map_list)
return map_list, success
def take_out_right(map_list, current_row, current_col, hole_loc):
success = True
if map_list[current_row][current_col+2] == ' ':
map_list[current_row][current_col+2]='o'
map_list[current_row][current_col+1]='P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'd')
elif map_list[current_row][current_col+2] == 'O':
map_list[current_row][current_col+2] = '0'
map_list[current_row][current_col+1] = 'P'
map_list[current_row][current_col] = treat_hole(map_list, hole_loc, current_row, current_col)
show.show_success(map_list, 'd')
else:
success = show.show_fail(map_list)
return map_list, success
# 플레이어가 있던 자리가 원래 구멍이었는지 아닌지에 따라 플레이어가 해당 자리에서 이동했을 때 해당 위치 정보(' ' or 'O') 표시.
def treat_hole(map_list, hole_loc, current_row, current_col):
if [current_row, current_col] in hole_loc:
map_list[current_row][current_col] = 'O'
else:
map_list[current_row][current_col] = ' '
return map_list[current_row][current_col]
# map 정보를 커맨드에 따라 갱신.
def update_map(map_list, command, hole_loc):
current_row = locate_player(map_list)[0]
current_col = locate_player(map_list)[1]
success = True
if command=='w' or command=='W':
if map_list[current_row-1][current_col] == ' ' or map_list[current_row-1][current_col] == 'O': # 공백일 경우 P 위치 변경
map_list = move_up(map_list, current_row, current_col, hole_loc)
elif map_list[current_row-1][current_col] == 'o':
map_list, success = push_up(map_list, current_row, current_col, hole_loc)
elif map_list[current_row-1][current_col] == '0':
map_list, success = take_out_up(map_list, current_row, current_col, hole_loc)
else:
success = show.show_fail(map_list)
elif command=='a' or command== 'A':
if map_list[current_row][current_col-1] == ' ' or map_list[current_row][current_col-1] == 'O':
map_list = move_left(map_list, current_row, current_col, hole_loc)
elif map_list[current_row][current_col-1] == 'o':
map_list, success = push_left(map_list, current_row, current_col, hole_loc)
elif map_list[current_row][current_col-1] == '0':
map_list, success = take_out_left(map_list, current_row, current_col, hole_loc)
else:
success = show.show_fail(map_list)
elif command=='s':
if map_list[current_row+1][current_col] == ' ' or map_list[current_row+1][current_col] == 'O':
map_list = move_down(map_list, current_row, current_col, hole_loc)
elif map_list[current_row+1][current_col] == 'o':
map_list, success = push_down(map_list, current_row, current_col, hole_loc)
elif map_list[current_row+1][current_col] == '0':
map_list, success = take_out_down(map_list, current_row, current_col, hole_loc)
else:
success = show.show_fail(map_list)
elif command=='d' or command == 'D':
if map_list[current_row][current_col+1] == ' ' or map_list[current_row][current_col+1] == 'O':
map_list = move_right(map_list, current_row, current_col, hole_loc)
elif map_list[current_row][current_col+1] == 'o':
map_list, success = push_right(map_list, current_row, current_col, hole_loc)
elif map_list[current_row][current_col+1] == '0':
map_list, success = take_out_right(map_list, current_row, current_col, hole_loc)
else:
success = show.show_fail(map_list)
return map_list, success
플레이어 이동을 다음의 경우들로 분리했다.
각 경우에 따라서 올바른 이동을 수행하도록 했고, 만약 벽에 가로막히는 등 이동이 불가능할 경우 아무것도 실행되지 않도록 했다.
플레이어가 구멍이 있던 위치로 이동했을 경우 출력되는 지도 정보에는 구멍에 대한 심벌('0') 없이 플레이어 심벌('P')만 존재한다.
때문에 구멍 위에 서있던 플레이어가 다른 곳으로 이동했을 때 구멍 심벌을 다시 출력해줘야 했다.
save_hole_loc
함수를 이용해 각 스테이지마다 구멍의 위치를 저장해두고
treat_hole
함수를 이용해 매 이동마다 플레이어가 구멍 위에 서있었다면 이동 후 구멍 심벌이 다시 출력되도록 했다.