Initial commit – Global Match Memory

This commit is contained in:
Alina Bhatti 2025-11-12 12:10:43 +01:00
commit 7beeb6fe61
32 changed files with 1146 additions and 0 deletions

7
.env Normal file
View File

@ -0,0 +1,7 @@
CARD_BACK_COLOR = (100, 100, 200)
CARD_FRONT_COLOR = (230, 230, 250)
MATCH_COLOR = (120, 200, 120)
TEXT_COLOR = (0, 0, 0)
BG_COLOR = (50, 50, 80)
SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
FPS = 30

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

10
.idea/Memory GlobalMatch.iml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (Memory GlobalMatch)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (Memory GlobalMatch)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Memory GlobalMatch.iml" filepath="$PROJECT_DIR$/.idea/Memory GlobalMatch.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

6
Africa-Dependent.txt Executable file
View File

@ -0,0 +1,6 @@
Mayotte Mamoudzou
Réunion Saint-Denis
Saint-Helena Jamestown
Ascension-Island Georgetown
Tristan-da-Cunha Edinburgh-of-the-Seven-Seas
Western-Sahara El-Aaiún

20
Africa-Major.txt Executable file
View File

@ -0,0 +1,20 @@
Algeria Algiers
Angola Luanda
Egypt Cairo
Ethiopia Addis-Ababa
Ghana Accra
Kenya Nairobi
Libya Tripoli
Morocco Rabat
Mozambique Maputo
Nigeria Abuja
South-Africa Pretoria
Sudan Khartoum
Tanzania Dodoma
Tunisia Tunis
Uganda Kampala
Zimbabwe Harare
Democratic-Republic-of-Congo Kinshasa
Senegal Dakar
Mali Bamako
Côte-d'Ivoire Yamoussoukro

20
Africa-Minor.txt Executable file
View File

@ -0,0 +1,20 @@
Benin Porto-Novo
Burundi Gitega
Cape-Verde Praia
Comoros Moroni
Djibouti Djibouti
Equatorial-Guinea Malabo
Eritrea Asmara
Eswatini Mbabane
Gabon Libreville
Gambia Banjul
Lesotho Maseru
Liberia Monrovia
Mauritius Port-Louis
Namibia Windhoek
Rwanda Kigali
Seychelles Victoria
Sierra-Leone Freetown
São-Tomé-and-Príncipe São-Tomé
Togo Lomé
Burkina-Faso Ouagadougou

5
Asia-Dependent.txt Executable file
View File

@ -0,0 +1,5 @@
Hong-Kong Hong-Kong
Macau Macau
Taiwan Taipei
Palestine Ramallah
Kuwait-Northern-Region (for historical/admin regions if needed)

20
Asia-Major.txt Executable file
View File

@ -0,0 +1,20 @@
China Beijing
India New-Delhi
Japan Tokyo
Indonesia Jakarta
Pakistan Islamabad
Bangladesh Dhaka
Russia Moscow
Saudi-Arabia Riyadh
Iran Tehran
Turkey Ankara
South-Korea Seoul
North-Korea Pyongyang
Thailand Bangkok
Vietnam Hanoi
Philippines Manila
Myanmar Naypyidaw
Iraq Baghdad
Afghanistan Kabul
Malaysia Kuala-Lumpur
Uzbekistan Tashkent

25
Asia-Minor.txt Executable file
View File

@ -0,0 +1,25 @@
Armenia Yerevan
Azerbaijan Baku
Bahrain Manama
Bhutan Thimphu
Brunei Bandar Seri Begawan
Cambodia Phnom-Penh
East-Timor Dili
Georgia Tbilisi
Israel Jerusalem
Jordan Amman
Kuwait Kuwait-City
Laos Vientiane
Lebanon Beirut
Maldives Malé
Mongolia Ulaanbaatar
Nepal Kathmandu
Oman Muscat
Qatar Doha
Singapore Singapore
Sri-Lanka Sri-Jayawardenepura-Kotte
Syria Damascus
Tajikistan Dushanbe
Turkmenistan Ashgabat
United-Arab-Emirates Abu-Dhabi
Yemen Sana'a

