Add configurable napola rule with instant win on a full denari sweep
This commit is contained in:
@@ -239,6 +239,95 @@ class ScoringTest(unittest.TestCase):
|
||||
self.assertEqual([0, 0], points)
|
||||
|
||||
|
||||
class NapolaTest(unittest.TestCase):
|
||||
def test_napola_score_runs(self) -> None:
|
||||
self.assertEqual(0, engine.napola_score(
|
||||
[card(c) for c in ["02D", "03D", "04D"]])) # no ace
|
||||
self.assertEqual(0, engine.napola_score(
|
||||
[card(c) for c in ["01D", "02D"]])) # too short
|
||||
self.assertEqual(3, engine.napola_score(
|
||||
[card(c) for c in ["03D", "01D", "02D"]])) # order-independent
|
||||
self.assertEqual(4, engine.napola_score(
|
||||
[card(c) for c in ["01D", "02D", "03D", "04D", "07C"]]))
|
||||
self.assertEqual(3, engine.napola_score(
|
||||
[card(c) for c in ["01D", "02D", "03D", "05D"]])) # broken run
|
||||
self.assertEqual(10, engine.napola_score(
|
||||
[card(f"{rank:02d}D") for rank in range(1, 11)]))
|
||||
|
||||
def test_hand_points_napola(self) -> None:
|
||||
state = make_state(
|
||||
[[], [], [], []],
|
||||
table=[],
|
||||
captured=[
|
||||
["01D", "02D", "03D", "04C"], # seat 0, team A
|
||||
["05D", "06D", "07D", "08D"], # seat 1, team B
|
||||
["09D", "10D", "01C", "02C"], # seat 2, team A
|
||||
["03C", "05C", "06C", "07C"], # seat 3, team B
|
||||
],
|
||||
)
|
||||
points, details = engine.hand_points(state)
|
||||
# Team A has the ace-led run 01D-03D (3 points); team B's denari
|
||||
# start at the 5, so no napola. Carte tie (8 each), denara to A
|
||||
# (5 vs 4), settebello to B, primiere tied at 0 (missing suits).
|
||||
self.assertEqual({"A": 3, "B": 0}, details["napola"])
|
||||
self.assertEqual("A", details["award"]["napola"])
|
||||
self.assertEqual([4, 1], points)
|
||||
|
||||
def test_napola_disabled(self) -> None:
|
||||
state = make_state(
|
||||
[[], [], [], []],
|
||||
table=[],
|
||||
captured=[
|
||||
["01D", "02D", "03D", "04C"],
|
||||
["05D", "06D", "07D", "08D"],
|
||||
["09D", "10D", "01C", "02C"],
|
||||
["03C", "05C", "06C", "07C"],
|
||||
],
|
||||
)
|
||||
state.napola = False
|
||||
points, details = engine.hand_points(state)
|
||||
self.assertNotIn("napola", details)
|
||||
self.assertEqual([1, 1], points)
|
||||
|
||||
def test_full_denari_sweep_wins_match_instantly(self) -> None:
|
||||
# Team A already captured the whole denari suit; the last play of
|
||||
# the hand cannot capture. Team B leads 50-0, yet the napola ends
|
||||
# the match in team A's favour, well below the target of 100.
|
||||
state = make_state(
|
||||
[["02C"], [], [], []],
|
||||
table=[],
|
||||
target=100,
|
||||
captured=[
|
||||
[f"{rank:02d}D" for rank in range(1, 11)],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
],
|
||||
)
|
||||
state.scores = [0, 50]
|
||||
engine.play(state, "p0", "02C")
|
||||
self.assertEqual(PHASE_FINISHED, state.phase)
|
||||
self.assertEqual(0, state.winner)
|
||||
self.assertLess(state.scores[0], 100)
|
||||
self.assertEqual(10, state.hand_scores[-1]["napola"]["A"])
|
||||
|
||||
def test_napola_serialization_roundtrip(self) -> None:
|
||||
state = make_state([["02D"], [], [], []], table=[])
|
||||
self.assertTrue(state.napola)
|
||||
state.napola = False
|
||||
self.assertFalse(GameState.from_json(state.to_json()).napola)
|
||||
# States serialized before the option existed default to enabled.
|
||||
data = state.to_json()
|
||||
del data["napola"]
|
||||
self.assertTrue(GameState.from_json(data).napola)
|
||||
|
||||
def test_create_game_napola_default_and_override(self) -> None:
|
||||
self.assertTrue(engine.create_game("g", "CODE42", "p0", "p0").napola)
|
||||
self.assertFalse(
|
||||
engine.create_game("g", "CODE42", "p0", "p0", napola=False).napola
|
||||
)
|
||||
|
||||
|
||||
class MatchFlowTest(unittest.TestCase):
|
||||
def test_join_starts_when_full(self) -> None:
|
||||
state = engine.create_game("g", "CODE42", "p0", "p0", target_score=11)
|
||||
|
||||
@@ -71,6 +71,24 @@ class GamesRouteTest(unittest.TestCase):
|
||||
self.assertNotIn("hand", state["players"][0])
|
||||
self.assertEqual(1, state["turn"])
|
||||
|
||||
@async_test
|
||||
async def test_create_napola_option(self) -> None:
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
|
||||
with oidc_user("alice"):
|
||||
default = await client.post("/api/games", json={})
|
||||
self.assertEqual(201, default.status_code)
|
||||
self.assertTrue(default.json()["napola"])
|
||||
|
||||
with oidc_user("alice"):
|
||||
disabled = await client.post("/api/games", json={"napola": False})
|
||||
self.assertEqual(201, disabled.status_code)
|
||||
self.assertFalse(disabled.json()["napola"])
|
||||
|
||||
with oidc_user("alice"):
|
||||
invalid = await client.post("/api/games", json={"napola": "yes"})
|
||||
self.assertEqual(400, invalid.status_code)
|
||||
|
||||
@async_test
|
||||
async def test_join_errors(self) -> None:
|
||||
transport = ASGITransport(app=app)
|
||||
|
||||
Reference in New Issue
Block a user