mirror of
https://github.com/System-End/haxmas-day-9.git
synced 2026-04-19 15:18:26 +00:00
init
This commit is contained in:
commit
8fc099ab67
2 changed files with 266 additions and 0 deletions
BIN
__pycache__/main.cpython-313.pyc
Normal file
BIN
__pycache__/main.cpython-313.pyc
Normal file
Binary file not shown.
266
main.py
Normal file
266
main.py
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
import datetime
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Center, Grid, Vertical
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Button, Footer, Header, Label, Static
|
||||
|
||||
|
||||
class DayScreen(Screen):
|
||||
CSS = """
|
||||
DayScreen {
|
||||
align: center middle;
|
||||
background: #0a0015;
|
||||
}
|
||||
|
||||
#dialog {
|
||||
width: 70;
|
||||
height: auto;
|
||||
border: heavy #9d4edd;
|
||||
background: #1a0033;
|
||||
padding: 3;
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
#dialog Label {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 1;
|
||||
color: #e0aaff;
|
||||
}
|
||||
|
||||
#proto-header {
|
||||
color: #c77dff;
|
||||
text-style: bold;
|
||||
margin-bottom: 2;
|
||||
}
|
||||
|
||||
#gift-text {
|
||||
color: #7b2cbf;
|
||||
text-style: bold italic;
|
||||
margin-top: 1;
|
||||
margin-bottom: 2;
|
||||
}
|
||||
|
||||
#dialog Button {
|
||||
width: auto;
|
||||
background: #5a189a;
|
||||
color: #e0aaff;
|
||||
border: solid #9d4edd;
|
||||
}
|
||||
|
||||
#dialog Button:hover {
|
||||
background: #7b2cbf;
|
||||
border: solid #c77dff;
|
||||
}
|
||||
"""
|
||||
|
||||
gifts = {
|
||||
1: "neural uplink initialized",
|
||||
2: "quantum processor unlocked",
|
||||
3: "plasma blade module installed",
|
||||
4: "holographic interface upgrade",
|
||||
5: "neon trail effects enabled",
|
||||
6: "cyberdeck access granted",
|
||||
7: "ghosting protocol learned",
|
||||
8: "synth wave audio pack",
|
||||
9: "augmented reality overlay",
|
||||
10: "encryption key obtained",
|
||||
11: "memory expansion complete",
|
||||
12: "stealth mode activated",
|
||||
13: "biometric scanner upgraded",
|
||||
14: "ion cannon calibrated",
|
||||
15: "neural bridge established",
|
||||
16: "plasma shield generator",
|
||||
17: "code injection tools",
|
||||
18: "quantum entanglement link",
|
||||
19: "void walker ability",
|
||||
20: "data mining algorithms",
|
||||
21: "electromagnetic pulse",
|
||||
22: "chrono distortion field",
|
||||
23: "full system override",
|
||||
24: "transcendence achieved",
|
||||
}
|
||||
|
||||
def __init__(self, day: int) -> None:
|
||||
self.day = day
|
||||
super().__init__()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Vertical(id="dialog"):
|
||||
yield Label("▲ SYSTEM ACCESS GRANTED ▲", id="proto-header")
|
||||
yield Label(f"// DAY {str(self.day).zfill(2)} //")
|
||||
yield Label(
|
||||
f">>> {self.gifts.get(self.day, 'ERROR: DATA CORRUPTED')}",
|
||||
id="gift-text",
|
||||
)
|
||||
yield Label("*static crackle*")
|
||||
with Center():
|
||||
yield Button("[ DISCONNECT ]", id="close")
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
self.dismiss()
|
||||
event.stop()
|
||||
|
||||
|
||||
class AdventCalendarApp(App):
|
||||
CSS = """
|
||||
Screen {
|
||||
background: #0a0015;
|
||||
}
|
||||
|
||||
Grid {
|
||||
grid-size: 6 4;
|
||||
grid-gutter: 1 1;
|
||||
padding: 2;
|
||||
}
|
||||
|
||||
Grid Button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 4;
|
||||
border: solid #5a189a;
|
||||
background: #240046;
|
||||
color: #c77dff;
|
||||
}
|
||||
|
||||
Grid Button:hover {
|
||||
background: #3c096c;
|
||||
border: solid #9d4edd;
|
||||
color: #e0aaff;
|
||||
text-style: bold;
|
||||
}
|
||||
|
||||
Grid Button.opened {
|
||||
background: #5a189a;
|
||||
border: solid #c77dff;
|
||||
text-style: bold;
|
||||
color: #e0aaff;
|
||||
}
|
||||
|
||||
Grid Button.locked {
|
||||
background: #10002b;
|
||||
border: solid #3c096c;
|
||||
color: #5a189a;
|
||||
}
|
||||
|
||||
#title {
|
||||
padding: 1;
|
||||
text-align: center;
|
||||
color: #e0aaff;
|
||||
text-style: bold;
|
||||
background: #10002b;
|
||||
}
|
||||
|
||||
#stats {
|
||||
dock: bottom;
|
||||
height: 3;
|
||||
background: #10002b;
|
||||
color: #c77dff;
|
||||
padding: 1;
|
||||
text-align: center;
|
||||
border-top: solid #5a189a;
|
||||
}
|
||||
|
||||
Header {
|
||||
background: #10002b;
|
||||
color: #c77dff;
|
||||
}
|
||||
|
||||
Footer {
|
||||
background: #10002b;
|
||||
}
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
("d", "toggle_dark", "theme"),
|
||||
("r", "reset_calendar", "reset"),
|
||||
("s", "show_stats", "stats"),
|
||||
("q", "quit", "exit"),
|
||||
]
|
||||
|
||||
START_DATE = datetime.date(datetime.date.today().year, 12, 1)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.opened_days = set()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header(show_clock=True)
|
||||
yield Static("▼ PROTOGEN//ADVENT v2.4 ▼", id="title")
|
||||
with Grid():
|
||||
for day in range(1, 25):
|
||||
button = Button(f"{day:02d}", id=f"day-{day}")
|
||||
unlock_day = self.START_DATE + datetime.timedelta(days=day - 1)
|
||||
if datetime.date.today() < unlock_day:
|
||||
button.add_class("locked")
|
||||
yield button
|
||||
yield Static(self.get_stats_text(), id="stats")
|
||||
yield Footer()
|
||||
|
||||
def get_stats_text(self) -> str:
|
||||
total = 24
|
||||
opened = len(self.opened_days)
|
||||
pct = int((opened / total) * 100)
|
||||
bar_filled = int(pct / 5)
|
||||
bar = "█" * bar_filled + "░" * (20 - bar_filled)
|
||||
return f"[{bar}] {opened}/{total} :: {pct}% COMPLETE"
|
||||
|
||||
def update_stats(self) -> None:
|
||||
stats_widget = self.query_one("#stats", Static)
|
||||
stats_widget.update(self.get_stats_text())
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
button = event.button
|
||||
|
||||
if button.id == "close":
|
||||
return
|
||||
|
||||
day = int(button.id.split("-")[1])
|
||||
unlock_day = self.START_DATE + datetime.timedelta(days=day - 1)
|
||||
|
||||
if datetime.date.today() < unlock_day:
|
||||
self.notify(
|
||||
f"⚠ ACCESS DENIED :: UNLOCKS {unlock_day}",
|
||||
severity="warning",
|
||||
timeout=3,
|
||||
)
|
||||
return None
|
||||
|
||||
if not button.has_class("opened"):
|
||||
button.add_class("opened")
|
||||
button.remove_class("locked")
|
||||
self.opened_days.add(day)
|
||||
self.update_stats()
|
||||
self.notify(f"✓ DAY {day:02d} ACCESSED", timeout=2)
|
||||
|
||||
self.push_screen(DayScreen(day))
|
||||
|
||||
def action_toggle_dark(self) -> None:
|
||||
self.theme = (
|
||||
"textual-dark" if self.theme == "textual-light" else "textual-light"
|
||||
)
|
||||
self.notify("THEME SHIFT", timeout=1)
|
||||
|
||||
def action_reset_calendar(self) -> None:
|
||||
for day in range(1, 25):
|
||||
button = self.query_one(f"#day-{day}", Button)
|
||||
if button.has_class("opened"):
|
||||
button.remove_class("opened")
|
||||
self.opened_days.clear()
|
||||
self.update_stats()
|
||||
self.notify("⟲ SYSTEM RESET", severity="information")
|
||||
|
||||
def action_show_stats(self) -> None:
|
||||
opened = len(self.opened_days)
|
||||
remaining = 24 - opened
|
||||
self.notify(f"OPENED: {opened} :: REMAINING: {remaining}", timeout=4)
|
||||
|
||||
|
||||
def main():
|
||||
app = AdventCalendarApp()
|
||||
app.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Reference in a new issue