13
Europe-Dependent.txt Executable file
View File

@ -0,0 +1,13 @@
Faroe-Islands Torshavn
Gibraltar Gibraltar
Guernsey St-Peter-Port
Jersey Saint-Helier
Isle-of-Man Douglas
Greenland Nuuk
Svalbard Longyearbyen
Åland-Islands Mariehamn
Kosovo Pristina
Transnistria Tiraspol
Abkhazia Sukhumi
South-Ossetia Tskhinvali
Nagorno-Karabakh Stepanakert

24
Europe-Major.txt Executable file
View File

@ -0,0 +1,24 @@
Austria Vienna
Belgium Brussels
Croatia Zagreb
Czechia Prague
Denmark Copenhagen
Finland Helsinki
France Paris
Germany Berlin
Greece Athens
Hungary Budapest
Ireland Dublin
Italy Rome
Netherlands Amsterdam
Norway Oslo
Poland Warsaw
Portugal Lisbon
Romania Bucharest
Spain Madrid
Sweden Stockholm
Switzerland Bern
United-Kingdom London
Ukraine Kyiv

19
Europe-Minor.txt Executable file
View File

@ -0,0 +1,19 @@
Andorra Andorra-la-Vella
Liechtenstein Vaduz
Luxembourg Luxembourg
Malta Valletta
Monaco Monaco
San-Marino San-Marino
Vatican-City Vatican-City
Kosovo Pristina
Montenegro Podgorica
North-Macedonia Skopje
Estonia Tallinn
Latvia Riga
Lithuania Vilnius
Iceland Reykjavik
Slovenia Ljubljana
Albania Tirana
Moldova Chisinau
Bosnia-and-Herzegovina Sarajevo
Cyprus Nicosia

327
Finished_Memory_Mouse.py Executable file
View File

