Quellcode für miniworlds_data.stack_world

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)
TOP_COLOR: Color = (220, 60, 60, 255)
TEXT_COLOR: Color = (20, 20, 20, 255)
EMPTY_COLOR: Color = (120, 120, 120, 255)


class _StackBox(Rectangle):
    """A single element box inside a :class:`StackWorld`."""

    def __init__(
        self,
        value: object,
        *,
        world: "StackWorld",
        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 StackWorld(World): """Pixel world that visualizes a LIFO stack growing upward. Elements are pushed onto the top and popped from the top. The topmost box is highlighted in red. Example: :: from miniworlds_data import StackWorld world = StackWorld() world.push(3) world.push(7) world.push(2) print(world.peek()) # 2 print(world.pop()) # 2 world.run() """ def __init__( self, *, capacity: int = 8, box_width: int = 120, box_height: int = 40, 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[_StackBox] = [] width = box_width + margin * 2 height = capacity * box_height + margin * 2 super().__init__(width, height) self.background.fill_color = background self._top_label = Text((margin, 2), "top", world=self) self._top_label.color = EMPTY_COLOR self._top_label.font_size = 12 self._top_label.is_static = True @property def size(self) -> int: # type: ignore[override] """Number of elements currently on the stack.""" 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 peek(self) -> object: """Return the top element without removing it. Raises: IndexError: when the stack is empty. """ if self.is_empty(): raise IndexError("peek from empty stack") return self._boxes[-1].value
[Doku] def values(self) -> List[object]: """Return the current elements, bottom-to-top order.""" return [box.value for box in self._boxes]
[Doku] def push(self, value: object) -> None: """Push ``value`` onto the top of the stack. Raises: OverflowError: when the stack is already at capacity. """ if self.is_full(): raise OverflowError(f"stack overflow (capacity {self._capacity})") box = _StackBox( value, world=self, color=DEFAULT_COLOR, width=self._box_width, height=self._box_height, ) self._boxes.append(box) self._relayout()
[Doku] def pop(self) -> object: """Remove and return the top element. Raises: IndexError: when the stack is empty. """ if self.is_empty(): raise IndexError("pop from empty stack") box = self._boxes.pop() box.remove_label() box.remove() self._relayout() return box.value
[Doku] def clear(self) -> None: # type: ignore[override] """Remove every element from the stack.""" for box in self._boxes: box.remove_label() box.remove() self._boxes.clear() self._relayout()
def _relayout(self) -> None: x = self._margin for index, box in enumerate(self._boxes): y = self.height - self._margin - (index + 1) * self._box_height box.move_to_slot(x, y) box.fill_color = TOP_COLOR if index == len(self._boxes) - 1 else DEFAULT_COLOR self._top_label.text = "top" if self._boxes else "empty"