Initial commit: Plants turn-based battle game
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
import pygame
|
||||
|
||||
from engine.plants import RARITY_COLORS
|
||||
from engine.relics import RELIC_POOL
|
||||
|
||||
BG = (24, 30, 24)
|
||||
TEXT = (230, 235, 230)
|
||||
MUTED = (150, 155, 150)
|
||||
HINT = (255, 220, 120)
|
||||
PANEL = (30, 36, 30)
|
||||
PANEL_BORDER = (150, 190, 150)
|
||||
CARD = (36, 44, 36)
|
||||
CARD_OWNED = (46, 54, 42)
|
||||
GOLD = (245, 200, 90)
|
||||
OWNED = (110, 220, 255) # bright cyan: distinct from the legendary gold border
|
||||
|
||||
TIER_ORDER = ("common", "rare", "epic", "legendary")
|
||||
TIER_LABELS = {"common": "Common", "rare": "Rare", "epic": "Epic", "legendary": "Legendary"}
|
||||
|
||||
|
||||
def _wrap(text, font, width):
|
||||
words = text.split()
|
||||
lines = []
|
||||
cur = ""
|
||||
for w in words:
|
||||
if font.size(cur + " " + w)[0] > width and cur:
|
||||
lines.append(cur)
|
||||
cur = w
|
||||
else:
|
||||
cur = (cur + " " + w).strip()
|
||||
if cur:
|
||||
lines.append(cur)
|
||||
return lines
|
||||
|
||||
|
||||
class RelicsOverview:
|
||||
"""Full-screen overlay listing every blessing, grouped by rarity, with the
|
||||
ones collected this run highlighted. Click a relic to inspect it up close.
|
||||
Host screens draw this onto their own surface (no pygame display ownership),
|
||||
so it can open mid-battle without tearing down the window."""
|
||||
|
||||
def __init__(self, screen, bold, small, tiny, pool=None, owned_keys=()):
|
||||
self.screen = screen
|
||||
self.bold = bold
|
||||
self.small = small
|
||||
self.tiny = tiny
|
||||
self.pool = list(pool if pool is not None else RELIC_POOL)
|
||||
self.owned = set(owned_keys)
|
||||
self.scroll = 0
|
||||
self.detail = None
|
||||
w, h = screen.get_size()
|
||||
pw = min(760, w - 60)
|
||||
ph = min(500, h - 140)
|
||||
self.panel = pygame.Rect((w - pw) // 2, (h - ph) // 2, pw, ph)
|
||||
pad = 24
|
||||
self.cards_top = self.panel.y + 78
|
||||
self.inner = pygame.Rect(self.panel.x + pad, self.cards_top,
|
||||
self.panel.w - 2 * pad,
|
||||
self.panel.bottom - 20 - self.cards_top)
|
||||
self.close_rect = pygame.Rect(self.panel.right - 74, self.panel.y + 14, 58, 28)
|
||||
rank = {t: i for i, t in enumerate(TIER_ORDER)}
|
||||
self.pool.sort(key=lambda r: (rank.get(r.rarity, 9), r.name.lower()))
|
||||
cols = 3
|
||||
gap = 14
|
||||
cw = (self.inner.w - gap * (cols - 1)) // cols
|
||||
ch = 118
|
||||
self.cards = []
|
||||
x, y = self.inner.x, self.inner.y
|
||||
for i, r in enumerate(self.pool):
|
||||
self.cards.append((r, pygame.Rect(x, y, cw, ch)))
|
||||
if (i + 1) % cols == 0:
|
||||
x = self.inner.x
|
||||
y += ch + gap
|
||||
else:
|
||||
x += cw + gap
|
||||
self.content_h = y - self.inner.y if self.cards else 0
|
||||
self.scroll_max = max(0, self.content_h - self.inner.h)
|
||||
|
||||
def handle_wheel(self, y):
|
||||
if self.detail is not None:
|
||||
return
|
||||
self.scroll = max(0, min(self.scroll_max, self.scroll - y * 40))
|
||||
|
||||
def handle_click(self, pos):
|
||||
if self.detail is not None:
|
||||
self.detail = None
|
||||
return None
|
||||
if self.close_rect.collidepoint(pos):
|
||||
return "close"
|
||||
if not self.inner.collidepoint(pos):
|
||||
return "close" if not self.panel.collidepoint(pos) else None
|
||||
for r, rect in self.cards:
|
||||
draw_rect = rect.move(0, -self.scroll)
|
||||
if draw_rect.collidepoint(pos) and draw_rect.colliderect(self.inner):
|
||||
self.detail = r
|
||||
return None
|
||||
return None
|
||||
|
||||
def _draw_card(self, r, rect):
|
||||
owned = r.key in self.owned
|
||||
bg = CARD_OWNED if owned else CARD
|
||||
border = OWNED if owned else RARITY_COLORS.get(r.rarity, (150, 150, 150))
|
||||
pygame.draw.rect(self.screen, bg, rect, border_radius=8)
|
||||
pygame.draw.rect(self.screen, border, rect, 3 if owned else 1, border_radius=8)
|
||||
name = self.small.render(r.name, True, RARITY_COLORS.get(r.rarity, TEXT))
|
||||
self.screen.blit(name, (rect.x + 10, rect.y + 8))
|
||||
if owned:
|
||||
tag = self.tiny.render("OWNED", True, OWNED)
|
||||
self.screen.blit(tag, (rect.right - tag.get_width() - 8, rect.y + 10))
|
||||
y = rect.y + 34
|
||||
for ln in _wrap(r.desc, self.tiny, rect.w - 20)[:3]:
|
||||
self.screen.blit(self.tiny.render(ln, True, MUTED), (rect.x + 10, y))
|
||||
y += 16
|
||||
tip = self.tiny.render(f"{TIER_LABELS.get(r.rarity, r.rarity)}" +
|
||||
(" · click to inspect" if owned else " · not owned"),
|
||||
True, OWNED if owned else (110, 120, 110))
|
||||
self.screen.blit(tip, (rect.x + 10, rect.bottom - 18))
|
||||
|
||||
def _draw_detail(self):
|
||||
r = self.detail
|
||||
if r is None:
|
||||
return
|
||||
shade = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA)
|
||||
shade.fill((0, 0, 0, 130))
|
||||
self.screen.blit(shade, (0, 0))
|
||||
w, h = 440, 230
|
||||
panel = pygame.Rect((self.screen.get_width() - w) // 2,
|
||||
(self.screen.get_height() - h) // 2, w, h)
|
||||
pygame.draw.rect(self.screen, (36, 44, 36), panel, border_radius=10)
|
||||
pygame.draw.rect(self.screen, RARITY_COLORS.get(r.rarity, PANEL_BORDER),
|
||||
panel, 2, border_radius=10)
|
||||
name = self.bold.render(r.name, True, RARITY_COLORS.get(r.rarity, TEXT))
|
||||
self.screen.blit(name, name.get_rect(midtop=(panel.centerx, panel.y + 22)))
|
||||
rar = self.small.render(TIER_LABELS.get(r.rarity, r.rarity).capitalize(), True, MUTED)
|
||||
self.screen.blit(rar, rar.get_rect(midtop=(panel.centerx, panel.y + 54)))
|
||||
y = panel.y + 92
|
||||
for ln in _wrap(r.desc, self.small, panel.w - 48)[:5]:
|
||||
line = self.small.render(ln, True, TEXT)
|
||||
self.screen.blit(line, line.get_rect(midtop=(panel.centerx, y)))
|
||||
y += 26
|
||||
owned = r.key in self.owned
|
||||
st = self.tiny.render("OWNED this run" if owned else "Not owned yet", True,
|
||||
OWNED if owned else MUTED)
|
||||
self.screen.blit(st, st.get_rect(midtop=(panel.centerx, y + 4)))
|
||||
close = self.tiny.render("Click anywhere to close", True, MUTED)
|
||||
self.screen.blit(close, close.get_rect(center=(panel.centerx, panel.bottom - 20)))
|
||||
|
||||
def draw(self, mouse):
|
||||
overlay = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA)
|
||||
overlay.fill((0, 0, 0, 180))
|
||||
self.screen.blit(overlay, (0, 0))
|
||||
pygame.draw.rect(self.screen, PANEL, self.panel, border_radius=12)
|
||||
pygame.draw.rect(self.screen, PANEL_BORDER, self.panel, 2, border_radius=12)
|
||||
title = self.bold.render("All blessings", True, HINT)
|
||||
self.screen.blit(title, title.get_rect(midtop=(self.panel.centerx, self.panel.y + 14)))
|
||||
sub = self.tiny.render(
|
||||
f"{len(self.owned)} owned this run · cyan border = collected · click a card to inspect",
|
||||
True, MUTED)
|
||||
self.screen.blit(sub, sub.get_rect(midtop=(self.panel.centerx, self.panel.y + 44)))
|
||||
pygame.draw.rect(self.screen, (90, 110, 90), self.close_rect, border_radius=6)
|
||||
pygame.draw.rect(self.screen, PANEL_BORDER, self.close_rect, 1, border_radius=6)
|
||||
c = self.small.render("Close", True, TEXT)
|
||||
self.screen.blit(c, c.get_rect(center=self.close_rect.center))
|
||||
old = self.screen.get_clip()
|
||||
self.screen.set_clip(self.inner)
|
||||
for r, rect in self.cards:
|
||||
draw_rect = rect.move(0, -self.scroll)
|
||||
if not draw_rect.colliderect(self.inner):
|
||||
continue
|
||||
self._draw_card(r, draw_rect)
|
||||
self.screen.set_clip(old)
|
||||
if self.scroll_max > 0:
|
||||
hint = ("▲" if self.scroll > 0 else "") + ("▼" if self.scroll < self.scroll_max else "")
|
||||
if hint:
|
||||
ht = self.tiny.render(hint, True, MUTED)
|
||||
self.screen.blit(ht, ht.get_rect(midbottom=(self.inner.centerx, self.inner.bottom)))
|
||||
if self.detail is not None:
|
||||
self._draw_detail()
|
||||
Reference in New Issue
Block a user