Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions projects/001-lotto-game/python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
Empty file.
28 changes: 28 additions & 0 deletions projects/001-lotto-game/python/bill_generator/bill.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class Bill:
"""Represents a single Lotto ticket.

Attributes:
giocata (str): Type of bet (ambata, ambo, etc.).
ruota (str): Wheel on which the game is played.
numbers (List[int]): Numbers generated for the ticket.
"""

def __init__(self, giocata: str, ruota: str, numbers: list[int]):
"""Initializes a Lotto bet slip.

Args:
giocata (str): Type of bet.
ruota (str): Selected wheel.
numbers (List[int]): List of generated numbers.
"""
self.giocata = giocata
self.ruota = ruota
self.numbers = numbers

def __str__(self):
"""Returns a textual representation of the ticket.

Returns:
str: String containing the type of giocata, numbers, and ruota.
"""
return f"{self.giocata} {self.ruota} {self.numbers} "
59 changes: 59 additions & 0 deletions projects/001-lotto-game/python/bill_generator/billGenerator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import random
from bill_generator.rules import Ruote, Giocate


class BillGenerator:
"""Provides utility methods to generate Lotto ticket components.

This class includes methods for generating random numbers,
selecting a wheel, and choosing a bet type (giocata).
"""

@staticmethod
def get_numbers(n_numbers: int) -> list[int]:
"""Generates unique random numbers between 1 and 90.

Args:
n_numbers (int): Number of unique numbers to generate.

Returns:
list[int]: List of generated unique numbers.
"""
numbers: list[int] = []
while len(numbers) < n_numbers:
n = random.randint(1, 90)
if n not in numbers:
numbers.append(n)
return numbers

@staticmethod
def get_ruota() -> str:
"""Prompts the user to select a Lotto wheel.

Displays the available wheels and returns the selected one.

Returns:
str: The wheel selected by the user.
"""
ruote = Ruote("ruote.txt")
print("\nChoose a 'ruota' from the list below:\n")
ruote.print_ruote()
selection = ruote.select_ruote()
return selection

@staticmethod
def giocata() -> tuple[str, int] | None:
"""Prompts the user to select a bet type (giocata).

Displays the available bet types and returns the selected one
along with its associated rule (minimum required matches).

Returns:
tuple[str, int] | None: A tuple containing the name of the
selected bet and its required number of matches. Returns
None if no valid selection is made.
"""
giocate = Giocate("giocate.txt")
print("\nChoose a 'giocata' from the list below:\n")
giocate.print_giocata()
return giocate.select_giocata()
183 changes: 183 additions & 0 deletions projects/001-lotto-game/python/bill_generator/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import sys
from typing import List, Any

from bill_generator.bill import Bill
from bill_generator.billGenerator import BillGenerator
from bill_generator.user_interface import UserInterface
from bill_generator.extraction import Extraction
from bill_generator.winning_bill import WinningBill


class Controller:
"""Coordinates the application flow for Lotto ticket generation.

This class handles user interaction, ticket creation, number extraction,
and evaluation of winning tickets.

Attributes:
bill_generator (BillGenerator): Service used to generate ticket data.
extraction (Extraction): Service used to generate Lotto extractions.
winning_bill (WinningBill): Class used to evaluate tickets.
bills (dict[int, Bill]): Collection of generated tickets indexed by ID.
"""

def __init__(self, bill_generator, extraction, winning_bill):
"""Initializes the controller.

Args:
bill_generator (BillGenerator): Instance responsible for generating tickets.
extraction (Extraction): Instance responsible for generating extractions.
winning_bill (WinningBill): Class used to evaluate winning tickets.
"""
self.winning_bill = winning_bill
self.extraction = extraction
self.bill_generator = bill_generator
self.bills: dict[int, Bill] = {}

def how_many_bills(self) -> int:
"""Prompts the user to choose how many tickets to generate.

The user can select a number between 1 and 5, or 0 to exit.

Returns:
int: Number of tickets to generate.

Raises:
SystemExit: If the user chooses to exit (input = 0).
"""
while True:
try:
if (
num_bills := int(
input(
"How many bills would you like to generate? (1–5, 0 to exit): "
)
)
) == 0:
sys.exit("Thank you for using this program")

if 1 <= num_bills <= 5:
return num_bills

print("Invalid choice. Please select a number between 1 and 5.")

except ValueError:
print("Invalid input, insert only numbers.")

