Spaces:
Running on Zero
Running on Zero
File size: 6,445 Bytes
9fca766 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | """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()
|