]> git.mar77i.info Git - elevator/commitdiff
initial commit
authormar77i <mar77i@protonmail.ch>
Sun, 18 Aug 2024 20:42:13 +0000 (22:42 +0200)
committermar77i <mar77i@protonmail.ch>
Sun, 18 Aug 2024 20:42:13 +0000 (22:42 +0200)
.gitignore [new file with mode: 0644]
elevator.py [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ae945fa
--- /dev/null
@@ -0,0 +1,2 @@
+.idea/
+venv/
diff --git a/elevator.py b/elevator.py
new file mode 100755 (executable)
index 0000000..3007e4b
--- /dev/null
@@ -0,0 +1,331 @@
+#!/usr/bin/env python3
+
+import sys
+from functools import partial
+from pathlib import Path
+from time import time
+
+import pygame
+
+
+def scale(a, b):
+    return (int(a[0] * b[0]), int(a[1] * b[1]))
+
+
+class Door:
+    OPEN = 0
+    CLOSED = 100
+    BACKGROUND = "black"
+    FOREGROUND = "brown"
+    BUTTON_COLOR = "black"
+    DOOR_MARGIN = (0.01, 0.05)
+    PADDING = (0.014, 0.022)
+    WIDTH = 0.3
+
+    DOOR_GAP = 0.01
+    BUTTON_X_OFF = 1.02
+
+    DEFAULT_STATE = CLOSED
+    OPEN_TIMEOUT_SEC = 5
+
+    def __init__(self, rect):
+        self.rect = rect
+        self.button_rect = pygame.Rect(
+            (rect.left + self.BUTTON_X_OFF * rect.width, rect.top),
+            (rect.height // 2, rect.height // 2),
+        )
+        self.state = self.DEFAULT_STATE
+        self.direction = 0
+        self.timeout = None
+
+    @classmethod
+    def get_doors(cls, num_levels):
+        pad = ElevatorApp.scale(cls.PADDING)
+        size = ElevatorApp.scale((cls.WIDTH, 1 / num_levels))
+        return [
+            cls(
+                pygame.Rect(
+                    (pad[0], size[1] * i + pad[1]),
+                    (size[0] - 2 * pad[0], size[1] - 2 * pad[1]),
+                )
+            )
+            for i in range(num_levels)
+        ]
+
+    def open(self):
+        self.direction = -1
+
+    def update(self):
+        dirty = self.direction != 0
+        if self.timeout is not None and time() >= self.timeout:
+            self.direction = -1 if self.state == self.CLOSED else 1
+            self.timeout = None
+            dirty = True
+        self.state += self.direction
+        if self.direction:
+            if self.state <= self.OPEN:
+                self.state = self.OPEN
+                self.direction = 0
+                self.timeout = time() + self.OPEN_TIMEOUT_SEC
+            elif self.state >= self.CLOSED:
+                self.state = self.CLOSED
+                self.direction = 0
+        return dirty
+
+    def draw(self, surf):
+        subsurf = surf.subsurface(self.rect)
+        subsurf.fill(self.BACKGROUND)
+        door_scale = partial(scale, subsurf.get_size())
+        left_open = self.DOOR_MARGIN[0]
+        left_closed = (1 - self.DOOR_GAP) / 2
+        right_closed = (1 + self.DOOR_GAP) / 2
+        right_open = 1 - self.DOOR_MARGIN[0]
+
+        door_size = (
+            (1 - 2 * self.DOOR_MARGIN[0] - self.DOOR_GAP) / 2,
+            1 - 2 * self.DOOR_MARGIN[1],
+        )
+
+        state_fract = self.state / self.CLOSED
+        left_current = left_open + (left_closed - left_open) * state_fract
+        right_current = right_open - (right_open - right_closed) * state_fract
+
+        if left_current < door_size[0]:
+            rect = pygame.Rect(
+                door_scale((0, self.DOOR_MARGIN[1])),
+                door_scale((left_current, door_size[1])),
+            )
+        else:
+            rect = pygame.Rect(
+                door_scale((left_current - door_size[0], self.DOOR_MARGIN[1])),
+                door_scale(door_size),
+            )
+        pygame.draw.rect(subsurf, self.FOREGROUND, rect)
+
+        if right_current > (1 - door_size[0]):
+            rect = pygame.Rect(
+                door_scale((right_current, self.DOOR_MARGIN[1])),
+                door_scale((1 - right_current, door_size[1])),
+            )
+        else:
+            rect = pygame.Rect(
+                door_scale((right_current, self.DOOR_MARGIN[1])),
+                door_scale(door_size),
+            )
+        pygame.draw.rect(subsurf, self.FOREGROUND, rect)
+        pygame.draw.rect(surf, self.BUTTON_COLOR, self.button_rect)
+
+
+class Elevator:
+    MARGIN_Y = 0.022
+    OFF_X = 0.37
+    WIDTH = 0.05
+    CABIN_MARGINS = (0.005, 0.01)
+    BACKGROUND = "black"
+    CABIN_COLOR = "darkorange3"
+    CABLE_COLOR = "yellow"
+    LEVEL_STEPS = 100000
+
+    def __init__(self, num_levels):
+        self.num_levels = num_levels
+        self.current_level = 0
+        self.destination = None
+
+    def update(self):
+        if self.destination is None:
+            return False
+        dest = self.destination * self.LEVEL_STEPS
+        self.current_level = (min if dest > self.current_level else max)(
+            dest,
+            self.current_level + 800 * (
+                1 if dest > self.current_level else -1
+            )
+        )
+        return True
+
+    def draw(self, surf):
+        shaft_height = 1 - 2 * self.MARGIN_Y
+        cabin_height = shaft_height / self.num_levels
+        rect = pygame.Rect(
+            ElevatorApp.scale((self.OFF_X, self.MARGIN_Y)),
+            ElevatorApp.scale((self.WIDTH, shaft_height)),
+        )
+        cabin_rect = pygame.Rect(
+            ElevatorApp.scale(
+                (
+                    self.OFF_X + self.CABIN_MARGINS[0],
+                    self.MARGIN_Y + self.CABIN_MARGINS[1] + cabin_height * (
+                        self.num_levels - 1 -
+                        self.current_level / self.LEVEL_STEPS
+                    ),
+                )
+            ),
+            ElevatorApp.scale(
+                (
+                    self.WIDTH - 2 * self.CABIN_MARGINS[0],
+                    cabin_height - 2 * self.CABIN_MARGINS[1],
+                )
+            ),
+        )
+        pygame.draw.rect(surf, self.BACKGROUND, rect)
+        pygame.draw.rect(surf, self.CABIN_COLOR, cabin_rect)
+        pygame.draw.line(surf, self.CABLE_COLOR, rect.midtop, cabin_rect.midtop)
+
+
+class ElevatorPanel:
+    OUTER_RECT = ((0.53, 0.1), (0.4, 0.8))
+    OUTER_COLOR = "darkorange2"
+    INNER_COLOR = "burlywood4"
+    INNER_SIZE = (0.8, 0.8)
+    BUTTON_COLOR = "navajowhite"
+    LABEL_COLOR = "black"
+    ALARM_COLOR = "goldenrod"
+    BUTTON_SIZE = 0.9
+
+    def __init__(self, num_levels):
+        self.num_levels = num_levels
+        self.outer_rect = pygame.Rect(
+            ElevatorApp.scale(self.OUTER_RECT[0]),
+            ElevatorApp.scale(self.OUTER_RECT[1]),
+        )
+        inner_margin = ((1 - self.INNER_SIZE[0]) / 2, (1 - self.INNER_SIZE[1]) / 2)
+        self.inner_rect = pygame.Rect(
+            (
+                self.outer_rect.left + self.outer_rect.width * inner_margin[0],
+                self.outer_rect.top + self.outer_rect.height * inner_margin[1],
+            ),
+            scale(self.outer_rect.size, self.INNER_SIZE)
+        )
+        num_buttons = self.num_levels + 1
+        button_outer_size = int(self.inner_rect.height / num_buttons)
+        button_inner_size = int(self.inner_rect.height * self.BUTTON_SIZE / num_buttons)
+        button_space = button_outer_size - button_inner_size
+        self.font = pygame.font.Font(None, size=button_outer_size * 2 // 3)
+        self.buttons = [
+            pygame.Rect(
+                (
+                    self.inner_rect.centerx - button_inner_size // 2,
+                    self.inner_rect.top
+                    + int((self.inner_rect.height - button_space) / num_buttons) * (num_buttons - 1 - i)
+                    + button_space
+                ),
+                (button_inner_size, button_inner_size),
+            ) for i in range(num_buttons)
+        ]
+        last_topleft = self.buttons[0].topleft
+        self.buttons.extend(
+            (
+                pygame.Rect(
+                    (last_topleft[0] - button_outer_size, last_topleft[1]),
+                    (button_inner_size, button_inner_size)
+                ),
+                pygame.Rect(
+                    (last_topleft[0] + button_outer_size, last_topleft[1]),
+                    (button_inner_size, button_inner_size)
+                ),
+            )
+        )
+
+    def draw(self, surf):
+        pygame.draw.rect(surf, self.OUTER_COLOR, self.outer_rect)
+        pygame.draw.rect(surf, self.INNER_COLOR, self.inner_rect)
+        for i, button in enumerate(self.buttons):
+            pygame.draw.rect(surf, self.BUTTON_COLOR, button)
+            if i == 0 or i > self.num_levels:
+                continue
+            fs = self.font.render(str(i - 1), True, self.LABEL_COLOR)
+            fs_size = fs.get_size()
+            surf.blit(
+                fs,
+                (button.centerx - fs_size[0] // 2, button.centery - fs_size[1] // 2)
+            )
+        alarm = self.buttons[0]
+        bell_orig = pygame.image.load(Path(__file__).parent / "assets" / "bell.png")
+        bell = pygame.transform.scale(bell_orig, (alarm.width // 2, alarm.height // 2))
+        bell_rect = bell.get_rect()
+        surf.blit(
+            bell, (alarm.centerx - bell_rect.centerx, alarm.centery - bell_rect.centery)
+        )
+        for button, s in zip(self.buttons[self.num_levels + 1:], ("<|>", ">|<")):
+            fs = self.font.render(s, True, self.LABEL_COLOR)
+            fs_size = fs.get_size()
+            surf.blit(
+                fs,
+                (button.centerx - fs_size[0] // 2, button.centery - fs_size[1] // 2)
+            )
+
+
+class ElevatorApp:
+    FPS = 60
+    SET_MODE_KWARGS = {
+        "flags": pygame.FULLSCREEN,
+        #"size": (1024, 768),
+    }
+    BACKGROUND = "green4"
+
+    def __init__(self):
+        pygame.init()
+        pygame.display.set_mode(**self.SET_MODE_KWARGS)
+        pygame.display.set_caption(sys.argv[0])
+        self.running = True
+        self.dirty = False
+        self.clock = pygame.time.Clock()
+        self.num_levels = 5
+        self.doors = Door.get_doors(self.num_levels)
+        self.elevator = Elevator(self.num_levels)
+        self.elevator_panel = ElevatorPanel(self.num_levels)
+        self.last_pos = None
+
+    @staticmethod
+    def scale(value):
+        size = pygame.display.get_surface().get_size()
+        return int(size[0] * value[0]), int(size[1] * value[1])
+
+    def handle_event(self, ev):
+        if ev.type == pygame.QUIT:
+            self.running = False
+        elif ev.type == pygame.KEYDOWN:
+            if ev.key == pygame.K_ESCAPE:
+                self.running = False
+        elif ev.type == pygame.MOUSEBUTTONDOWN:
+            if ev.button == 1:
+                for i, door in enumerate(self.doors):
+                    if door.button_rect.collidepoint(ev.pos):
+                        self.elevator.destination = self.num_levels - 1 - i
+            self.last_pos = ev.pos
+        elif ev.type == pygame.MOUSEBUTTONUP:
+            self.last_pos = ev.pos
+        elif ev.type == pygame.MOUSEMOTION:
+            self.last_pos = ev.pos
+        elif ev.type == pygame.WINDOWEXPOSED:
+            self.dirty = True
+
+    def update(self):
+        for door in self.doors:
+            self.dirty |= door.update()
+        self.dirty |= self.elevator.update()
+
+    def draw(self):
+        surf = pygame.display.get_surface()
+        surf.fill(self.BACKGROUND)
+        for door in self.doors:
+            door.draw(surf)
+        self.elevator.draw(surf)
+        self.elevator_panel.draw(surf)
+        pygame.display.update()
+
+    def run(self):
+        while True:
+            for ev in pygame.event.get():
+                self.handle_event(ev)
+                if not self.running:
+                    return
+            self.update()
+            if self.dirty:
+                self.draw()
+                self.dirty = False
+            self.clock.tick(self.FPS)
+
+
+if __name__ == "__main__":
+    ElevatorApp().run()