pysokoban/main.py
2025-08-29 22:10:07 -04:00

310 lines
9.9 KiB
Python

import os
import random
from pynput import keyboard
from assets.maps.dh1 import levels as dh1_levels
from assets.maps.dh2 import levels as dh2_levels
from assets.maps.mas_sasquatch import levels as mas_sasquatch_levels
from assets.maps.microban import levels as microban_levels
from assets.maps.microcosmos import levels as microcosmos_levels
from assets.maps.minicosmos import levels as minicosmos_levels
from assets.maps.nabokosmos import levels as nabokosmos_levels
from assets.maps.picokosmos import levels as picokosmos_levels
from assets.maps.sasquatch import levels as sasquatch_levels
from assets.maps.sasquatch_iii import levels as sasquatch_iii_levels
from assets.maps.sasquatch_iv import levels as sasquatch_iv_levels
from assets.maps.sasquatch_v import levels as sasquatch_v_levels
from assets.maps.yoshio_murase import levels as yoshio_murase_levels
from assets.text import logo, lose, win, help
WORLD_MAP = {
'dh1': dh1_levels,
'dh2': dh2_levels,
'mas_sasquatch': mas_sasquatch_levels,
'microban': microban_levels,
'microcosmos': microcosmos_levels,
'minicosmos': minicosmos_levels,
'nabokosomos': nabokosmos_levels,
'picokosmos': picokosmos_levels,
'sasquatch': sasquatch_levels,
'sasquatch_iii': sasquatch_iii_levels,
'sasquatch_iv': sasquatch_iv_levels,
'sasquatch_v': sasquatch_v_levels,
'yoshio_murase': yoshio_murase_levels
}
class Game:
def __init__(self):
self.map_instance = None
self.level = 0
self.lives = 3
self.points = 0
self.total_moves = 0
self.level_moves = {}
def initialize_level(self):
self.map_instance = Map()
self.map_instance.generate_map_array()
self.loop()
def loop(self):
while self.map_instance.break_condition:
self.map_instance.display()
self.map_instance.wait_for_input()
self.map_instance.check_win_condition()
self.initialize_level()
def handle_points(self, points):
self.points += points
if self.points + points < 0:
self.points = 0
class Map:
def __init__(self):
self.level = g.level
self.level_map = levels[self.level][0]
self.level_par = levels[self.level][1]
self.map_array = []
self.storage_locations = []
self.listener = None
self.player_x = None
self.player_y = None
self.previous_element = ' '
self.win = False
self.lose = False
self.break_condition = True
self.total_win = g.level + 1 == len(levels)
self.final_points_given = False
def generate_map_array(self):
g.level_moves[self.level] = 0
level_array = self.level_map.split('\n')
for idx, line in enumerate(level_array):
if idx == 0 or idx == len(level_array) - 1:
continue
row_elements = list(line)
if '@' in row_elements:
self.player_y = idx - 1
self.player_x = row_elements.index('@')
for storage_type in ['.', '*']:
if storage_type in row_elements:
for xidx, element in enumerate(row_elements):
if element == storage_type:
self.storage_locations.append((idx - 1, xidx))
self.map_array.append(row_elements)
def display(self):
if self.total_win and self.win and not self.final_points_given:
self.final_points_given = True
g.handle_points(self.calculate_points())
os.system('clear')
for storage_location in self.storage_locations:
if self.map_array[storage_location[0]][storage_location[1]] == ' ':
self.map_array[storage_location[0]][storage_location[1]] = '.'
print(logo)
print(f'world: {world}')
print(f'lvl: {g.level} | mvs: {g.level_moves[self.level]} | pts: {g.points} | lives: {g.lives}')
if self.lose:
print(lose)
elif self.total_win and self.win:
print(win)
else:
for row in self.map_array:
print('\t', ''.join(row))
if self.total_win:
print('\n\n Press [r] to to play again or [q] to quit')
elif self.win:
print('\n\n NICE! Press [w] to continue!')
elif self.lose:
print('\n\n Press [r] to retry')
else:
print('\n\npress [r] to reset the map')
def on_release(self, key):
self.listener = None
if 'char' in dir(key):
if key.char == 'q':
self.quit()
if key.char == '?':
os.system('clear')
print(help)
input('Back to the game? Press any key to contiue: ')
os.system('clear')
if (self.total_win and self.win) or self.lose:
if key.char == 'r':
self.retry()
else:
if key.char == 'h' or key.char == '4':
self.move('left')
if key.char == 'l' or key.char == '6':
self.move('right')
if key.char == 'j' or key.char == '2':
self.move('down')
if key.char == 'k' or key.char == '8':
self.move('up')
if key.char == 'y' or key.char == '7':
self.move('up left')
if key.char == 'u' or key.char == '9':
self.move('up right')
if key.char == 'b' or key.char == '1':
self.move('down left')
if key.char == 'n' or key.char == '3':
self.move('down right')
if key.char == 'r':
self.reset_level()
if self.win:
if key.char == 'w':
self.win_level()
return False
def wait_for_input(self):
with keyboard.Listener(on_press=self.on_release) as self.listener:
self.listener.join()
def move(self, direction):
g.level_moves[self.level] += 1
g.total_moves += 1
tmp_player_x, tmp_player_y = self.calculate_future_position(self.player_x, self.player_y, direction)
future_position = self.map_array[tmp_player_y][tmp_player_x]
if future_position not in ['#']:
can_move = True
if future_position in ['0', '*']:
future_position = ' '
can_move = self.push_boulder(tmp_player_y, tmp_player_x, direction)
if can_move:
self.map_array[self.player_y][self.player_x] = self.previous_element
self.previous_element = future_position
self.map_array[tmp_player_y][tmp_player_x] = '@'
self.player_x = tmp_player_x
self.player_y = tmp_player_y
def calculate_future_position(self, x, y, direction):
dir_list = direction.split(' ')
if 'right' in dir_list:
x += 1
elif 'left' in dir_list:
x -= 1
if 'down' in dir_list:
y += 1
elif 'up' in dir_list:
y -= 1
return x, y
def push_boulder(self, tmp_player_y, tmp_player_x, direction):
if len(direction.split(' ')) > 1:
return False
tmp_boulder_x, tmp_boulder_y = self.calculate_future_position(tmp_player_x, tmp_player_y, direction)
future_position = self.map_array[tmp_boulder_y][tmp_boulder_x]
if future_position not in ['#', '0', '*']:
if future_position == '.':
self.map_array[tmp_boulder_y][tmp_boulder_x] = '*'
else:
self.map_array[tmp_boulder_y][tmp_boulder_x] = '0'
return True
else:
return False
def check_win_condition(self):
self.win = True
for yidx, line in enumerate(self.map_array):
for xidx, element in enumerate(line):
if element == '0':
if (yidx, xidx) not in self.storage_locations:
self.win = False
def win_level(self):
if not self.total_win:
g.handle_points(self.calculate_points())
g.level += 1
self.break_condition = False
def calculate_points(self):
multiplier = 1
penality = 0
if g.level_moves[g.level] < self.level_par:
multiplier = 1.5
elif g.level_moves[g.level] == self.level_par:
multiplier = 1.2
elif g.level_moves[g.level] > self.level_par:
penality = (g.level_moves[g.level] - self.level_par) * 0.1
return (10 - penality) * multiplier
def reset_level(self):
g.lives -= 1
g.handle_points(-3.5)
if g.lives <= 0:
self.lose = True
else:
self.break_condition = False
def retry(self):
global g
g = Game()
g.initialize_level()
def quit(self):
os.system('clear')
response = input('Are you sure you want to quit? [Y/N]: ')[-1]
if response.lower() == 'y':
os.system('clear')
os._exit(1)
if __name__ == '__main__':
world_map_key_list = list(WORLD_MAP.keys())
def get_world_response():
response = input('Which world would you like to play? Input a number: ')
if response not in [str(idx) for idx, _ in enumerate(world_map_key_list)] and response != str(len(world_map_key_list)):
return get_world_response()
return response
os.system('clear')
for idx, name in enumerate(world_map_key_list):
print(f'[{idx}] {name}')
random_idx = idx + 1
print(f'[{random_idx}] random world')
response = int(get_world_response())
if int(response) == random_idx:
response = random.randint(0, len(world_map_key_list))
world = list(WORLD_MAP.keys())[response]
levels = WORLD_MAP.get(world)
g = Game()
g.initialize_level()
os.system('clear')