class Vec(pygame.math.Vector2):
+ # don't want no dot product
def __mul__(self, other):
return super().elementwise().__mul__(other)
if pygame.mixer.get_busy():
return
- freq, volume, length_sec = 432 * 2 ** 1.5, 0.125, 1.5
+ freq, volume, length_sec = 432 * 2 ** (5 / 4), 0.125, 1.5
SAMPLERATE, FORMAT, CHANNELS = pygame.mixer.get_init()
bio = BytesIO()
class Door:
OPEN = 0
CLOSED = 100
+ OPEN_DIRECTION = -1
+ CLOSE_DIRECTION = 1
BACKGROUND = "black"
FOREGROUND = "brown"
BUTTON_COLOR = "black"
DEFAULT_STATE = CLOSED
OPEN_TIMEOUT_SEC = 5
- def __init__(self, rect):
- self.rect = rect
+ def __init__(self, elevator, surf_size, level, num_levels):
+ self.elevator = elevator
+ pad = Door.PADDING * surf_size
+ size = Vec(Door.WIDTH, 1 / num_levels) * surf_size
+ self.level = level
+ self.rect = Rect(
+ pad + Vec(0, size[1] * (num_levels - 1 - level)), size - 2 * pad
+ )
self.button_rect = Rect(
- rect.topleft + self.BUTTON_OFFSET * rect.size,
- Vec(rect.height, rect.height) // 3,
+ self.rect.topleft + self.BUTTON_OFFSET * self.rect.size,
+ Vec(self.rect.height, self.rect.height) // 3,
)
self.state = self.DEFAULT_STATE
self.direction = 0
self.timeout = None
- @classmethod
- def get_doors(cls, surf_size, num_levels):
- pad = cls.PADDING * surf_size
- size = Vec(cls.WIDTH, 1 / num_levels) * surf_size
- return [
- cls(Rect(pad + Vec(0, size[1] * (num_levels - 1 - i)), size - 2 * pad))
- for i in range(num_levels)
- ]
-
def open(self):
- if self.direction == 1:
- return
- self.direction = -1
+ """
+ Open the door, even while closing it
+ """
+ self.direction = self.OPEN_DIRECTION
def close(self):
- if self.direction == -1:
- return
- self.direction = 1
- self.timeout = None
+ """
+ Close the door. It must be open.
+ """
+ if self.state == self.OPEN:
+ self.direction = self.CLOSE_DIRECTION
+ self.timeout = None
def update(self):
+ """
+ If a timeout is set and expired, toggle the door.
+ If a direction is set, move the door, and check if we're done opening/closing
+ """
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
+ if self.state == self.CLOSED:
+ self.direction = self.OPEN_DIRECTION
+ elif self.state == self.OPEN:
+ self.direction = self.CLOSE_DIRECTION
dirty = True
self.state += self.direction
if self.direction:
def draw(self, surf):
# clean me up!
subsurf = surf.subsurface(self.rect)
- subsurf.fill(self.BACKGROUND)
+ level, mod = divmod(self.elevator.current_level, Elevator.LEVEL_STEPS)
+ if mod == 0 and level == self.level and self.elevator.destination in (None, level):
+ background = Elevator.CABIN_COLOR
+ else:
+ background = self.BACKGROUND
+ subsurf.fill(background)
left_open = self.DOOR_MARGIN[0]
left_closed = (1 - self.DOOR_GAP) / 2
right_closed = (1 + self.DOOR_GAP) / 2
LEVEL_STEPS = 100000
SPEED = 800
- def __init__(self, doors):
- self.doors = doors
+ def __init__(self, surf_size, num_levels):
+ self.doors = [Door(self, surf_size, i, num_levels) for i in range(num_levels)]
self.current_level = 0
self.destination = None
self.queued_dest = None
+ self.call_queue = []
+
+ def pick_destination(self):
+ """
+ If not all doors are closed, bail.
+ If we have a queued_dest, go there
+ If we have something on the call_queue, go there
+ """
+ if not all(door.state == door.CLOSED for door in self.doors):
+ return False
+ if self.queued_dest is not None:
+ self.destination = self.queued_dest
+ while self.destination in self.call_queue:
+ self.call_queue.remove(self.destination)
+ self.queued_dest = None
+ return True
+ elif len(self.call_queue) > 0:
+ self.destination = self.call_queue.pop(0)
+ if self.destination == self.queued_dest:
+ self.destination = None
+ return True
+ return False
def update(self):
+ """
+ If no destination is available, try picking one
+ If we arrived at a destination, open the door and discard the destination
+ Also move the cabin.
+ """
if self.destination is None:
- if all(door.state == door.CLOSED for door in self.doors):
- self.destination = self.queued_dest
- self.queued_dest = None
- return False
+ return self.pick_destination()
dest = self.destination * self.LEVEL_STEPS
if dest == self.current_level:
state = self.doors[self.destination].state
)
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)
+ pygame.draw.line(
+ surf, self.CABLE_COLOR, rect.midtop, (rect.centerx, cabin_rect.top)
+ )
def goto(self, level):
- self.queued_dest = level
+ """
+ Set a queued_dest if no destination or queued_dest is set.
+ """
+ if self.destination is None and self.queued_dest is None:
+ self.queued_dest = level
- def open(self):
+ def get_whole_level(self):
level, mod = divmod(self.current_level, self.LEVEL_STEPS)
- if mod == 0:
- self.doors[level].open()
+ if mod == 0 and self.destination in (None, level):
+ return level
+ return None
+
+ def call_from(self, call_level):
+ """
+ If we are at the destination and the door is open, disregard
+ If level is not already a destination, add it to the call_queue
+ """
+ if self.doors[call_level].state == Door.OPEN:
+ return
+ level_queue = {
+ self.get_whole_level(), *self.call_queue, self.queued_dest, self.destination
+ }
+ if call_level not in level_queue:
+ self.call_queue.append(call_level)
+
+ def open(self):
+ """
+ If we are on a whole level, open the door
+ """
+ level = self.get_whole_level()
+ if level is None:
+ return
+ self.doors[level].open()
def close(self):
- level, mod = divmod(self.current_level, self.LEVEL_STEPS)
- if mod == 0:
- self.doors[level].close()
+ """
+ If we are on a whole level, close the door
+ """
+ level = self.get_whole_level()
+ if level is None:
+ return
+ self.doors[level].close()
class ElevatorPanel:
BUTTON_COLOR = "navajowhite"
LABEL_COLOR = "black"
ALARM_COLOR = "goldenrod"
+ GLOW_COLOR = "red"
BUTTON_SIZE = 0.9
- def __init__(self, surf_size, num_levels):
- self.num_levels = num_levels
+ def __init__(self, elevator, surf_size, num_levels):
+ self.elevator = elevator
self.outer_rect = Rect(*(v * surf_size for v in self.OUTER_RECT))
+ self.num_levels = num_levels
inner_margin = ((1, 1) - self.INNER_SIZE) / 2
self.inner_rect = Rect(
self.outer_rect.topleft + inner_margin * self.outer_rect.size,
self.INNER_SIZE * self.outer_rect.size,
)
- num_buttons = self.num_levels + 1
+ num_buttons = 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
fs,
(button.centerx - fs_size[0] // 2, button.centery - fs_size[1] // 2)
)
+ if (
+ self.elevator.queued_dest is None and self.elevator.destination == i - 1
+ ) or self.elevator.queued_dest == i - 1:
+ pygame.draw.rect(surf, self.GLOW_COLOR, button, 2)
for shape in BELL:
vector_draw(surf, self.buttons[0], shape)
class ElevatorApp:
FPS = 60
SET_MODE_KWARGS = {
- #"flags": pygame.FULLSCREEN,
- "size": (1024, 768),
+ "flags": pygame.FULLSCREEN,
+ #"size": (1024, 768),
}
BACKGROUND = "green4"
self.clock = pygame.time.Clock()
self.num_levels = 5
surf_size = surf.get_size()
- self.elevator = Elevator(Door.get_doors(surf_size, self.num_levels))
- self.elevator_panel = ElevatorPanel(surf_size, self.num_levels)
+ self.elevator = Elevator(surf_size, self.num_levels)
+ self.elevator_panel = ElevatorPanel(self.elevator, surf_size, self.num_levels)
self.last_pos = None
def handle_event(self, ev):
if ev.button == 1:
for i, door in enumerate(self.elevator.doors):
if door.button_rect.collidepoint(ev.pos):
- self.elevator.goto(i)
+ self.elevator.call_from(i)
break
for i, button in enumerate(self.elevator_panel.buttons):
if button.collidepoint(ev.pos):
beep()
elif i - 1 < self.num_levels:
self.elevator.goto(i - 1)
+ self.dirty = True
elif i == self.num_levels + 1:
self.elevator.open()
elif i == self.num_levels + 2: