]> git.mar77i.info Git - zenbook_gui/commitdiff
add vs_memory
authormar77i <mar77i@protonmail.ch>
Sat, 15 Mar 2025 15:48:13 +0000 (16:48 +0100)
committermar77i <mar77i@protonmail.ch>
Sat, 15 Mar 2025 15:48:13 +0000 (16:48 +0100)
vs_memory/vs_memory.py [new file with mode: 0755]

diff --git a/vs_memory/vs_memory.py b/vs_memory/vs_memory.py
new file mode 100755 (executable)
index 0000000..c09cf50
--- /dev/null
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3
+
+import re
+import sys
+from pathlib import Path
+from secrets import choice, randbelow
+from time import time
+
+import pygame
+
+sys.path.append(str(Path(__file__).parents[1]))
+
+from ui import Button, Child, Label, Root, TextInput
+
+
+class Team:
+    def __init__(self, path):
+        self.name = path.stem
+        self.logo = pygame.image.load(path)
+
+
+class MemoryDestination(Child):
+    def __init__(self, parent, rect, enabled=True):
+        super().__init__(parent, enabled)
+        self.rect = rect
+        self.team = None
+        self.team_visible = True
+        self.highlight = False
+
+    def draw(self):
+        if self.team is not None and self.team_visible:
+            self.surf.blit(self.team.logo, self.rect.topleft)
+        else:
+            pygame.draw.rect(
+                self.surf, "red" if self.highlight else "dimgray", self.rect, 16
+            )
+
+
+class MemoryCard(Child):
+    def __init__(self, parent, rect, team, callback, enabled=True):
+        super().__init__(parent, enabled)
+        self.rect = rect
+        self.team = team
+        self.callback = callback
+        self.pushed = None
+        self.frozen = False
+
+    def handle_mousebuttondown(self, ev):
+        if self.frozen or ev.button != 1 or not self.rect.collidepoint(ev.pos):
+            return
+        self.pushed = (ev.pos[0] - self.rect.left, ev.pos[1] - self.rect.top)
+        self.root.stop_event = True
+
+    def update_pos(self, pos):
+        left = pos[0] - self.pushed[0]
+        top = pos[1] - self.pushed[1]
+        if (left, top) != self.rect.topleft:
+            self.dirty = True
+            self.rect.left = left
+            self.rect.top = top
+
+    def handle_mousemotion(self, ev):
+        if self.pushed is None:
+            return
+        self.update_pos(ev.pos)
+        if self.frozen or ev.buttons[0] == 0:
+            self.callback()
+            return
+
+    def handle_mousebuttonup(self, ev):
+        if ev.button != 1 or self.pushed is None:
+            return
+        self.update_pos(ev.pos)
+        self.pushed = None
+        self.callback()
+
+    def draw(self):
+        self.surf.blit(self.team.logo, self.rect.topleft)
+
+
+class VSMemory(Root):
+    BACKGROUND_COLOR = "black"
+    MEMORY_RECT = pygame.Rect((640, 384), (512, 1190))
+    NUMBER_REGEX = re.compile(r"[-+]?\d*(\.\d+)?")
+
+    def get_memory_destinations(self):
+        height = self.surf.get_height()
+        y_cell = (height - 384) // (len(self.teams) // 2 + 1)
+        for y in range(len(self.teams) // 2):
+            y = 384 + y * y_cell
+            yield MemoryDestination(self, pygame.Rect((256, y), (128, 128)), False)
+            yield MemoryDestination(self, pygame.Rect((512, y), (128, 128)), False)
+
+    def get_score(self):
+        return f"Score: {self.failed} failed, {self.successful} successful"
+
+    def __init__(self):
+        self.look_timeout = 5
+        pygame.init()
+        super().__init__(
+            pygame.display.set_mode((0, 0), pygame.FULLSCREEN),
+            pygame.font.Font(None, size=96),
+        )
+        self.teams = tuple(
+            Team(file) for file in (Path(__file__).parent / "logos").iterdir()
+        )
+        assert len(self.teams) < 15  # fits one column of memory destinations
+        self.failed = 0
+        self.successful = 0
+        self.score = Label(
+            self,
+            pygame.Rect((256, 192), (512, 128)),
+            self.get_score(),
+        )
+        self.start_button = Button(
+            self,
+            pygame.Rect((256, 384), (256, 192)),
+            "Start",
+            self.start_round,
+            True,
+        )
+        self.look_timeout_input = TextInput(
+            self,
+            pygame.Rect((640, 416), (512, 128)),
+            self.set_look_timeout,
+            str(self.look_timeout),
+            self.NUMBER_REGEX.fullmatch,
+        )
+        self.end_round_button = Button(
+            self,
+            pygame.Rect((768, self.surf.get_height() // 2 - 128), (256, 256)),
+            "End",
+            self.end_round,
+        )
+        self.end_round_button.enabled = False
+        self.memory_destinations = tuple(self.get_memory_destinations())
+        self.memory_cards = tuple(
+            MemoryCard(
+                self,
+                pygame.Rect(self.MEMORY_RECT.topleft, (128, 128)),
+                team,
+                self.drop_card,
+                False,
+            )
+            for i, team in enumerate(self.teams)
+        )
+        self.started_timer = None
+
+    def start_round(self):
+        self.start_button.enabled = False
+        self.look_timeout_input.enabled = False
+        available_teams = list(self.teams)
+        for child in self.memory_destinations:
+            child.enabled = True
+            child.highlight = False
+            child.team = choice(available_teams)
+            child.team_visible = True
+            available_teams.remove(child.team)
+        self.started_timer = time()
+        self.dirty = True
+
+    @classmethod
+    def place_memory_card(cls, rect):
+        rect.left = cls.MEMORY_RECT.left + randbelow(cls.MEMORY_RECT.width - rect.width)
+        rect.top = cls.MEMORY_RECT.top + randbelow(cls.MEMORY_RECT.height - rect.height)
+
+    def interactive_round(self):
+        for child in self.memory_destinations:
+            child.team_visible = False
+        for memory_card in self.memory_cards:
+            memory_card.enabled = True
+            memory_card.frozen = False
+            self.place_memory_card(memory_card.rect)
+            while any(
+                c.rect.colliderect(memory_card.rect)
+                for c in self.memory_cards if c != memory_card
+            ):
+                self.place_memory_card(memory_card.rect)
+        self.dirty = True
+
+    def end_interactive_round(self, assignments):
+        failed = False
+        for dest, memory_card in assignments.items():
+            memory_card.frozen = True
+            if memory_card.team != dest.team:
+                dest.highlight = True
+                failed = True
+        if failed:
+            self.failed += 1
+        else:
+            self.successful += 1
+        self.score.value = self.get_score()
+        self.end_round_button.enabled = True
+        self.dirty = True
+
+    def end_round(self):
+        self.end_round_button.enabled = False
+        self.start_button.enabled = True
+        for child in (*self.memory_cards, *self.memory_destinations):
+            child.enabled = False
+        self.dirty = True
+
+    def update(self):
+        if (
+            self.started_timer is not None
+            and self.started_timer + self.look_timeout < time()
+        ):
+            self.interactive_round()
+            self.started_timer = None
+
+    def drop_card(self):
+        memory_cards = list(self.memory_cards)
+        assignments = {}
+        for dest in self.memory_destinations:
+            for memory_card in memory_cards:
+                if dest.rect.colliderect(memory_card.rect):
+                    break
+            else:
+                continue
+            assignments[dest] = memory_card
+            memory_cards.remove(memory_card)
+        if len(memory_cards) != 0:
+            return
+        self.end_interactive_round(assignments)
+
+    def set_look_timeout(self, value):
+        self.look_timeout = float(value)
+        self.dirty = True
+
+
+if __name__ == "__main__":
+    VSMemory().run()