diff --git a/BingoBoard.py b/BingoBoard.py new file mode 100644 index 0000000..c0bbc80 --- /dev/null +++ b/BingoBoard.py @@ -0,0 +1,104 @@ +from textual.reactive import reactive +from textual.widget import Widget +from textual.app import ComposeResult + +from datetime import datetime +import random +from asyncio import sleep + +from BingoField import BingoField + +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 diff --git a/BingoField.py b/BingoField.py new file mode 100644 index 0000000..a8caf20 --- /dev/null +++ b/BingoField.py @@ -0,0 +1,43 @@ +from textual.widgets import Static +from textual.message import Message +from textual.reactive import reactive + +class BingoField(Static): + '''A Bingo field widget.''' + + cursor_x, cursor_y = 2, 2 + text = reactive('') + + 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) diff --git a/bingo.py b/bingo.py index 0910281..c4c8e98 100644 --- a/bingo.py +++ b/bingo.py @@ -2,73 +2,29 @@ from textual.app import App, ComposeResult from textual.widgets import Header, Input, Static, Button -from textual.widget import Widget -from textual.message import Message 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) +from BingoBoard import BingoBoard class BingoApp(App): - """A Textual app to run a Bingo board.""" + '''A Textual app to run a Bingo board.''' CSS_PATH = "bingo.tcss" def compose(self) -> ComposeResult: - """Create child widgets for the app.""" + '''Create child widgets for the app.''' yield Header() yield BingoDisplay() def action_toggle_dark(self) -> None: - """An action to toggle dark mode.""" + '''An action to toggle dark mode.''' self.dark = not self.dark class BingoDisplay(Static): def compose(self) -> ComposeResult: - """Create child widgets for the app.""" + '''Create child widgets for the app.''' self.board = BingoBoard() yield self.board self.input_field = Input( @@ -89,110 +45,15 @@ class BingoDisplay(Static): ) def on_button_pressed(self, event: Button.Pressed) -> None: - """""" - # reroll + '''Re-roll the board state with current time as seed''' 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: + '''Re-roll the board state with the seed from the input''' 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()