py_stepik/mod_graph/maze.py

290 lines
184 KiB
Python
Raw Normal View History

2024-03-27 13:55:45 +00:00
"""
Создание лабиринтов в игре
https://stepik.org/lesson/502494/step/3?unit=494196
Задача:
Сделайте "догонялку" в лабиринте. Придумайте дизайн лабиринта, рисунки для стенки и проходов. Через стенки Ваш герой не должен
передвигаться.
Решение:
Игра за призрака, нужно из входа в лабиринт "догнать" выход.
Управление стрелками и WASD, выход по Esc или Q.
Чтобы пройти игру, нужно дойти до выхода.
Картинки захардкожены прямо в скрипт.
Зависимости: graph pillow
Библиотека graph: https://kpolyakov.spb.ru/download/pygraph.zip
"""
2024-03-27 12:08:54 +00:00
from graph import *
import PIL
import os
from typing import NamedTuple
from contextlib import contextmanager
import types
import tempfile
import base64
2024-03-27 13:10:57 +00:00
import pickle
import zlib
2024-03-27 12:08:54 +00:00
from random import randrange, choice
2024-03-27 13:10:57 +00:00
# объявление переменные для хранения картинок
HARDCODED_DATA: bytes
@contextmanager
def get_hardcoded(hardcoded_data: bytes):
"""запись захардкоженых файлов во временные"""
data = base64.b85decode(hardcoded_data)
data = zlib.decompress(data)
data = pickle.loads(data)
files = {}
# создание временных файлов
for filename in data:
_, ext = os.path.splitext(filename)
with tempfile.NamedTemporaryFile(mode="wb", suffix=ext, delete=False) as fp:
fp.write(data[filename])
files[filename] = fp.name
# передача управления
yield files
# очистка
for _, filename in files.items():
try:
os.remove(filename)
except FileNotFoundError:
pass
2024-03-27 12:08:54 +00:00
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": [],
}
2024-03-27 13:10:57 +00:00
def make_level(scene_sz, hard):
2024-03-27 12:08:54 +00:00
data["maze"] = maze_gen()
2024-03-27 13:10:57 +00:00
# размер ячейки
2024-03-27 12:08:54 +00:00
box_sz = Point(scene_sz.x // len(data["maze"][0]), scene_sz.y // len(data["maze"]))
data["hero_pos"] = find_start(data)
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
2024-03-27 13:10:57 +00:00
# отрисовка стен
2024-03-27 12:08:54 +00:00
for i, row in enumerate(data["maze"]):
for j, item in enumerate(row):
if item < 1:
continue
p = Point(j, i).transform(box_sz)
2024-03-27 13:10:57 +00:00
obj = image(*p, hard["assets/brick.png"])
2024-03-27 12:08:54 +00:00
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"]:
2024-03-27 13:55:45 +00:00
new_pos = data["hero_pos"].transform(box_sz)
moveObjectTo(data["hero"], *new_pos)
2024-03-27 12:08:54 +00:00
data["hero_moved"] = False
2024-03-27 13:10:57 +00:00
# конец лабиринта|игры / победа
2024-03-27 12:08:54 +00:00
if data["finished"]:
data["callbacks"].remove(update)
2024-03-27 13:10:57 +00:00
image(350, 350, hard["assets/win.png"])
2024-03-27 12:08:54 +00:00
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):
2024-03-27 13:10:57 +00:00
"""проверка и выполнение движения героя в лабиринте"""
2024-03-27 12:08:54 +00:00
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
2024-03-27 13:55:45 +00:00
match event.keysym:
case "Left" | "a":
2024-03-27 12:08:54 +00:00
delta = Point(-1, 0)
data["right"] = False
2024-03-27 13:55:45 +00:00
case "Right" | "d":
2024-03-27 12:08:54 +00:00
delta = Point(1, 0)
data["right"] = True
2024-03-27 13:55:45 +00:00
case "Up" | "w":
2024-03-27 12:08:54 +00:00
delta = Point(0, -1)
2024-03-27 13:55:45 +00:00
case "Down" | "s":
2024-03-27 12:08:54 +00:00
delta = Point(0, 1)
2024-03-27 13:55:45 +00:00
case "Escape" | "q":
2024-03-27 12:08:54 +00:00
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
2024-03-27 13:10:57 +00:00
def app(hard):
2024-03-27 12:08:54 +00:00
scene_sz = Point(1000, 1000)
canvasSize(*scene_sz)
# рисуем фон
# with hardcoded_as_tmp(hardcoded_bg) as bg_filename:
2024-03-27 13:10:57 +00:00
image(0, 0, hard["assets/bg1k.png"])
2024-03-27 12:08:54 +00:00
2024-03-27 13:10:57 +00:00
hero_img = PIL.Image.open(hard["assets/ghost.png"])
2024-03-27 12:08:54 +00:00
data["hero_img_left"] = hero_img
data["hero_img_right"] = hero_img.transpose(Image.FLIP_LEFT_RIGHT)
2024-03-27 13:10:57 +00:00
data["hero"] = image(0, 0, hard["assets/ghost.png"])
2024-03-27 12:08:54 +00:00
2024-03-27 13:10:57 +00:00
make_level(scene_sz, hard)
2024-03-27 12:08:54 +00:00
data["update_timer"] = onTimer(update, 40)
onKey(keyPressed)
run()
2024-03-27 13:10:57 +00:00
def main():
with get_hardcoded(HARDCODED_DATA) as hard:
app(hard)
HARDCODED_DATA = "c-ri{cR1T^|1caKs1EH_s-;RPYHuo5Q6q@GM^Qp!#R#!!Rb8cM#i-boP%G3XNL5u)d)02MMv!O`DrtDr_PX!ieZTK>JiqJr$9o*laXfb%Ilkxk>~)^!H>a3m>A?yNfS;epwCj&qT>=9k@Ic8s9@1VCe!d=QnkHocz#|hQ-P0_bEOemLAiY~=0Kn1JpI>H1`Utpa><j(zHq62XZsvmE^$UQxL%kuq@K8SpFC+x&4giEq`<Tp}?&Ll7!y_U2BZu?btEbajG`g!gjM8tZo}fkuaxBWJZCZyJp4CrvsIN17#S7*P;%7;2=eu9gB*k`PEHN+Yv<y_H>Sm&c%Fg$X?>;0gA)l%hy>#RPt03nezdJwpNN}O^Mcg^0qSUDt^^^LeVNtIYKC+OJ?ga_!oblAQ49}UFT3Y-gM*djs<<CdIs-!k?xg=VB(Ej4>&?M2;b*<q^bU5%ydxx<tl_RGm3IJe;g=%S;fV8y!p%Gn=oXDrDdYv~fcR873%tX!*P2OD93aUv=xRD`pp+gfYTDRsLK;V#r>X|uQK0bLrmM77YiNnK@+u^vz*Q0NyIzYqP`ahh^?w(uh@K>Lo3!iN}`XD&Bbou5H6ZyV(#i@4+n?x9If6`^U`rusJv(l2l#L$rBs_^gM!#GxK=T{Ccck1`J#TZ{RlA5djQ}Is^Me*EiHU9^e0&(ASLPiQs{-GY=7`&ep*yVE1L+5csaNZ3~F)NA<$<<2U;`61?&z^gxNwPUu-GKR9t%E1pgrZ$upew&L83mpa7Q32s>ejov&mRKC-ayCoq@QAF==kWdZyxUikmZaz$;agiGy{HojQ)zVuT@c95{yfI^)b;W`ti|wjs>GN#7);3QV!t2<D>riK9`R(3AtlsoXE|<Kl7?9$QZd7)5pzAK^r{$q8EkIY(CYRKdi`p@2mJ(hrsZ4(&7%oJ9Fzk#aA6yrA!Mqf>IEy<h|X^EZ=B#4^qJM_mO>ccbpD}(gVx}YzTCP`AE39!R|mLLVWywMXri^h@XqA7X;3G2jT(sRpr~n;rV!>ZmN9N@?a^jpBCgUR4*(5VjgB>;Tq=Us^rF}uEwGg0;Ci0fxumOLwvk_1A!r`e81=d>FuAxl6<_sK;T}gd^TVcUM*MvgjY^NPC`mtI|Ldm!>7i=s}kVm4m7)^^A`zvPnGX39PS5{ltds95(rrdSb&G5w33pNq?C-LjEp!PLOd|k7w!@w?i<Melf-Wtw;+M80Z>0U6z0qOv+r^T76e!2<D<{>{zE<=KQQ>8<b4DGvI5;6k|8dBlF|}Vl0H6?|B4X^*A9mN?=b=`=y@t>1_^`(1-L@AgCV|f{(mLm=K4>3zn}o`U+TEIN<zFLK6KDPIt}T6W2p-QoBW#zJfJ>)zp&`y{~IJ6>hAu(mhI=vFX8@Q+W+r0`33yTM!;Jz*Px%`fo`et{p1gHgSkT8fWMm3H}1$wxywn5-*A<76PLT=;wFB_?S_)LtBm{|Svh${IXQQ!e<20(4TQV+x<Y=EqK`;G>2&VgkiT<7PD)B#+Vu{d#El!S;&&j5isCn<r4-#2Wfk3}ATIwx!Z-j*k4hKsf0gPdDK|PP1$jkjIf#^-xQvv$l(-z^j*PgAtecFuf{UUe#7#<CMoQ|pG;Xdy9aw;m3*DVi9~TdZB)vNSn)qpOpoR%Zl}|=O>c2)zyj|e#bOd@1Kz-d{h`|4vvVi(P%;7FS?J2D&EiH3HPEJ-qQC3M>M&ZAJtRMk_^jQ2!Ra#0y_V>(B$NW@>u7nHx*R1qE^aN@JKwRLk01Ft*Tb1u83ErQW|C|QX6Uxm6?sCfo4xtm3l92;S$pU5METrXua*9AHB~f};>R;etZcz8o{|@?R`tYj!HnScykj_8!*U;~rGKcv8?)~n1Lw|R8f29S`#r3xefiA%iH@<(Ha=q)~>j9zH56OQYhyE{uL6+_`cNZy$xV$^vjdF_aZsJNx3JT&k<Xzq6<)meAxXQ@=JC#6~I~?H>0MYQE+mUW7dQ|?h74P-GTqXSP@QAw*-`_?Mmy#9#rxBzT_$2>QR`Or9RgwJv*yA_Azm%Atl}qxk(nYU?lK(7)f7$E*=g)tb{D;YZnEZ#yf0+D-$$yyqAH{^_zYbFnU-}LRK|d;e*A{(4KV}{C&;V-y054Nc{%|=??=#*p(7gpX{P|bVR)VCDobc1L4FmvA{_*q6FfteQgFeUv2Z6PjR*zmhC3*I9^vZnzfENI|rC|{=y)oxgA=VSQu(yCO3wIwFja()LWKv%_K9z&m6<@h)mjLMtHvCX<OZ&vxR}5~q?mgyZ^*hTOdj1Re$T8-}fD@^&q)%}@I*osHobkSx(!JB2dWI&c48RP2)LHG3=K`J}3B+dGip!N2+*&?|YT)3Mz-CrnV98*X(h8M9V)K5J>?rHFC3(G!R5KW{`{wws!z9+?<Jn)0n>U$$J&(bEJ(yR2Jz`?c{CYBQ|9YGq`t|aJx8Ufn?h)X>qWneSA1HrO_zUG96#hc_i^6}H{*Re{bQigCtm(jkBq*C%`5hz+k^!k5;xhZqx4K{qzmX^9^9DOZgGcL(<~p0v$g*KM`L?)iF=*AFEqKMW^CJwuX#3@6HKV-47H&`Msq@HVQ5b(^Jrsm+elTfgy3!0;_Owz7jYGG&6jt%*AI??r5C-n@uD}_73G{KA5ngZp4#7e=iZIk&tT?-TVqdsJiE-3Fo&0U(VHnveHFZM0HP=zZb8dl~;AXWh@k>RzaEcniP?d5^0p!y5q0hS!w0@Wh)-qiK`Guu-eeMb<977mxvXbJ@Ir^KCyA)FMZoV$70b?iFOv+rU^s$Dek%4;c)^3x<E%Q?<AwLZm1DWu1{Vp5vHhfM@XMo{vTGF|*`aNgE<GwaW5g++taHLa<qu<H78O-8uSKRAMS=sg@wQI`AXL@A9R>RztaHx&m)PQ&&y6#TznF+r=*Tk3TH-C`3WMJOdC-wrA<e-tKDA~U}_M*&q%tAxhBR9D;dXx6(>N{8Axch!*e$y^rOl9mKm!_Ptm<;M>bLdT%^Qtk-e(DJxX&Rt>Bo;W)nyuM7x2!eCz!Q~E*I@_INe7wH3d4!iqXH#{zjgVT@FfS*Z1q(K3vnITCVU*pTA8d;L#t;!$5!xxF?By=r47t13emS@n0`gvD?z<|Ik;s0YCCgRqVSeAo|d@MJvSc8)=EP(yc`@o`paO857)0W`MBoI-1hF)pV5-~iHjAV(D?fie<P9Zj;;&qsW`(v>vOeT`RtSWfrzZEmMD>DYzh$7hM>z3*j`V|<u841X1k;EpBMnImiErQpN`U8h<qS69T5|gd>Y0!?Kkg+MBHUFG($QHqX)VW=LV3twJ!!GNloaBhTXL~Q}L6<pHxd7gL-AM8}Zhc#wt}aNh9;TWz2FP000L|p2C5VCjIqI{v7CJtRXda=z!5j?dHvHcNs6;p-rM&x@m7{XyV7z=jfck?gc3bg2-M>4c7n<*ePWArjpoA!{f%TEo`}pjSH{4?#1d9o+e~VDf8mIJWSRQXKLrOj<c)y9R<9??43i2sWvW#?MG@L%WjABT*Zkpn*WJu|DG#m&6{)d7~H!Cn!8h??y!1*^|BI*3w#}DrOd7tZZiHwi2o~aMOF}+;lR_*zEf(gs}EMzoL?e+tt+#HEUXbo8YA{X<}{c(?dITV!Bmdv$Dt&R8oNW9x<Bo8rdM0FwP)XE8~{*l`5d&MQo18s#k#Yt?v}b)p8IF}#0<a(-^oH7J3P?4qML9GPB&~nL@?LT^R#$*&zz@s_TlE8W9<e}MiRmxq1u+nfwINTXH!V%mVKY&LYNI<o|}NiDt;zD0cVh|q@gCveWr`P0RXdXSL=>m+R|iADZA}ncN{5qeLTeK%afmqYArf@+y*q7wK|2O#<Xt(JnYpt`mV7{zxWgT0X?92J*>@x7qmRN28jRwyghUaBb5wK5%cRC_O?03-+rJ$+Fm6MXkR|^k+QAL>z>0&ccZq`8$#UQEt?<oT7m2Uz>q3HfpfjUwbHT`*YjSvDp1KO_`IkLQb*gwrJ)XbTJUsY^*OKnqX4WfHL@WIY9C;od0|cfGuz<0>gV3-nAuv@Y57c!S>xLu1MyiFjONU1vqFWfbhawy{38k^u!1F+WI<$<RYh6oH(x8Q`skLi)7+T6Q#3tUF<|V|<X{FZc8U(EfYu9T`I
2024-03-27 12:08:54 +00:00
main()