import graph from graph import * import PIL import os from typing import NamedTuple from contextlib import contextmanager import types import tempfile import base64 from random import randrange, choice consts = types.SimpleNamespace() consts.VK_Q = 81 consts.VK_W = 87 consts.VK_A = 65 consts.VK_S = 83 consts.VK_D = 68 # объявление переменные для хранения картинки hardcoded_image: bytes hardcoded_bg: bytes class Point(NamedTuple): """ Вспомогательный класс для упрощения работы с координатами (сложение) """ x: int | float y: int | float def __add__(self, other): if isinstance(other, self.__class__): return self.__class__(self.x + other.x, self.y + other.y) if isinstance(other, (int, float)): return self.__class__(self.x + other, self.y + other) return NotImplemented def __sub__(self, other): if isinstance(other, self.__class__): return self.__class__(self.x - other.x, self.y - other.y) if isinstance(other, (int, float)): return self.__class__(self.x - other, self.y - other) return NotImplemented def swap(self): return self.__class__(self.y, self.x) def transform(self, ref: "Point"): return self.__class__(self.x * ref.x, self.y * ref.y) data = { "hero_pos": Point(0, 0), "finished": False, "hero_moved": False, "right": False, "last_right": False, "hero_img_left": None, "hero_img_right": None, "hero": None, "maze": [], "callbacks": [], } @contextmanager def hardcoded_as_tmp(b64data: bytes, ext: str = ".png"): with tempfile.NamedTemporaryFile( mode="wb", suffix=ext, delete_on_close=False ) as fp: data = base64.b64decode(b64data) fp.write(data) fp.close() yield fp.name @contextmanager def tmp_flipped_img(src: str): """создание временнрого файла с зеркальной копией картинки""" # получаем оригинальное расширение _, ext = os.path.splitext(src) if not ext.startswith("."): ext = f".{ext}" # открытие image_obj = PIL.Image.open(src) rotated_image = image_obj.transpose(Image.FLIP_LEFT_RIGHT) # зеркалирование with tempfile.NamedTemporaryFile( mode="wb", suffix=ext, delete_on_close=False ) as fp: fp.close() # сохранение rotated_image.save(fp.name) yield fp.name def make_level(scene_sz): data["maze"] = maze_gen() box_sz = Point(scene_sz.x // len(data["maze"][0]), scene_sz.y // len(data["maze"])) data["hero_pos"] = find_start(data) print(data["hero_pos"]) data["hero_img_right"] = PIL.ImageTk.PhotoImage( data["hero_img_right"].resize(box_sz) ) data["hero_img_left"] = PIL.ImageTk.PhotoImage(data["hero_img_left"].resize(box_sz)) changeProperty(data["hero"], image=data["hero_img_right"]) data["hero_moved"] = True # brick = PIL.ImageTk.PhotoImage( # PIL.Image.open("assets/brick.png").resize(box_sz) # ) for i, row in enumerate(data["maze"]): for j, item in enumerate(row): if item < 1: continue p = Point(j, i).transform(box_sz) obj = image(*p, "assets/brick.png") # changeProperty(obj, image=brick) def update(): # отрабатываем изменение направления if data["right"] != data["last_right"]: img = data["hero_img_right"] if data["right"] else data["hero_img_left"] changeProperty(data["hero"], image=img) data["last_right"] = data["right"] # отрисовка движения if data["hero_moved"]: moveObjectTo(data["hero"], *data["hero_pos"].transform(box_sz)) data["hero_moved"] = False # победа if data["finished"]: data["callbacks"].remove(update) image(350, 350, "assets/win.png") penColor("blue") text("Красава!", 450, 450, font=("Arial", 30)) data.setdefault("callbacks", []).append(update) def find_start(data): for i in range(len(data["maze"])): if data["maze"][i][0] == 0: return Point(0, i) raise Exception("Не найдено начальное положение") def move_hero(data, delta): new_pos = data["hero_pos"] + delta if ( new_pos.x < 0 or new_pos.y < 0 or new_pos.x >= len(data["maze"]) or new_pos.y >= len(data["maze"][0]) ): return if data["maze"][new_pos.y][new_pos.x] > 0: return data["hero_pos"] = new_pos data["hero_moved"] = True if new_pos.x == len(data["maze"]) - 1: data["finished"] = True def keyPressed(event): delta = None match event.keycode: case graph.VK_LEFT: delta = Point(-1, 0) data["right"] = False case graph.VK_RIGHT: delta = Point(1, 0) data["right"] = True case graph.VK_UP: delta = Point(0, -1) case graph.VK_DOWN: delta = Point(0, 1) case consts.VK_Q | graph.VK_ESCAPE: killTimer(data["update_timer"]) close() if delta is not None: move_hero(data, delta) def update(): for callback in data["callbacks"]: callback() data["delta"] = Point(0, 0) def maze_gen(row=4, col=4): """генератор карты взял с коментария https://stepik.org/lesson/502494/step/3?discussion=6527620&unit=494196 """ row = max(2 * row + 1, 3) col = max(2 * col + 1, 3) maze = [[2] * col] maze.extend([[2] + [1] * (col - 2) + [2] for _ in range(row - 2)]) maze.append(maze[0]) curr = (randrange(1, len(maze) - 1, 2), randrange(1, len(maze[0]) - 1, 2)) path = [curr] maze[curr[0]][curr[1]] = 0 while path: nexts = [ (r1, c1, r2, c2) for r, c in zip((1, 0, -1, 0), (0, 1, 0, -1)) if ( (r1 := curr[0] + r) is None or (c1 := curr[1] + c) is None or (r2 := curr[0] + 2 * r) is None or (c2 := curr[1] + 2 * c) is None or 1 == maze[r1][c1] == maze[r2][c2] ) ] if len(nexts): r1, c1, r2, c2 = choice(nexts) maze[r1][c1] = maze[r2][c2] = 0 path.append((r2, c2)) else: curr = path.pop() upd = { ("22", "20"): (None, "00"), ("02", "22"): ("00", None), ("11101", "00101", "11111", "10100", "10111"): ( None, None, None, "10001", None, ), ("10111", "10100", "11111", "00101", "11101"): ( None, "10001", None, None, None, ), } for key in upd: for i in range(len(maze) - len(key) + 1): for j in range(len(maze[0]) - len(key[0]) + 1): if all( maze[i + k][j : j + len(v)] == list(map(int, v)) for k, v in enumerate(key) ): for k, v in filter(lambda x: x[1], enumerate(upd[key])): maze[i + k][j : j + len(v)] = list(map(int, v)) return maze def main(): scene_sz = Point(1000, 1000) canvasSize(*scene_sz) # рисуем фон # with hardcoded_as_tmp(hardcoded_bg) as bg_filename: image(0, 0, "assets/bg1k.png") hero_img = PIL.Image.open("assets/ghost.png") data["hero_img_left"] = hero_img data["hero_img_right"] = hero_img.transpose(Image.FLIP_LEFT_RIGHT) data["hero"] = image(0, 0, "assets/ghost.png") make_level(scene_sz) data["update_timer"] = onTimer(update, 40) onKey(keyPressed) run() main()