1
0
Fork 0
mirror of https://github.com/EDeev/pixel_gamble.git synced 2026-10-07 23:49:29 +03:00
pixel_gamble/tests/test_game.py
Egor Deev 4ecb9257c8 Pixel Gamble 2.0: законченная игра на ресурсах Ninja Adventure
- 10 волн, 5 видов врагов со своим поведением и босс в три фазы
- меч, копьё и посох, 11 улучшений между волнами, опыт и уровни
- меню, пауза, настройки, итоги партии и рекорд, русский и английский
- мир 480×270 с масштабированием, интерфейс в 960×540, скорости от dt
- асинхронный цикл: та же игра в браузере через pygbag
- тесты без окна, автоигрок, CI, релизы exe/Linux, GitHub Pages
- README на двух языках; исходная версия — релиз v1.0.0
2026-10-07 12:29:41 +00:00

171 lines
5.3 KiB
Python
Raw Permalink 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.

"""Проверки без окна: SDL_VIDEODRIVER=dummy, автоигрок вместо человека."""
import os
import random
import sys
from pathlib import Path
os.environ.setdefault("SDL_VIDEODRIVER", "dummy")
os.environ.setdefault("SDL_AUDIODRIVER", "dummy")
GAME = Path(__file__).resolve().parent.parent / "game"
sys.path.insert(0, str(GAME))
import pytest # noqa: E402
from pixelgamble import save, upgrades # noqa: E402
from pixelgamble.app import App, PlayScene, ResultsScene # noqa: E402
from pixelgamble.bot import Bot # noqa: E402
from pixelgamble.game import Game # noqa: E402
from pixelgamble.player import Player # noqa: E402
from pixelgamble.waves import WAVES, Spawner # noqa: E402
@pytest.fixture
def app(tmp_path, monkeypatch):
monkeypatch.setenv("PIXEL_GAMBLE_SAVE", str(tmp_path / "save.json"))
return App(Bot(seed=3))
def run(app, frames):
for _ in range(frames):
app.frame(1 / 60)
def test_bot_clears_first_waves(app):
run(app, 60 * 60) # минута игрового времени
game = app.game
assert game.kills >= 10
assert game.wave >= 2
assert game.log and game.log[0][0] == 1
def test_menus_render(app):
app.bot = None
for scene in ("controls", "settings"):
app.to_menu()
app.scene.menu.index = [i for i, _ in app.scene.menu.items].index(scene)
app.scene.handle(_key("return"))
run(app, 3)
app.lang.toggle()
run(app, 3)
def test_boss_goes_through_phases(app):
app.new_game()
game = app.game
game.wave = 10
game.start_wave()
game.player.invulnerable = 10 ** 6 # проверяем босса, а не умение бота уворачиваться
run(app, 60 * 6)
assert game.boss is not None
boss = game.boss
for phase in (2, 3):
boss.state = "move"
boss.hurt(boss.max_hp * 0.4, game.player.pos, 0)
assert boss.phase == phase
run(app, 60 * 3)
boss.state = "move"
boss.hurt(boss.max_hp * 2, game.player.pos, 0)
game.on_enemy_hit(boss)
run(app, 2)
assert game.boss is None and game.result == "victory"
run(app, 60 * 6)
assert isinstance(app.scene, ResultsScene)
assert app.data["best"] == game.score > 2000
def test_death_shows_results(app):
app.new_game()
game = app.game
game.player.invulnerable = 0
game.damage_player(game.player.max_hp, game.player.pos)
assert game.result == "dead"
run(app, 60 * 6)
assert isinstance(app.scene, ResultsScene)
def test_upgrades_apply(app):
app.new_game()
player, taken = app.game.player, {}
for key in upgrades.POOL:
upgrades.apply(key, player, taken)
assert player.weapons == ["sword", "lance", "wand"]
assert player.max_hp == 16 and player.hp == player.max_hp
assert player.multishot and player.damage_mult > 1
offer = upgrades.offer(player, {}, 1, random.Random(1))
assert len(offer) == 3 and len(set(offer)) == 3
def test_every_wave_spawns_its_plan():
for number, plan in enumerate(WAVES, start=1):
spawner = Spawner(number, random.Random(number))
assert spawner.total == sum(plan.values())
spawned = []
points = [pygame_vector(500, 500), pygame_vector(40, 40)]
while not spawner.done():
spawned += spawner.update(1.0, 0, pygame_vector(0, 0), points)
assert sorted(k for k, _ in spawned) == sorted(k for k, c in plan.items() for _ in range(c))
def test_save_roundtrip(tmp_path, monkeypatch):
monkeypatch.setenv("PIXEL_GAMBLE_SAVE", str(tmp_path / "s.json"))
data = save.load()
assert data["best"] == 0
data.update(best=1234, lang="en")
save.store(data)
assert save.load()["best"] == 1234 and save.load()["lang"] == "en"
(tmp_path / "s.json").write_text("{broken", encoding="utf-8")
assert save.load()["best"] == 0
def test_player_dies_once(app):
player = Player((100, 100))
assert player.hurt(5, (90, 100))
assert not player.hurt(5, (90, 100)) # неуязвимость после удара
player.invulnerable = 0
player.hurt(100, (90, 100))
assert player.dead and player.hp == 0
def test_rock_breaks(app):
app.new_game()
game = app.game
rock = next(p for p in game.world.props if p.breakable)
before = len(game.obstacles)
game.break_rock(rock)
assert len(game.obstacles) == before - 1
def pygame_vector(x, y):
import pygame
return pygame.Vector2(x, y)
def _key(name):
import pygame
return pygame.event.Event(pygame.KEYDOWN, key=pygame.key.key_code(name), mod=0, unicode="")
def test_play_scene_is_active(app):
run(app, 2)
assert isinstance(app.scene, PlayScene) and isinstance(app.game, Game)
def test_entity_never_tunnels_through_walls(app):
app.new_game()
game = app.game
player = game.player
wall = game.walls[2] # левая стена
player.pos.update(wall.right - 2, 300) # стоим чуть внутри стены
player.hitbox.center = (round(player.pos.x), round(player.pos.y))
for step in ((3, 0), (-3, 0), (0, 3), (3, 3)):
player.move(pygame_vector(*step), game.obstacles)
assert player.hitbox.left >= wall.right - 1
def test_slow_movement_accumulates(app):
app.new_game()
player = app.game.player
start = player.pos.x
for _ in range(60):
player.move(pygame_vector(0.3, 0), [])
assert player.pos.x - start > 15