add maze
This commit is contained in:
parent
1bb1584389
commit
5b3ae56020
BIN
assets/brick.png
Normal file
BIN
assets/brick.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
assets/win.png
Normal file
BIN
assets/win.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
280
mod_graph/maze.py
Normal file
280
mod_graph/maze.py
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
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()
|
Loading…
Reference in New Issue
Block a user