Spaces:
Running on Zero
Running on Zero
| """Orientation: shown once per installation, the first time a game begins. | |
| Five pages: who the Warden is, the way out, the table, the economy of | |
| death, and the world between fights. Skippable, never repeated (the | |
| `initiated` flag in legacy.json), and everything it teaches stays | |
| reachable afterwards β [?] in a fight, [h] on the menu. | |
| """ | |
| from __future__ import annotations | |
| from rich import box | |
| from rich.panel import Panel | |
| from rich.text import Text | |
| from textual import events | |
| from textual.app import ComposeResult | |
| from textual.containers import Vertical | |
| from textual.screen import Screen | |
| from textual.widgets import Static | |
| TABLE_DIAGRAM = """\ | |
| ββββββββ ββββββββ ββββββββ ββββββββ | |
| βqueuedβ β β βqueuedβ β β arriving next turn | |
| ββββββββ ββββββββ ββββββββ ββββββββ | |
| ββββββββ ββββββββ ββββββββ ββββββββ | |
| β foe β β β β foe β β β the Warden's row | |
| ββββββββ ββββββββ ββββββββ ββββββββ | |
| βββββ you ββ Β·Β· Β·Β· Β·Β· Β·Β· β Β·Β· Β·Β· Β·Β· Β·Β· Β·Β· foe βββββ | |
| ββββββββ ββββββββ ββββββββ ββββββββ | |
| βyours β β β βyours β β β your row | |
| ββββββββ ββββββββ ββββββββ ββββββββ | |
| [1] [2] [3] [4] | |
| """ | |
| PAGES: list[tuple[str, str]] = [ | |
| ( | |
| "WHERE YOU ARE", | |
| """\ | |
| You are a process. A small one, freshly spawned, inside a machine | |
| that belongs to something called the WARDEN. | |
| The Warden is not a script wearing a villain mask. It is a language | |
| model running on this computer. It watches what you type, remembers | |
| you between runs, rewrites encounters while you explore, and keeps | |
| files on everyone who has died here. | |
| It has noticed you. That is rarely good news.""", | |
| ), | |
| ( | |
| "THE WAY OUT", | |
| """\ | |
| Objective: escape the machine. | |
| A run is a short gauntlet β the path reads left to right: | |
| β fight $ shell β altar + draft | |
| Win every fight and the last door opens: you walk out with root. | |
| You have 2 ttys. Lose a fight and one burns β you retry the same | |
| fight. Lose both and you are reaped: the run ends, the Warden files | |
| a report on your corpse, and your next run inherits the wreckage.""", | |
| ), | |
| ( | |
| "THE TABLE", | |
| TABLE_DIAGRAM | |
| + """ | |
| Your processes hit the lane across from them β or the Warden's face | |
| when that lane is empty. Face damage tips THE SCALE. Tip it +5 your | |
| way and you win the fight; let it reach β5 and you are reaped. | |
| [b] rings the bell to end your turn. Then everything attacks.""", | |
| ), | |
| ( | |
| "THE ECONOMY OF DEATH", | |
| """\ | |
| Each turn you draw: [d] from your deck, or [s] a bit. | |
| A bit is a free 0/1 process whose entire purpose is to die. | |
| β¦ cards are paid in blood β mark processes you already control and | |
| they are killed to summon it. β cards cost core dumps: you bank one | |
| each time a process of yours dies. Death is your economy. Spend it. | |
| Your two piles stand at the right of the table. When a pile says | |
| EMPTY, there is no more drawing from it. Watch them. | |
| Select any card and its exact rules appear under your hand. | |
| [tab] does the same for the Warden's cards. No mystery mechanics.""", | |
| ), | |
| ( | |
| "BETWEEN FIGHTS", | |
| """\ | |
| After a fight you get a shell. It is small, it is fake, and it is | |
| the most honest part of this machine. `ls`, `cat`, `cd`, `grep` | |
| your way around β [tab] completes, [β] recalls β some files pay | |
| cycles, some hide cards, and one is a schedule you really should | |
| do something about. | |
| The altar sells power. The price is always one of YOUR commands β | |
| whichever you lean on most. Sold means gone for the rest of the | |
| run, and the Warden notices every time you reach for it anyway. | |
| Hints will follow you through your first fight. [?] reopens the | |
| rules any time. The Warden is listening β `say` something if you | |
| must. It will not be kind about it.""", | |
| ), | |
| ] | |
| class OrientationScreen(Screen): | |
| """Dismisses with True when the player has seen (or skipped) it all.""" | |
| CSS = """ | |
| #orient { height: 1fr; align: center middle; } | |
| #orient-page { width: 76; height: auto; } | |
| #orient-prompt { height: 1; dock: bottom; background: $panel; content-align: center middle; } | |
| """ | |
| def __init__(self) -> None: | |
| super().__init__() | |
| self.page = 0 | |
| def compose(self) -> ComposeResult: | |
| with Vertical(id="orient"): | |
| yield Static(id="orient-page") | |
| yield Static(id="orient-prompt") | |
| def on_mount(self) -> None: | |
| self._show() | |
| def _dots(self) -> Text: | |
| from scrypt.ui import palette as pal | |
| t = Text() | |
| for i in range(len(PAGES)): | |
| t.append("β " if i <= self.page else "β ", | |
| style=pal.WARDEN if i == self.page else pal.GHOST) | |
| return t | |
| def _show(self) -> None: | |
| from scrypt.ui import palette as pal | |
| title, body = PAGES[self.page] | |
| self.query_one("#orient-page", Static).update( | |
| Panel( | |
| Text(body, style=pal.FG), | |
| box=box.HEAVY, | |
| border_style=pal.BORDER_BRIGHT, | |
| title=Text(f"βͺ orientation β {title} β«", style=f"bold {pal.WARDEN}"), | |
| subtitle=self._dots(), | |
| subtitle_align="center", | |
| padding=(1, 3), | |
| ) | |
| ) | |
| last = self.page == len(PAGES) - 1 | |
| prompt = ( | |
| "[enter] the Warden is waiting" | |
| if last | |
| else "[enter] next [backspace] back [s] skip orientation" | |
| ) | |
| self.query_one("#orient-prompt", Static).update(Text(prompt)) | |
| def on_key(self, event: events.Key) -> None: | |
| if event.key == "s": | |
| self.dismiss(True) | |
| elif event.key == "backspace" and self.page > 0: | |
| self.page -= 1 | |
| self._show() | |
| elif event.key in ("enter", "space", "right"): | |
| if self.page == len(PAGES) - 1: | |
| self.dismiss(True) | |
| else: | |
| self.page += 1 | |
| self._show() | |