global_match_memory/global_match_memory.py

335 lines
13 KiB
Python
Raw 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.

import pygame
import sys
import random
import os
from typing import List, Tuple
# ----------------------------------------------------------
# Design / Layout
# ----------------------------------------------------------
WINDOW_W, WINDOW_H = 900, 900
BG_COLOR = (10, 58, 94) # Global-Match Blau
HUD_BAR_H = 88
PADDING = 28
GRID_COLS, GRID_ROWS = 4, 3 # 12 Karten (6 Paare)
GAP = 18
CARD_RADIUS = 20
CARD_BORDER = (220, 226, 235)
CARD_FACE = (255, 255, 255)
CARD_TEXT = (22, 44, 66)
BADGE_BG = (18, 122, 138)
BADGE_TEXT = (255, 255, 255)
HIGHLIGHT = (255, 224, 128)
LOGO_FILE = "LogoSpiel.png" # <— Dein Logo (Rückseite)
MISMATCH_DELAY_MS = 900
SINGLEPLAYER_SECONDS = 90
FPS = 60
# ----------------------------------------------------------
# Daten (Deutsch)
# ----------------------------------------------------------
DATA = {
"Europa": [
("Deutschland", "Berlin"), ("Frankreich", "Paris"), ("Italien", "Rom"),
("Spanien", "Madrid"), ("Portugal", "Lissabon"), ("Niederlande", "Amsterdam"),
("Belgien", "Brüssel"), ("Österreich", "Wien"), ("Schweiz", "Bern"),
("Polen", "Warschau"), ("Tschechien", "Prag"), ("Ungarn", "Budapest"),
("Dänemark", "Kopenhagen"), ("Schweden", "Stockholm"), ("Norwegen", "Oslo")
],
"Amerika": [
("USA", "Washington, D.C."), ("Kanada", "Ottawa"), ("Mexiko", "Mexiko-Stadt"),
("Brasilien", "Brasília"), ("Argentinien", "Buenos Aires"), ("Chile", "Santiago"),
("Peru", "Lima"), ("Kolumbien", "Bogotá"), ("Venezuela", "Caracas"),
("Uruguay", "Montevideo"), ("Bolivien", "Sucre"), ("Ecuador", "Quito")
],
"Asien": [
("China", "Peking"), ("Japan", "Tokio"), ("Südkorea", "Seoul"),
("Indien", "Neu-Delhi"), ("Pakistan", "Islamabad"),
("Bangladesch", "Dhaka"), ("Sri Lanka", "Sri Jayewardenepura Kotte"),
("Nepal", "Kathmandu"), ("Saudi-Arabien", "Riad"),
("VAE", "Abu Dhabi"), ("Türkei", "Ankara"), ("Indonesien", "Jakarta"),
("Malaysia", "Kuala Lumpur"), ("Thailand", "Bangkok"), ("Vietnam", "Hanoi")
],
}
# ----------------------------------------------------------
# Helpers
# ----------------------------------------------------------
def load_logo_scaled(size: Tuple[int, int]) -> pygame.Surface:
"""Lädt LogoSpiel.png und skaliert mit 10% Rand. Fallback: dunkles GM-Pattern."""
w, h = size
surf = pygame.Surface((w, h), pygame.SRCALPHA)
# weiße Karte
pygame.draw.rect(surf, CARD_FACE, (0, 0, w, h), border_radius=CARD_RADIUS)
pygame.draw.rect(surf, CARD_BORDER, (0, 0, w, h), width=2, border_radius=CARD_RADIUS)
inner_pad = int(min(w, h) * 0.12)
logo_rect = pygame.Rect(inner_pad, inner_pad, w - 2*inner_pad, h - 2*inner_pad)
if os.path.exists(LOGO_FILE):
try:
img = pygame.image.load(LOGO_FILE).convert_alpha()
img = scale_to_fit(img, logo_rect.size)
surf.blit(img, (
logo_rect.centerx - img.get_width() // 2,
logo_rect.centery - img.get_height() // 2
))
return surf
except Exception:
pass
# Fallback: Text "GLOBAL MATCH"
font = pygame.font.SysFont(None, int(h * 0.16), bold=True)
t1 = font.render("GLOBAL", True, (10, 80, 110))
t2 = font.render("MATCH", True, (240, 180, 60))
surf.blit(t1, (w//2 - t1.get_width()//2, h//2 - t1.get_height()))
surf.blit(t2, (w//2 - t2.get_width()//2, h//2 + 4))
return surf
def scale_to_fit(img: pygame.Surface, target_size: Tuple[int, int]) -> pygame.Surface:
tw, th = target_size
iw, ih = img.get_width(), img.get_height()
scale = min(tw / iw, th / ih)
return pygame.transform.smoothscale(img, (int(iw * scale), int(ih * scale)))
def wrap_text(text: str, font: pygame.font.Font, max_w: int) -> List[str]:
words = text.split()
lines, line = [], ""
for w in words:
t = (line + " " + w).strip()
if font.size(t)[0] <= max_w:
line = t
else:
if line:
lines.append(line)
line = w
if line:
lines.append(line)
return lines
def pick_payloads(level: str, n_pairs=6) -> List[dict]:
pool = DATA[level][:] if level in DATA else DATA["Europa"][:]
random.shuffle(pool)
chosen = pool[:n_pairs]
payloads = []
pid = 0
for land, cap in chosen:
payloads.append({"pair": pid, "label": land, "kind": "country"})
payloads.append({"pair": pid, "label": cap, "kind": "capital"})
pid += 1
random.shuffle(payloads)
return payloads
# ----------------------------------------------------------
# Card
# ----------------------------------------------------------
class Card:
def __init__(self, rect: pygame.Rect, payload: dict, fonts, back_img: pygame.Surface):
self.rect = rect
self.payload = payload
self.font, self.small = fonts
self.back = pygame.transform.smoothscale(back_img, (rect.w, rect.h))
self.front = pygame.Surface((rect.w, rect.h), pygame.SRCALPHA)
self.is_revealed = False
self.is_matched = False
self._render_front()
def _render_front(self):
w, h = self.rect.w, self.rect.h
# weiße Karte + Rand
pygame.draw.rect(self.front, CARD_FACE, (0, 0, w, h), border_radius=CARD_RADIUS)
pygame.draw.rect(self.front, CARD_BORDER, (0, 0, w, h), width=2, border_radius=CARD_RADIUS)
# Badge
badge = "Land" if self.payload["kind"] == "country" else "Hauptstadt"
bsurf = self.small.render(badge, True, BADGE_TEXT)
bw, bh = bsurf.get_size()
badge_bg = pygame.Surface((bw + 18, bh + 8), pygame.SRCALPHA)
pygame.draw.rect(badge_bg, BADGE_BG, badge_bg.get_rect(), border_radius=12)
badge_bg.blit(bsurf, (9, 4))
self.front.blit(badge_bg, (w - badge_bg.get_width() - 12, 12))
# Text
lines = wrap_text(self.payload["label"], self.font, w - 28)
total_h = len(lines) * self.font.get_height()
y = h//2 - total_h//2
for line in lines:
ts = self.font.render(line, True, CARD_TEXT)
self.front.blit(ts, (w//2 - ts.get_width()//2, y))
y += self.font.get_height()
def draw(self, screen: pygame.Surface):
screen.blit(self.front if (self.is_revealed or self.is_matched) else self.back,
(self.rect.x, self.rect.y))
def handle_click(self, pos) -> bool:
if self.rect.collidepoint(pos) and not self.is_matched and not self.is_revealed:
self.is_revealed = True
return True
return False
# ----------------------------------------------------------
# Main Game
# ----------------------------------------------------------
def run_memory(level: str = "Europa", mode: str = "single"):
"""
level: 'Europa' | 'Amerika' | 'Asien'
mode : 'single' (Zeit) | 'two' (Punkte, abwechselnd)
"""
pygame.init()
screen = pygame.display.set_mode((WINDOW_W, WINDOW_H))
pygame.display.set_caption(f"Global Match {level} ({'1 Spieler' if mode=='single' else '2 Spieler'})")
clock = pygame.time.Clock()
ui_font = pygame.font.SysFont(None, 32, bold=True)
big_font = pygame.font.SysFont(None, 50, bold=True)
card_font = pygame.font.SysFont(None, 28, bold=True)
small_font = pygame.font.SysFont(None, 20, bold=True)
# Grid berechnen
grid_w = WINDOW_W - 2 * PADDING
grid_h = WINDOW_H - HUD_BAR_H - 2 * PADDING
cell_w = (grid_w - (GRID_COLS - 1) * GAP) // GRID_COLS
cell_h = (grid_h - (GRID_ROWS - 1) * GAP) // GRID_ROWS
card_size = min(cell_w, cell_h)
start_x = PADDING + (grid_w - (card_size * GRID_COLS + GAP * (GRID_COLS - 1))) // 2
start_y = HUD_BAR_H + PADDING + (grid_h - (card_size * GRID_ROWS + GAP * (GRID_ROWS - 1))) // 2
# Karten erzeugen
back_img = load_logo_scaled((card_size, card_size))
payloads = pick_payloads(level, n_pairs=6)
cards: List[Card] = []
k = 0
for r in range(GRID_ROWS):
for c in range(GRID_COLS):
if k >= len(payloads):
continue
rect = pygame.Rect(start_x + c * (card_size + GAP),
start_y + r * (card_size + GAP),
card_size, card_size)
cards.append(Card(rect, payloads[k], (card_font, small_font), back_img))
k += 1
# Spielstatus
revealed: List[Card] = []
lock_until = 0
total_matches = 0
need_matches = len(payloads) // 2
p_turn = 1
score = {1: 0, 2: 0}
time_left = SINGLEPLAYER_SECONDS * 1000 if mode == "single" else None
end_text = None
# Loop
while True:
dt = clock.tick(FPS)
now = pygame.time.get_ticks()
# Events
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit()
sys.exit()
if e.type == pygame.KEYDOWN and end_text:
if e.key in (pygame.K_RETURN, pygame.K_SPACE):
pygame.quit()
return
if e.type == pygame.MOUSEBUTTONDOWN and e.button == 1 and not end_text:
if now < lock_until:
continue
for card in cards:
if card.handle_click(e.pos):
revealed.append(card)
if len(revealed) == 2:
a, b = revealed
# Match (gleiches Paar, aber unterschiedliche Art)
if a.payload["pair"] == b.payload["pair"] and a.payload["kind"] != b.payload["kind"]:
a.is_matched = b.is_matched = True
revealed.clear()
total_matches += 1
if mode == "two":
score[p_turn] += 1
if total_matches == need_matches:
if mode == "single":
end_text = "Geschafft! Alle 6 Paare."
else:
if score[1] > score[2]:
end_text = f"Spielende Spieler 1 gewinnt ({score[1]}:{score[2]})"
elif score[2] > score[1]:
end_text = f"Spielende Spieler 2 gewinnt ({score[2]}:{score[1]})"
else:
end_text = f"Unentschieden ({score[1]}:{score[2]})"
else:
lock_until = now + MISMATCH_DELAY_MS
elif len(revealed) > 2:
# Sicherheitsreset nur die letzten 2 behalten
for old in revealed[:-2]:
old.is_revealed = False
revealed = revealed[-2:]
# Timer (Singleplayer)
if not end_text and mode == "single" and time_left is not None:
time_left -= dt
if time_left <= 0:
time_left = 0
end_text = "Zeit abgelaufen!"
# Mismatch zurückdrehen + Spielerwechsel
if not end_text and len(revealed) == 2 and lock_until and now >= lock_until:
a, b = revealed
a.is_revealed = False
b.is_revealed = False
revealed.clear()
lock_until = 0
if mode == "two":
p_turn = 2 if p_turn == 1 else 1
# ----------------- Render -----------------
screen.fill(BG_COLOR)
# HUD
pygame.draw.rect(screen, (7, 42, 70), (0, 0, WINDOW_W, HUD_BAR_H))
title = ui_font.render(f"Global Match {level}", True, (255, 255, 255))
screen.blit(title, (PADDING, HUD_BAR_H//2 - title.get_height()//2))
if mode == "single":
tsec = (time_left // 1000) if time_left is not None else 0
timer = ui_font.render(f"Zeit: {tsec}s", True, HIGHLIGHT)
screen.blit(timer, (WINDOW_W - PADDING - timer.get_width(), HUD_BAR_H//2 - timer.get_height()//2))
else:
s = ui_font.render(f"P1: {score[1]} P2: {score[2]} Zug: P{p_turn}", True, HIGHLIGHT)
screen.blit(s, (WINDOW_W - PADDING - s.get_width(), HUD_BAR_H//2 - s.get_height()//2))
# Karten
for card in cards:
card.draw(screen)
# Overlay / Ende
if end_text:
overlay = pygame.Surface((WINDOW_W, WINDOW_H), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 140))
screen.blit(overlay, (0, 0))
msg = big_font.render(end_text, True, (255, 255, 255))
sub = ui_font.render("ENTER/SPACE zum Beenden", True, (240, 240, 240))
screen.blit(msg, (WINDOW_W//2 - msg.get_width()//2, WINDOW_H//2 - 30))
screen.blit(sub, (WINDOW_W//2 - sub.get_width()//2, WINDOW_H//2 + 20))
pygame.display.flip()
# ----------------------------------------------------------
# Direktstart (zu Testzwecken)
# ----------------------------------------------------------
if __name__ == "__main__":
lvl = "Europa"
md = "single"
if len(sys.argv) >= 2:
lvl = sys.argv[1]
if len(sys.argv) >= 3:
md = sys.argv[2]
run_memory(level=lvl, mode=md)