Compare commits

...

2 Commits

Author SHA1 Message Date
512bd42572 moveimg: add game title and remove pillow dependency 2024-03-30 23:28:23 +03:00
1ddc649165 add coins and flip hero 2024-03-30 22:01:00 +03:00

View File

@ -10,9 +10,7 @@
Картинки берутся из папки assets, если их нет то автоматически скачиваются из репозитория. Картинки берутся из папки assets, если их нет то автоматически скачиваются из репозитория.
Если скачать не получилось то будут нарисованы заглушки. Если скачать не получилось то будут нарисованы заглушки.
Зависимости: pygame pillow Зависимости: pygame
TODO: отбработка выхода из лабиринта
""" """
import os import os
@ -21,12 +19,10 @@ import tempfile
import urllib.request import urllib.request
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from contextlib import contextmanager from contextlib import contextmanager
from random import choice, randrange from random import choice, randrange, sample
from typing import NamedTuple, Optional from typing import NamedTuple, Optional
import PIL
import pygame import pygame
from PIL import ImageDraw
def download_asset(asset, path): def download_asset(asset, path):
@ -47,13 +43,20 @@ def download_asset(asset, path):
def make_stub_image(path, name): def make_stub_image(path, name):
"""Создание пустой картинки, на случай если скачать не получилось""" """Создание пустой картинки, на случай если скачать не получилось"""
img = PIL.Image.new("RGBA", (200, 200)) img = pygame.surface.Surface((200, 200), flags=pygame.SRCALPHA)
draw = ImageDraw.Draw(img) img.fill((255, 255, 255, 0))
draw.rectangle([(50, 50), (150, 150)], outline="black", width=2) pygame.draw.line(img, "#ff000065", (5, 5), (195, 195), 2)
draw.line((50, 50, 150, 150), fill="red", width=2) pygame.draw.line(img, "#ff000065", (195, 5), (5, 195), 2)
draw.line((50, 150, 150, 50), fill="red", width=2)
draw.text((50, 170), name, fill="blue") rect = pygame.Rect(5, 5, 190, 190)
img.save(path) 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 @contextmanager
@ -131,16 +134,6 @@ class Coords(NamedTuple):
return cls(0, 0) return cls(0, 0)
def resize_img(assets: dict, name: str, sz: Coords):
"""
Изменение размера картинки и сохранение в файл
"""
img = PIL.Image.open(assets[name])
if img.size != sz:
img = img.resize(sz)
img.save(assets[name])
def choose_plural(amount, declensions): def choose_plural(amount, declensions):
"""Возвращает количество объектов в виде строки """Возвращает количество объектов в виде строки
например 5 копеек, 1 копейка и т.д. например 5 копеек, 1 копейка и т.д.
@ -283,8 +276,13 @@ class Hero(GameObject):
super().__init__(coords, parent, assets) super().__init__(coords, parent, assets)
self._surface = pygame.image.load(self.assets["ghost.png"]) self._surface = pygame.image.load(self.assets["ghost.png"])
self.rect = self.surface.get_rect() 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.rect.topleft = coords
self.active = True self.active = True
self.looking_right = False
# картинка изначально влево, а надо бы начинать со взгляда вправо
self.flip()
def draw(self): def draw(self):
self.parent.surface.blit(self.surface, self.rect) self.parent.surface.blit(self.surface, self.rect)
@ -320,36 +318,57 @@ class Hero(GameObject):
has_collision = self._check_collision(coords) has_collision = self._check_collision(coords)
super().set_coords(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): def handle_event(self, event: pygame.event.Event):
if not self.active: if not self.active:
return return
up, down = Coords(0, -1), Coords(0, 1) wide, short = 30, 5
left, right = Coords(-1, 0), Coords(1, 0)
da, db = 30, 5
if event.type == pygame.KEYDOWN: if event.type == pygame.KEYDOWN:
match event.key: match event.key:
case pygame.K_UP: case pygame.K_UP:
self.coords += up * da self.move_up(wide)
case pygame.K_DOWN: case pygame.K_DOWN:
self.coords += down * da self.move_down(wide)
case pygame.K_LEFT: case pygame.K_LEFT:
self.coords += left * da self.move_left(wide)
case pygame.K_RIGHT: case pygame.K_RIGHT:
self.coords += right * da self.move_right(wide)
case pygame.K_w: case pygame.K_w:
self.coords += up * db self.move_up(short)
case pygame.K_s: case pygame.K_s:
self.coords += down * db self.move_down(short)
case pygame.K_a: case pygame.K_a:
self.coords += left * db self.move_left(short)
case pygame.K_d: case pygame.K_d:
self.coords += right * db self.move_right(short)
class WallBlock(GameObject): class WallBlock(GameObject):
"""объект елемента стены""" """объект элемента стены"""
def __init__( def __init__(
self, self,
@ -361,6 +380,9 @@ class WallBlock(GameObject):
self._surface = pygame.image.load(self.assets["brick.png"]) self._surface = pygame.image.load(self.assets["brick.png"])
self.rect = self.surface.get_rect() self.rect = self.surface.get_rect()
self.rect.topleft = coords self.rect.topleft = coords
# уменьшаем размер монетки
sf = Coords(1, 1)
self._surface, self.rect = self.scene.scale_box(self.surface, self.rect, sf)
def draw(self): def draw(self):
self.parent.surface.blit(self.surface, self.rect) self.parent.surface.blit(self.surface, self.rect)
@ -396,6 +418,103 @@ class Walls(GameObject):
return False 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): class EndLevel(GameObject):
def __init__(self, scene: GameObject): def __init__(self, scene: GameObject):
super().__init__(Coords.zero(), scene, scene.assets) super().__init__(Coords.zero(), scene, scene.assets)
@ -417,10 +536,19 @@ class EndLevel(GameObject):
self.hint_rect.center = self.parent.rect.center self.hint_rect.center = self.parent.rect.center
self.hint_rect = self.hint_rect.move(Coords(0, 300)) 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): def draw(self):
if not self.active: if not self.active:
return return
self.parent.surface.blit(self.image, self.rect) 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.surface, self.rect)
self.parent.surface.blit(self.hint, self.hint_rect) self.parent.surface.blit(self.hint, self.hint_rect)
@ -439,34 +567,36 @@ class Scene(GameObject):
# кнопки для выхода из игры # кнопки для выхода из игры
exit_keys = (pygame.K_ESCAPE, pygame.K_q) exit_keys = (pygame.K_ESCAPE, pygame.K_q)
def __init__(self, assets: dict, screen_sz: Coords, maze_sz: Coords): def __init__(
self, assets: dict, screen_sz: Coords, maze_sz: Coords, coins_count: int
):
super().__init__(Coords.zero(), None, assets) super().__init__(Coords.zero(), None, assets)
self.maze = maze_gen(*maze_sz) self.maze = maze_gen(*maze_sz)
maze_sz = get_maze_sz(self.maze) maze_sz = get_maze_sz(self.maze)
box_sz = screen_sz // get_maze_sz(self.maze) box_sz = screen_sz // get_maze_sz(self.maze)
self.box_sz = box_sz self.box_sz = box_sz
resize_img(self.assets, "brick.png", 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_sz = Coords(*map(int, box_sz * 0.8))
resize_img(self.assets, "ghost.png", hero_sz)
hero_y_offset = (box_sz.y - hero_sz.y) // 2 + box_sz.y hero_y_offset = (box_sz.y - hero_sz.y) // 2 + box_sz.y
self._surface = pygame.display.set_mode(screen_sz)
self.rect = self._surface.get_rect()
self.hero = Hero(Coords(0, hero_y_offset), self) self.hero = Hero(Coords(0, hero_y_offset), self)
resize_img(assets, "bg1k.png", screen_sz)
self.background = pygame.image.load(self.assets["bg1k.png"])
self.done = False self.done = False
self.level_completed = False self.level_completed = False
self.maze = maze_gen(6, 6) self.maze = maze_gen(6, 6)
self.walls = Walls(self, self.maze, box_sz) self.walls = Walls(self, self.maze, box_sz)
self.coins = Coins(self, self.maze, box_sz, coins_count)
self.end = EndLevel(self) self.end = EndLevel(self)
self.end.active = False self.end.active = False
self.want_new_level = False self.want_new_level = False
self.exit_rect = self.get_exit_rect() self.exit_rect = self.get_exit_rect()
# для тестирования экрана конца уровня # #для тестирования экрана конца уровня
# self.hero.coords = Coords(*self.exit_rect.topleft) + Coords( # self.hero.coords = Coords(*self.exit_rect.topleft) + Coords(
# -self.box_sz.x // 2, 5 # -self.box_sz.x // 2, 5
# ) # )
@ -491,13 +621,22 @@ class Scene(GameObject):
def draw(self): def draw(self):
if self.done: if self.done:
return return
self.surface.fill("white")
self.surface.blit(self.background, self.coords) self.surface.blit(self.background, self.coords)
if self.level_completed: if self.level_completed:
self.end.draw() self.end.draw()
else: else:
# pygame.draw.rect(self._surface, pygame.Color("#42c53d25"), self.get_exit_rect())
self.hero.draw() self.hero.draw()
self.walls.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): def handle_event(self, event: pygame.event.Event):
if self.done: if self.done:
@ -522,14 +661,14 @@ class Scene(GameObject):
def game(assets): def game(assets):
pygame.init()
screen_sz = Coords(1000, 1000) screen_sz = Coords(1000, 1000)
maze_sz = Coords(6, 6) maze_sz = Coords(6, 6)
pygame.display.set_caption("Движение рисунка на Pygame") coins_count = 10
pygame.display.set_caption("Призрачный лабиринт: сокровища небесного замка")
want_new_level = True want_new_level = True
while want_new_level: while want_new_level:
scene = Scene(assets, screen_sz, maze_sz) scene = Scene(assets, screen_sz, maze_sz, coins_count)
scene.event_loop() scene.event_loop()
want_new_level = scene.want_new_level want_new_level = scene.want_new_level
@ -537,11 +676,13 @@ def game(assets):
def main(): def main():
pygame.init()
assets = [ assets = [
"bg1k.png", "bg1k.png",
"ghost.png", "ghost.png",
"brick.png", "brick.png",
"win.png", "win.png",
"coin.png",
] ]
with get_assets(assets) as assets: with get_assets(assets) as assets:
game(assets) game(assets)