From 6fe2daeb29d807a03722731f8d941deba98e2114 Mon Sep 17 00:00:00 2001 From: Jaylon Date: Mon, 3 Nov 2025 00:58:11 -0500 Subject: [PATCH 1/6] Add Slots game with CLI and tests --- src/nimbuscasino/__init__.py | 14 ++++ src/nimbuscasino/slots.py | 143 +++++++++++++++++++++++++++++++++++ tests/test_slots.py | 17 +++++ 3 files changed, 174 insertions(+) create mode 100755 src/nimbuscasino/__init__.py create mode 100644 src/nimbuscasino/slots.py create mode 100644 tests/test_slots.py diff --git a/src/nimbuscasino/__init__.py b/src/nimbuscasino/__init__.py new file mode 100755 index 0000000..7ef730e --- /dev/null +++ b/src/nimbuscasino/__init__.py @@ -0,0 +1,14 @@ +""" +This file is required for Python to recognize the directory as a "regular" package. + +Python "regular" packages use the __init__.py file to initialize the package and +can include package-level variables, import statements, and initialization code +you wish to run when the package is first imported into a program. + +For more information, see the official documentation: +https://docs.python.org/3/reference/import.html#regular-packages +""" + +from .slots import spin_slots, SlotsResult as SpinResult + +__all__ = ["spin_slots", "SpinResult", "SlotsResult"] diff --git a/src/nimbuscasino/slots.py b/src/nimbuscasino/slots.py new file mode 100644 index 0000000..c3b5eb6 --- /dev/null +++ b/src/nimbuscasino/slots.py @@ -0,0 +1,143 @@ +# slots.py + +# first line for future type hints +from __future__ import annotations + +# import libraries +import random +from typing import Optional, TypedDict, Dict, List, Literal + +# info for one winning line +class SlotsLineInfo(TypedDict): + symbol: str + count: int + payout: int + +# full result of one spin +class SlotsResult(TypedDict): + game: Literal["slots"] + grid: List[List[str]] + lines: Dict[str, SlotsLineInfo] + total_payout: int + +# symbols, odds, and payout table +DEFAULT_SYMBOLS = ["šŸ’", "šŸ‹", "šŸ””", "⭐", "7"] +DEFAULT_WEIGHTS = [30, 25, 20, 15, 10] +DEFAULT_PAYTABLE = { + "šŸ’": {3: 5}, + "šŸ‹": {3: 6}, + "šŸ””": {3: 12}, + "⭐": {3: 20}, + "7": {2: 5, 3: 50}, +} + +# 5 standard winning lines +LINES = { + "top": [(0, 0), (0, 1), (0, 2)], + "middle": [(1, 0), (1, 1), (1, 2)], + "bottom": [(2, 0), (2, 1), (2, 2)], + "diag_down": [(0, 0), (1, 1), (2, 2)], + "diag_up": [(2, 0), (1, 1), (0, 2)], +} + +# do one spin and return result +def spin_slots( + bet: int = 1, + rng: Optional[random.Random] = None, + symbols: Optional[List[str]] = None, + weights: Optional[List[int]] = None, + paytable: Optional[Dict[str, Dict[int, int]]] = None, + rows: int = 3, + cols: int = 3, +) -> SlotsResult: + # check inputs + if not isinstance(bet, int) or bet <= 0: + raise ValueError("bet must be positive") + if rows <= 0 or cols <= 0: + raise ValueError("rows and cols must be positive") + + # choose random and settings + r = rng or random.Random() + syms = symbols or DEFAULT_SYMBOLS + wts = weights or DEFAULT_WEIGHTS + pt = paytable or DEFAULT_PAYTABLE + + # make grid (3x3) + grid: List[List[str]] = [[None] * cols for _ in range(rows)] # type: ignore + for c in range(cols): + for rr in range(rows): + grid[rr][c] = r.choices(syms, wts, k=1)[0] + + # check lines for wins + lines_out: Dict[str, SlotsLineInfo] = {} + total = 0 + for name, coords in LINES.items(): + if any(rr >= rows or cc >= cols for rr, cc in coords): + continue + seq = [grid[rr][cc] for rr, cc in coords] + if seq[0] == seq[1] == seq[2]: + sym = seq[0] + mult = pt.get(sym, {}).get(3, 0) + payout = bet * mult + if payout: + lines_out[name] = SlotsLineInfo(symbol=sym, count=3, payout=payout) + total += payout + elif seq.count("7") == 2: + mult = pt.get("7", {}).get(2, 0) + payout = bet * mult + if payout: + lines_out[name] = SlotsLineInfo(symbol="7", count=2, payout=payout) + total += payout + + # return everything + return SlotsResult(game="slots", grid=grid, lines=lines_out, total_payout=total) + +# game loop (run this file to play) +if __name__ == "__main__": + rng = random.Random() + print("šŸŽ° Welcome to NimbusCasino: Slots Edition šŸŽ°") + credits = 100 + + while True: + print(f"\nšŸ’° Current credits: {credits}") + cmd = input("Type 'spin' to play, 'quit' to exit: ").strip().lower() + + if cmd == "quit": + print(f"šŸ‘‹ Thanks for playing! Final credits: {credits}") + break + if cmd != "spin": + print("āŒ Type 'spin' or 'quit'.") + continue + + try: + bet = int(input("Enter your bet amount: ")) + except ValueError: + print("āŒ Bet must be a number.") + continue + if bet <= 0: + print("āŒ Bet must be positive.") + continue + if bet > credits: + print("āŒ Not enough credits.") + continue + + res = spin_slots(bet=bet, rng=rng) + print("🧩 Grid:") + for row in res["grid"]: + print(" ", " | ".join(row)) + + if res["lines"]: + print("šŸ† Line wins:") + for line, info in res["lines"].items(): + print(f" {line}: {info['count']}Ɨ{info['symbol']} → +{info['payout']}") + else: + print("— No wins —") + + payout = res["total_payout"] + net = payout - bet + credits = credits - bet + payout + + if net >= 0: + print(f"āœ… WIN +{net} (payout {payout} on bet {bet})") + else: + print(f"šŸ’€ LOSS {net} (payout {payout} on bet {bet})") diff --git a/tests/test_slots.py b/tests/test_slots.py new file mode 100644 index 0000000..920418f --- /dev/null +++ b/tests/test_slots.py @@ -0,0 +1,17 @@ +import random +from nimbuscasino import spin_slots, SpinResult + +def test_spin_slots_shapes_and_types(): + rng = random.Random(123) + res = spin_slots(bet=2, rng=rng) + assert isinstance(res, SpinResult) + assert len(res.grid) == 3 + assert all(len(row) == 3 for row in res.grid) + +def test_spin_slots_deterministic_with_seed(): + rng1 = random.Random(42) + rng2 = random.Random(42) + a = spin_slots(bet=1, rng=rng1) + b = spin_slots(bet=1, rng=rng2) + assert a.grid == b.grid + assert a.total_payout == b.total_payout From 2634457edfdc72bad8932c3dd47b05ffc796ee55 Mon Sep 17 00:00:00 2001 From: Jaylon Date: Tue, 4 Nov 2025 12:53:39 -0500 Subject: [PATCH 2/6] Merge upstream pipfile-experiment into feature/slots-game --- pyproject.toml | 32 +++++++++++++++++++++ src/nimbuscasino/coinflip.py | 44 +++++++++++++++++++++++++++++ src/nimbuscasino/play_coinflip.py | 30 ++++++++++++++++++++ src/nimbuscasino/rps.py | 47 +++++++++++++++++++++++++++++++ src/nimbuscasino/slots.py | 1 + tests/rpstest.py | 44 +++++++++++++++++++++++++++++ tests/test_coinflip.py | 40 ++++++++++++++++++++++++++ 7 files changed, 238 insertions(+) create mode 100755 pyproject.toml create mode 100755 src/nimbuscasino/coinflip.py create mode 100755 src/nimbuscasino/play_coinflip.py create mode 100755 src/nimbuscasino/rps.py create mode 100755 tests/rpstest.py create mode 100755 tests/test_coinflip.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100755 index 0000000..1843c6f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "nimbuscasino" +description = "An example of a package developed with pipenv, built with build using setuptools, uploaded to PyPI using twine, and distributed via pip." +version = "0.1.6" +authors = [ + { name="Mojin Yuan", email="my2384@nyu.edu" }, +] +license = { file = "LICENSE" } +readme = "README.md" +keywords = ["python", "package", "build", "minigames"] +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Python :: 3", + "Intended Audience :: Education", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", +] + +[project.optional-dependencies] +dev = ["pytest"] + +[project.urls] +"Homepage" = "https://github.com/swe-students-fall2025/3-python-package-team_nimbus" +"Repository" = "https://github.com/swe-students-fall2025/3-python-package-team_nimbus.git" +"Bug Tracker" = "https://github.com/swe-students-fall2025/3-python-package-team_nimbus/issues" + +[project.scripts] +nimbuscasino = "nimbuscasino.__main__:main" \ No newline at end of file diff --git a/src/nimbuscasino/coinflip.py b/src/nimbuscasino/coinflip.py new file mode 100755 index 0000000..f1d0209 --- /dev/null +++ b/src/nimbuscasino/coinflip.py @@ -0,0 +1,44 @@ +from __future__ import annotations +import random +from typing import Optional, TypedDict, Literal + + +class CoinflipResult(TypedDict): + game: Literal["coinflip"] + guess: Literal["heads", "tails"] + flip: Literal["heads", "tails"] + win: bool + payout: int + prob_heads: float + + +def coinflip( + guess: str, + bet: int = 1, + rng: Optional[random.Random] = None, + bias: float = 0.5, +) -> CoinflipResult: + if not isinstance(guess, str): + raise ValueError("guess must be a string 'heads' or 'tails'.") + g = guess.strip().lower() + if g not in {"heads", "tails"}: + raise ValueError("guess must be 'heads' or 'tails'.") + if not isinstance(bet, int) or bet <= 0: + raise ValueError("bet must be a positive integer.") + if not (0.0 <= bias <= 1.0): + raise ValueError("bias must be between 0.0 and 1.0 inclusive.") + + r = rng if rng is not None else random.Random() + x = r.random() + flip = "heads" if x < bias else "tails" + win = (flip == g) + payout = bet if win else -bet + + return CoinflipResult( + game="coinflip", + guess=g, + flip=flip, + win=win, + payout=payout, + prob_heads=bias, + ) diff --git a/src/nimbuscasino/play_coinflip.py b/src/nimbuscasino/play_coinflip.py new file mode 100755 index 0000000..54bc80e --- /dev/null +++ b/src/nimbuscasino/play_coinflip.py @@ -0,0 +1,30 @@ +from nimbuscasino.coinflip import coinflip +import random + +print("šŸŽ° Welcome to NimbusCasino: Coin Flip Edition šŸŽ°") +rng = random.Random() + +credits = 100 +while True: + print(f"\nšŸ’° Current credits: {credits}") + guess = input("Guess 'heads' or 'tails' (or type 'quit' to exit): ").strip().lower() + if guess == "quit": + print(f"šŸ‘‹ Thanks for playing! Final credits: {credits}") + break + if guess not in ["heads", "tails"]: + print("āŒ Invalid input, please type 'heads' or 'tails'.") + continue + + try: + bet = int(input("Enter your bet amount: ")) + except ValueError: + print("āŒ Invalid bet! Please enter a number.") + continue + + res = coinflip(guess, bet=bet, rng=rng) + if res["win"]: + print(f"āœ… It was {res['flip']}! You WIN 🄳 +{res['payout']} credits") + else: + print(f"šŸ’€ It was {res['flip']}! You LOSE 😭 {res['payout']} credits") + + credits += res["payout"] diff --git a/src/nimbuscasino/rps.py b/src/nimbuscasino/rps.py new file mode 100755 index 0000000..dea6c34 --- /dev/null +++ b/src/nimbuscasino/rps.py @@ -0,0 +1,47 @@ +import random + +def rps(player, bet=1): + """ + Plays a Rock-Paper-Scissors round against the computer. + + Parameters: + ----------- + player : str + Player's choice: "rock", "paper", or "scissors". + bet : int or float, optional + Amount of money wagered for the round. Default = 1. + + Returns: + -------- + tuple (result, payout) + result: "win", "lose", or "tie" + payout: numeric value of the win/loss based on bet + """ + + # Normalize input + player = player.lower() + valid_choices = ["rock", "paper", "scissors"] + + if player not in valid_choices: + raise ValueError(f"Invalid choice '{player}'. Choose from {valid_choices}.") + + # Computer randomly chooses + computer = random.choice(valid_choices) + + # Determine outcome + if player == computer: + result = "tie" + payout = 0 + elif ( + (player == "rock" and computer == "scissors") or + (player == "scissors" and computer == "paper") or + (player == "paper" and computer == "rock") + ): + result = "win" + payout = bet + else: + result = "lose" + payout = -bet + + print(f"You chose {player}, computer chose {computer} → {result.upper()}") + return result, payout diff --git a/src/nimbuscasino/slots.py b/src/nimbuscasino/slots.py index c3b5eb6..7a00c4b 100644 --- a/src/nimbuscasino/slots.py +++ b/src/nimbuscasino/slots.py @@ -141,3 +141,4 @@ def spin_slots( print(f"āœ… WIN +{net} (payout {payout} on bet {bet})") else: print(f"šŸ’€ LOSS {net} (payout {payout} on bet {bet})") + \ No newline at end of file diff --git a/tests/rpstest.py b/tests/rpstest.py new file mode 100755 index 0000000..88ef8e8 --- /dev/null +++ b/tests/rpstest.py @@ -0,0 +1,44 @@ +import pytest +from nimbuscasino import rps + + +class Tests: + # + # Fixtures - these are functions that can do any optional setup or teardown before or after a test function is run. + # + + @pytest.fixture + def example_fixture(self): + """ + An example of a pytest fixture - a function that can be used for setup and teardown before and after test functions are run. + """ + + + + # place any setup you want to do before any test function that uses this fixture is run + + yield # at th=e yield point, the test function will run and do its business + + # place with any teardown you want to do after any test function that uses this fixture has completed + + # + # Test functions + # + + +def test_sanity_check(self, example_fixture): + """ + Test debugging... making sure that we can run a simple test that always passes. + Note the use of the example_fixture in the parameter list - any setup and teardown in that fixture will be run before and after this test function executes + From the main project directory, run the `python3 -m pytest` command to run all tests. + """ + expected = True # the value we expect to be present + actual = True # the value we see in reality + winorlose, bet = rps.rps('rock') + if winorlose == 'win': + assert bet == 1 + elif winorlose == 'lose': + assert bet == -1 + elif winorlose == 'tie': + assert bet == 0 + \ No newline at end of file diff --git a/tests/test_coinflip.py b/tests/test_coinflip.py new file mode 100755 index 0000000..9324506 --- /dev/null +++ b/tests/test_coinflip.py @@ -0,0 +1,40 @@ +import pytest +# tests/test_coinflip.py +from nimbuscasino.coinflip import coinflip + + + +class FakeRNG: + def __init__(self, values): + self.values = list(values) + self.i = 0 + + def random(self): + if self.i < len(self.values): + v = self.values[self.i] + self.i += 1 + return v + return self.values[-1] + + +def test_coinflip_win_heads_with_fair_bias(): + rng = FakeRNG([0.2]) + res = coinflip("heads", bet=3, rng=rng, bias=0.5) + assert res["flip"] == "heads" + assert res["win"] is True + assert res["payout"] == 3 + assert res["game"] == "coinflip" + +def test_coinflip_lose_heads_with_fair_bias(): + rng = FakeRNG([0.9]) + res = coinflip("heads", bet=2, rng=rng, bias=0.5) + assert res["flip"] == "tails" + assert res["win"] is False + assert res["payout"] == -2 + +def test_bias_edge_cases_always_heads_at_1(): + rng = FakeRNG([0.9999]) + res = coinflip("heads", bet=5, rng=rng, bias=1.0) + assert res["flip"] == "heads" + assert res["win"] is True + assert res["payout"] == 5 From 684c3e1d1d138d1d59d4c856001f17da93bc493a Mon Sep 17 00:00:00 2001 From: Jaylon Date: Tue, 4 Nov 2025 13:05:07 -0500 Subject: [PATCH 3/6] Rename __init__.py to _init_.py --- src/nimbuscasino/{__init__.py => _init_.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/nimbuscasino/{__init__.py => _init_.py} (100%) diff --git a/src/nimbuscasino/__init__.py b/src/nimbuscasino/_init_.py similarity index 100% rename from src/nimbuscasino/__init__.py rename to src/nimbuscasino/_init_.py From f25c6a96a8c8282bb5c00df57f55c23133aa775b Mon Sep 17 00:00:00 2001 From: Jaylon Date: Tue, 4 Nov 2025 13:16:05 -0500 Subject: [PATCH 4/6] Add Slots game with CLI and tests --- src/nimbuscasino/__init__.py | 14 ++++++++ src/nimbuscasino/slots.py | 64 ++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 36 deletions(-) create mode 100755 src/nimbuscasino/__init__.py diff --git a/src/nimbuscasino/__init__.py b/src/nimbuscasino/__init__.py new file mode 100755 index 0000000..20794ea --- /dev/null +++ b/src/nimbuscasino/__init__.py @@ -0,0 +1,14 @@ +""" +This file is required for Python to recognize the directory as a "regular" package. + +Python "regular" packages use the __init__.py file to initialize the package and +can include package-level variables, import statements, and initialization code +you wish to run when the package is first imported into a program. + +For more information, see the official documentation: +https://docs.python.org/3/reference/import.html#regular-packages +""" + +from .slots import spin_slots, SpinResult + +__all__ = ["spin_slots", "SpinResult"] diff --git a/src/nimbuscasino/slots.py b/src/nimbuscasino/slots.py index 7a00c4b..c74a42c 100644 --- a/src/nimbuscasino/slots.py +++ b/src/nimbuscasino/slots.py @@ -1,46 +1,41 @@ # slots.py - -# first line for future type hints from __future__ import annotations -# import libraries +from dataclasses import dataclass # NEW: dataclass for test compatibility import random -from typing import Optional, TypedDict, Dict, List, Literal +from typing import Optional, Dict, List, Literal, TypedDict -# info for one winning line +# keep a simple dict shape for line info (not used by the test) class SlotsLineInfo(TypedDict): symbol: str count: int payout: int -# full result of one spin -class SlotsResult(TypedDict): - game: Literal["slots"] +# NEW: dataclass that matches test usage (attribute access + isinstance) +@dataclass(frozen=True) +class SpinResult: grid: List[List[str]] - lines: Dict[str, SlotsLineInfo] + lines: Dict[str, Dict[str, int]] total_payout: int -# symbols, odds, and payout table DEFAULT_SYMBOLS = ["šŸ’", "šŸ‹", "šŸ””", "⭐", "7"] DEFAULT_WEIGHTS = [30, 25, 20, 15, 10] -DEFAULT_PAYTABLE = { +DEFAULT_PAYTABLE: Dict[str, Dict[int, int]] = { "šŸ’": {3: 5}, "šŸ‹": {3: 6}, "šŸ””": {3: 12}, - "⭐": {3: 20}, - "7": {2: 5, 3: 50}, + "⭐": {3: 20}, + "7": {2: 5, 3: 50}, } -# 5 standard winning lines LINES = { - "top": [(0, 0), (0, 1), (0, 2)], - "middle": [(1, 0), (1, 1), (1, 2)], - "bottom": [(2, 0), (2, 1), (2, 2)], - "diag_down": [(0, 0), (1, 1), (2, 2)], - "diag_up": [(2, 0), (1, 1), (0, 2)], + "top": [(0, 0), (0, 1), (0, 2)], + "middle": [(1, 0), (1, 1), (1, 2)], + "bottom": [(2, 0), (2, 1), (2, 2)], + "diag_down": [(0, 0), (1, 1), (2, 2)], + "diag_up": [(2, 0), (1, 1), (0, 2)], } -# do one spin and return result def spin_slots( bet: int = 1, rng: Optional[random.Random] = None, @@ -49,50 +44,48 @@ def spin_slots( paytable: Optional[Dict[str, Dict[int, int]]] = None, rows: int = 3, cols: int = 3, -) -> SlotsResult: - # check inputs +) -> SpinResult: if not isinstance(bet, int) or bet <= 0: raise ValueError("bet must be positive") if rows <= 0 or cols <= 0: raise ValueError("rows and cols must be positive") - # choose random and settings r = rng or random.Random() syms = symbols or DEFAULT_SYMBOLS wts = weights or DEFAULT_WEIGHTS pt = paytable or DEFAULT_PAYTABLE - # make grid (3x3) + if len(syms) != len(wts): + raise ValueError("symbols and weights must be same length") + grid: List[List[str]] = [[None] * cols for _ in range(rows)] # type: ignore for c in range(cols): for rr in range(rows): grid[rr][c] = r.choices(syms, wts, k=1)[0] - # check lines for wins - lines_out: Dict[str, SlotsLineInfo] = {} + lines_out: Dict[str, Dict[str, int]] = {} total = 0 for name, coords in LINES.items(): if any(rr >= rows or cc >= cols for rr, cc in coords): continue seq = [grid[rr][cc] for rr, cc in coords] + if seq[0] == seq[1] == seq[2]: sym = seq[0] mult = pt.get(sym, {}).get(3, 0) payout = bet * mult if payout: - lines_out[name] = SlotsLineInfo(symbol=sym, count=3, payout=payout) + lines_out[name] = {"symbol": sym, "count": 3, "payout": payout} total += payout elif seq.count("7") == 2: mult = pt.get("7", {}).get(2, 0) payout = bet * mult if payout: - lines_out[name] = SlotsLineInfo(symbol="7", count=2, payout=payout) + lines_out[name] = {"symbol": "7", "count": 2, "payout": payout} total += payout - # return everything - return SlotsResult(game="slots", grid=grid, lines=lines_out, total_payout=total) + return SpinResult(grid=grid, lines=lines_out, total_payout=total) -# game loop (run this file to play) if __name__ == "__main__": rng = random.Random() print("šŸŽ° Welcome to NimbusCasino: Slots Edition šŸŽ°") @@ -123,17 +116,17 @@ def spin_slots( res = spin_slots(bet=bet, rng=rng) print("🧩 Grid:") - for row in res["grid"]: + for row in res.grid: print(" ", " | ".join(row)) - if res["lines"]: + if res.lines: print("šŸ† Line wins:") - for line, info in res["lines"].items(): + for line, info in res.lines.items(): print(f" {line}: {info['count']}Ɨ{info['symbol']} → +{info['payout']}") else: print("— No wins —") - payout = res["total_payout"] + payout = res.total_payout net = payout - bet credits = credits - bet + payout @@ -141,4 +134,3 @@ def spin_slots( print(f"āœ… WIN +{net} (payout {payout} on bet {bet})") else: print(f"šŸ’€ LOSS {net} (payout {payout} on bet {bet})") - \ No newline at end of file From d135de02ff227af2d4fe84c507ed9748657173bc Mon Sep 17 00:00:00 2001 From: Jaylon Date: Tue, 4 Nov 2025 13:20:16 -0500 Subject: [PATCH 5/6] Resolve upstream conflicts; keep __init__.py, remove _init_.py --- src/nimbuscasino/__init__.py | 12 +----------- src/nimbuscasino/_init_.py | 14 -------------- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100755 src/nimbuscasino/_init_.py diff --git a/src/nimbuscasino/__init__.py b/src/nimbuscasino/__init__.py index 20794ea..d9c6483 100755 --- a/src/nimbuscasino/__init__.py +++ b/src/nimbuscasino/__init__.py @@ -1,14 +1,4 @@ -""" -This file is required for Python to recognize the directory as a "regular" package. - -Python "regular" packages use the __init__.py file to initialize the package and -can include package-level variables, import statements, and initialization code -you wish to run when the package is first imported into a program. - -For more information, see the official documentation: -https://docs.python.org/3/reference/import.html#regular-packages -""" - from .slots import spin_slots, SpinResult __all__ = ["spin_slots", "SpinResult"] + diff --git a/src/nimbuscasino/_init_.py b/src/nimbuscasino/_init_.py deleted file mode 100755 index 7ef730e..0000000 --- a/src/nimbuscasino/_init_.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -This file is required for Python to recognize the directory as a "regular" package. - -Python "regular" packages use the __init__.py file to initialize the package and -can include package-level variables, import statements, and initialization code -you wish to run when the package is first imported into a program. - -For more information, see the official documentation: -https://docs.python.org/3/reference/import.html#regular-packages -""" - -from .slots import spin_slots, SlotsResult as SpinResult - -__all__ = ["spin_slots", "SpinResult", "SlotsResult"] From 59e3401f1bf9d5e3cf3e4edcb0cffd4fe7858312 Mon Sep 17 00:00:00 2001 From: Jaylon Date: Tue, 4 Nov 2025 21:52:59 -0500 Subject: [PATCH 6/6] Finalize slots.py logic (no game loop) and update tests --- src/nimbuscasino/slots.py | 58 ++------------------------------------- 1 file changed, 3 insertions(+), 55 deletions(-) diff --git a/src/nimbuscasino/slots.py b/src/nimbuscasino/slots.py index c74a42c..6336ef2 100644 --- a/src/nimbuscasino/slots.py +++ b/src/nimbuscasino/slots.py @@ -1,17 +1,15 @@ -# slots.py +# src/nimbuscasino/slots.py from __future__ import annotations -from dataclasses import dataclass # NEW: dataclass for test compatibility +from dataclasses import dataclass import random -from typing import Optional, Dict, List, Literal, TypedDict +from typing import Optional, Dict, List, TypedDict -# keep a simple dict shape for line info (not used by the test) class SlotsLineInfo(TypedDict): symbol: str count: int payout: int -# NEW: dataclass that matches test usage (attribute access + isinstance) @dataclass(frozen=True) class SpinResult: grid: List[List[str]] @@ -54,7 +52,6 @@ def spin_slots( syms = symbols or DEFAULT_SYMBOLS wts = weights or DEFAULT_WEIGHTS pt = paytable or DEFAULT_PAYTABLE - if len(syms) != len(wts): raise ValueError("symbols and weights must be same length") @@ -85,52 +82,3 @@ def spin_slots( total += payout return SpinResult(grid=grid, lines=lines_out, total_payout=total) - -if __name__ == "__main__": - rng = random.Random() - print("šŸŽ° Welcome to NimbusCasino: Slots Edition šŸŽ°") - credits = 100 - - while True: - print(f"\nšŸ’° Current credits: {credits}") - cmd = input("Type 'spin' to play, 'quit' to exit: ").strip().lower() - - if cmd == "quit": - print(f"šŸ‘‹ Thanks for playing! Final credits: {credits}") - break - if cmd != "spin": - print("āŒ Type 'spin' or 'quit'.") - continue - - try: - bet = int(input("Enter your bet amount: ")) - except ValueError: - print("āŒ Bet must be a number.") - continue - if bet <= 0: - print("āŒ Bet must be positive.") - continue - if bet > credits: - print("āŒ Not enough credits.") - continue - - res = spin_slots(bet=bet, rng=rng) - print("🧩 Grid:") - for row in res.grid: - print(" ", " | ".join(row)) - - if res.lines: - print("šŸ† Line wins:") - for line, info in res.lines.items(): - print(f" {line}: {info['count']}Ɨ{info['symbol']} → +{info['payout']}") - else: - print("— No wins —") - - payout = res.total_payout - net = payout - bet - credits = credits - bet + payout - - if net >= 0: - print(f"āœ… WIN +{net} (payout {payout} on bet {bet})") - else: - print(f"šŸ’€ LOSS {net} (payout {payout} on bet {bet})")