docstrings, type hints
This commit is contained in:
parent
292bb56a8f
commit
2879d2abb1
@ -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 ] )
|
||||
|
@ -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)
|
||||
|
39
bingo.py
39
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()
|
||||
|
Loading…
Reference in New Issue
Block a user