Skip to content
Closed
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
32 changes: 32 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
4 changes: 4 additions & 0 deletions src/nimbuscasino/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .slots import spin_slots, SpinResult

__all__ = ["spin_slots", "SpinResult"]

44 changes: 44 additions & 0 deletions src/nimbuscasino/coinflip.py
Original file line number Diff line number Diff line change
@@ -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,
)
30 changes: 30 additions & 0 deletions src/nimbuscasino/play_coinflip.py
Original file line number Diff line number Diff line change
@@ -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"]
47 changes: 47 additions & 0 deletions src/nimbuscasino/rps.py
Original file line number Diff line number Diff line change
@@ -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
84 changes: 84 additions & 0 deletions src/nimbuscasino/slots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# src/nimbuscasino/slots.py
from __future__ import annotations

from dataclasses import dataclass
import random
from typing import Optional, Dict, List, TypedDict

class SlotsLineInfo(TypedDict):
symbol: str
count: int
payout: int

@dataclass(frozen=True)
class SpinResult:
grid: List[List[str]]
lines: Dict[str, Dict[str, int]]
total_payout: int

DEFAULT_SYMBOLS = ["🍒", "🍋", "🔔", "⭐", "7"]
DEFAULT_WEIGHTS = [30, 25, 20, 15, 10]
DEFAULT_PAYTABLE: Dict[str, Dict[int, int]] = {
"🍒": {3: 5},
"🍋": {3: 6},
"🔔": {3: 12},
"⭐": {3: 20},
"7": {2: 5, 3: 50},
}

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)],
}

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,
) -> 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")

r = rng or random.Random()
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")

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]

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] = {"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] = {"symbol": "7", "count": 2, "payout": payout}
total += payout

return SpinResult(grid=grid, lines=lines_out, total_payout=total)
44 changes: 44 additions & 0 deletions tests/rpstest.py
Original file line number Diff line number Diff line change
@@ -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

40 changes: 40 additions & 0 deletions tests/test_coinflip.py
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions tests/test_slots.py
Original file line number Diff line number Diff line change
@@ -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
Loading