Skip to content

Commit

Permalink
Added Merlin to examples
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Dec 13, 2023
1 parent a85edb7 commit c6aef4b
Showing 1 changed file with 173 additions and 0 deletions.
173 changes: 173 additions & 0 deletions examples/merlin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
from __future__ import annotations

import random
from datetime import timedelta
from time import monotonic

from textual import events
from textual.app import App, ComposeResult
from textual.containers import Grid
from textual.reactive import var
from textual.renderables.gradient import LinearGradient
from textual.widget import Widget
from textual.widgets import Digits, Label, Switch

# A nice rainbow of colors.
COLORS = [
"#881177",
"#aa3355",
"#cc6666",
"#ee9944",
"#eedd00",
"#99dd55",
"#44dd88",
"#22ccbb",
"#00bbcc",
"#0099cc",
"#3366bb",
"#663399",
]


# Maps a switch number on to other switch numbers, which should be toggled.
TOGGLES: dict[int, tuple[int, ...]] = {
1: (2, 4, 5),
2: (1, 3),
3: (2, 5, 6),
4: (1, 7),
5: (2, 4, 6, 8),
6: (3, 9),
7: (4, 5, 8),
8: (7, 9),
9: (5, 6, 8),
}


class LabelSwitch(Widget):
"""Switch with a numeric label."""

DEFAULT_CSS = """
LabelSwitch Label {
text-align: center;
width: 1fr;
text-style: bold;
}
LabelSwitch Label#label-5 {
color: $text-disabled;
}
"""

def __init__(self, switch_no: int) -> None:
self.switch_no = switch_no
super().__init__()

def compose(self) -> ComposeResult:
"""Compose the label and a switch."""
yield Label(str(self.switch_no), id=f"label-{self.switch_no}")
yield Switch(id=f"switch-{self.switch_no}", name=str(self.switch_no))


class Timer(Digits):
"""Displays a timer that stops when you win."""

DEFAULT_CSS = """
Timer {
text-align: center;
width: auto;
margin: 2 8;
color: $warning;
}
"""
start_time = var(0.0)
running = var(True)

def on_mount(self) -> None:
"""Start the timer on mount."""
self.start_time = monotonic()
self.set_interval(1, self.tick)
self.tick()

def tick(self) -> None:
"""Called from `set_interval` to update the clock."""
if self.start_time == 0 or not self.running:
return
time_elapsed = timedelta(seconds=int(monotonic() - self.start_time))
self.update(str(time_elapsed))


class MerlinApp(App):
"""A simple reproduction of one game on the Merlin hand held console."""

CSS = """
Screen {
align: center middle;
}
Screen.-win {
background: transparent;
}
Screen.-win Timer {
color: $success;
}
Grid {
width: auto;
height: auto;
border: thick $primary;
padding: 1 2;
grid-size: 3 3;
grid-rows: auto;
grid-columns: auto;
grid-gutter: 1 1;
background: $surface;
}
"""

def render(self) -> LinearGradient:
"""Renders a gradient, when the background is transparent."""
stops = [(i / (len(COLORS) - 1), c) for i, c in enumerate(COLORS)]
return LinearGradient(30.0, stops)

def compose(self) -> ComposeResult:
"""Compose a timer, and a grid of 9 switches."""
yield Timer()
with Grid():
for switch in (7, 8, 9, 4, 5, 6, 1, 2, 3):
yield LabelSwitch(switch)

def on_mount(self) -> None:
"""Randomize the switches on mount."""
for switch_no in range(1, 10):
if random.randint(0, 1):
self.query_one(f"#switch-{switch_no}", Switch).toggle()

def check_win(self) -> bool:
"""Check for a win."""
on_switches = {
int(switch.name or "0") for switch in self.query(Switch) if switch.value
}
return on_switches == {1, 2, 3, 4, 6, 7, 8, 9}

def on_switch_changed(self, event: Switch.Changed) -> None:
"""Called when a switch is toggled."""
# The switch that was pressed
switch_no = int(event.switch.name or "0")
# Also toggle corresponding switches
with self.prevent(Switch.Changed):
for toggle_no in TOGGLES[switch_no]:
self.query_one(f"#switch-{toggle_no}", Switch).toggle()
# Check the win
if self.check_win():
self.query_one("Screen").add_class("-win")
self.query_one(Timer).running = False

def on_key(self, event: events.Key) -> None:
"""Maps switches to keys, so we can use the keyboard as well."""
if event.character and event.character.isdigit():
self.query_one(f"#switch-{event.character}", Switch).toggle()


if __name__ == "__main__":
MerlinApp().run()

0 comments on commit c6aef4b

Please sign in to comment.