from __future__ import annotations
from typing import List, Optional, Tuple
from miniworlds import Rectangle, Text, World
Color = Tuple[int, int, int] | Tuple[int, int, int, int]
DEFAULT_COLOR: Color = (130, 170, 200, 255)
FRONT_COLOR: Color = (220, 60, 60, 255)
BACK_COLOR: Color = (160, 110, 220, 255)
TEXT_COLOR: Color = (20, 20, 20, 255)
EMPTY_COLOR: Color = (120, 120, 120, 255)
class _QueueBox(Rectangle):
"""A single element box inside a :class:`QueueWorld`."""
def __init__(
self,
value: object,
*,
world: "QueueWorld",
color: Color,
width: float,
height: float,
):
super().__init__((0, 0), width, height, world=world)
self.origin = "topleft"
self.fill_color = color
self.border = 1
self.border_color = (255, 255, 255, 255)
self.is_static = True
self.value = value
self._label = Text((0, 0), str(value), world=world)
self._label.origin = "topleft"
self._label.color = TEXT_COLOR
self._label.font_size = max(12, int(height * 0.5))
self._label.is_static = True
def move_to_slot(self, x: float, y: float) -> None:
self.topleft = (x, y)
label_x = x + self.width / 2 - self._label.get_text_width() / 2
label_y = y + self.height / 2 - self._label.font_size / 2
self._label.topleft = (label_x, label_y)
def remove_label(self) -> None:
if self._label.world is not None:
self._label.remove()
[Doku]
class QueueWorld(World):
"""Pixel world that visualizes a FIFO queue growing left-to-right.
Elements are enqueued at the back and dequeued at the front. The front
box is highlighted in red, the back box in purple.
Example:
::
from miniworlds_data import QueueWorld
world = QueueWorld()
world.enqueue(3)
world.enqueue(7)
world.enqueue(2)
print(world.front()) # 3
print(world.dequeue()) # 3
world.run()
"""
def __init__(
self,
*,
capacity: int = 8,
box_width: int = 80,
box_height: int = 50,
margin: int = 10,
background: Color = (245, 245, 245, 255),
):
if capacity <= 0:
raise ValueError("capacity must be > 0")
self._capacity = capacity
self._box_width = box_width
self._box_height = box_height
self._margin = margin
self._boxes: List[_QueueBox] = []
width = capacity * box_width + margin * 2
height = box_height + margin * 2 + 20
super().__init__(width, height)
self.background.fill_color = background
self._front_label = Text((margin, margin + box_height + 2), "front", world=self)
self._front_label.color = EMPTY_COLOR
self._front_label.font_size = 12
self._front_label.is_static = True
@property
def size(self) -> int: # type: ignore[override]
"""Number of elements currently in the queue."""
return len(self._boxes)
def __len__(self) -> int:
return len(self._boxes)
[Doku]
def is_empty(self) -> bool:
return not self._boxes
[Doku]
def is_full(self) -> bool:
return len(self._boxes) >= self._capacity
[Doku]
def front(self) -> object:
"""Return the front element without removing it.
Raises:
IndexError: when the queue is empty.
"""
if self.is_empty():
raise IndexError("front of empty queue")
return self._boxes[0].value
[Doku]
def back(self) -> object:
"""Return the back element without removing it.
Raises:
IndexError: when the queue is empty.
"""
if self.is_empty():
raise IndexError("back of empty queue")
return self._boxes[-1].value
[Doku]
def values(self) -> List[object]:
"""Return the current elements, front-to-back order."""
return [box.value for box in self._boxes]
[Doku]
def enqueue(self, value: object) -> None:
"""Add ``value`` to the back of the queue.
Raises:
OverflowError: when the queue is already at capacity.
"""
if self.is_full():
raise OverflowError(f"queue overflow (capacity {self._capacity})")
box = _QueueBox(
value,
world=self,
color=DEFAULT_COLOR,
width=self._box_width,
height=self._box_height,
)
self._boxes.append(box)
self._relayout()
[Doku]
def dequeue(self) -> object:
"""Remove and return the front element.
Raises:
IndexError: when the queue is empty.
"""
if self.is_empty():
raise IndexError("dequeue from empty queue")
box = self._boxes.pop(0)
box.remove_label()
box.remove()
self._relayout()
return box.value
[Doku]
def clear(self) -> None: # type: ignore[override]
"""Remove every element from the queue."""
for box in self._boxes:
box.remove_label()
box.remove()
self._boxes.clear()
self._relayout()
def _relayout(self) -> None:
y = self._margin
for index, box in enumerate(self._boxes):
x = self._margin + index * self._box_width
box.move_to_slot(x, y)
if index == 0:
box.fill_color = FRONT_COLOR
elif index == len(self._boxes) - 1:
box.fill_color = BACK_COLOR
else:
box.fill_color = DEFAULT_COLOR
self._front_label.text = "front" if self._boxes else "empty"