def how_many_numbers_for_bills(
self, min_required: int, max_required: int = 10
) -> int:
"""Prompts the user to choose how many numbers to include in a ticket.

The number must be within the allowed range defined by the selected
bet type (giocata).

Args:
min_required (int): Minimum number of numbers required.
max_required (int, optional): Maximum allowed numbers. Defaults to 10.

Returns:
int: Number of numbers selected by the user.
"""
while True:
try:
if (
numbers := int(
input(
f"How many numbers would you like to generate? (min {min_required}, max {max_required}): "
)
)
) >= min_required and numbers <= max_required:
return numbers

print(
f"You must insert a number between {min_required} and {max_required}."
)

except ValueError:
print("Invalid input, numbers only.")

def create_bills(self) -> None:
"""Generates all tickets requested by the user.

For each ticket, the user selects:
- bet type (giocata)
- number of values
- wheel (ruota)

The generated tickets are stored in `self.bills`.
"""
numbers_of_bills: int = self.how_many_bills()

for number in range(numbers_of_bills):
print(f"Generating bill number {number + 1}...")
giocata_name, min_numbers = self.bill_generator.giocata()

numbers_in_bills: int = self.how_many_numbers_for_bills(min_numbers)
ruota: str = self.bill_generator.get_ruota()
numbers: List[int] = self.bill_generator.get_numbers(numbers_in_bills)

bill: Bill = Bill(giocata_name, ruota, numbers)
self.bills[number] = bill

def print_bills(self) -> None:
"""Prints all generated tickets.

If no tickets were generated, a message is displayed.
"""
if not self.bills:
print("No bills were generated.")
return

for number, bill in enumerate(self.bills.values(), start=1):
UserInterface.print_bill(
number, bill.giocata, bill.ruota, bill.numbers
)

def control_winning_bill(self) -> list[dict]:
"""Evaluates all tickets against the extraction results.

Returns:
list[dict]: A list of dictionaries containing:
- index (int): Ticket index
- bill (Bill): The ticket object
- is_winner (bool): Whether the ticket is winning
- winning_numbers (list[int]): Matching numbers (if any)
"""
results = []

for index, bill in self.bills.items():
winning_bill = WinningBill(bill, self.extraction.extractions)
is_winner = winning_bill.retrieve_winning_bill()

results.append(
{
"index": index,
"bill": bill,
"is_winner": is_winner,
"winning_numbers": (
winning_bill.winning_numbers if is_winner else []
),
}
)

return results

def run(self) -> None:
"""Executes the full application workflow.

The flow includes:
- showing introduction and description
- generating tickets
- displaying tickets
- generating and displaying extractions
- evaluating and displaying winning tickets
"""
UserInterface.show_intro()
UserInterface.show_description()
self.create_bills()
self.print_bills()
self.extraction.start_extraction()
self.extraction.print_extractions()
results = self.control_winning_bill()
UserInterface.print_winning_bill(results)
51 changes: 51 additions & 0 deletions projects/001-lotto-game/python/bill_generator/extraction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import random
from bill_generator.rules import Ruote


class Extraction:
"""Generates and manages Lotto extractions for each wheel.

Attributes:
extractions (dict[str, list[int]]): Mapping between wheel names
and their extracted numbers.
"""

def __init__(self) -> None:
"""Initializes an empty extraction container."""
self.extractions: dict[str, list[int]] = {}

def start_extraction(self) -> dict[str, list[int]]:
"""Generates random extractions for each Lotto wheel.

For each wheel (excluding "Tutte"), generates 5 unique random
numbers between 1 and 90.

Returns:
dict[str, list[int]]: Dictionary where keys are wheel names
and values are lists of 5 unique extracted numbers.
"""
ruote = Ruote("ruote.txt")
for ruota in ruote.get_ruote():
if ruota == "Tutte":
continue

numbers_extracted: list[int] = []
while len(numbers_extracted) < 5:
number: int = random.randint(1, 90)
if number not in numbers_extracted:
numbers_extracted.append(number)

self.extractions[ruota] = numbers_extracted

return self.extractions

def print_extractions(self) -> None:
"""Prints all generated extractions grouped by wheel.

The output displays each wheel followed by its extracted numbers
in a readable format.
"""
print("\nExtractions Number:\n")
for ruota, numbers_extracted in self.extractions.items():
numbers_str: str = " ".join(str(n) for n in numbers_extracted)
print(f"{ruota}: {numbers_str}")
Loading