4 Commits

4 changed files with 83 additions and 29 deletions

43
pdm.lock generated
View File

@@ -5,11 +5,23 @@
groups = ["default"] groups = ["default"]
strategy = ["inherit_metadata"] strategy = ["inherit_metadata"]
lock_version = "4.5.0" lock_version = "4.5.0"
content_hash = "sha256:842afa14523f463c1a73e53c7aeb6d697673d95a2db9adbf935807b1fe5d021a" content_hash = "sha256:6cd4ed6668a18d93170023df0e7cf183ac36d04df220f4dcb4c091eb6623b65f"
[[metadata.targets]] [[metadata.targets]]
requires_python = "==3.13.*" requires_python = "==3.13.*"
[[package]]
name = "colorama"
version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
groups = ["default"]
marker = "sys_platform == \"win32\" and python_version == \"3.13\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]] [[package]]
name = "linkify-it-py" name = "linkify-it-py"
version = "2.0.3" version = "2.0.3"
@@ -25,6 +37,23 @@ files = [
{file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"},
] ]
[[package]]
name = "loguru"
version = "0.7.3"
requires_python = "<4.0,>=3.5"
summary = "Python logging made (stupidly) simple"
groups = ["default"]
marker = "python_version == \"3.13\""
dependencies = [
"aiocontextvars>=0.2.0; python_version < \"3.7\"",
"colorama>=0.3.4; sys_platform == \"win32\"",
"win32-setctime>=1.0.0; sys_platform == \"win32\"",
]
files = [
{file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"},
{file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"},
]
[[package]] [[package]]
name = "markdown-it-py" name = "markdown-it-py"
version = "4.0.0" version = "4.0.0"
@@ -167,3 +196,15 @@ files = [
{file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"},
{file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"},
] ]
[[package]]
name = "win32-setctime"
version = "1.2.0"
requires_python = ">=3.5"
summary = "A small Python utility to set file creation time on Windows"
groups = ["default"]
marker = "sys_platform == \"win32\" and python_version == \"3.13\""
files = [
{file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"},
{file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"},
]

View File

@@ -1,9 +1,9 @@
[project] [project]
name = "lottery-tui" name = "lottery-tui"
version = "0.2.3" version = "0.2.6"
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"] dependencies = ["textual>=8.0.0", "loguru>=0.7.3"]
requires-python = ">=3.10" requires-python = ">=3.10"
readme = "README.md" readme = "README.md"
license = { text = "MIT" } license = { text = "MIT" }
@@ -13,6 +13,7 @@ classifiers = [
"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",
] ]

View File

@@ -17,7 +17,7 @@ class Result(NamedTuple):
match self.kind: match self.kind:
case 'EuroMillions': case 'EuroMillions':
bonus_name = 'Lucky Stars' bonus_name = 'Lucky Stars'
case 'Set For Life': case 'SetForLife':
bonus_name = 'Life Ball' bonus_name = 'Life Ball'
case 'Thunderball': case 'Thunderball':
bonus_name = 'Thunderball' bonus_name = 'Thunderball'
@@ -45,19 +45,19 @@ class Lottery(ABC):
@register_lottery @register_lottery
class UKlotto(Lottery): class Lotto(Lottery):
"""A class representing the UK Lotto lottery. """A class representing the Lotto lottery.
Uk Lotto draws 6 numbers from a pool of 1 to 59, without replacement. Lotto draws 6 numbers from a pool of 1 to 59, without replacement.
There is no bonus number in UK Lotto. There is no bonus number in Lotto.
""" """
POSSIBLE_NUMBERS = range(1, 60) POSSIBLE_NUMBERS = range(1, 60)
def draw(self) -> Result: def draw(self) -> Result:
"""Perform a UK Lotto draw.""" """Perform a Lotto draw."""
result = random.sample(UKlotto.POSSIBLE_NUMBERS, 6) result = random.sample(Lotto.POSSIBLE_NUMBERS, 6)
return Result(kind='UK Lotto', numbers=result, bonus=None) return Result(kind=type(self).__name__, numbers=result, bonus=None)
@register_lottery @register_lottery
@@ -75,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='EuroMillions', numbers=numbers, bonus=bonus) return Result(kind=type(self).__name__, numbers=numbers, bonus=bonus)
@register_lottery @register_lottery
@@ -83,7 +83,7 @@ class SetForLife(Lottery):
"""A class representing the Set For Life lottery. """A class representing the Set For Life lottery.
Set For Life draws 5 numbers from a pool of 1 to 39, without replacement, Set For Life draws 5 numbers from a pool of 1 to 39, without replacement,
and 1 "Life Ball" number from a separate pool of 1 to 10, also without replacement. and 1 "Life Ball" number from a separate pool of 1 to 10.
""" """
POSSIBLE_NUMBERS = range(1, 40) POSSIBLE_NUMBERS = range(1, 40)
@@ -92,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='Set For Life', numbers=numbers, bonus=life_ball) return Result(kind=type(self).__name__, numbers=numbers, bonus=life_ball)
@register_lottery @register_lottery
@@ -100,7 +100,7 @@ class Thunderball(Lottery):
"""A class representing the Thunderball lottery. """A class representing the Thunderball lottery.
Thunderball draws 5 numbers from a pool of 1 to 39, without replacement, Thunderball draws 5 numbers from a pool of 1 to 39, without replacement,
and 1 "Thunderball" number from a separate pool of 1 to 14, also without replacement. and 1 "Thunderball" number from a separate pool of 1 to 14.
""" """
POSSIBLE_NUMBERS = range(1, 40) POSSIBLE_NUMBERS = range(1, 40)
@@ -109,7 +109,7 @@ 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='Thunderball', numbers=numbers, bonus=thunderball) return Result(kind=type(self).__name__, numbers=numbers, bonus=thunderball)
def request_lottery_obj(lottery_name: str) -> Lottery: def request_lottery_obj(lottery_name: str) -> Lottery:

View File

@@ -1,3 +1,5 @@
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.widgets import Button, Label, Select, Static from textual.widgets import Button, Label, Select, Static
@@ -17,7 +19,7 @@ class LotteryTUI(App):
Static('Pick a lottery to play:', id='instructions'), Static('Pick a lottery to play:', id='instructions'),
Select( Select(
options=[ options=[
('UK Lotto', 'uklotto'), ('Lotto', 'lotto'),
('EuroMillions', 'euromillions'), ('EuroMillions', 'euromillions'),
('Set For Life', 'setforlife'), ('Set For Life', 'setforlife'),
('Thunderball', 'thunderball'), ('Thunderball', 'thunderball'),
@@ -37,9 +39,13 @@ class LotteryTUI(App):
def on_button_pressed(self, event): 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()
def _draw_button_handler(self):
"""Handle the draw button press."""
if self.query_one('#lottery-select').is_blank(): if self.query_one('#lottery-select').is_blank():
self.query_one('#result-label').update( self._update_result_label(
'Please select a lottery before drawing.' Text('Please select a lottery before drawing.', style='bold #ff8c42')
) )
return return
@@ -47,11 +53,17 @@ class LotteryTUI(App):
try: try:
lottery_obj = request_lottery_obj(selected_lottery) lottery_obj = request_lottery_obj(selected_lottery)
result = lottery_obj.draw() except ValueError:
except ValueError as e: ERR_MSG = f'Invalid lottery selection: {selected_lottery}'
self.query_one('#result-label').update(str(e)) logger.exception(ERR_MSG)
raise
self.query_one('#result-label').update(str(result)) result = lottery_obj.draw()
self._update_result_label(str(result))
def _update_result_label(self, message: str):
"""Update the result label with a new message."""
self.query_one('#result-label').update(message)
def main(): def main():