#!/usr/bin/env python3 from textual.app import App, ComposeResult from textual.widgets import Header, Footer, Input, Label, Static, Button from textual.widget import Widget from textual.message import Message from textual.color import Color from textual.containers import Horizontal from textual.validation import Number from textual.reactive import reactive from asyncio import sleep from datetime import datetime import random class BingoField(Static): """A Bingo field widget.""" cursor_x, cursor_y = 2, 2 text = reactive("temp") class Selected(Message): """Send message to the board containing clicked field info""" def __init__(self, num: int, selected: bool) -> None: self.num = num self.selected = selected super().__init__() def __init__(self, num, text: str) -> None: self.num = num self.selected = False super().__init__() self.text = text def on_mount(self) -> None: self.styles.content_align = ("center", "middle") self.styles.border = ("solid", "green") self.styles.height = '100%' self.styles.width = '100%' def on_click(self) -> None: self.selected = not self.selected if self.selected: self.styles.animate("background", 'green', duration=0.1) self.styles.border = ("solid", "black") else: self.styles.animate("background", '#1c1c1c', duration=0.1) self.styles.border = ("solid", "green") # The post_message method sends an event to be handled in the DOM self.post_message(self.Selected(self.num, self.selected)) def render(self) -> str: return str(self.text) class BingoApp(App): """A Textual app to run a Bingo board.""" CSS_PATH = "bingo.tcss" def compose(self) -> ComposeResult: """Create child widgets for the app.""" yield Header() yield BingoDisplay() def action_toggle_dark(self) -> None: """An action to toggle dark mode.""" self.dark = not self.dark class BingoDisplay(Static): def compose(self) -> ComposeResult: """Create child widgets for the app.""" self.board = BingoBoard() yield self.board self.input_field = Input( str(self.board.seed), type='integer', placeholder='UNIX timestamp', max_length=10, classes='seed_input', validators=[ Number(minimum=1000000000, maximum = 2000000000) ] ) self.input_field.border_title = 'Seed' yield Horizontal( self.input_field, Button.error(':game_die: re-roll', classes='roll_btn'), classes='bottom_line' ) def on_button_pressed(self, event: Button.Pressed) -> None: """""" # reroll self.board.roll_board(int(datetime.now().timestamp())) self.input_field.value = str(self.board.seed) def on_input_submitted(self, event: Input.Submitted) -> None: if event.validation_result.is_valid: self.board.roll_board(int(event.value)) class BingoBoard(Widget): fields = reactive([], recompose = True) def __init__(self) -> None: self.fieldstate = [False for _ in range(25)] super().__init__() self.fields = [ 'Datenelch', '6 Stunden Schlaf', 'Tschunk getrunken', 'Spaß gehabt', 'Sticker getauscht', 'DECT genutzt', 'Hardware gehackt', 'Kabel vergessen', 'Halle gesucht', '$dinge gelötet', 'an SoS teilgenommen', 'Neue Leute kennengelernt', 'Wasser getrunken', 'Waffel gegessen', 'Corona Test gemacht', '2 Mahlzeiten gegessen', 'Fairydust bewundert', 'Talk angeschaut', 'CCC Merch getragen', 'getrollt', 'In der Lounge getanzt', 'Etwas Neues ausprobiert', 'Maske getragen', 'geduscht', 'Gulasch gegessen' ] self.default_fields = self.fields self.roll_board(int(datetime.now().timestamp())) def roll_board(self, seed): self.seed = seed random.seed(seed) self.fields = random.sample(self.default_fields, len(self.fields)) def watch_fields(self, new_state) -> None: self.fieldstate = [False for _ in range(25)] for idx, field in enumerate(self.query(BingoField)): field.text = new_state[idx] def compose(self) -> ComposeResult: """Create child widgets for the app.""" for _ in range(25): yield BingoField(_, self.fields[_]) async def on_bingo_field_selected(self, message: BingoField.Selected) -> None: self.fieldstate[message.num] = message.selected if self.is_bingo(): self.screen.styles.animate("background", 'red', duration=0.25) await sleep(0.25) self.screen.styles.animate("background", 'orange', duration=0.25) await sleep(0.25) self.screen.styles.animate("background", 'yellow', duration=0.25) await sleep(0.25) self.screen.styles.animate("background", 'green', duration=0.25) await sleep(0.25) self.screen.styles.animate("background", 'lightblue', duration=0.25) await sleep(0.25) self.screen.styles.animate("background", 'blue', duration=0.25) await sleep(0.25) self.screen.styles.animate("background", 'purple', duration=0.25) await sleep(0.25) self.screen.styles.animate("background", '#1c1c1c', duration=0.25) def is_bingo(self): array = [ self.fieldstate[i:i+5] for i in range(0, len(self.fieldstate), 5)] # check rows bingo = any( [ all(row) for row in array ] ) # check cols bingo = bingo or any([ all(self.fieldstate[num::5]) for num in range(5)]) # check bottom left to upper right bingo = bingo or all([ self.fieldstate[4], self.fieldstate[8], self.fieldstate[12], self.fieldstate[16], self.fieldstate[20] ]) # check top left to bottom right bingo = bingo or all([ self.fieldstate[0], self.fieldstate[6], self.fieldstate[12], self.fieldstate[18], self.fieldstate[24] ]) return bingo if __name__ == "__main__": app = BingoApp() app.run()