Compare commits

..

No commits in common. "main" and "v0.2.5" have entirely different histories.
main ... v0.2.5

3 changed files with 36 additions and 44 deletions

View File

@ -1,6 +1,6 @@
[project]
name = "lottery-tui"
version = "1.0.1"
version = "0.2.5"
description = "A terminal user interface for lottery games."
authors = [{ name = "onyx-and-iris", email = "code@onyxandiris.online" }]
dependencies = ["textual>=8.0.0", "loguru>=0.7.3"]
@ -8,7 +8,7 @@ requires-python = ">=3.10"
readme = "README.md"
license = { text = "MIT" }
classifiers = [
"Development Status :: 5 - Production/Stable",
"Development Status :: 4 - Beta",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",

View File

@ -1,30 +1,18 @@
import random
from abc import ABC, abstractmethod
from dataclasses import dataclass
from loguru import logger
from typing import NamedTuple
@dataclass(frozen=True)
class Result:
"""A dataclass to hold the results of a lottery draw with auto-sorted numbers."""
class Result(NamedTuple):
"""A named tuple to hold the results of a lottery draw."""
kind: str
numbers: list[int]
bonus: list[int] | None
def __post_init__(self):
"""Sort the numbers after initialization.
We use super().__setattr__ to bypass the frozen nature of the dataclass for sorting.
"""
super().__setattr__('numbers', sorted(self.numbers))
if self.bonus:
super().__setattr__('bonus', sorted(self.bonus))
def __str__(self) -> str:
"""Return a string representation of the lottery result."""
out = f'Numbers: {", ".join(str(n) for n in self.numbers)}'
out = f'Numbers: {", ".join(str(n) for n in sorted(self.numbers))}'
if self.bonus:
match self.kind:
case 'EuroMillions':
@ -35,7 +23,7 @@ class Result:
bonus_name = 'Thunderball'
case _:
bonus_name = 'Bonus Numbers'
out += f'\n{bonus_name}: {", ".join(str(n) for n in self.bonus)}'
out += f'\n{bonus_name}: {", ".join(str(n) for n in sorted(self.bonus))}'
return out
@ -57,18 +45,18 @@ class Lottery(ABC):
@register_lottery
class Lotto(Lottery):
"""A class representing the Lotto lottery.
class UKLotto(Lottery):
"""A class representing the UK Lotto lottery.
Lotto draws 6 numbers from a pool of 1 to 59, without replacement.
There is no bonus number in Lotto.
UK Lotto draws 6 numbers from a pool of 1 to 59, without replacement.
There is no bonus number in UK Lotto.
"""
POSSIBLE_NUMBERS = range(1, 60)
def draw(self) -> Result:
"""Perform a Lotto draw."""
result = random.sample(Lotto.POSSIBLE_NUMBERS, 6)
"""Perform a UK Lotto draw."""
result = random.sample(UKLotto.POSSIBLE_NUMBERS, 6)
return Result(kind=type(self).__name__, numbers=result, bonus=None)
@ -128,7 +116,5 @@ def request_lottery_obj(lottery_name: str) -> Lottery:
"""Return a lottery object based on the provided lottery name."""
lottery_cls = registry.get(lottery_name.lower())
if lottery_cls is None:
ERR_MSG = f"Lottery '{lottery_name}' not found. Available lotteries: {', '.join(registry.keys())}"
logger.error(ERR_MSG)
raise ValueError(ERR_MSG)
raise ValueError(f"Lottery '{lottery_name}' not found.")
return lottery_cls()

View File

@ -1,9 +1,7 @@
from typing import NoReturn
from loguru import logger
from rich.text import Text
from textual.app import App, ComposeResult
from textual.containers import Container
from textual.events import Key
from textual.types import SelectType
from textual.widgets import Button, Label, Select, Static
from .lottery import request_lottery_obj
@ -21,13 +19,11 @@ class LotteryTUI(App):
Static('Pick a lottery to play:', id='instructions'),
Select(
options=[
('Lotto', 'lotto'),
('UK Lotto', 'uklotto'),
('EuroMillions', 'euromillions'),
('Set For Life', 'setforlife'),
('Thunderball', 'thunderball'),
],
value='lotto',
allow_blank=False,
id='lottery-select',
),
Button('Draw', id='draw-button'),
@ -35,27 +31,37 @@ class LotteryTUI(App):
id='main-container',
)
def on_key(self, event: Key) -> NoReturn:
def on_key(self, event):
"""Handle key events."""
if event.key == 'q':
self.exit()
def on_button_pressed(self, event: Button.Pressed) -> None:
def on_button_pressed(self, event):
"""Handle button press events."""
if event.button.id == 'draw-button':
self._draw_button_handler()
def _draw_button_handler(self) -> None:
def _draw_button_handler(self):
"""Handle the draw button press."""
lottery_obj = request_lottery_obj(self._read_lottery_selection())
if self.query_one('#lottery-select').is_blank():
self._update_result_label(
Text('Please select a lottery before drawing.', style='bold #ff8c42')
)
return
selected_lottery = self.query_one('#lottery-select').value
try:
lottery_obj = request_lottery_obj(selected_lottery)
except ValueError:
ERR_MSG = f'Invalid lottery selection: {selected_lottery}'
logger.exception(ERR_MSG)
raise
result = lottery_obj.draw()
self._update_result_label(str(result))
def _read_lottery_selection(self) -> SelectType:
"""Read the selected lottery from the dropdown."""
return self.query_one('#lottery-select').value
def _update_result_label(self, message: str) -> None:
def _update_result_label(self, message: str):
"""Update the result label with a new message."""
self.query_one('#result-label').update(message)