|
|
@ -11,12 +11,36 @@ from asyncio import sleep
|
|
|
|
from BingoField import BingoField
|
|
|
|
from BingoField import BingoField
|
|
|
|
|
|
|
|
|
|
|
|
class BingoBoard(Widget):
|
|
|
|
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)
|
|
|
|
fields = reactive([], recompose = True)
|
|
|
|
|
|
|
|
|
|
|
|
cursor_x, cursor_y = 2, 2
|
|
|
|
cursor_x, cursor_y = 2, 2
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
|
|
|
'''Initialize the game'''
|
|
|
|
self.fieldstate = [False for _ in range(25)]
|
|
|
|
self.fieldstate = [False for _ in range(25)]
|
|
|
|
self.can_focus = True
|
|
|
|
self.can_focus = True
|
|
|
|
super().__init__()
|
|
|
|
super().__init__()
|
|
|
@ -51,32 +75,48 @@ class BingoBoard(Widget):
|
|
|
|
self.roll_board(int(datetime.now().timestamp()))
|
|
|
|
self.roll_board(int(datetime.now().timestamp()))
|
|
|
|
|
|
|
|
|
|
|
|
def fieldnum_from_cursor(self) -> int:
|
|
|
|
def fieldnum_from_cursor(self) -> int:
|
|
|
|
|
|
|
|
'''Returns the list index position of the highlighted field'''
|
|
|
|
return self.cursor_x + ( self.cursor_y * 5)
|
|
|
|
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
|
|
|
|
self.seed = seed
|
|
|
|
random.seed(seed)
|
|
|
|
random.seed(seed)
|
|
|
|
self.fields = random.sample(self.default_fields, len(self.fields))
|
|
|
|
self.fields = random.sample(self.default_fields, len(self.fields))
|
|
|
|
|
|
|
|
|
|
|
|
def watch_fields(self, new_state) -> None:
|
|
|
|
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)]
|
|
|
|
self.fieldstate = [False for _ in range(25)]
|
|
|
|
for idx, field in enumerate(self.query(BingoField)):
|
|
|
|
for idx, field in enumerate(self.query(BingoField)):
|
|
|
|
field.text = new_state[idx]
|
|
|
|
field.text = new_state[idx]
|
|
|
|
|
|
|
|
|
|
|
|
def compose(self) -> ComposeResult:
|
|
|
|
def compose(self) -> ComposeResult:
|
|
|
|
"""Create child widgets for the app."""
|
|
|
|
'''Create 25 bingo fields.'''
|
|
|
|
for _ in range(25):
|
|
|
|
for _ in range(25):
|
|
|
|
yield BingoField(_, self.fields[_])
|
|
|
|
yield BingoField(_, self.fields[_])
|
|
|
|
|
|
|
|
|
|
|
|
def on_focus(self, message: Message) -> None:
|
|
|
|
def on_focus(self, message: Message) -> None:
|
|
|
|
|
|
|
|
'''Called when the BingoBoard receives focus. Enables cursor.'''
|
|
|
|
fields = self.query(BingoField)
|
|
|
|
fields = self.query(BingoField)
|
|
|
|
fields[self.fieldnum_from_cursor()].set_highlighted(True)
|
|
|
|
fields[self.fieldnum_from_cursor()].set_highlighted(True)
|
|
|
|
|
|
|
|
|
|
|
|
def on_blur(self, message: Message) -> None:
|
|
|
|
def on_blur(self, message: Message) -> None:
|
|
|
|
|
|
|
|
'''Called when the BingoBoard loses focus. Disables cursor.'''
|
|
|
|
fields = self.query(BingoField)
|
|
|
|
fields = self.query(BingoField)
|
|
|
|
fields[self.fieldnum_from_cursor()].set_highlighted(False)
|
|
|
|
fields[self.fieldnum_from_cursor()].set_highlighted(False)
|
|
|
|
|
|
|
|
|
|
|
|
def on_key(self, event: events.Key) -> None:
|
|
|
|
def on_key(self, event: events.Key) -> None:
|
|
|
|
|
|
|
|
'''Handles keyboard input when BingoBoard is focused.'''
|
|
|
|
fields = self.query(BingoField)
|
|
|
|
fields = self.query(BingoField)
|
|
|
|
fields[self.fieldnum_from_cursor()].set_highlighted(False)
|
|
|
|
fields[self.fieldnum_from_cursor()].set_highlighted(False)
|
|
|
|
match event.key:
|
|
|
|
match event.key:
|
|
|
@ -94,6 +134,10 @@ class BingoBoard(Widget):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def on_bingo_field_selected(self, message: BingoField.Selected) -> None:
|
|
|
|
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
|
|
|
|
self.fieldstate[message.num] = message.selected
|
|
|
|
if self.is_bingo():
|
|
|
|
if self.is_bingo():
|
|
|
|
self.screen.styles.animate("background", 'red', duration=0.25)
|
|
|
|
self.screen.styles.animate("background", 'red', duration=0.25)
|
|
|
@ -112,7 +156,8 @@ class BingoBoard(Widget):
|
|
|
|
await sleep(0.25)
|
|
|
|
await sleep(0.25)
|
|
|
|
self.screen.styles.animate("background", '#1c1c1c', duration=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)]
|
|
|
|
array = [ self.fieldstate[i:i+5] for i in range(0, len(self.fieldstate), 5)]
|
|
|
|
# check rows
|
|
|
|
# check rows
|
|
|
|
bingo = any( [ all(row) for row in array ] )
|
|
|
|
bingo = any( [ all(row) for row in array ] )
|
|
|
|