From 2879d2abb1f5727b2eb9eb276f3616fd2bf064f5 Mon Sep 17 00:00:00 2001 From: Felix Pankratz Date: Sun, 19 May 2024 15:14:51 +0200 Subject: [PATCH] docstrings, type hints --- BingoBoard.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- BingoField.py | 23 ++++++++++++++++++++++- bingo.py | 39 +++++++++++++++++++++++++++++---------- 3 files changed, 99 insertions(+), 14 deletions(-) diff --git a/BingoBoard.py b/BingoBoard.py index 0acf456..0f39026 100644 --- a/BingoBoard.py +++ b/BingoBoard.py @@ -11,12 +11,36 @@ from asyncio import sleep from BingoField import BingoField class BingoBoard(Widget): + ''' + A widget to display the fields of the bingo board. + + Attributes + ---------- + fieldstate : list + A list of booleans indicating the selection state of each field + fields : reactive(list) + List of strings containing the actual text for each field + default_fields : list + List of field strings in default order, required for reproducability + seed : int + The seed used for the current board + + Methods + ------- + roll_board(seed): + Shuffle the fields according to the field and reset the board. + fieldnum_from_cursor(): + Get the list index of the currently highlighted cell + is_bingo(): + Check if there is a bingo. + ''' fields = reactive([], recompose = True) cursor_x, cursor_y = 2, 2 def __init__(self) -> None: + '''Initialize the game''' self.fieldstate = [False for _ in range(25)] self.can_focus = True super().__init__() @@ -51,32 +75,48 @@ class BingoBoard(Widget): self.roll_board(int(datetime.now().timestamp())) def fieldnum_from_cursor(self) -> int: + '''Returns the list index position of the highlighted field''' return self.cursor_x + ( self.cursor_y * 5) - def roll_board(self, seed): + def roll_board(self, seed: int) -> None: + ''' + Roll the board according to the seed. Board updated through reactivity. + + Parameters: + seed (int): A UNIX timestamp used as RNG seed + ''' self.seed = seed random.seed(seed) self.fields = random.sample(self.default_fields, len(self.fields)) def watch_fields(self, new_state) -> None: + ''' + Triggered when fields change (see roll_board). + + Parameters: + new_state (list): The field strings in newly shuffled order + ''' 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.""" + '''Create 25 bingo fields.''' for _ in range(25): yield BingoField(_, self.fields[_]) def on_focus(self, message: Message) -> None: + '''Called when the BingoBoard receives focus. Enables cursor.''' fields = self.query(BingoField) fields[self.fieldnum_from_cursor()].set_highlighted(True) def on_blur(self, message: Message) -> None: + '''Called when the BingoBoard loses focus. Disables cursor.''' fields = self.query(BingoField) fields[self.fieldnum_from_cursor()].set_highlighted(False) def on_key(self, event: events.Key) -> None: + '''Handles keyboard input when BingoBoard is focused.''' fields = self.query(BingoField) fields[self.fieldnum_from_cursor()].set_highlighted(False) match event.key: @@ -94,6 +134,10 @@ class BingoBoard(Widget): async def on_bingo_field_selected(self, message: BingoField.Selected) -> None: + ''' + Triggered by child when it is clicked. + Updates fieldstate and checks for bingo, possibly triggering win animation. + ''' self.fieldstate[message.num] = message.selected if self.is_bingo(): self.screen.styles.animate("background", 'red', duration=0.25) @@ -112,7 +156,8 @@ class BingoBoard(Widget): await sleep(0.25) self.screen.styles.animate("background", '#1c1c1c', duration=0.25) - def is_bingo(self): + def is_bingo(self) -> bool: + '''Check the board for a bingo (5 in a row).''' 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 ] ) diff --git a/BingoField.py b/BingoField.py index 6da0b60..12e6dc9 100644 --- a/BingoField.py +++ b/BingoField.py @@ -3,7 +3,25 @@ from textual.message import Message from textual.reactive import reactive class BingoField(Static): - '''A Bingo field widget.''' + ''' + A Bingo field widget. + + Attributes + ---------- + text : str + The text displayed in the field + num : int + The running number of this field (0-24) + selected : bool + Wether the bingo field has been checked or not + highlighted : bool + Wether the cursor is currently on this field or not + + Methods + ------- + set_highlighted(new_highlight): + Sets the highlighted state (cursor) of this field + ''' text = reactive('') @@ -22,6 +40,7 @@ class BingoField(Static): self.text = text def on_click(self) -> None: + '''Add CSS class field_selected to self and POST to BingoBoard''' self.selected = not self.selected if self.selected: self.add_class('field_selected') @@ -32,6 +51,7 @@ class BingoField(Static): self.post_message(self.Selected(self.num, self.selected)) def set_highlighted(self, new_highlight: bool) -> None: + '''Add or remove the field_highlighted CSS class from self''' self.highlighted = new_highlight if self.highlighted: self.add_class('field_highlighted') @@ -39,4 +59,5 @@ class BingoField(Static): self.remove_class('field_highlighted') def render(self) -> str: + '''Return the fields text''' return str(self.text) diff --git a/bingo.py b/bingo.py index b7a4e00..e8d15f9 100644 --- a/bingo.py +++ b/bingo.py @@ -5,7 +5,6 @@ from textual.widgets import Header, Input, Static, Button from textual.containers import Horizontal, Container from textual.validation import Number from textual.command import DiscoveryHit, Provider, Hits, Hit -#from textual.command import Provider, Hits, Hit from textual.reactive import reactive from datetime import datetime @@ -25,14 +24,16 @@ Built using [@click="app.open_link('https://textual.textualize.io/')"]Textual[/] ''' class AboutCommand(Provider): + '''Class to represent the About command in the menu''' + async def discover(self) -> Hits: yield DiscoveryHit(display='About', command=self.app.action_toggle_sidebar, help='Link to repo etc.') - #yield Hit(1, 'About', self.app.action_toggle_sidebar, help='Link to repo etc.') - def show_about(self): + def show_about(self) -> None: pass async def search(self, query: str) -> Hits: + '''Called when the search functionality is used''' matcher = self.matcher(query) command = "About" score = matcher.match(command) @@ -40,7 +41,10 @@ class AboutCommand(Provider): yield Hit(score, matcher.highlight(command), self.app.action_toggle_sidebar, 'Link to repo etc.') class Sidebar(Container): + '''Class to represent the sidebar''' + def compose(self) -> ComposeResult: + '''Create the widgets that make up the sidebar''' yield Static('CCC Bingo', classes='title') yield Static(MESSAGE, classes='message') self.button = Button('< Back', variant='primary', classes='btn_sidebar') @@ -48,11 +52,16 @@ class Sidebar(Container): yield self.button def on_button_pressed(self, event: Button.Pressed) -> None: - print('sidebar button press') + '''Closes the sidebar''' self.app.action_toggle_sidebar() class BingoApp(App): - '''A Textual app to run a Bingo board.''' + ''' + A Textual app to run a Bingo board. + + Contains the sidebar, header, and BingoDisplay. + ''' + CSS_PATH = "bingo.tcss" COMMANDS = App.COMMANDS | {AboutCommand} AUTO_FOCUS = 'Input' @@ -60,7 +69,7 @@ class BingoApp(App): show_sidebar = reactive(False) def action_toggle_sidebar(self) -> None: - print('sidebar toggle') + '''Toggle the sidebar on or off''' sidebar = self.query_one(Sidebar) self.set_focus(None) if sidebar.has_class("-hidden"): @@ -79,14 +88,24 @@ class BingoApp(App): yield BingoDisplay() def on_mount(self) -> None: + '''Set title of the app''' self.title = 'CCC Bingo' self.sub_title = 'GPN22 Edition' - def action_toggle_dark(self) -> None: - '''An action to toggle dark mode.''' - self.dark = not self.dark - class BingoDisplay(Static): + ''' + A Widget to represent the bingo UI. + + Contains the board, input field and re-roll button. + + Attributes + ---------- + board : BingoBoard + The BingoBoard object + input_field : Input + User input for game seed + ''' + def compose(self) -> ComposeResult: '''Create child widgets for the app.''' self.board = BingoBoard()