From: mar77i Date: Sun, 18 Aug 2024 20:42:13 +0000 (+0200) Subject: initial commit X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=51ef64c4b24a51ca44941ab69bc80116aa5ec940;p=elevator initial commit --- 51ef64c4b24a51ca44941ab69bc80116aa5ec940 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae945fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +venv/ diff --git a/elevator.py b/elevator.py new file mode 100755 index 0000000..3007e4b --- /dev/null +++ b/elevator.py @@ -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()