mirror of
https://github.com/EDeev/pixel_gamble.git
synced 2026-10-07 23:49:29 +03:00
- 10 волн, 5 видов врагов со своим поведением и босс в три фазы - меч, копьё и посох, 11 улучшений между волнами, опыт и уровни - меню, пауза, настройки, итоги партии и рекорд, русский и английский - мир 480×270 с масштабированием, интерфейс в 960×540, скорости от dt - асинхронный цикл: та же игра в браузере через pygbag - тесты без окна, автоигрок, CI, релизы exe/Linux, GitHub Pages - README на двух языках; исходная версия — релиз v1.0.0
496 lines
22 KiB
Python
496 lines
22 KiB
Python
"""Партия: волны, бой, улучшения между волнами, камера и интерфейс."""
|
||
|
||
import math
|
||
import random
|
||
|
||
import pygame
|
||
|
||
from . import assets, enemies, ui, upgrades
|
||
from .boss import Boss
|
||
from .effects import Effect, FloatText, Heart, Particle, Projectile, Shake
|
||
from .entity import Vector
|
||
from .player import WEAPONS, Player
|
||
from .settings import (
|
||
BLUE,
|
||
GOLD,
|
||
GREEN,
|
||
GREY,
|
||
HEART_DROP_CHANCE,
|
||
HEIGHT,
|
||
RED,
|
||
SCALE,
|
||
SCREEN_H,
|
||
SCREEN_W,
|
||
WAVE_BREAK,
|
||
WAVES_TOTAL,
|
||
WHITE,
|
||
WIDTH,
|
||
XP_BASE,
|
||
XP_STEP,
|
||
)
|
||
from .waves import Spawner, hp_multiplier
|
||
from .world import World
|
||
|
||
# поворот спрайта взмаха (нарисован для удара вниз) под направление удара
|
||
ARC_ANGLES = {(0, 1): 0, (0, -1): 180, (1, 0): 90, (-1, 0): -90}
|
||
|
||
|
||
def xp_needed(level):
|
||
return XP_BASE + XP_STEP * (level - 1)
|
||
|
||
|
||
class Game:
|
||
def __init__(self, app, start_wave=1, rng=None):
|
||
self.app, self.audio, self.lang = app, app.audio, app.lang
|
||
self.rng = rng or random.Random()
|
||
self.world = World()
|
||
self.walls = self.world.walls
|
||
self.obstacles = self.world.obstacles()
|
||
self.player = Player(self.world.center)
|
||
self.enemies = pygame.sprite.Group()
|
||
self.shots = pygame.sprite.Group() # снаряды игрока
|
||
self.enemy_shots = pygame.sprite.Group()
|
||
self.effects = pygame.sprite.Group()
|
||
self.hearts = pygame.sprite.Group()
|
||
self.texts = pygame.sprite.Group()
|
||
self.shake = Shake()
|
||
self.camera = Vector(self.player.pos) - Vector(WIDTH / 2, HEIGHT / 2)
|
||
self.clamp_camera()
|
||
self.drawn_offset = (0, 0)
|
||
|
||
self.wave = start_wave
|
||
self.taken = {}
|
||
self.kills = 0
|
||
self.score = 0
|
||
self.log = [] # (волна, секунд на волну, здоровье в конце) — для проверок баланса
|
||
self.time = 0.0
|
||
self.wave_time = 0.0
|
||
self.boss = None
|
||
self.result = None # "dead" / "victory" — когда партия закончилась
|
||
self.end_timer = 0.0
|
||
self.offer = []
|
||
self.card = 0
|
||
self.paused = False
|
||
self.pause_menu = ui.Menu([("resume", self.lang("resume")), ("to_menu", self.lang("to_menu"))],
|
||
(SCREEN_W // 2, 300))
|
||
|
||
self.smoke = assets.strip("fx/smoke.png", 32)
|
||
self.arc = assets.strip("fx/arc.png", 38)
|
||
self.ball = assets.strip("fx/energy_ball.png", 16)
|
||
self.shuriken = assets.strip("fx/shuriken.png", 16)
|
||
self.rock_bits = assets.strip("fx/rock.png", 16)
|
||
self.shadow = assets.image("chars/shadow.png").copy()
|
||
self.shadow.set_alpha(120)
|
||
self.big_shadow = pygame.transform.scale_by(self.shadow, 2)
|
||
self.big_shadow.set_alpha(120)
|
||
self.start_wave()
|
||
|
||
# ------------------------------------------------------------------ волны
|
||
def start_wave(self):
|
||
self.state, self.timer = "intro", 1.8
|
||
self.spawner = Spawner(self.wave, self.rng)
|
||
self.wave_time = 0.0
|
||
self.audio.music("boss" if self.wave == WAVES_TOTAL else "battle")
|
||
self.audio.play("boss_appear" if self.wave == WAVES_TOTAL else "wave_start")
|
||
|
||
def finish_wave(self):
|
||
self.log.append((self.wave, round(self.wave_time, 1), self.player.hp))
|
||
bonus = 100 * self.wave + max(0, int(90 - self.wave_time)) * 3
|
||
self.score += bonus
|
||
self.texts.add(FloatText(f"+{bonus}", self.player.pos + (0, -30), GOLD, 20, 1.4))
|
||
if self.wave == WAVES_TOTAL:
|
||
self.end("victory")
|
||
return
|
||
self.state, self.timer = "clear", WAVE_BREAK
|
||
self.audio.play("wave_clear")
|
||
|
||
def open_upgrades(self):
|
||
self.offer = upgrades.offer(self.player, self.taken, self.wave + 1, self.rng)
|
||
self.card = 0
|
||
self.state = "upgrade" if self.offer else "next"
|
||
|
||
def choose(self, index):
|
||
if 0 <= index < len(self.offer):
|
||
upgrades.apply(self.offer[index], self.player, self.taken)
|
||
self.audio.play("power_up")
|
||
self.wave += 1
|
||
self.start_wave()
|
||
|
||
def end(self, result):
|
||
if self.result:
|
||
return
|
||
self.result = result
|
||
self.end_timer = 2.0
|
||
if result == "victory":
|
||
self.score += 2000 + self.player.hp * 50
|
||
self.audio.play("victory")
|
||
else:
|
||
self.audio.play("game_over")
|
||
self.effects.add(Effect(self.smoke, self.player.pos + (0, -6), 12))
|
||
self.audio.fadeout(1500)
|
||
|
||
# ------------------------------------------------------------ для врагов
|
||
def damage_player(self, amount, source_pos):
|
||
if self.result:
|
||
return False
|
||
if self.player.hurt(amount, source_pos):
|
||
self.audio.play("player_hurt")
|
||
self.shake.add(3)
|
||
if self.player.dead:
|
||
self.end("dead")
|
||
return True
|
||
return False
|
||
|
||
def enemy_shot(self, pos, velocity, damage):
|
||
self.enemy_shots.add(Projectile(self.shuriken, pos, velocity, damage, "enemy", life=2.6, radius=3, spin=True))
|
||
self.audio.play("enemy_shoot")
|
||
|
||
def summon(self, kinds, near):
|
||
for kind in kinds:
|
||
angle = self.rng.random() * math.tau
|
||
pos = Vector(near) + Vector(math.cos(angle), math.sin(angle)) * 40
|
||
pos.x = min(max(pos.x, self.world.inner.left + 8), self.world.inner.right - 8)
|
||
pos.y = min(max(pos.y, self.world.inner.top + 8), self.world.inner.bottom - 8)
|
||
self.add_enemy(kind, pos)
|
||
|
||
def add_enemy(self, kind, pos):
|
||
if kind == "boss":
|
||
enemy = Boss(pos)
|
||
self.boss = enemy
|
||
else:
|
||
enemy = enemies.make(kind, pos, hp_multiplier(self.wave))
|
||
self.enemies.add(enemy)
|
||
self.effects.add(Effect(self.smoke, (pos[0], pos[1] - 4), 10))
|
||
|
||
# ------------------------------------------------------------- события
|
||
def handle(self, event):
|
||
if self.paused:
|
||
choice = self.pause_menu.handle(event, self.audio)
|
||
if choice == "resume" or (event.type == pygame.KEYDOWN and event.key in (pygame.K_ESCAPE, pygame.K_p)):
|
||
self.paused = False
|
||
elif choice == "to_menu":
|
||
self.app.to_menu()
|
||
return
|
||
if event.type == pygame.KEYDOWN:
|
||
if event.key in (pygame.K_ESCAPE, pygame.K_p) and not self.result:
|
||
self.paused = True
|
||
self.pause_menu.index = 0
|
||
self.audio.play("menu_back")
|
||
elif self.state == "upgrade":
|
||
if event.key in (pygame.K_1, pygame.K_2, pygame.K_3, pygame.K_KP1, pygame.K_KP2, pygame.K_KP3):
|
||
self.choose({pygame.K_1: 0, pygame.K_2: 1, pygame.K_3: 2, pygame.K_KP1: 0,
|
||
pygame.K_KP2: 1, pygame.K_KP3: 2}[event.key])
|
||
elif event.key in (pygame.K_LEFT, pygame.K_a):
|
||
self.card = (self.card - 1) % len(self.offer)
|
||
self.audio.play("menu_move")
|
||
elif event.key in (pygame.K_RIGHT, pygame.K_d):
|
||
self.card = (self.card + 1) % len(self.offer)
|
||
self.audio.play("menu_move")
|
||
elif event.key in (pygame.K_RETURN, pygame.K_SPACE, pygame.K_KP_ENTER):
|
||
self.choose(self.card)
|
||
elif event.key in (pygame.K_q, pygame.K_TAB):
|
||
if self.player.switch(1):
|
||
self.audio.play("switch")
|
||
elif event.key in (pygame.K_1, pygame.K_2, pygame.K_3):
|
||
if self.player.switch(index=event.key - pygame.K_1):
|
||
self.audio.play("switch")
|
||
elif event.type == pygame.MOUSEWHEEL and self.state != "upgrade":
|
||
if self.player.switch(-1 if event.y > 0 else 1):
|
||
self.audio.play("switch")
|
||
elif self.state == "upgrade" and event.type == pygame.MOUSEMOTION:
|
||
for i, rect in enumerate(ui.card_rects(len(self.offer))):
|
||
if rect.collidepoint(event.pos):
|
||
self.card = i
|
||
elif self.state == "upgrade" and event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
|
||
for i, rect in enumerate(ui.card_rects(len(self.offer))):
|
||
if rect.collidepoint(event.pos):
|
||
self.choose(i)
|
||
|
||
def read_controls(self):
|
||
"""Движение и атака с клавиатуры/мыши (бот подменяет этот метод)."""
|
||
keys = pygame.key.get_pressed()
|
||
move = Vector((keys[pygame.K_d] or keys[pygame.K_RIGHT]) - (keys[pygame.K_a] or keys[pygame.K_LEFT]),
|
||
(keys[pygame.K_s] or keys[pygame.K_DOWN]) - (keys[pygame.K_w] or keys[pygame.K_UP]))
|
||
mouse = pygame.mouse.get_pressed()[0] and pygame.mouse.get_focused()
|
||
attack = mouse or keys[pygame.K_SPACE] or keys[pygame.K_j]
|
||
target = Vector(pygame.mouse.get_pos()) / SCALE + self.camera if mouse else None
|
||
return move, attack, target
|
||
|
||
# ------------------------------------------------------------- кадр
|
||
def update(self, dt, controls=None):
|
||
if self.paused:
|
||
return
|
||
if self.result:
|
||
self.end_timer -= dt
|
||
dt *= 0.35 # замедление в конце партии
|
||
if self.end_timer <= 0:
|
||
self.app.show_results(self)
|
||
return
|
||
self.time += dt
|
||
self.wave_time += dt
|
||
|
||
move, attack, target = controls or self.read_controls()
|
||
busy = self.state == "upgrade" or self.result
|
||
if busy:
|
||
move, attack = Vector(), False
|
||
self.player.update(dt, move, self.obstacles)
|
||
if attack:
|
||
self.attack(target)
|
||
|
||
if self.state == "intro":
|
||
self.timer -= dt
|
||
if self.timer <= 0:
|
||
self.state = "fight"
|
||
elif self.state == "fight":
|
||
alive = sum(1 for e in self.enemies if not e.boss)
|
||
for kind, pos in self.spawner.update(dt, alive, self.player.pos, self.world.spawns):
|
||
self.add_enemy(kind, pos)
|
||
if self.spawner.done() and not self.enemies and not self.result:
|
||
self.finish_wave()
|
||
elif self.state == "clear":
|
||
self.timer -= dt
|
||
if self.timer <= 0:
|
||
self.open_upgrades()
|
||
elif self.state == "next":
|
||
self.wave += 1
|
||
self.start_wave()
|
||
|
||
for enemy in list(self.enemies):
|
||
enemy.update(dt, self)
|
||
self.separate()
|
||
for actor in [self.player, *self.enemies]:
|
||
actor.keep_inside(self.world.inner)
|
||
self.contact_damage()
|
||
self.update_shots(dt)
|
||
self.effects.update(dt)
|
||
self.texts.update(dt)
|
||
for heart in list(self.hearts):
|
||
heart.update(dt, self.player)
|
||
if heart.alive() and heart.rect.colliderect(self.player.hitbox.inflate(6, 10)):
|
||
if self.player.heal(4): # при полном здоровье сердечко остаётся лежать
|
||
self.audio.play("heal")
|
||
heart.kill()
|
||
self.follow(dt)
|
||
|
||
def attack(self, target):
|
||
direction = self.player.try_attack(target)
|
||
if direction is None:
|
||
return
|
||
player = self.player
|
||
weapon = WEAPONS[player.weapon]
|
||
self.audio.play(player.weapon)
|
||
if weapon["kind"] == "bolt":
|
||
spread = (-14, 0, 14) if player.multishot else (0,)
|
||
for angle in spread:
|
||
vel = direction.rotate(angle) * weapon["speed"]
|
||
start = player.pos + Vector(0, -6) + direction * 6
|
||
damage = player.damage() * (1.0 if angle == 0 else 0.6) # боковые заряды слабее
|
||
self.shots.add(Projectile(self.ball, start, vel, damage, "player", weapon["life"],
|
||
weapon["knock"] * player.knock_mult))
|
||
return
|
||
box = player.melee_box(direction)
|
||
self.melee_effect(direction, box, weapon["kind"])
|
||
for enemy in list(self.enemies):
|
||
body = enemy.hitbox.inflate(6, 12).move(0, -5)
|
||
if box.colliderect(body) and enemy.hurt(player.damage(), player.pos, weapon["knock"] * player.knock_mult):
|
||
self.on_enemy_hit(enemy)
|
||
for prop in list(self.world.props):
|
||
if prop.breakable and box.colliderect(prop.hitbox):
|
||
self.break_rock(prop)
|
||
|
||
def melee_effect(self, direction, box, kind):
|
||
if kind == "arc":
|
||
angle = ARC_ANGLES[(round(direction.x), round(direction.y))]
|
||
frames = [pygame.transform.rotate(f, angle) for f in self.arc]
|
||
self.effects.add(Effect(frames, box.center, 34, sort_y=box.centery + 10))
|
||
else:
|
||
image = assets.image("items/lance_hand.png")
|
||
angle = -math.degrees(math.atan2(direction.y, direction.x)) - 90
|
||
spear = pygame.transform.rotate(pygame.transform.flip(image, False, True), angle)
|
||
hand = self.player.pos + Vector(0, -5)
|
||
effect = Effect([spear] * 5, hand + direction * 16, 30, sort_y=self.player.sort_y + 1)
|
||
effect.thrust = (Vector(hand), direction)
|
||
self.effects.add(effect)
|
||
quarter = ARC_ANGLES[(round(direction.x), round(direction.y))]
|
||
tip = [pygame.transform.rotate(f, quarter) for f in self.arc[2:]]
|
||
self.effects.add(Effect(tip, box.center + direction * 6, 40, sort_y=box.centery + 10))
|
||
|
||
def on_enemy_hit(self, enemy):
|
||
self.audio.play("enemy_hit")
|
||
if enemy.hp <= 0:
|
||
self.kill_enemy(enemy)
|
||
|
||
def kill_enemy(self, enemy):
|
||
enemy.kill()
|
||
self.kills += 1
|
||
self.score += enemy.score
|
||
self.effects.add(Effect(self.smoke, enemy.hitbox.center, 14))
|
||
self.audio.play("enemy_die")
|
||
if enemy.boss:
|
||
self.boss = None
|
||
self.shake.add(5)
|
||
for other in list(self.enemies):
|
||
other.kill()
|
||
self.effects.add(Effect(self.smoke, other.hitbox.center, 14))
|
||
return
|
||
levels = self.player.gain_xp(enemy.xp, xp_needed)
|
||
if levels:
|
||
self.player.damage_mult *= 1.02 ** levels
|
||
self.player.heal(3 * levels)
|
||
self.audio.play("level_up")
|
||
self.texts.add(FloatText(self.lang("level_up"), self.player.pos + (0, -24), GOLD, 20, 1.2))
|
||
if self.player.vampire and self.rng.random() < self.player.vampire:
|
||
if self.player.heal(1):
|
||
self.texts.add(FloatText("+", self.player.pos + (0, -18), GREEN))
|
||
chance = 0.5 if enemy.kind == "cyclop" else HEART_DROP_CHANCE
|
||
if self.rng.random() < chance:
|
||
self.hearts.add(Heart(enemy.pos))
|
||
|
||
def break_rock(self, prop):
|
||
prop.kill()
|
||
self.obstacles = self.world.obstacles()
|
||
self.audio.play("rock_break")
|
||
self.score += 5
|
||
for _ in range(6):
|
||
angle = self.rng.random() * math.tau
|
||
self.effects.add(Particle(self.rng.choice(self.rock_bits), prop.hitbox.center,
|
||
Vector(math.cos(angle), math.sin(angle) - 0.8) * self.rng.uniform(30, 70)))
|
||
if self.rng.random() < 0.2:
|
||
self.hearts.add(Heart(prop.hitbox.center))
|
||
|
||
def separate(self):
|
||
"""Враги не слипаются в одну точку."""
|
||
crowd = [e for e in self.enemies if e.active and not e.boss]
|
||
for i, a in enumerate(crowd):
|
||
for b in crowd[i + 1:]:
|
||
delta = a.pos - b.pos
|
||
dist2 = delta.length_squared()
|
||
if 0 < dist2 < 144:
|
||
push = delta.normalize() * (12 - math.sqrt(dist2)) * 0.5
|
||
a.move(push, self.walls if a.flying else self.obstacles)
|
||
b.move(-push, self.walls if b.flying else self.obstacles)
|
||
|
||
def contact_damage(self):
|
||
player = self.player
|
||
for enemy in self.enemies:
|
||
if enemy.active and enemy.hitbox.colliderect(player.hitbox.inflate(2, 2)):
|
||
damage = enemy.contact
|
||
if getattr(enemy, "state", "") in ("charge", "dash"):
|
||
damage += 1
|
||
self.damage_player(damage, enemy.pos)
|
||
|
||
def update_shots(self, dt):
|
||
self.shots.update(dt)
|
||
self.enemy_shots.update(dt)
|
||
for shot in list(self.shots):
|
||
box = shot.hitbox()
|
||
for enemy in self.enemies:
|
||
if enemy.active and box.colliderect(enemy.hitbox.inflate(6, 12).move(0, -5)):
|
||
if enemy.hurt(shot.damage, shot.pos - shot.vel.normalize() * 8, shot.knock):
|
||
self.on_enemy_hit(enemy)
|
||
shot.kill()
|
||
break
|
||
if not shot.alive():
|
||
continue
|
||
if box.collidelist(self.walls) != -1:
|
||
shot.kill()
|
||
continue
|
||
for prop in self.world.props:
|
||
if box.colliderect(prop.hitbox):
|
||
if prop.breakable:
|
||
self.break_rock(prop)
|
||
shot.kill()
|
||
break
|
||
for shot in list(self.enemy_shots):
|
||
box = shot.hitbox()
|
||
if box.colliderect(self.player.hitbox.inflate(2, 6)):
|
||
self.damage_player(shot.damage, shot.pos)
|
||
shot.kill()
|
||
elif box.collidelist(self.obstacles) != -1:
|
||
shot.kill()
|
||
|
||
# ------------------------------------------------------------- камера
|
||
def clamp_camera(self):
|
||
self.camera.x = min(max(self.camera.x, 0), self.world.width - WIDTH)
|
||
self.camera.y = min(max(self.camera.y, 0), self.world.height - HEIGHT)
|
||
|
||
def follow(self, dt):
|
||
goal = self.player.pos - Vector(WIDTH / 2, HEIGHT / 2)
|
||
self.camera += (goal - self.camera) * min(1.0, dt * 8)
|
||
self.clamp_camera()
|
||
|
||
# ------------------------------------------------------------- рисование
|
||
def draw_world(self, view, dt):
|
||
shake = self.shake.offset(dt)
|
||
ox, oy = round(self.camera.x - shake.x), round(self.camera.y - shake.y)
|
||
self.drawn_offset = (ox, oy)
|
||
view.blit(self.world.floor, (-ox, -oy))
|
||
area = pygame.Rect(ox - 48, oy - 48, WIDTH + 96, HEIGHT + 96)
|
||
|
||
actors = [e for e in self.enemies if e.active]
|
||
if not self.player.dead:
|
||
actors.append(self.player)
|
||
for actor in actors:
|
||
shadow = self.big_shadow if actor.heavy else self.shadow
|
||
view.blit(shadow, shadow.get_rect(center=(actor.hitbox.centerx - ox, actor.hitbox.bottom - oy)))
|
||
|
||
drawables = [p for p in self.world.props if area.colliderect(p.rect)]
|
||
drawables += [e for e in self.enemies if area.colliderect(e.rect)]
|
||
drawables += list(self.hearts) + list(self.shots) + list(self.enemy_shots) + list(self.effects)
|
||
if not self.player.dead:
|
||
drawables.append(self.player)
|
||
for sprite in sorted(drawables, key=lambda s: s.sort_y):
|
||
thrust = getattr(sprite, "thrust", None)
|
||
if thrust: # копьё выдвигается вперёд за время удара
|
||
base, direction = thrust
|
||
pos = base + direction * (8 + 18 * min(1.0, sprite.t * 10))
|
||
rect = sprite.image.get_rect(center=(round(pos.x), round(pos.y)))
|
||
view.blit(sprite.image, rect.move(-ox, -oy))
|
||
else:
|
||
view.blit(sprite.image, sprite.rect.move(-ox, -oy))
|
||
|
||
def draw_ui(self, screen):
|
||
lang, player = self.lang, self.player
|
||
ox, oy = self.drawn_offset
|
||
for sprite in self.texts:
|
||
pos = ((sprite.pos.x - ox) * SCALE, (sprite.pos.y - oy) * SCALE)
|
||
screen.blit(sprite.image, sprite.image.get_rect(center=(round(pos[0]), round(pos[1]))))
|
||
|
||
ui.hearts(screen, player, (12, 10))
|
||
ui.text(screen, lang("level", n=player.level), (12, 46), ui.SMALL, GOLD)
|
||
ui.bar(screen, (80, 52, 128, 12), player.xp / xp_needed(player.level), BLUE)
|
||
ui.weapon_slots(screen, player, (12, SCREEN_H - 66))
|
||
if len(player.weapons) > 1:
|
||
ui.text(screen, "Q", (12 + len(player.weapons) * 44 + 4, SCREEN_H - 40), ui.SMALL, GREY)
|
||
|
||
ui.text(screen, lang("wave", n=self.wave, total=WAVES_TOTAL), (SCREEN_W // 2, 10), ui.SMALL, WHITE, "midtop")
|
||
if self.state == "fight" and not self.boss:
|
||
left = len(self.spawner.queue) + len(self.enemies)
|
||
ui.text(screen, lang("enemies_left", n=left), (SCREEN_W // 2, 34), ui.SMALL, GREY, "midtop")
|
||
ui.text(screen, f"{self.score:,}".replace(",", " "), (SCREEN_W - 12, 10), ui.SMALL, GOLD, "topright")
|
||
|
||
if self.boss and self.boss.alive():
|
||
rect = pygame.Rect(SCREEN_W // 2 - 220, SCREEN_H - 40, 440, 18)
|
||
ui.bar(screen, rect, max(0.0, self.boss.hp / self.boss.max_hp), RED)
|
||
ui.text(screen, lang("boss_name"), (SCREEN_W // 2, rect.top - 4), ui.SMALL, WHITE, "midbottom")
|
||
face = ui.big(assets.image("chars/boss_face.png"))
|
||
screen.blit(face, face.get_rect(midright=(rect.left - 8, rect.centery - 12)))
|
||
|
||
middle = (SCREEN_W // 2, SCREEN_H // 2 - 60)
|
||
if self.state == "intro":
|
||
label = lang("boss_wave") if self.wave == WAVES_TOTAL else lang("wave_big", n=self.wave)
|
||
ui.text(screen, label, middle, ui.HUGE, GOLD, "center")
|
||
elif self.state == "clear":
|
||
ui.text(screen, lang("wave_clear"), middle, ui.LARGE, GREEN, "center")
|
||
elif self.state == "upgrade":
|
||
ui.dim(screen, 140)
|
||
ui.text(screen, lang("choose"), (SCREEN_W // 2, 80), ui.LARGE, GOLD, "center")
|
||
ui.cards(screen, self.offer, lang, self.card)
|
||
ui.text(screen, lang("choose_hint"), (SCREEN_W // 2, SCREEN_H - 52), ui.SMALL, GREY, "center")
|
||
if self.result:
|
||
label = lang("victory") if self.result == "victory" else lang("game_over")
|
||
ui.text(screen, label, middle, ui.HUGE, GOLD if self.result == "victory" else RED, "center")
|
||
if self.paused:
|
||
ui.dim(screen, 150)
|
||
ui.text(screen, lang("pause"), (SCREEN_W // 2, 200), ui.LARGE, WHITE, "center")
|
||
self.pause_menu.draw(screen)
|