Initial commit – Global Match Memory
This commit is contained in:
commit
7beeb6fe61
7
.env
Normal file
7
.env
Normal 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
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
10
.idea/Memory GlobalMatch.iml
generated
Normal file
10
.idea/Memory GlobalMatch.iml
generated
Normal 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>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal 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
7
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
6
Africa-Dependent.txt
Executable 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
20
Africa-Major.txt
Executable 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
20
Africa-Minor.txt
Executable 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
5
Asia-Dependent.txt
Executable 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
20
Asia-Major.txt
Executable 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
25
Asia-Minor.txt
Executable 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
13
Europe-Dependent.txt
Executable 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
24
Europe-Major.txt
Executable 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
19
Europe-Minor.txt
Executable 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
327
Finished_Memory_Mouse.py
Executable file
@ -0,0 +1,327 @@
|
|||||||
|
import pygame
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# -------------------------------
|
||||||
|
# Country–Capital 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("Country–Capital 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
23
Nord-Amerika.txt
Executable 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
14
North-America-Dependent.txt
Executable 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
16
North-America-Major.txt
Executable 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
8
North-America-Small.txt
Executable 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
11
Oceania-Dependent.txt
Executable 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
8
Oceania-Major.txt
Executable 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
6
Oceania-Minor.txt
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
Kiribati Tarawa
|
||||||
|
Marshall-Islands Majuro
|
||||||
|
Micronesia Palikir
|
||||||
|
Nauru Yaren
|
||||||
|
Palau Ngerulmud
|
||||||
|
Tuvalu Funafuti
|
||||||
0
Ozeanien.txt
Executable file
0
Ozeanien.txt
Executable file
2
South-America-Dependent.txt
Executable file
2
South-America-Dependent.txt
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
French-Guiana Cayenne
|
||||||
|
Falkland-Islands Stanley
|
||||||
12
South-America-Major.txt
Executable file
12
South-America-Major.txt
Executable 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
3
South-America-Small.txt
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
Suriname Paramaribo
|
||||||
|
Guyana Georgetown
|
||||||
|
Uruguay Montevideo
|
||||||
334
global_match_memory.py
Normal file
334
global_match_memory.py
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
BIN
images/GlobalHintergrund.png
Normal file
BIN
images/GlobalHintergrund.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 430 KiB |
BIN
images/GlobalMatch.png
Normal file
BIN
images/GlobalMatch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
183
index.html
Normal file
183
index.html
Normal 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>
|
||||||
Loading…
x
Reference in New Issue
Block a user