import argparse 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) response = 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): 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__': parser = argparse.ArgumentParser(prog='pysokoban') parser.add_argument('-w', '--world', dest='world', choices=WORLD_MAP.keys()) args = parser.parse_args() if args.world in WORLD_MAP: world = args.world levels = WORLD_MAP.get(args.world) else: world = list(WORLD_MAP.keys())[random.randint(0, len(WORLD_MAP.keys()))] levels = WORLD_MAP.get(world) g = Game() g.initialize_level() os.system('clear')