From 565914b1bdbb5bd6fb136577547a05f0e4bcbe4a Mon Sep 17 00:00:00 2001 From: Daniele Fiocca Date: Sun, 30 Nov 2025 12:54:18 +0100 Subject: [PATCH 1/4] first solution 001-lotto-game --- .../python/bill_generator/__init__.py | 0 .../python/bill_generator/bill.py | 28 ++++++ .../python/bill_generator/bill_generator.py | 83 +++++++++++++++++ .../python/bill_generator/controller.py | 93 +++++++++++++++++++ .../python/bill_generator/user_interface.py | 62 +++++++++++++ projects/001-lotto-game/python/main.py | 5 + 6 files changed, 271 insertions(+) create mode 100644 projects/001-lotto-game/python/bill_generator/__init__.py create mode 100644 projects/001-lotto-game/python/bill_generator/bill.py create mode 100644 projects/001-lotto-game/python/bill_generator/bill_generator.py create mode 100644 projects/001-lotto-game/python/bill_generator/controller.py create mode 100644 projects/001-lotto-game/python/bill_generator/user_interface.py diff --git a/projects/001-lotto-game/python/bill_generator/__init__.py b/projects/001-lotto-game/python/bill_generator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/001-lotto-game/python/bill_generator/bill.py b/projects/001-lotto-game/python/bill_generator/bill.py new file mode 100644 index 0000000..e662e0d --- /dev/null +++ b/projects/001-lotto-game/python/bill_generator/bill.py @@ -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} " diff --git a/projects/001-lotto-game/python/bill_generator/bill_generator.py b/projects/001-lotto-game/python/bill_generator/bill_generator.py new file mode 100644 index 0000000..e43140a --- /dev/null +++ b/projects/001-lotto-game/python/bill_generator/bill_generator.py @@ -0,0 +1,83 @@ +import random +from typing import List + + +class BillGenerator: + """Class to generate numbers, wheels, and play selections.""" + + @staticmethod + def get_numbers(n_numbers: int) -> list[int]: + """Generates a list of unique random numbers between 1 and 90. + + Args: + n_numbers (int): Number of numbers to generate. + + Returns: + List[int]: List of generated 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: + """Displays available wheels and allows the user to select one. + + Returns: + str: Wheel selected by the user. + """ + print("\nChoose a 'ruota' from the list below:\n") + ruote: List[str] = [ + "Bari", + "Cagliari", + "Firenze", + "Genova", + "Milano", + "Napoli", + "Palermo", + "Roma", + "Torino", + "Venezia", + "Tutte", + ] + + while True: + for i, route in enumerate(ruote, start=1): + print(f"{i}. {route}") + + if (choice := input("Select a route: ")).isdigit(): + choice = int(choice) + if 1 <= choice <= len(ruote): + return ruote[choice - 1] + + print("Invalid 'ruota', try again.\n") + + @staticmethod + def giocata() -> tuple[str, int] | None: + """Displays the available types of play and allows selection. + + Returns: + Tuple[str, int]: Name of the play and minimum number of numbers required. + """ + print("\nChoose a 'giocata' from the list below:\n") + giocate_list: dict[str, int] = { + "ambata": 2, + "ambo": 2, + "terno": 3, + "quaterna": 4, + "cinquina": 5, + } + keys: List[str] = list(giocate_list.keys()) + + for i, play in enumerate(keys, start=1): + print(f"{i}. {play}") + + while (choice := input("Select a 'giocata': ")) is not None: + if choice.isdigit() and 1 <= int(choice) <= len(keys): + selected = keys[int(choice) - 1] + return selected, giocate_list[selected] + print("Invalid choice, select a number from the list.\n") + return None diff --git a/projects/001-lotto-game/python/bill_generator/controller.py b/projects/001-lotto-game/python/bill_generator/controller.py new file mode 100644 index 0000000..bfd889e --- /dev/null +++ b/projects/001-lotto-game/python/bill_generator/controller.py @@ -0,0 +1,93 @@ +import sys +from typing import List + +from bill_generator.bill import Bill +from bill_generator.bill_generator import BillGenerator +from bill_generator.user_interface import UserInterface + + +class Controller: + """Controller class to manage ticket generation and display.""" + + def __init__(self): + """Initializes the controller and the ticket generator.""" + self.bill_generator: BillGenerator = BillGenerator() + self.bills: dict[int, Bill] = {} + + def how_many_bills(self) -> int: + """Asks the user how many tickets to generate. + + Returns: + int: Number of tickets to generate. + """ + 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) -> int: + """Asks the user how many numbers to generate for a ticket. + + Args: + min_required (int): Minimum number of numbers required for the selected play. + + Returns: + int: Number of numbers chosen by the user. + """ + while True: + try: + if ( + numbers := int( + input( + f"How many numbers would you like to generate? (min {min_required}): " + ) + ) + ) >= min_required: + return numbers + + print(f"You must insert at least {min_required} numbers.") + + except ValueError: + print("Invalid input, numbers only.") + + def create_bills(self) -> None: + """Generates all tickets requested by the user.""" + 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): + """Prints all generated tickets.""" + if not self.bills: + print("No bills were generated.") + for number, bill in enumerate(self.bills.values(), start=1): + UserInterface.print_bill(number, bill.giocata, bill.ruota, bill.numbers) + + def run(self): + """Runs the full program flow.""" + UserInterface.show_intro() + UserInterface.show_description() + self.create_bills() + self.print_bills() diff --git a/projects/001-lotto-game/python/bill_generator/user_interface.py b/projects/001-lotto-game/python/bill_generator/user_interface.py new file mode 100644 index 0000000..7b63e9e --- /dev/null +++ b/projects/001-lotto-game/python/bill_generator/user_interface.py @@ -0,0 +1,62 @@ +from typing import List + + +class UserInterface: + """Handles displaying tickets on screen.""" + + def show_intro(): + print("Welcome to Lotto Bill Generator") + + def show_description(): + print("With this software you can generate betting slips for the 'Lotto game'") + print( + "\nDISCLAIMER: There is no scientific proof of the correlation between the tickets generated and the probability of winning.\n" + ) + + @staticmethod + def print_bill( + bill_number: int, giocata: str, ruota: str, numbers: List[int] + ) -> None: + """Prints a ticket in a dynamic centered table. + + Args: + bill_number (int): Number of the generated ticket. + giocata (str): Type of play. + ruota (str): Selected wheel. + numbers (List[int]): Generated numbers for the ticket. + """ + numbers_str: str = " ".join(str(n) for n in numbers) + headers: List[str] = ["Bill", "Giocata", "Ruota", "Generate Numbers"] + row: List[str] = [f"Bill {bill_number}", giocata, ruota, numbers_str] + + col_widths: List[int] = [ + max(len(headers[i]), len(str(row[i]))) + 2 for i in range(len(headers)) + ] + + def make_line(left: str, fill: str, sep: str, right: str) -> str: + return left + sep.join(fill * w for w in col_widths) + right + + def center_text(text: str, width: int) -> str: + return text.center(width) + + def make_row(items: List[str]) -> str: + cells: List[str] = [ + center_text(items[i], col_widths[i]) for i in range(len(items)) + ] + return "║" + "║".join(cells) + "║" + + top: str = make_line("╔", "═", "╦", "╗") + sep: str = make_line("╠", "═", "╬", "╣") + bottom: str = make_line("╚", "═", "╩", "╝") + + bill: str = f""" + + + {top} + {make_row(headers)} + {sep} + {make_row(row)} + {bottom} + + """ + print(bill) diff --git a/projects/001-lotto-game/python/main.py b/projects/001-lotto-game/python/main.py index e69de29..064c2c2 100644 --- a/projects/001-lotto-game/python/main.py +++ b/projects/001-lotto-game/python/main.py @@ -0,0 +1,5 @@ +from bill_generator.controller import Controller + +if __name__ == "__main__": + controller = Controller() + controller.run() From f5bd8ca5726ff900e4c272a219020bcac2fc60d3 Mon Sep 17 00:00:00 2001 From: Daniele Fiocca Date: Fri, 17 Apr 2026 20:29:24 +0200 Subject: [PATCH 2/4] first solution 002-lotto-fake-extraction --- projects/001-lotto-game/python/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 228 bytes .../__pycache__/bill.cpython-313.pyc | Bin 0 -> 1465 bytes .../__pycache__/billGenerator.cpython-313.pyc | Bin 0 -> 3464 bytes .../__pycache__/controller.cpython-313.pyc | Bin 0 -> 6131 bytes .../__pycache__/extraction.cpython-313.pyc | Bin 0 -> 2419 bytes .../user_interface.cpython-313.pyc | Bin 0 -> 5110 bytes .../__pycache__/winning_bill.cpython-313.pyc | Bin 0 -> 2219 bytes .../{bill_generator.py => billGenerator.py} | 10 +-- .../python/bill_generator/controller.py | 44 ++++++++-- .../python/bill_generator/extraction.py | 70 ++++++++++++++++ .../python/bill_generator/user_interface.py | 18 ++++ .../python/bill_generator/winning_bill.py | 79 ++++++++++++++++++ projects/001-lotto-game/python/main.py | 4 + 14 files changed, 214 insertions(+), 11 deletions(-) create mode 100644 projects/001-lotto-game/python/__init__.py create mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/__init__.cpython-313.pyc create mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/bill.cpython-313.pyc create mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/billGenerator.cpython-313.pyc create mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/controller.cpython-313.pyc create mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/extraction.cpython-313.pyc create mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/user_interface.cpython-313.pyc create mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/winning_bill.cpython-313.pyc rename projects/001-lotto-game/python/bill_generator/{bill_generator.py => billGenerator.py} (94%) create mode 100644 projects/001-lotto-game/python/bill_generator/extraction.py create mode 100644 projects/001-lotto-game/python/bill_generator/winning_bill.py diff --git a/projects/001-lotto-game/python/__init__.py b/projects/001-lotto-game/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/projects/001-lotto-game/python/bill_generator/__pycache__/__init__.cpython-313.pyc b/projects/001-lotto-game/python/bill_generator/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3589cefd8a66e8c2bdadce729bcf4d044f42193e GIT binary patch literal 228 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 DB5+mi literal 0 HcmV?d00001 diff --git a/projects/001-lotto-game/python/bill_generator/__pycache__/controller.cpython-313.pyc b/projects/001-lotto-game/python/bill_generator/__pycache__/controller.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..494d50c35ef87ed8bce430cc2dcc909debf701b1 GIT binary patch literal 6131 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 P{3iN7Q+S1d1jqgXh>-5W literal 0 HcmV?d00001 diff --git a/projects/001-lotto-game/python/bill_generator/bill_generator.py b/projects/001-lotto-game/python/bill_generator/billGenerator.py similarity index 94% rename from projects/001-lotto-game/python/bill_generator/bill_generator.py rename to projects/001-lotto-game/python/bill_generator/billGenerator.py index e43140a..364be22 100644 --- a/projects/001-lotto-game/python/bill_generator/bill_generator.py +++ b/projects/001-lotto-game/python/bill_generator/billGenerator.py @@ -64,11 +64,11 @@ def giocata() -> tuple[str, int] | None: """ print("\nChoose a 'giocata' from the list below:\n") giocate_list: dict[str, int] = { - "ambata": 2, - "ambo": 2, - "terno": 3, - "quaterna": 4, - "cinquina": 5, + "Ambata": 1, + "Ambo": 2, + "Terno": 3, + "Quaterna": 4, + "Cinquina": 5, } keys: List[str] = list(giocate_list.keys()) diff --git a/projects/001-lotto-game/python/bill_generator/controller.py b/projects/001-lotto-game/python/bill_generator/controller.py index bfd889e..4615a60 100644 --- a/projects/001-lotto-game/python/bill_generator/controller.py +++ b/projects/001-lotto-game/python/bill_generator/controller.py @@ -1,9 +1,11 @@ import sys -from typing import List +from typing import List, Any from bill_generator.bill import Bill -from bill_generator.bill_generator import BillGenerator +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: @@ -12,6 +14,10 @@ class Controller: def __init__(self): """Initializes the controller and the ticket generator.""" self.bill_generator: BillGenerator = BillGenerator() + self.extraction = Extraction() + self.winning_bill: WinningBill + + self.bills: dict[int, Bill] = {} def how_many_bills(self) -> int: @@ -39,7 +45,7 @@ def how_many_bills(self) -> int: except ValueError: print("Invalid input, insert only numbers.") - def how_many_numbers_for_bills(self, min_required: int) -> int: + def how_many_numbers_for_bills(self, min_required: int, max_required: int=10) -> int: """Asks the user how many numbers to generate for a ticket. Args: @@ -47,19 +53,20 @@ def how_many_numbers_for_bills(self, min_required: int) -> int: Returns: int: Number of numbers chosen by the user. + :param max_required: """ while True: try: if ( numbers := int( input( - f"How many numbers would you like to generate? (min {min_required}): " + f"How many numbers would you like to generate? (min {min_required}, max {max_required}): " ) ) - ) >= min_required: + ) >= min_required and numbers <= max_required: return numbers - print(f"You must insert at least {min_required} numbers.") + print(f"You must insert at least {min_required} numbers or {max_required} numbers.") except ValueError: print("Invalid input, numbers only.") @@ -77,6 +84,8 @@ def create_bills(self) -> None: numbers: List[int] = self.bill_generator.get_numbers(numbers_in_bills) bill: Bill = Bill(giocata_name, ruota, numbers) self.bills[number] = bill + print(type(self.bills)) + def print_bills(self): """Prints all generated tickets.""" @@ -85,9 +94,32 @@ def print_bills(self): 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]: + """Checks all bills against the extraction and prints winning ones.""" + results = [] + + for index, bill in self.bills.items(): + winning_bill = WinningBill(bill, self.extraction.extractions) + is_winner = winning_bill.retrive_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): """Runs the full program flow.""" 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) + + + diff --git a/projects/001-lotto-game/python/bill_generator/extraction.py b/projects/001-lotto-game/python/bill_generator/extraction.py new file mode 100644 index 0000000..c5505a6 --- /dev/null +++ b/projects/001-lotto-game/python/bill_generator/extraction.py @@ -0,0 +1,70 @@ +import random +from typing import List + + +class Utility: + """Utility class providing helper methods for Lotto operations.""" + + def get_ruota() -> List[str]: + """Returns the list of available Lotto wheels. + + Returns: + List[str]: A list containing the names of all Lotto wheels. + """ + ruote: List[str] = [ + "Bari", + "Cagliari", + "Firenze", + "Genova", + "Milano", + "Napoli", + "Palermo", + "Roma", + "Torino", + "Venezia" + ] + return ruote + + +class Extraction: + """Handles the generation and display of Lotto extractions.""" + + def __init__(self) -> None: + """Initializes the Extraction instance. + + Attributes: + utility (Utility): Reference to the Utility class for helper methods. + extractions (dict[str, list[int]]): Dictionary storing extracted numbers for each wheel. + """ + self.utility: Utility = Utility + self.extractions: dict[str, list[int]] = {} + + def start_extraction(self) -> dict[str, list[int]]: + """Generates random extractions for each Lotto wheel. + + For each wheel, this method generates 5 unique random numbers + between 1 and 90 and stores them in the `extractions` dictionary. + + Returns: + dict[str, list[int]]: A dictionary where keys are wheel names + and values are lists of extracted numbers. + """ + for ruote in self.utility.get_ruota(): + numbers_extracted: list[int] = [] + while len(numbers_extracted) < 5: + numbers: int = random.randint(1, 90) + if numbers not in numbers_extracted: + numbers_extracted.append(numbers) + self.extractions[ruote] = numbers_extracted + return self.extractions + + def print_extractions(self) -> None: + """Prints the extracted numbers for each Lotto wheel. + + The output is formatted for readability, showing each wheel + followed by its extracted numbers. + """ + print("\nExtractions Number:\n") + for ruote, numbers_extracted in self.extractions.items(): + numbers_str: str = " ".join(str(n) for n in numbers_extracted) + print(f"{ruote}: {numbers_str}") \ No newline at end of file diff --git a/projects/001-lotto-game/python/bill_generator/user_interface.py b/projects/001-lotto-game/python/bill_generator/user_interface.py index 7b63e9e..aa1d6f8 100644 --- a/projects/001-lotto-game/python/bill_generator/user_interface.py +++ b/projects/001-lotto-game/python/bill_generator/user_interface.py @@ -60,3 +60,21 @@ def make_row(items: List[str]) -> str: """ print(bill) + + @staticmethod + def print_winning_bill(results: list[dict]) -> None: + """Prints winning check results for all bills""" + print("\nChecking Winning Bills Numbers...\n") + + for r in results: + index, bill =r["index"], r["bill"] + if r["is_winner"]: + print( + f"Bill {index + 1} WINNER!" + f"\nGiocata: {bill.giocata}" + f"\nRuota: {bill.ruota}" + f"\nNumbers played: {bill.numbers}" + f"\nWinning numbers: {r['winning_numbers']}\n" + ) + else: + print(f"Bill {index + 1} not winning.\n") diff --git a/projects/001-lotto-game/python/bill_generator/winning_bill.py b/projects/001-lotto-game/python/bill_generator/winning_bill.py new file mode 100644 index 0000000..4d31a75 --- /dev/null +++ b/projects/001-lotto-game/python/bill_generator/winning_bill.py @@ -0,0 +1,79 @@ +from bill_generator.extraction import Extraction +from bill_generator.bill import Bill + + +class WinningBill: + """Represents the evaluation of a Lotto ticket against extraction results. + + This class is responsible for checking whether a given ticket (`Bill`) + + is a winning one based on the extracted numbers. + + """ + GIOCATE_RULES = { + "Ambata": 1, + "Ambo": 2, + "Terno": 3, + "Quaterna": 4, + "Cinquina": 5, + } + + def __init__(self, bill: Bill, extractions: Extraction): + """Initializes a WinningBill instance. + + Args: + + bill (Bill): The Lotto ticket to evaluate. + + extractions (Extraction): A dictionary-like object containing + + extracted numbers for each wheel. + + """ + self.bill = bill + self.extractions = extractions + self.winning_numbers = [] + + def retrieve_winning_bill(self) -> bool: + """Determines whether the ticket is a winning one. + + The method checks the extracted numbers against the numbers in the + + ticket. If the ticket is played on all wheels ("Tutte"), it compares + + against every wheel. Otherwise, it checks only the selected wheel. + + Returns: + + bool: True if the number of matching numbers satisfies the rule + + for the selected type of bet (giocata), False otherwise. + + """ + ruota = self.bill.ruota + + if ruota == "Tutte": + for extracted_numbers in self.extractions.values(): + self._compare_numbers(extracted_numbers) + else: + extracted_numbers = self.extractions.get(ruota, []) + self._compare_numbers(extracted_numbers) + + required = self.GIOCATE_RULES[self.bill.giocata] + return len(self.winning_numbers) >= required + + def _compare_numbers(self, extracted_numbers: list[int]): + """Compares ticket numbers with extracted numbers. + + Adds matching numbers to the `winning_numbers` list, avoiding duplicates. + + Args: + + extracted_numbers (list[int]): List of numbers extracted for a + + specific wheel. + + """ + for number in self.bill.numbers: + if number in extracted_numbers and number not in self.winning_numbers: + self.winning_numbers.append(number) diff --git a/projects/001-lotto-game/python/main.py b/projects/001-lotto-game/python/main.py index 064c2c2..cddfdeb 100644 --- a/projects/001-lotto-game/python/main.py +++ b/projects/001-lotto-game/python/main.py @@ -1,5 +1,9 @@ from bill_generator.controller import Controller + if __name__ == "__main__": controller = Controller() + controller.run() + + From 83b2a1da0ecc08edc835eed6b0822f078fdd2988 Mon Sep 17 00:00:00 2001 From: Daniele Fiocca Date: Fri, 17 Apr 2026 20:42:29 +0200 Subject: [PATCH 3/4] remove __pycache__ and add to .gitignore --- projects/001-lotto-game/python/.gitignore | 2 ++ .../__pycache__/__init__.cpython-313.pyc | Bin 228 -> 0 bytes .../__pycache__/bill.cpython-313.pyc | Bin 1465 -> 0 bytes .../__pycache__/billGenerator.cpython-313.pyc | Bin 3464 -> 0 bytes .../__pycache__/controller.cpython-313.pyc | Bin 6131 -> 0 bytes .../__pycache__/extraction.cpython-313.pyc | Bin 2419 -> 0 bytes .../__pycache__/user_interface.cpython-313.pyc | Bin 5110 -> 0 bytes .../__pycache__/winning_bill.cpython-313.pyc | Bin 2219 -> 0 bytes 8 files changed, 2 insertions(+) create mode 100644 projects/001-lotto-game/python/.gitignore delete mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/__init__.cpython-313.pyc delete mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/bill.cpython-313.pyc delete mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/billGenerator.cpython-313.pyc delete mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/controller.cpython-313.pyc delete mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/extraction.cpython-313.pyc delete mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/user_interface.cpython-313.pyc delete mode 100644 projects/001-lotto-game/python/bill_generator/__pycache__/winning_bill.cpython-313.pyc diff --git a/projects/001-lotto-game/python/.gitignore b/projects/001-lotto-game/python/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/projects/001-lotto-game/python/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/projects/001-lotto-game/python/bill_generator/__pycache__/__init__.cpython-313.pyc b/projects/001-lotto-game/python/bill_generator/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 3589cefd8a66e8c2bdadce729bcf4d044f42193e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 228 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 DB5+mi diff --git a/projects/001-lotto-game/python/bill_generator/__pycache__/controller.cpython-313.pyc b/projects/001-lotto-game/python/bill_generator/__pycache__/controller.cpython-313.pyc deleted file mode 100644 index 494d50c35ef87ed8bce430cc2dcc909debf701b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6131 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 P{3iN7Q+S1d1jqgXh>-5W From 9ac4c011894393a07991117f76436d9da93bdd98 Mon Sep 17 00:00:00 2001 From: Daniele Fiocca Date: Sun, 3 May 2026 12:15:50 +0200 Subject: [PATCH 4/4] Refactoring code after review --- projects/001-lotto-game/python/__init__.py | 0 .../python/bill_generator/billGenerator.py | 78 +++------ .../python/bill_generator/controller.py | 124 ++++++++++---- .../python/bill_generator/extraction.py | 75 ++++----- .../python/bill_generator/rules.py | 156 ++++++++++++++++++ .../python/bill_generator/winning_bill.py | 76 ++++----- projects/001-lotto-game/python/giocate.txt | 5 + projects/001-lotto-game/python/main.py | 10 +- projects/001-lotto-game/python/ruote.txt | 11 ++ .../002-lotto-fake-extraction/python/main.py | 13 ++ 10 files changed, 372 insertions(+), 176 deletions(-) delete mode 100644 projects/001-lotto-game/python/__init__.py create mode 100644 projects/001-lotto-game/python/bill_generator/rules.py create mode 100644 projects/001-lotto-game/python/giocate.txt create mode 100644 projects/001-lotto-game/python/ruote.txt diff --git a/projects/001-lotto-game/python/__init__.py b/projects/001-lotto-game/python/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/projects/001-lotto-game/python/bill_generator/billGenerator.py b/projects/001-lotto-game/python/bill_generator/billGenerator.py index 364be22..eba2c93 100644 --- a/projects/001-lotto-game/python/bill_generator/billGenerator.py +++ b/projects/001-lotto-game/python/bill_generator/billGenerator.py @@ -1,19 +1,23 @@ import random -from typing import List +from bill_generator.rules import Ruote, Giocate class BillGenerator: - """Class to generate numbers, wheels, and play selections.""" + """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 a list of unique random numbers between 1 and 90. + """Generates unique random numbers between 1 and 90. Args: - n_numbers (int): Number of numbers to generate. + n_numbers (int): Number of unique numbers to generate. Returns: - List[int]: List of generated numbers. + list[int]: List of generated unique numbers. """ numbers: list[int] = [] while len(numbers) < n_numbers: @@ -24,60 +28,32 @@ def get_numbers(n_numbers: int) -> list[int]: @staticmethod def get_ruota() -> str: - """Displays available wheels and allows the user to select one. + """Prompts the user to select a Lotto wheel. + + Displays the available wheels and returns the selected one. Returns: - str: Wheel selected by the user. + str: The wheel selected by the user. """ + ruote = Ruote("ruote.txt") print("\nChoose a 'ruota' from the list below:\n") - ruote: List[str] = [ - "Bari", - "Cagliari", - "Firenze", - "Genova", - "Milano", - "Napoli", - "Palermo", - "Roma", - "Torino", - "Venezia", - "Tutte", - ] - - while True: - for i, route in enumerate(ruote, start=1): - print(f"{i}. {route}") - - if (choice := input("Select a route: ")).isdigit(): - choice = int(choice) - if 1 <= choice <= len(ruote): - return ruote[choice - 1] - - print("Invalid 'ruota', try again.\n") + ruote.print_ruote() + selection = ruote.select_ruote() + return selection @staticmethod def giocata() -> tuple[str, int] | None: - """Displays the available types of play and allows selection. + """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]: Name of the play and minimum number of numbers required. + 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_list: dict[str, int] = { - "Ambata": 1, - "Ambo": 2, - "Terno": 3, - "Quaterna": 4, - "Cinquina": 5, - } - keys: List[str] = list(giocate_list.keys()) - - for i, play in enumerate(keys, start=1): - print(f"{i}. {play}") - - while (choice := input("Select a 'giocata': ")) is not None: - if choice.isdigit() and 1 <= int(choice) <= len(keys): - selected = keys[int(choice) - 1] - return selected, giocate_list[selected] - print("Invalid choice, select a number from the list.\n") - return None + giocate.print_giocata() + return giocate.select_giocata() \ No newline at end of file diff --git a/projects/001-lotto-game/python/bill_generator/controller.py b/projects/001-lotto-game/python/bill_generator/controller.py index 4615a60..c5050ea 100644 --- a/projects/001-lotto-game/python/bill_generator/controller.py +++ b/projects/001-lotto-game/python/bill_generator/controller.py @@ -9,22 +9,41 @@ class Controller: - """Controller class to manage ticket generation and display.""" + """Coordinates the application flow for Lotto ticket generation. - def __init__(self): - """Initializes the controller and the ticket generator.""" - self.bill_generator: BillGenerator = BillGenerator() - self.extraction = Extraction() - self.winning_bill: WinningBill + 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: - """Asks the user how many tickets to generate. + """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: @@ -45,15 +64,20 @@ def how_many_bills(self) -> int: except ValueError: print("Invalid input, insert only numbers.") - def how_many_numbers_for_bills(self, min_required: int, max_required: int=10) -> int: - """Asks the user how many numbers to generate for a ticket. + 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 for the selected play. + min_required (int): Minimum number of numbers required. + max_required (int, optional): Maximum allowed numbers. Defaults to 10. Returns: - int: Number of numbers chosen by the user. - :param max_required: + int: Number of numbers selected by the user. """ while True: try: @@ -66,13 +90,23 @@ def how_many_numbers_for_bills(self, min_required: int, max_required: int=10) -> ) >= min_required and numbers <= max_required: return numbers - print(f"You must insert at least {min_required} numbers or {max_required} 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.""" + """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): @@ -82,36 +116,63 @@ def create_bills(self) -> None: 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 - print(type(self.bills)) + def print_bills(self) -> None: + """Prints all generated tickets. - def print_bills(self): - """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) + UserInterface.print_bill( + number, bill.giocata, bill.ruota, bill.numbers + ) def control_winning_bill(self) -> list[dict]: - """Checks all bills against the extraction and prints winning ones.""" + """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.retrive_winning_bill() - - results.append({ - "index": index, - "bill": bill, - "is_winner": is_winner, - "winning_numbers": winning_bill.winning_numbers if is_winner else [] - }) + 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): - """Runs the full program flow.""" + 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() @@ -119,7 +180,4 @@ def run(self): self.extraction.start_extraction() self.extraction.print_extractions() results = self.control_winning_bill() - UserInterface.print_winning_bill(results) - - - + UserInterface.print_winning_bill(results) \ No newline at end of file diff --git a/projects/001-lotto-game/python/bill_generator/extraction.py b/projects/001-lotto-game/python/bill_generator/extraction.py index c5505a6..e37e19f 100644 --- a/projects/001-lotto-game/python/bill_generator/extraction.py +++ b/projects/001-lotto-game/python/bill_generator/extraction.py @@ -1,70 +1,51 @@ import random -from typing import List - - -class Utility: - """Utility class providing helper methods for Lotto operations.""" - - def get_ruota() -> List[str]: - """Returns the list of available Lotto wheels. - - Returns: - List[str]: A list containing the names of all Lotto wheels. - """ - ruote: List[str] = [ - "Bari", - "Cagliari", - "Firenze", - "Genova", - "Milano", - "Napoli", - "Palermo", - "Roma", - "Torino", - "Venezia" - ] - return ruote +from bill_generator.rules import Ruote class Extraction: - """Handles the generation and display of Lotto extractions.""" + """Generates and manages Lotto extractions for each wheel. - def __init__(self) -> None: - """Initializes the Extraction instance. + Attributes: + extractions (dict[str, list[int]]): Mapping between wheel names + and their extracted numbers. + """ - Attributes: - utility (Utility): Reference to the Utility class for helper methods. - extractions (dict[str, list[int]]): Dictionary storing extracted numbers for each wheel. - """ - self.utility: Utility = Utility + 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, this method generates 5 unique random numbers - between 1 and 90 and stores them in the `extractions` dictionary. + For each wheel (excluding "Tutte"), generates 5 unique random + numbers between 1 and 90. Returns: - dict[str, list[int]]: A dictionary where keys are wheel names - and values are lists of extracted numbers. + dict[str, list[int]]: Dictionary where keys are wheel names + and values are lists of 5 unique extracted numbers. """ - for ruote in self.utility.get_ruota(): + ruote = Ruote("ruote.txt") + for ruota in ruote.get_ruote(): + if ruota == "Tutte": + continue + numbers_extracted: list[int] = [] while len(numbers_extracted) < 5: - numbers: int = random.randint(1, 90) - if numbers not in numbers_extracted: - numbers_extracted.append(numbers) - self.extractions[ruote] = numbers_extracted + 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 the extracted numbers for each Lotto wheel. + """Prints all generated extractions grouped by wheel. - The output is formatted for readability, showing each wheel - followed by its extracted numbers. + The output displays each wheel followed by its extracted numbers + in a readable format. """ print("\nExtractions Number:\n") - for ruote, numbers_extracted in self.extractions.items(): + for ruota, numbers_extracted in self.extractions.items(): numbers_str: str = " ".join(str(n) for n in numbers_extracted) - print(f"{ruote}: {numbers_str}") \ No newline at end of file + print(f"{ruota}: {numbers_str}") \ No newline at end of file diff --git a/projects/001-lotto-game/python/bill_generator/rules.py b/projects/001-lotto-game/python/bill_generator/rules.py new file mode 100644 index 0000000..0e0af82 --- /dev/null +++ b/projects/001-lotto-game/python/bill_generator/rules.py @@ -0,0 +1,156 @@ +class Utility: + """Utility class providing helper methods for CLI interactions.""" + + @staticmethod + def print_list(items: list[str]) -> None: + """Prints a numbered list of items. + + Args: + items (list[str]): List of strings to be displayed. + """ + for i, item in enumerate(items, start=1): + print(f"{i}. {item}") + + @staticmethod + def select_from_list(items: list[str], prompt: str) -> int | None: + """Prompts the user to select an item from a list. + + The user must input a number corresponding to the item index. + + Args: + items (list[str]): List of selectable items. + prompt (str): Message displayed to the user for input. + + Returns: + int | None: The selected index (0-based) if valid, otherwise None. + """ + while (choice := input(prompt)) is not None: + if choice.isdigit(): + index = int(choice) + if 1 <= index <= len(items): + return index - 1 + print("Invalid choice, try again.\n") + return None + + +class Ruote: + """Represents a collection of Lotto wheels loaded from a file. + + Attributes: + filename (str): Path to the file containing wheel names. + _ruote (list[str]): List of wheel names. + """ + + def __init__(self, filename: str) -> None: + """Initializes the Ruote instance and loads data from file. + + Args: + filename (str): Path to the file containing wheel names. + """ + self.filename: str = filename + self._ruote: list[str] = [] + self._load_data() + + def _load_data(self) -> None: + """Loads wheel names from the file into the internal list. + + Raises: + FileNotFoundError: If the specified file does not exist. + """ + try: + with open(self.filename, 'r', encoding='utf-8') as file: + self._ruote = [line.strip() for line in file if line.strip()] + except FileNotFoundError: + raise FileNotFoundError(f"File {self.filename} not found.") + + def get_ruote(self) -> list[str]: + """Returns the list of loaded wheel names. + + Returns: + list[str]: List of wheel names. + """ + return self._ruote + + def print_ruote(self) -> None: + """Prints all available wheel names as a numbered list.""" + for i, ruota in enumerate(self._ruote, start=1): + print(f"{i}. {ruota}") + + def select_ruote(self) -> str: + """Prompts the user to select a wheel. + + Returns: + str: The selected wheel name. + """ + index = Utility.select_from_list(self._ruote, "Select a ruota: ") + return self._ruote[index] + + +class Giocate: + """Represents Lotto bet types loaded from a file. + + Each bet type is associated with a numeric value. + + Attributes: + filename (str): Path to the file containing bet types. + _giocata (dict[str, int]): Dictionary mapping bet names to values. + """ + + def __init__(self, filename: str): + """Initializes the Giocate instance and loads data from file. + + Args: + filename (str): Path to the file containing bet types. + """ + self.filename = filename + self._giocata: dict[str, int] = {} + self._load_data() + + def _load_data(self) -> None: + """Loads bet types and their values from the file. + + Expected file format: + key:value + + Raises: + FileNotFoundError: If the specified file does not exist. + """ + try: + with open(self.filename, 'r', encoding='utf-8') as file: + for line in file: + line = line.strip() + if ":" in line: + key, value = line.split(":", 1) + self._giocata[key.strip()] = int(value.strip()) + except FileNotFoundError: + raise FileNotFoundError(f"File {self.filename} not found.") + + def get_giocata(self) -> dict[str, int]: + """Returns all available bet types. + + Returns: + dict[str, int]: Dictionary of bet names and their values. + """ + return self._giocata + + def print_giocata(self) -> None: + """Prints all available bet types as a numbered list.""" + for i, giocata in enumerate(self._giocata, start=1): + print(f"{i}. {giocata}") + + def select_giocata(self) -> tuple[str, int]: + """Prompts the user to select a bet type. + + Returns: + tuple[str, int]: A tuple containing the selected bet name + and its associated value. + """ + keys = list(self._giocata.keys()) + index = Utility.select_from_list(keys, "Select a giocata: ") + selected = keys[index] + return selected, self._giocata[selected] + + + + + diff --git a/projects/001-lotto-game/python/bill_generator/winning_bill.py b/projects/001-lotto-game/python/bill_generator/winning_bill.py index 4d31a75..f632119 100644 --- a/projects/001-lotto-game/python/bill_generator/winning_bill.py +++ b/projects/001-lotto-game/python/bill_generator/winning_bill.py @@ -1,55 +1,48 @@ from bill_generator.extraction import Extraction from bill_generator.bill import Bill +from bill_generator.rules import Giocate class WinningBill: - """Represents the evaluation of a Lotto ticket against extraction results. + """Evaluates whether a Lotto ticket is a winning one. - This class is responsible for checking whether a given ticket (`Bill`) + This class compares the numbers on a given ticket (`Bill`) with + the extracted numbers and determines if the ticket satisfies + the winning conditions based on the selected bet type. - is a winning one based on the extracted numbers. - - """ - GIOCATE_RULES = { - "Ambata": 1, - "Ambo": 2, - "Terno": 3, - "Quaterna": 4, - "Cinquina": 5, - } + Attributes: + bill (Bill): The Lotto ticket to evaluate. + extractions (Extraction): Object containing extraction results. + winning_numbers (list[int]): List of matched numbers between + the ticket and the extractions. + """ def __init__(self, bill: Bill, extractions: Extraction): """Initializes a WinningBill instance. - Args: - - bill (Bill): The Lotto ticket to evaluate. - - extractions (Extraction): A dictionary-like object containing - - extracted numbers for each wheel. - - """ + Args: + bill (Bill): The Lotto ticket to evaluate. + extractions (Extraction): Object containing extracted numbers + for each wheel. + """ self.bill = bill self.extractions = extractions - self.winning_numbers = [] + self.winning_numbers: list[int] = [] def retrieve_winning_bill(self) -> bool: """Determines whether the ticket is a winning one. - The method checks the extracted numbers against the numbers in the - - ticket. If the ticket is played on all wheels ("Tutte"), it compares + If the ticket is played on all wheels ("Tutte"), the method checks + against every wheel. Otherwise, it checks only the selected wheel. - against every wheel. Otherwise, it checks only the selected wheel. + The ticket is considered winning if the number of matched numbers + is greater than or equal to the required amount defined by the + selected bet type (giocata). - Returns: - - bool: True if the number of matching numbers satisfies the rule - - for the selected type of bet (giocata), False otherwise. - - """ + Returns: + bool: True if the ticket is a winning one, False otherwise. + """ + giocate_rules = Giocate("giocate.txt").get_giocata() ruota = self.bill.ruota if ruota == "Tutte": @@ -59,21 +52,18 @@ def retrieve_winning_bill(self) -> bool: extracted_numbers = self.extractions.get(ruota, []) self._compare_numbers(extracted_numbers) - required = self.GIOCATE_RULES[self.bill.giocata] + required = giocate_rules[self.bill.giocata] return len(self.winning_numbers) >= required - def _compare_numbers(self, extracted_numbers: list[int]): + def _compare_numbers(self, extracted_numbers: list[int]) -> None: """Compares ticket numbers with extracted numbers. - Adds matching numbers to the `winning_numbers` list, avoiding duplicates. + Matching numbers are added to `winning_numbers`, avoiding duplicates. Args: - - extracted_numbers (list[int]): List of numbers extracted for a - - specific wheel. - - """ + extracted_numbers (list[int]): List of numbers extracted for + a specific wheel. + """ for number in self.bill.numbers: if number in extracted_numbers and number not in self.winning_numbers: - self.winning_numbers.append(number) + self.winning_numbers.append(number) \ No newline at end of file diff --git a/projects/001-lotto-game/python/giocate.txt b/projects/001-lotto-game/python/giocate.txt new file mode 100644 index 0000000..13a19dd --- /dev/null +++ b/projects/001-lotto-game/python/giocate.txt @@ -0,0 +1,5 @@ +Ambata: 1 +Ambo: 2 +Terno: 3 +Quaterna: 4 +Cinquina: 5 \ No newline at end of file diff --git a/projects/001-lotto-game/python/main.py b/projects/001-lotto-game/python/main.py index cddfdeb..24b9e83 100644 --- a/projects/001-lotto-game/python/main.py +++ b/projects/001-lotto-game/python/main.py @@ -1,8 +1,14 @@ from bill_generator.controller import Controller - +from bill_generator.extraction import Extraction +from bill_generator.winning_bill import WinningBill +from bill_generator.billGenerator import BillGenerator +from bill_generator.bill import Bill if __name__ == "__main__": - controller = Controller() + bill_generator = BillGenerator() + extraction = Extraction() + winning_bill = WinningBill(Bill,extraction) + controller = Controller(bill_generator, extraction, winning_bill) controller.run() diff --git a/projects/001-lotto-game/python/ruote.txt b/projects/001-lotto-game/python/ruote.txt new file mode 100644 index 0000000..995f162 --- /dev/null +++ b/projects/001-lotto-game/python/ruote.txt @@ -0,0 +1,11 @@ +Bari +Cagliari +Firenze +Genova +Milano +Napoli +Palermo +Roma +Torino +Venezia +Tutte \ No newline at end of file diff --git a/projects/002-lotto-fake-extraction/python/main.py b/projects/002-lotto-fake-extraction/python/main.py index e69de29..384ee9a 100644 --- a/projects/002-lotto-fake-extraction/python/main.py +++ b/projects/002-lotto-fake-extraction/python/main.py @@ -0,0 +1,13 @@ + + + + + + + + + + + + +