mirror of
https://github.com/onyx-and-iris/lottery-tui.git
synced 2026-02-26 11:09:11 +00:00
Compare commits
No commits in common. "main" and "v0.2.4" have entirely different histories.
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "lottery-tui"
|
name = "lottery-tui"
|
||||||
version = "1.0.1"
|
version = "0.2.4"
|
||||||
description = "A terminal user interface for lottery games."
|
description = "A terminal user interface for lottery games."
|
||||||
authors = [{ name = "onyx-and-iris", email = "code@onyxandiris.online" }]
|
authors = [{ name = "onyx-and-iris", email = "code@onyxandiris.online" }]
|
||||||
dependencies = ["textual>=8.0.0", "loguru>=0.7.3"]
|
dependencies = ["textual>=8.0.0", "loguru>=0.7.3"]
|
||||||
@ -8,12 +8,11 @@ requires-python = ">=3.10"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 4 - Beta",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,30 +1,18 @@
|
|||||||
import random
|
import random
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from typing import NamedTuple
|
||||||
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
class Result(NamedTuple):
|
||||||
class Result:
|
"""A named tuple to hold the results of a lottery draw."""
|
||||||
"""A dataclass to hold the results of a lottery draw with auto-sorted numbers."""
|
|
||||||
|
|
||||||
kind: str
|
kind: str
|
||||||
numbers: list[int]
|
numbers: list[int]
|
||||||
bonus: list[int] | None
|
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:
|
def __str__(self) -> str:
|
||||||
"""Return a string representation of the lottery result."""
|
"""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:
|
if self.bonus:
|
||||||
match self.kind:
|
match self.kind:
|
||||||
case 'EuroMillions':
|
case 'EuroMillions':
|
||||||
@ -35,7 +23,7 @@ class Result:
|
|||||||
bonus_name = 'Thunderball'
|
bonus_name = 'Thunderball'
|
||||||
case _:
|
case _:
|
||||||
bonus_name = 'Bonus Numbers'
|
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
|
return out
|
||||||
|
|
||||||
|
|
||||||
@ -57,19 +45,19 @@ class Lottery(ABC):
|
|||||||
|
|
||||||
|
|
||||||
@register_lottery
|
@register_lottery
|
||||||
class Lotto(Lottery):
|
class UKlotto(Lottery):
|
||||||
"""A class representing the Lotto lottery.
|
"""A class representing the UK Lotto lottery.
|
||||||
|
|
||||||
Lotto draws 6 numbers from a pool of 1 to 59, without replacement.
|
Uk Lotto draws 6 numbers from a pool of 1 to 59, without replacement.
|
||||||
There is no bonus number in Lotto.
|
There is no bonus number in UK Lotto.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
POSSIBLE_NUMBERS = range(1, 60)
|
POSSIBLE_NUMBERS = range(1, 60)
|
||||||
|
|
||||||
def draw(self) -> Result:
|
def draw(self) -> Result:
|
||||||
"""Perform a Lotto draw."""
|
"""Perform a UK Lotto draw."""
|
||||||
result = random.sample(Lotto.POSSIBLE_NUMBERS, 6)
|
result = random.sample(UKlotto.POSSIBLE_NUMBERS, 6)
|
||||||
return Result(kind=type(self).__name__, numbers=result, bonus=None)
|
return Result(kind='UK Lotto', numbers=result, bonus=None)
|
||||||
|
|
||||||
|
|
||||||
@register_lottery
|
@register_lottery
|
||||||
@ -87,7 +75,7 @@ class EuroMillions(Lottery):
|
|||||||
"""Perform a EuroMillions draw."""
|
"""Perform a EuroMillions draw."""
|
||||||
numbers = random.sample(EuroMillions.POSSIBLE_NUMBERS, 5)
|
numbers = random.sample(EuroMillions.POSSIBLE_NUMBERS, 5)
|
||||||
bonus = random.sample(EuroMillions.POSSIBLE_BONUS_NUMBERS, 2)
|
bonus = random.sample(EuroMillions.POSSIBLE_BONUS_NUMBERS, 2)
|
||||||
return Result(kind=type(self).__name__, numbers=numbers, bonus=bonus)
|
return Result(kind='EuroMillions', numbers=numbers, bonus=bonus)
|
||||||
|
|
||||||
|
|
||||||
@register_lottery
|
@register_lottery
|
||||||
@ -104,7 +92,7 @@ class SetForLife(Lottery):
|
|||||||
"""Perform a Set For Life draw."""
|
"""Perform a Set For Life draw."""
|
||||||
numbers = random.sample(SetForLife.POSSIBLE_NUMBERS, 5)
|
numbers = random.sample(SetForLife.POSSIBLE_NUMBERS, 5)
|
||||||
life_ball = [random.randint(1, 10)]
|
life_ball = [random.randint(1, 10)]
|
||||||
return Result(kind=type(self).__name__, numbers=numbers, bonus=life_ball)
|
return Result(kind='Set For Life', numbers=numbers, bonus=life_ball)
|
||||||
|
|
||||||
|
|
||||||
@register_lottery
|
@register_lottery
|
||||||
@ -121,14 +109,12 @@ class Thunderball(Lottery):
|
|||||||
"""Perform a Thunderball draw."""
|
"""Perform a Thunderball draw."""
|
||||||
numbers = random.sample(Thunderball.POSSIBLE_NUMBERS, 5)
|
numbers = random.sample(Thunderball.POSSIBLE_NUMBERS, 5)
|
||||||
thunderball = [random.randint(1, 14)]
|
thunderball = [random.randint(1, 14)]
|
||||||
return Result(kind=type(self).__name__, numbers=numbers, bonus=thunderball)
|
return Result(kind='Thunderball', numbers=numbers, bonus=thunderball)
|
||||||
|
|
||||||
|
|
||||||
def request_lottery_obj(lottery_name: str) -> Lottery:
|
def request_lottery_obj(lottery_name: str) -> Lottery:
|
||||||
"""Return a lottery object based on the provided lottery name."""
|
"""Return a lottery object based on the provided lottery name."""
|
||||||
lottery_cls = registry.get(lottery_name.lower())
|
lottery_cls = registry.get(lottery_name.lower())
|
||||||
if lottery_cls is None:
|
if lottery_cls is None:
|
||||||
ERR_MSG = f"Lottery '{lottery_name}' not found. Available lotteries: {', '.join(registry.keys())}"
|
raise ValueError(f"Lottery '{lottery_name}' not found.")
|
||||||
logger.error(ERR_MSG)
|
|
||||||
raise ValueError(ERR_MSG)
|
|
||||||
return lottery_cls()
|
return lottery_cls()
|
||||||
|
|||||||
@ -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.app import App, ComposeResult
|
||||||
from textual.containers import Container
|
from textual.containers import Container
|
||||||
from textual.events import Key
|
|
||||||
from textual.types import SelectType
|
|
||||||
from textual.widgets import Button, Label, Select, Static
|
from textual.widgets import Button, Label, Select, Static
|
||||||
|
|
||||||
from .lottery import request_lottery_obj
|
from .lottery import request_lottery_obj
|
||||||
@ -21,13 +19,11 @@ class LotteryTUI(App):
|
|||||||
Static('Pick a lottery to play:', id='instructions'),
|
Static('Pick a lottery to play:', id='instructions'),
|
||||||
Select(
|
Select(
|
||||||
options=[
|
options=[
|
||||||
('Lotto', 'lotto'),
|
('UK Lotto', 'uklotto'),
|
||||||
('EuroMillions', 'euromillions'),
|
('EuroMillions', 'euromillions'),
|
||||||
('Set For Life', 'setforlife'),
|
('Set For Life', 'setforlife'),
|
||||||
('Thunderball', 'thunderball'),
|
('Thunderball', 'thunderball'),
|
||||||
],
|
],
|
||||||
value='lotto',
|
|
||||||
allow_blank=False,
|
|
||||||
id='lottery-select',
|
id='lottery-select',
|
||||||
),
|
),
|
||||||
Button('Draw', id='draw-button'),
|
Button('Draw', id='draw-button'),
|
||||||
@ -35,27 +31,37 @@ class LotteryTUI(App):
|
|||||||
id='main-container',
|
id='main-container',
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_key(self, event: Key) -> NoReturn:
|
def on_key(self, event):
|
||||||
"""Handle key events."""
|
"""Handle key events."""
|
||||||
if event.key == 'q':
|
if event.key == 'q':
|
||||||
self.exit()
|
self.exit()
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event):
|
||||||
"""Handle button press events."""
|
"""Handle button press events."""
|
||||||
if event.button.id == 'draw-button':
|
if event.button.id == 'draw-button':
|
||||||
self._draw_button_handler()
|
self._draw_button_handler()
|
||||||
|
|
||||||
def _draw_button_handler(self) -> None:
|
def _draw_button_handler(self):
|
||||||
"""Handle the draw button press."""
|
"""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()
|
result = lottery_obj.draw()
|
||||||
self._update_result_label(str(result))
|
self._update_result_label(str(result))
|
||||||
|
|
||||||
def _read_lottery_selection(self) -> SelectType:
|
def _update_result_label(self, message: str):
|
||||||
"""Read the selected lottery from the dropdown."""
|
|
||||||
return self.query_one('#lottery-select').value
|
|
||||||
|
|
||||||
def _update_result_label(self, message: str) -> None:
|
|
||||||
"""Update the result label with a new message."""
|
"""Update the result label with a new message."""
|
||||||
self.query_one('#result-label').update(message)
|
self.query_one('#result-label').update(message)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user