685 lines
23 KiB
Python
685 lines
23 KiB
Python
"""
|
||
3.5 Вставка рисунков. Управление рисунков
|
||
https://stepik.org/lesson/937437/step/2?unit=943412
|
||
и
|
||
https://stepik.org/lesson/937437/step/3?unit=943412
|
||
|
||
Задача:
|
||
Попробуйте сделать управление объектом, который есть не просто прямоугольник, а, например, рисунок.
|
||
и
|
||
Сделайте программу, чтобы при движении влево было одно изображение, а при движении вправо другое изображение героя.
|
||
|
||
Решение:
|
||
Игра "Призрачный лабиринт: сокровища небесного замка".
|
||
Призрак в лабиринте, управление стрелками и WASD, выход Esc или Q.
|
||
Чтобы пройти уровень, нужно дойти до выхода, желательно собрав все монетки.
|
||
После прохождения уровня можно начать новый или выйти из игры.
|
||
|
||
Картинки берутся из папки assets, если их нет то автоматически скачиваются из репозитория.
|
||
Если скачать не получилось то будут нарисованы заглушки.
|
||
|
||
Зависимости: pygame
|
||
"""
|
||
|
||
import os
|
||
import shutil
|
||
import tempfile
|
||
import urllib.request
|
||
from abc import ABC, abstractmethod
|
||
from contextlib import contextmanager
|
||
from random import choice, randrange, sample
|
||
from typing import NamedTuple, Optional
|
||
|
||
import pygame
|
||
|
||
|
||
def download_asset(asset, path):
|
||
"""
|
||
Загрузка картинок из репозитория
|
||
"""
|
||
prefix = "https://gitea.b4tman.ru/temp/py_stepik/raw/branch/master/assets/"
|
||
print("Качаю картинку", asset, end=" ... ", flush=True)
|
||
try:
|
||
urllib.request.urlretrieve(prefix + asset, path)
|
||
except Exception:
|
||
print("не смог :(")
|
||
return False
|
||
print("скачал!")
|
||
return True
|
||
|
||
|
||
def make_stub_image(path, name):
|
||
"""Создание пустой картинки, на случай если скачать не получилось"""
|
||
|
||
img = pygame.surface.Surface((200, 200), flags=pygame.SRCALPHA)
|
||
img.fill((255, 255, 255, 0))
|
||
pygame.draw.line(img, "#ff000065", (5, 5), (195, 195), 2)
|
||
pygame.draw.line(img, "#ff000065", (195, 5), (5, 195), 2)
|
||
|
||
rect = pygame.Rect(5, 5, 190, 190)
|
||
pygame.draw.rect(img, "black", rect, 3)
|
||
|
||
font = pygame.font.SysFont("Arial", 44)
|
||
text1 = font.render(name, True, "blue")
|
||
text1_rect = text1.get_rect()
|
||
text1_rect.center = img.get_rect().center
|
||
img.blit(text1, text1_rect)
|
||
pygame.image.save(img, path)
|
||
|
||
|
||
@contextmanager
|
||
def get_assets(names):
|
||
"""Получение соответствия с расположением файлов картинок
|
||
|
||
Размер картинок нужно менять поэтому они всегда сохраняются во временные файлы.
|
||
"""
|
||
|
||
assets_dir = "assets"
|
||
files = {}
|
||
# поиск файлов (загрузка если их нет) и создание временных
|
||
for asset in names:
|
||
_, ext = os.path.splitext(asset)
|
||
temppath = tempfile.mktemp(suffix=ext)
|
||
filepath = os.path.join(assets_dir, asset)
|
||
if os.path.isfile(filepath):
|
||
shutil.copyfile(filepath, temppath)
|
||
else:
|
||
if not download_asset(asset, temppath):
|
||
make_stub_image(temppath, asset)
|
||
files[asset] = temppath
|
||
|
||
# передача управления
|
||
yield files
|
||
# очистка
|
||
for _, filename in files.items():
|
||
try:
|
||
os.remove(filename)
|
||
except FileNotFoundError:
|
||
pass
|
||
|
||
|
||
class Coords(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 __floordiv__(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 __mul__(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 transform(self, ref: "Coords"):
|
||
return self * ref
|
||
|
||
@classmethod
|
||
def zero(cls):
|
||
return cls(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 get_maze_sz(maze: list[list[int]]) -> Coords:
|
||
return Coords(len(maze[0]), len(maze))
|
||
|
||
|
||
class GameObject(ABC):
|
||
"""обобщение игрового элемента"""
|
||
|
||
coords = property(
|
||
lambda self: self.get_coords(), lambda self, c: self.set_coords(c)
|
||
)
|
||
|
||
def __init__(
|
||
self,
|
||
coords: Coords,
|
||
parent: Optional["GameObject"] = None,
|
||
assets: dict | None = None,
|
||
):
|
||
self.parent = parent
|
||
self.rect = pygame.Rect(coords, coords)
|
||
self.assets = assets or (parent.assets if parent else None)
|
||
self._surface = None
|
||
|
||
@property
|
||
def surface(self) -> pygame.Surface | None:
|
||
return self._surface or (self.parent.surface if self.parent else None)
|
||
|
||
@property
|
||
def scene(self):
|
||
return self.parent.scene if self.parent else self
|
||
|
||
def get_coords(self) -> Coords:
|
||
return Coords(*self.rect.topleft)
|
||
|
||
def set_coords(self, coords: Coords):
|
||
new_rect = self.rect.copy()
|
||
new_rect.topleft = coords
|
||
if self.parent:
|
||
if self.parent.rect:
|
||
if not self.parent.rect.contains(new_rect):
|
||
return
|
||
self.rect = new_rect
|
||
|
||
@abstractmethod
|
||
def draw(self):
|
||
pass
|
||
|
||
def handle_event(self, event: pygame.event.Event):
|
||
pass
|
||
|
||
|
||
class Hero(GameObject):
|
||
"""объект главного героя"""
|
||
|
||
def __init__(
|
||
self,
|
||
coords: Coords,
|
||
parent: GameObject,
|
||
assets: dict | None = None,
|
||
):
|
||
super().__init__(coords, parent, assets)
|
||
self._surface = pygame.image.load(self.assets["ghost.png"])
|
||
self.rect = self.surface.get_rect()
|
||
sf = Coords(0.8, 0.8)
|
||
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
|
||
self.rect.topleft = coords
|
||
self.active = True
|
||
self.looking_right = False
|
||
# картинка изначально влево, а надо бы начинать со взгляда вправо
|
||
self.flip()
|
||
|
||
def draw(self):
|
||
self.parent.surface.blit(self.surface, self.rect)
|
||
|
||
def _check_collision(self, coords):
|
||
"""Проверка пересечения со стенами"""
|
||
new_rect = self.rect.copy()
|
||
new_rect.topleft = coords
|
||
return self.scene.walls.check_collision(new_rect)
|
||
|
||
def _reduce_step(self, coords):
|
||
"""Уменьшение шага движения, с целью подойти вплотную к стене"""
|
||
delta = coords - self.coords
|
||
dx, dy = 0, 0
|
||
if abs(delta.x) > 1:
|
||
dx = 1 * (delta.x < 0 or -1)
|
||
if abs(delta.y) > 1:
|
||
dy = 1 * (delta.y < 0 or -1)
|
||
return coords + Coords(dx, dy)
|
||
|
||
def set_coords(self, coords: Coords):
|
||
# проверка колизии
|
||
has_collision = self._check_collision(coords)
|
||
if not has_collision:
|
||
return super().set_coords(coords)
|
||
|
||
# уменьшение шага
|
||
while has_collision and coords != self.coords:
|
||
coords_new = self._reduce_step(coords)
|
||
if coords_new == coords:
|
||
return # не могу уменьшить шаг
|
||
coords = coords_new
|
||
has_collision = self._check_collision(coords)
|
||
super().set_coords(coords)
|
||
|
||
def flip(self):
|
||
self.looking_right = not self.looking_right
|
||
self._surface = pygame.transform.flip(self.surface, True, False)
|
||
|
||
def move(self, direction: Coords, step: int = 1):
|
||
if direction.x != 0:
|
||
going_right = direction.x > 0
|
||
if self.looking_right != going_right:
|
||
self.flip()
|
||
self.coords += direction * step
|
||
self.scene.coins.collect(self.rect)
|
||
|
||
def move_left(self, step: int = 1):
|
||
self.move(Coords(-1, 0), step)
|
||
|
||
def move_right(self, step: int = 1):
|
||
self.move(Coords(1, 0), step)
|
||
|
||
def move_up(self, step: int = 1):
|
||
self.move(Coords(0, -1), step)
|
||
|
||
def move_down(self, step: int = 1):
|
||
self.move(Coords(0, 1), step)
|
||
|
||
def handle_event(self, event: pygame.event.Event):
|
||
if not self.active:
|
||
return
|
||
|
||
wide, short = 30, 5
|
||
if event.type == pygame.KEYDOWN:
|
||
match event.key:
|
||
case pygame.K_UP:
|
||
self.move_up(wide)
|
||
case pygame.K_DOWN:
|
||
self.move_down(wide)
|
||
case pygame.K_LEFT:
|
||
self.move_left(wide)
|
||
case pygame.K_RIGHT:
|
||
self.move_right(wide)
|
||
case pygame.K_w:
|
||
self.move_up(short)
|
||
case pygame.K_s:
|
||
self.move_down(short)
|
||
case pygame.K_a:
|
||
self.move_left(short)
|
||
case pygame.K_d:
|
||
self.move_right(short)
|
||
|
||
|
||
class WallBlock(GameObject):
|
||
"""объект элемента стены"""
|
||
|
||
def __init__(
|
||
self,
|
||
coords: Coords,
|
||
parent: GameObject,
|
||
assets: dict | None = None,
|
||
):
|
||
super().__init__(coords, parent, assets)
|
||
self._surface = pygame.image.load(self.assets["brick.png"])
|
||
self.rect = self.surface.get_rect()
|
||
self.rect.topleft = coords
|
||
# уменьшаем размер монетки
|
||
sf = Coords(1, 1)
|
||
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
|
||
|
||
def draw(self):
|
||
self.parent.surface.blit(self.surface, self.rect)
|
||
|
||
|
||
class Walls(GameObject):
|
||
"""объект стен"""
|
||
|
||
def __init__(
|
||
self,
|
||
parent: GameObject,
|
||
maze: list[list[int]],
|
||
box_sz: Coords,
|
||
assets: dict | None = None,
|
||
):
|
||
super().__init__(Coords.zero(), parent, assets)
|
||
self.box_sz = box_sz
|
||
self.blocks = [
|
||
WallBlock(Coords(j, i).transform(box_sz), self, self.assets)
|
||
for i, row in enumerate(maze)
|
||
for j, item in enumerate(row)
|
||
if item > 0
|
||
]
|
||
|
||
def draw(self):
|
||
for block in self.blocks:
|
||
block.draw()
|
||
|
||
def check_collision(self, rect: pygame.Rect) -> bool:
|
||
for block in self.blocks:
|
||
if block.rect.colliderect(rect):
|
||
return True
|
||
return False
|
||
|
||
|
||
class Coin(GameObject):
|
||
"""объект монетки"""
|
||
|
||
def __init__(
|
||
self,
|
||
coords: Coords,
|
||
parent: GameObject,
|
||
assets: dict | None = None,
|
||
):
|
||
super().__init__(coords, parent, assets)
|
||
self._surface = pygame.image.load(self.assets["coin.png"])
|
||
self.rect = self.surface.get_rect()
|
||
self.rect.topleft = coords
|
||
# уменьшаем размер монетки
|
||
sf = Coords(0.7, 0.7)
|
||
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
|
||
|
||
def draw(self):
|
||
self.parent.surface.blit(self.surface, self.rect)
|
||
|
||
|
||
class Coins(GameObject):
|
||
"""объект коллекции монеток"""
|
||
|
||
def __init__(
|
||
self,
|
||
parent: GameObject,
|
||
maze: list[list[int]],
|
||
box_sz: Coords,
|
||
count: int,
|
||
assets: dict | None = None,
|
||
):
|
||
super().__init__(Coords.zero(), parent, assets)
|
||
self.box_sz = box_sz
|
||
self._capacity = count
|
||
|
||
free_points = []
|
||
excluded = Coords(0, 1), get_maze_sz(maze) - Coords(1, 2)
|
||
for i, row in enumerate(maze):
|
||
for j, item in enumerate(row):
|
||
p = Coords(j, i)
|
||
if item < 1 and p not in excluded:
|
||
free_points.append(p)
|
||
continue
|
||
|
||
coin_points = sample(free_points, min(count, len(free_points)))
|
||
self.coins = [
|
||
Coin(point.transform(box_sz), self, self.assets) for point in coin_points
|
||
]
|
||
self.collected_coins = []
|
||
|
||
# Надпись, если все монетки собраны
|
||
font = pygame.font.SysFont("Arial", 30)
|
||
text = "Все монетки собраны!"
|
||
self.done_txt = font.render(text, 1, "#050366e3")
|
||
self.done_txt_rect = self.done_txt.get_rect()
|
||
self.done_txt_rect.topleft = Coords(10, 10)
|
||
|
||
@property
|
||
def capacity(self):
|
||
return self._capacity
|
||
|
||
@property
|
||
def coins_left(self):
|
||
return len(self.coins)
|
||
|
||
@property
|
||
def coins_collected(self):
|
||
return self.capacity - self.coins_left
|
||
|
||
@property
|
||
def all_collected(self):
|
||
return self.coins_left == 0
|
||
|
||
def draw(self):
|
||
for coin in self.collected_coins:
|
||
coin.draw()
|
||
for coin in self.coins:
|
||
coin.draw()
|
||
if self.all_collected:
|
||
self.parent.surface.blit(self.done_txt, self.done_txt_rect)
|
||
|
||
def add_to_collected(self, coin: Coin):
|
||
last_pos = Coords(10, 10)
|
||
if self.collected_coins:
|
||
last_pos = Coords(*self.collected_coins[-1].rect.topright)
|
||
last_pos -= Coords(coin.rect.width // 2, 0)
|
||
coin.coords = last_pos
|
||
self.collected_coins.append(coin)
|
||
|
||
def collect(self, rect: pygame.Rect):
|
||
mined = [*filter(lambda coin: coin.rect.colliderect(rect), self.coins)]
|
||
for coin in mined:
|
||
self.coins.remove(coin)
|
||
self.add_to_collected(coin)
|
||
|
||
|
||
class EndLevel(GameObject):
|
||
def __init__(self, scene: GameObject):
|
||
super().__init__(Coords.zero(), scene, scene.assets)
|
||
self.image = pygame.image.load(scene.assets["win.png"])
|
||
self.active = False
|
||
|
||
# надпись завершения игры
|
||
font = pygame.font.SysFont("Arial", 70)
|
||
text = "Конец игры!"
|
||
self._surface = font.render(text, 1, "#1b10a8c4")
|
||
self.rect = self._surface.get_rect()
|
||
self.rect.center = self.parent.rect.center
|
||
|
||
# совет по кнопкам
|
||
hint = "Для новой игры нажмите N, для выхода Q"
|
||
font_hint = pygame.font.SysFont("Arial", 27)
|
||
self.hint = font_hint.render(hint, 1, "#24053da4")
|
||
self.hint_rect = self.hint.get_rect()
|
||
self.hint_rect.center = self.parent.rect.center
|
||
self.hint_rect = self.hint_rect.move(Coords(0, 300))
|
||
|
||
# Надпись для хорошего финала
|
||
text = "Все монетки собраны!"
|
||
self.goodtxt = font_hint.render(text, 1, "#96081ba4")
|
||
self.goodtxt_rect = self.goodtxt.get_rect()
|
||
self.goodtxt_rect.center = self.parent.rect.center
|
||
self.goodtxt_rect = self.goodtxt_rect.move(Coords(0, -100))
|
||
|
||
def draw(self):
|
||
if not self.active:
|
||
return
|
||
if self.scene.coins.all_collected:
|
||
self.parent.surface.blit(self.image, self.rect)
|
||
self.parent.surface.blit(self.goodtxt, self.goodtxt_rect)
|
||
self.parent.surface.blit(self.surface, self.rect)
|
||
self.parent.surface.blit(self.hint, self.hint_rect)
|
||
|
||
def handle_event(self, event: pygame.event.Event):
|
||
if not self.active:
|
||
return
|
||
if event.type == pygame.KEYDOWN:
|
||
if event.key == pygame.K_n:
|
||
self.parent.want_new_level = True
|
||
self.parent.done = True
|
||
|
||
|
||
class Scene(GameObject):
|
||
"""основной игровой объект"""
|
||
|
||
# кнопки для выхода из игры
|
||
exit_keys = (pygame.K_ESCAPE, pygame.K_q)
|
||
|
||
def __init__(
|
||
self, assets: dict, screen_sz: Coords, maze_sz: Coords, coins_count: int
|
||
):
|
||
super().__init__(Coords.zero(), None, assets)
|
||
self.maze = maze_gen(*maze_sz)
|
||
maze_sz = get_maze_sz(self.maze)
|
||
|
||
box_sz = screen_sz // get_maze_sz(self.maze)
|
||
self.box_sz = box_sz
|
||
self._surface = pygame.display.set_mode(screen_sz)
|
||
self.surface.fill("white")
|
||
self.rect = self._surface.get_rect()
|
||
self.background = pygame.image.load(self.assets["bg1k.png"])
|
||
self.background = pygame.transform.scale(self.background, self.rect.size)
|
||
|
||
hero_sz = Coords(*map(int, box_sz * 0.8))
|
||
hero_y_offset = (box_sz.y - hero_sz.y) // 2 + box_sz.y
|
||
self.hero = Hero(Coords(0, hero_y_offset), self)
|
||
self.done = False
|
||
self.level_completed = False
|
||
|
||
self.maze = maze_gen(6, 6)
|
||
self.walls = Walls(self, self.maze, box_sz)
|
||
self.coins = Coins(self, self.maze, box_sz, coins_count)
|
||
|
||
self.end = EndLevel(self)
|
||
self.end.active = False
|
||
self.want_new_level = False
|
||
self.exit_rect = self.get_exit_rect()
|
||
# #для тестирования экрана конца уровня
|
||
# self.hero.coords = Coords(*self.exit_rect.topleft) + Coords(
|
||
# -self.box_sz.x // 2, 5
|
||
# )
|
||
|
||
def get_exit_rect(self) -> pygame.Rect:
|
||
# находим клетку в которой будет выход с карты
|
||
maze_sz = get_maze_sz(self.maze)
|
||
coords = (maze_sz - Coords(1, 2)) * self.box_sz
|
||
rect = pygame.Rect(coords, coords)
|
||
rect.width, rect.height = self.box_sz
|
||
# уменьшаем размер клетки и перемещаем её вправо
|
||
rect.width = self.box_sz.x // 4
|
||
rect = rect.move(Coords(self.box_sz.x // 2, 0))
|
||
return rect
|
||
|
||
def check_level_completed(self):
|
||
self.level_completed = self.exit_rect.colliderect(self.hero.rect)
|
||
if self.level_completed:
|
||
self.end.active = True
|
||
self.hero.active = False
|
||
|
||
def draw(self):
|
||
if self.done:
|
||
return
|
||
self.surface.fill("white")
|
||
self.surface.blit(self.background, self.coords)
|
||
if self.level_completed:
|
||
self.end.draw()
|
||
else:
|
||
self.hero.draw()
|
||
self.walls.draw()
|
||
self.coins.draw()
|
||
|
||
def scale_box(
|
||
self, surface: pygame.Surface, rect: pygame.Rect, scale_factor: Coords
|
||
):
|
||
rect.size = self.box_sz
|
||
rect.scale_by_ip(*scale_factor)
|
||
surface = pygame.transform.scale(surface, rect.size)
|
||
return surface, rect
|
||
|
||
def handle_event(self, event: pygame.event.Event):
|
||
if self.done:
|
||
return
|
||
if (
|
||
event.type == pygame.QUIT
|
||
or event.type == pygame.KEYDOWN
|
||
and event.key in self.exit_keys
|
||
):
|
||
self.done = True
|
||
if not self.done:
|
||
self.hero.handle_event(event)
|
||
self.check_level_completed()
|
||
self.end.handle_event(event)
|
||
|
||
def event_loop(self):
|
||
while not self.done:
|
||
event = pygame.event.wait()
|
||
self.handle_event(event)
|
||
self.draw()
|
||
pygame.display.flip()
|
||
|
||
|
||
def game(assets):
|
||
screen_sz = Coords(1000, 1000)
|
||
maze_sz = Coords(6, 6)
|
||
coins_count = 10
|
||
pygame.display.set_caption("Призрачный лабиринт: сокровища небесного замка")
|
||
|
||
want_new_level = True
|
||
while want_new_level:
|
||
scene = Scene(assets, screen_sz, maze_sz, coins_count)
|
||
scene.event_loop()
|
||
want_new_level = scene.want_new_level
|
||
|
||
pygame.quit()
|
||
|
||
|
||
def main():
|
||
pygame.init()
|
||
assets = [
|
||
"bg1k.png",
|
||
"ghost.png",
|
||
"brick.png",
|
||
"win.png",
|
||
"coin.png",
|
||
]
|
||
with get_assets(assets) as assets:
|
||
game(assets)
|
||
|
||
|
||
main()
|