py_stepik/mod_graph/maze.py
2024-03-27 15:08:54 +03:00

281 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()