@ -0,0 +1,327 @@
import pygame
import random
import sys
import os
# -------------------------------
# CountryCapital Memory Game
# -------------------------------
CARD_BACK_COLOR = (100, 100, 200)
CARD_FRONT_COLOR = (230, 230, 250)
MATCH_COLOR = (120, 200, 120)
TEXT_COLOR = (0, 0, 0)
BG_COLOR = (50, 50, 80)
SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
FPS = 30
# Design-Bild (für Kartenrückseite + optionalen Hintergrund)
DESIGN_FILE = "GlobalHintergrund.png"
class MemoryGame:
def __init__(self):
self.deck = []
self.pair_map = {}
self.matched = []
self.revealed = []
self.scores = [0, 0]
self.current_player = 0
self.font = None
self.card_rects = []
self.selected = []
self.found_pairs = 0
self.total_pairs = 0
self.running = True
self.awaiting_confirmation = False
self.confirmation_result = None
self.correct_answer_expected = None
self.state = "continent" # continent → americas → difficulty → pairs → game
self.buttons = []
self.selected_continents = []
self.level = None
self.pair_count = 6 # Default
# -------------------------------
# Card Loading
# -------------------------------
def load_cards(self, filename):
"""Loads pairs from a text file."""
if not os.path.exists(filename):
print(f"⚠️ File not found: {filename}")
return []
pairs = []
with open(filename, "r", encoding="utf-8") as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 2:
pairs.append((parts[0], parts[1]))
return pairs
def prepare_deck(self):
"""Loads all relevant continent + difficulty files."""
self.deck = []
for continent in self.selected_continents:
base = continent
if self.level == "Easy":
self.deck += self.load_cards(base + "-major.txt")
elif self.level == "Normal":
self.deck += self.load_cards(base + "-major.txt")
self.deck += self.load_cards(base + "-Minor.txt")
elif self.level == "Hard":
self.deck += self.load_cards(base + "-major.txt")
self.deck += self.load_cards(base + "-Minor.txt")
self.deck += self.load_cards(base + "-Dependent.txt")
if not self.deck:
print("⚠️ No cards loaded, check text files.")
sys.exit()
random.shuffle(self.deck)
# Limit to selected pair count
self.deck = self.deck[:self.pair_count]
def setup_game(self):
self.cards = []
self.pair_map = {}
for country, capital in self.deck:
self.cards.append(country)
self.cards.append(capital)
self.pair_map[country] = capital
self.pair_map[capital] = country
random.shuffle(self.cards)
self.matched = [False] * len(self.cards)
self.revealed = [False] * len(self.cards)
self.total_pairs = len(self.deck)
self.card_rects = []
self.selected = []
cols = 4
rows = (len(self.cards) + cols - 1) // cols
margin = 10
card_width = (SCREEN_WIDTH - (cols + 1) * margin) // cols
card_height = (SCREEN_HEIGHT - (rows + 1) * margin - 100) // rows
y_offset = 80
for i, _ in enumerate(self.cards):
col = i % cols
row = i // cols
x = margin + col * (card_width + margin)
y = y_offset + margin + row * (card_height + margin)
rect = pygame.Rect(x, y, card_width, card_height)
self.card_rects.append(rect)
# -------------------------------
# Drawing Menus
# -------------------------------
def draw_menu(self, screen, title, options):
screen.fill(BG_COLOR)
title_text = self.font.render(title, True, (255, 255, 255))
screen.blit(title_text, (SCREEN_WIDTH // 2 - title_text.get_width() // 2, 100))
self.buttons = []
for i, option in enumerate(options):
rect = pygame.Rect(SCREEN_WIDTH // 2 - 150, 200 + i * 70, 300, 50)
pygame.draw.rect(screen, (100, 100, 250), rect)
pygame.draw.rect(screen, (255, 255, 255), rect, 2)
text = self.font.render(option, True, (255, 255, 255))
screen.blit(text, (rect.centerx - text.get_width() // 2, rect.centery - text.get_height() // 2))
self.buttons.append((rect, option))
pygame.display.flip()
def draw_game(self, screen):
screen.fill(BG_COLOR)
title = self.font.render(f"Player {self.current_player + 1}'s turn", True, (255, 255, 255))
screen.blit(title, (20, 20))
score_text = self.font.render(f"Scores: P1={self.scores[0]} P2={self.scores[1]}", True, (200, 200, 200))
screen.blit(score_text, (20, 50))
for i, rect in enumerate(self.card_rects):
if self.matched[i]:
color = MATCH_COLOR
elif self.revealed[i]:
color = CARD_FRONT_COLOR
else:
color = CARD_BACK_COLOR
pygame.draw.rect(screen, color, rect)
pygame.draw.rect(screen, (0, 0, 0), rect, 2)
if self.revealed[i] or self.matched[i]:
text = self.font.render(self.cards[i], True, TEXT_COLOR)
text_rect = text.get_rect(center=rect.center)
screen.blit(text, text_rect)
if self.awaiting_confirmation:
self.draw_confirmation_box(screen)
pygame.display.flip()
def draw_confirmation_box(self, screen): #y-axis Box
box_rect = pygame.Rect(SCREEN_WIDTH // 2 - 150, SCREEN_HEIGHT // 2 - 320, 300, 160)
pygame.draw.rect(screen, (250, 250, 250), box_rect)
pygame.draw.rect(screen, (0, 0, 0), box_rect, 3)
text = self.font.render("Is that correct?", True, (0, 0, 0))
screen.blit(text, (box_rect.centerx - text.get_width() // 2, box_rect.y + 20))
yes_rect = pygame.Rect(box_rect.x + 50, box_rect.y + 90, 80, 40)
no_rect = pygame.Rect(box_rect.x + 170, box_rect.y + 90, 80, 40)
pygame.draw.rect(screen, (0, 200, 0), yes_rect)
pygame.draw.rect(screen, (200, 0, 0), no_rect)
yes_text = self.font.render("Yes", True, (255, 255, 255))
no_text = self.font.render("No", True, (255, 255, 255))
screen.blit(yes_text, (yes_rect.centerx - yes_text.get_width() // 2, yes_rect.centery - yes_text.get_height() // 2))
screen.blit(no_text, (no_rect.centerx - no_text.get_width() // 2, no_rect.centery - no_text.get_height() // 2))
self.yes_rect, self.no_rect = yes_rect, no_rect
# -------------------------------
# Handling Clicks
# -------------------------------
def handle_click(self, pos):
if self.state in ["continent", "americas", "difficulty", "pairs"]:
for rect, option in self.buttons:
if rect.collidepoint(pos):
if self.state == "continent":
if option == "Americas":
self.state = "americas"
elif option == "All Continents":
self.selected_continents = ["Europe", "Asia", "Africa", "Oceania", "North-America", "South-America"]
self.state = "difficulty"
else:
self.selected_continents = [option]
self.state = "difficulty"
elif self.state == "americas":
if option == "North-America":
self.selected_continents = ["North-America"]
elif option == "South-America":
self.selected_continents = ["South-America"]
elif option == "Americas":
self.selected_continents = ["North-America", "South-America"]
self.state = "difficulty"
elif self.state == "difficulty":
self.level = option
self.state = "pairs"
elif self.state == "pairs":
self.pair_count = int(option)
self.prepare_deck()
self.setup_game()
self.state = "game"
return
elif self.state == "game":
if self.awaiting_confirmation:
if self.yes_rect.collidepoint(pos):
self.confirmation_result = "yes"
elif self.no_rect.collidepoint(pos):
self.confirmation_result = "no"
return
for i, rect in enumerate(self.card_rects):
if rect.collidepoint(pos) and not self.revealed[i] and not self.matched[i]:
self.revealed[i] = True
self.selected.append(i)
return
# -------------------------------
# Game Logic Most important
# -------------------------------
def check_selected(self):
if self.state != "game":
return
# Wenn zwei Karten ausgewählt wurden → prüfen
if len(self.selected) == 2 and not self.awaiting_confirmation:
a, b = self.selected
is_match = self.pair_map.get(self.cards[a]) == self.cards[b]
self.correct_answer_expected = "yes" if is_match else "no"
self.awaiting_confirmation = True
# Wenn der Spieler im "Is that correct?"-Dialog geantwortet hat
elif self.awaiting_confirmation and self.confirmation_result:
a, b = self.selected
expected = self.correct_answer_expected
player_correct = self.confirmation_result == expected
if player_correct:
# Spieler hat korrekt geantwortet
if expected == "yes":
# Richtiges Paar bestätigt
self.matched[a] = self.matched[b] = True
self.scores[self.current_player] += 1
self.found_pairs += 1
else:
# "No" richtig bestätigt → kein Paar
self.revealed[a] = self.revealed[b] = False
else:
# Spieler hat sich vertan → Karten umdrehen & Punkt abziehen
self.scores[self.current_player] -= 1
self.revealed[a] = self.revealed[b] = False
# Reset für nächsten Zug
self.awaiting_confirmation = False
self.confirmation_result = None
self.selected = []
self.current_player = 1 - self.current_player
# -------------------------------
# Main Game Loop
# -------------------------------
def run(self):
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("CountryCapital Memory Game")
clock = pygame.time.Clock()
self.font = pygame.font.SysFont(None, 32)
while self.running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
self.handle_click(event.pos)
if self.state == "continent":
self.draw_menu(screen, "Select Continent", ["Europe", "Americas", "Asia", "Africa", "Oceania", "All Continents"])
elif self.state == "americas":
self.draw_menu(screen, "Select Region", ["North-America", "South-America", "Americas"])
elif self.state == "difficulty":
self.draw_menu(screen, "Select Difficulty", ["Easy", "Normal", "Hard"])
elif self.state == "pairs":
self.draw_menu(screen, "Select Number of Pairs", ["4", "6", "8", "10", "12"])
elif self.state == "game":
self.draw_game(screen)
self.check_selected()
if self.found_pairs == self.total_pairs:
self.display_winner(screen)
pygame.display.flip()
pygame.time.wait(4000)
self.running = False
clock.tick(FPS)
pygame.quit()
sys.exit()
# -------------------------------
# Winner Display
# -------------------------------
def display_winner(self, screen):
if self.scores[0] > self.scores[1]:
text = "🏆 Player 1 Wins!"
elif self.scores[1] > self.scores[0]:
text = "🏆 Player 2 Wins!"
else:
text = "🤝 Draw!"
win_text = self.font.render(text, True, (255, 255, 0))
rect = win_text.get_rect(center=(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2))
screen.blit(win_text, rect)
# -------------------------------
# Run the Game
# -------------------------------
if __name__ == "__main__":
game = MemoryGame()
game.run()

23
Nord-Amerika.txt Executable file
View File

@ -0,0 +1,23 @@
Kanada Ottawa
USA Washington
Mexiko Mexiko-Stadt
Bahamas Nassau
Kuba Havanna
Jamaika Kingston
Haiti Port-au-Prince
Dominikanische-Republik Santa-Domingo
Nervige Inseln:
St.Kitts-und-Nevis Basseterre
Antigua-und-Barbuda Saint-John´s
Dominica Roseau
St.Lucia Castries
Barbados Bridgetown
Grenada St.George´s
Trinidad-und-Tobago Port-of-Spain
Saint-Vincent-und-die-Grenadinen Kingstown
Anguilla The-Valley
Bermuda Hamilton

14
North-America-Dependent.txt Executable file
View File

@ -0,0 +1,14 @@
Puerto-Rico San-Juan
Greenland Nuuk
Bermuda Hamilton
Groenland Nuuk
Cayman-Islands George-Town
Aruba Oranjestad
Curacao Willemstad
Saint-Martin Marigot
Sint-Maarten Philipsburg
Turks-and-Caicos-Islands Cockburn-Town
British-Virgin-Islands Road-Town
US-Virgin-Islands Charlotte-Amalie
Anguilla The-Valley
Montserrat Plymouth

16
North-America-Major.txt Executable file
View File

@ -0,0 +1,16 @@
Canada Ottawa
United-States Washington-D.C.
Mexico Mexico-City
Guatemala Guatemala-City
Belize Belmopan
El-Salvador San-Salvador
Honduras Tegucigalpa
Nicaragua Managua
Costa-Rica San-Jose
Panama Panama-City
Cuba Havana
Dominican-Republic Santo-Domingo
Haiti Port-au-Prince
Jamaica Kingston
Bahamas Nassau
Trinidad-and-Tobago Port-of-Spain

8
North-America-Small.txt Executable file
View File

@ -0,0 +1,8 @@
Antigua-and-Barbuda Saint-John's
Barbados Bridgetown
Dominica Roseau
Grenada St.-George's
Saint-Kitts-and-Nevis Basseterre
Saint-Lucia Castries
Saint-Vincent-and-the-Grenadines Kingstown
Barbuda Codrington

11
Oceania-Dependent.txt Executable file
View File

@ -0,0 +1,11 @@
American-Samoa Pago-Pago
Cook-Islands Avarua
French-Polynesia Papeete
Guam Hagåtña
New-Caledonia Nouméa
Niue Alofi
Norfolk-Island Kingston
Northern-Mariana-Islands Saipan
Pitcairn-Islands Adamstown
Tokelau Atafu
Wallis-and-Futuna Mata-Utu

8
Oceania-Major.txt Executable file
View File

@ -0,0 +1,8 @@
Australia Canberra
New-Zealand Wellington
Papua-New-Guinea Port-Moresby
Fiji Suva
Solomon-Islands Honiara
Vanuatu Port-Vila
Samoa Apia
Tonga Nuku'alofa

6
Oceania-Minor.txt Executable file
View File

@ -0,0 +1,6 @@
Kiribati Tarawa
Marshall-Islands Majuro
Micronesia Palikir
Nauru Yaren
Palau Ngerulmud
Tuvalu Funafuti

0
Ozeanien.txt Executable file
View File

2
South-America-Dependent.txt Executable file
View File

@ -0,0 +1,2 @@
French-Guiana Cayenne
Falkland-Islands Stanley

12
South-America-Major.txt Executable file
View File

@ -0,0 +1,12 @@
Argentina Buenos-Aires
Bolivia Sucre
Brazil Brasilia
Chile Santiago
Colombia Bogota
Ecuador Quito
Guyana Georgetown
Paraguay Asuncion
Peru Lima
Suriname Paramaribo
Uruguay Montevideo
Venezuela Caracas

3
South-America-Small.txt Executable file
View File

@ -0,0 +1,3 @@
Suriname Paramaribo
Guyana Georgetown
Uruguay Montevideo

334
global_match_memory.py Normal file
View File

@ -0,0 +1,334 @@
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

BIN
images/GlobalMatch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

183
index.html Normal file
View File

@ -0,0 +1,183 @@
from global_match_memory import run_memory
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Global Match Start</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@500;700&family=Nunito:wght@400;700;900&display=swap" rel="stylesheet">
<style>
:root{
/* Farben ans Design angepasst */
--teal-900:#0a2a35; /* sehr dunkelblau-grün */
--teal-700:#0f4a57; /* tiefes Petrol */
--teal-500:#127a8a; /* Mittel-Teal */
--teal-300:#27a7b8; /* helleres Blaugrün */
--gold-400:#f6c04e; /* warmes Gold */
--gold-500:#ffcf5f; /* helleres Gold */
--ink:#0a1a22; /* fast schwarz, bläulich */
--card:#0d2d37cc; /* Glas/Kartenhintergrund */
--text:#eaf7fb; /* fast weiß */
--shadow: 0 15px 50px rgba(0,0,0,.35);
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0; font-family:"Nunito", system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
background: radial-gradient(1200px 800px at 70% -10%, #174058 0%, var(--teal-900) 60%) fixed;
color:var(--text);
overflow:hidden;
}
/* Sterne im Hintergrund */
.stars, .stars:after{
content:""; position:absolute; inset:0; pointer-events:none; background-repeat:repeat; background-size:600px 600px;
background-image: radial-gradient(2px 2px at 20px 30px, rgba(255,255,255,.7) 40%, transparent 41%),
radial-gradient(1.5px 1.5px at 200px 120px, rgba(255,255,255,.4) 40%, transparent 41%),
radial-gradient(1.5px 1.5px at 340px 260px, rgba(255,255,255,.6) 40%, transparent 41%),
radial-gradient(2px 2px at 520px 420px, rgba(255,255,255,.5) 40%, transparent 41%),
radial-gradient(1.5px 1.5px at 560px 90px, rgba(255,255,255,.35) 40%, transparent 41%);
animation: twinkle 12s linear infinite;
}
.stars:after{ transform:scale(1.6); opacity:.6; filter:blur(.3px); animation-duration:18s }
@keyframes twinkle{ from{background-position:0 0} to{background-position:600px 600px} }
/* Container */
.wrap{ position:relative; z-index:10; height:100%; display:grid; place-items:center; padding: clamp(16px, 3vw, 40px); }
.card{
width:min(920px, 92vw); text-align:center; padding:clamp(20px, 4vw, 40px);
background: linear-gradient(180deg, #0f3040cc 0%, #0a2732cc 100%);
border:2px solid rgba(255,255,255,.08); border-radius:28px; box-shadow:var(--shadow);
backdrop-filter: blur(8px);
}
/* Logo/Title */
.brand{ display:inline-grid; gap:.6rem; justify-items:center; margin-bottom: clamp(16px, 3vw, 22px); }
.logo{
display:flex; align-items:center; justify-content:center; gap:12px;
font-family:"Fredoka", sans-serif; font-weight:900; letter-spacing:.5px; text-transform:uppercase;
filter: drop-shadow(0 8px 0 rgba(0,0,0,.25));
}
.logo-top{ font-size: clamp(40px, 8vw, 68px); color:#2ad0e1; text-shadow: 0 3px 0 #08303b, 0 10px 28px rgba(0,0,0,.6); }
.logo-bottom{ font-size: clamp(36px, 7vw, 62px); color:var(--gold-500); text-shadow: 0 3px 0 #1a2b35, 0 10px 28px rgba(0,0,0,.6); margin-top:-12px; }
.subtitle{ opacity:.9; font-size: clamp(14px, 2.2vw, 18px); margin-top:.2rem }
/* Auswahl */
.controls{ display:grid; gap:18px; place-items:center; margin: clamp(10px, 3vw, 22px) 0; }
.segmented{
display:inline-flex; background:#08232d; border:2px solid #0b3a48; border-radius:16px; overflow:hidden;
}
.segmented input{ display:none }
.segmented label{
padding:12px 18px; cursor:pointer; min-width:128px; font-weight:800; letter-spacing:.3px;
color:#b7e5ec; transition:.25s transform ease, .25s background, .25s color;
}
.segmented label:hover{ transform:translateY(-1px) }
.segmented input:checked + label{
background: linear-gradient(180deg, #21a8bb 0%, #11788a 100%);
color:#06151a;
}
.start{
--h: clamp(46px, 5.4vw, 58px);
height:var(--h); padding:0 28px; border-radius:18px; font-weight:900; font-size:clamp(16px, 2.4vw, 20px);
border:0; cursor:pointer; letter-spacing:.4px; color:#0b1a1f;
background: linear-gradient(180deg, var(--gold-500), var(--gold-400));
box-shadow: 0 10px 0 #b48a2c, 0 18px 30px rgba(0,0,0,.35);
transition: transform .06s ease, box-shadow .06s ease, filter .2s;
}
.start:active{ transform:translateY(3px); box-shadow:0 7px 0 #b48a2c, 0 10px 20px rgba(0,0,0,.35) }
.start:disabled{ filter:grayscale(.5) brightness(.75); cursor:not-allowed }
/* Wellen 4 Layer, parallax & endloses "Schwimmen" */
.start:active{transform:translateY(3px);box-shadow:0 7px 0 #b48a2c,0 10px 20px rgba(0,0,0,.35)}
.waves{position:absolute;bottom:0;left:0;width:100%;height:52vh;overflow:hidden;/* weiche Oberkante gegen Spitzen */-webkit-mask-image:linear-gradient(to top, black 92%, transparent 100%);mask-image:linear-gradient(to top, black 92%, transparent 100%);}
.wave{position:absolute;bottom:0;left:0;width:220%;height:100%;background-repeat:repeat-x;background-size:50% 100%;animation:swim var(--dur) linear infinite;filter:blur(.2px);}
/* Einheitlich fließende Wellen, gleiche Höhe und Übergang */
.wave.one{--dur:36s;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 200" preserveAspectRatio="none"><path fill="%230b3a48" d="M0 120 C 150 80, 300 160, 450 120 S 750 80, 900 120 S 1050 160, 1200 120 V200 H0 Z"/></svg>');opacity:.9;z-index:1;}
.wave.two{--dur:28s;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 200" preserveAspectRatio="none"><path fill="%23115869" d="M0 120 C 150 85, 300 155, 450 120 S 750 85, 900 120 S 1050 155, 1200 120 V200 H0 Z"/></svg>');opacity:.85;z-index:2;mix-blend-mode:screen;}
.wave.three{--dur:22s;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 200" preserveAspectRatio="none"><path fill="%2327a7b8" d="M0 120 C 150 90, 300 150, 450 120 S 750 90, 900 120 S 1050 150, 1200 120 V200 H0 Z"/></svg>');opacity:.9;z-index:3;mix-blend-mode:screen;}
.wave.four{--dur:18s;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 200" preserveAspectRatio="none"><path fill="%23f6c04e" d="M0 120 C 150 95, 300 145, 450 120 S 750 95, 900 120 S 1050 145, 1200 120 V200 H0 Z"/></svg>');opacity:.82;z-index:4;}
@keyframes swim{from{transform:translateX(0)}to{transform:translateX(-50%)}}to{transform:translateX(-50%)}}
footer{position:absolute;bottom:10px;left:0;right:0;text-align:center;opacity:.7;font-size:12px;z-index:5;}
/* Kleine leuchtende Kugeln (Planeten) */
.orb{ position:absolute; border-radius:50%; background: radial-gradient(circle at 30% 30%, #fff8 0, #fff2 12%, #fbe38b 20%, #f6c04e 55%, #c79226 100%); box-shadow:0 0 40px #f6c04e55; filter:saturate(120%); }
.orb.one{ width:26vmin; height:26vmin; right:8vw; top:10vh; opacity:.9; animation: float 12s ease-in-out infinite }
.orb.two{ width:10vmin; height:10vmin; left:6vw; bottom:38vh; opacity:.8; animation: float 16s ease-in-out infinite reverse }
@keyframes float{ 0%,100%{ transform: translateY(-6px)} 50%{ transform: translateY(6px)} }
footer{ position:absolute; bottom:10px; left:0; right:0; text-align:center; opacity:.7; font-size:12px }
/* Responsiveness tweak */
@media (max-width:520px){ .segmented label{ min-width:110px; padding:10px 14px } }
</style>
</head>
<body>
<div class="stars"></div>
<div class="orb one"></div>
<div class="orb two"></div>
<main class="wrap">
<section class="card" role="region" aria-labelledby="title">
<div class="brand">
<!-- Logo als Text du kannst hier optional ein PNG/SVG einfügen -->
<div class="logo" aria-hidden="true">
<div class="logo-top">GLO
<svg width="52" height="52" viewBox="0 0 100 100" style="vertical-align:middle">
<defs><radialGradient id="g" cx="50%" cy="35%" r="60%"><stop offset="0%" stop-color="#9ee7ef"/><stop offset="100%" stop-color="#127a8a"/></radialGradient></defs>
<circle cx="50" cy="50" r="46" fill="url(#g)" stroke="#08303b" stroke-width="6"/>
<path d="M40 30l8 6-3 8-10 4 1 6 7 3 2 10-9 3-8-6 2-9 5-5-2-6 7-5zM60 28l10 4-2 6 6 8-6 4-8-2-3-6 3-6zM64 60l6 4-2 8-8 4-6-4 2-6 8-6z" fill="#0a2a35" fill-opacity=".6"/>
</svg>
BAL</div>
<div class="logo-bottom">MATCH</div>
</div>
<p id="title" class="subtitle">Finde Länder & Hauptstädte allein oder im Duell!</p>
</div>
<div class="controls">
<div class="segmented" role="radiogroup" aria-label="Spielerzahl wählen">
<input type="radio" name="players" id="p1" value="1" checked>
<label for="p1">1 Spieler</label>
<input type="radio" name="players" id="p2" value="2">
<label for="p2">2 Spieler</label>
</div>
<button class="start" id="startBtn" aria-label="Spiel starten">Start</button>
</div>
</section>
</main>
<!-- Wellen-Layer unten -->
<div class="waves">
<div class="wave one"></div>
<div class="wave two"></div>
<div class="wave three"></div>
<div class="wave four"></div>
</div>
<footer>© <span id="y"></span> Global Match</footer>
<script>
// Jahr in Footer
document.getElementById('y').textContent = new Date().getFullYear();
// Start-Button: weiterleiten zum Spiel (game.html anpassen)
document.getElementById('startBtn').addEventListener('click', () => {
const players = document.querySelector('input[name="players"]:checked').value;
// Falls du eine andere Zielseite nutzt, ersetze "game.html"
window.location.href = `game.html?players=${players}`;
});
</script>
</body>
</html>