Fable will fix it!
This commit is contained in:
@@ -104,60 +104,74 @@ def test_character_creation_and_swap(session):
|
||||
assert "deep" in roles
|
||||
assert "pirat" in roles
|
||||
|
||||
def test_scene_start_and_challenges(session):
|
||||
def make_scene_game(session, num_pirats=1):
|
||||
"""Helper: a game in scene phase with 1 Deep player and num_pirats Pi-Rats."""
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1", is_creator=True)
|
||||
p2 = crud.add_player(session, game.id, "P2")
|
||||
|
||||
# Bypass char creation for quick scene test
|
||||
p1.rank = 3
|
||||
p1.role = "deep"
|
||||
p1.previous_role = "deep"
|
||||
|
||||
p2.rank = 1
|
||||
p2.role = "pirat"
|
||||
p2.previous_role = "pirat"
|
||||
|
||||
session.add_all([p1, p2])
|
||||
deep = crud.add_player(session, game.id, "Deep1", is_creator=True)
|
||||
deep.rank = 3
|
||||
deep.role = "deep"
|
||||
deep.previous_role = "deep"
|
||||
session.add(deep)
|
||||
pirats = []
|
||||
for i in range(num_pirats):
|
||||
p = crud.add_player(session, game.id, f"Rat{i+1}")
|
||||
p.rank = 1 if i == 0 else 2
|
||||
p.role = "pirat"
|
||||
p.previous_role = "pirat"
|
||||
session.add(p)
|
||||
pirats.append(p)
|
||||
session.commit()
|
||||
|
||||
# Confirm setup (1 Deep, 1 Pi-Rat)
|
||||
success, msg = crud.confirm_scene_setup(session, game.id)
|
||||
assert success
|
||||
|
||||
# Stub shuffle so the rebuilt deck keeps its construction order (Jokers last)
|
||||
# and no Joker can be drawn as an Obstacle, which would bump extra_obstacles
|
||||
import random
|
||||
orig_shuffle = random.shuffle
|
||||
random.shuffle = lambda x: None
|
||||
try:
|
||||
success, msg = crud.confirm_scene_setup(session, game.id)
|
||||
finally:
|
||||
random.shuffle = orig_shuffle
|
||||
assert success, msg
|
||||
session.refresh(game)
|
||||
return game, deep, pirats
|
||||
|
||||
def test_scene_start_and_challenges(session):
|
||||
game, deep, (p2,) = make_scene_game(session)
|
||||
assert game.phase == "scene"
|
||||
assert len(game.obstacles) == 2 # Deeps (1) + 1 = 2 obstacles
|
||||
|
||||
obs_list = game.obstacles
|
||||
obs = obs_list[0]
|
||||
|
||||
|
||||
obs = game.obstacles[0]
|
||||
|
||||
# Give p2 a known hand for testing
|
||||
p2.hand_cards = json.dumps(["10D", "2H", "JS", "Joker1"])
|
||||
session.add(p2)
|
||||
session.commit()
|
||||
|
||||
# Play card: 10D (value 10) vs Obstacle. Let's make sure obstacle value is less than 10.
|
||||
# Set obstacle value to 5
|
||||
obs.current_value = 5
|
||||
session.add(obs)
|
||||
session.add_all([p2, obs])
|
||||
session.commit()
|
||||
|
||||
# Play 10D (red) on obstacle. Let's check original obstacle color.
|
||||
# If original obstacle was red, we expect a card draw.
|
||||
|
||||
# Playing a card without an open Challenge must be rejected
|
||||
ok, msg, res = crud.play_challenge_card(session, p2.id, obs.id, "10D")
|
||||
assert not ok
|
||||
assert "Challenge" in msg
|
||||
|
||||
# The Deep calls a Challenge applying the obstacle
|
||||
ok, msg = crud.create_challenge(session, game.id, deep.id, p2.id, [obs.id], stakes="The cheese is on the line")
|
||||
assert ok, msg
|
||||
session.refresh(game)
|
||||
challenge = game.challenges[0]
|
||||
assert challenge.status == "open"
|
||||
assert challenge.acting_player_id == p2.id
|
||||
|
||||
orig_is_red = cards.parse_card(obs.original_card)["color"] == "red"
|
||||
deck_before = len(crud.get_game_deck(game))
|
||||
|
||||
ok, msg, res = crud.play_card_on_obstacle(session, p2.id, obs.id, "10D")
|
||||
|
||||
ok, msg, res = crud.play_challenge_card(session, p2.id, obs.id, "10D")
|
||||
assert ok
|
||||
assert res["success"]
|
||||
assert "Success" in res["details"]
|
||||
|
||||
|
||||
session.refresh(p2)
|
||||
session.refresh(obs)
|
||||
# Obstacle current value should update to 10
|
||||
assert obs.current_value == 10
|
||||
|
||||
|
||||
# Check if hand size matches card draw rule
|
||||
# 10D was played (-1 card). If colors matched, drew 1 back (+1 card).
|
||||
hand = crud.get_player_hand(p2)
|
||||
@@ -168,27 +182,48 @@ def test_scene_start_and_challenges(session):
|
||||
assert len(hand) == 3
|
||||
assert res["drew_card"] is None
|
||||
|
||||
# The Deep resolves the challenge: at least one success -> succeeded
|
||||
ok, msg = crud.resolve_challenge(session, challenge.id, deep.id)
|
||||
assert ok
|
||||
session.refresh(challenge)
|
||||
assert challenge.status == "succeeded"
|
||||
|
||||
def test_assistant_does_not_draw(session):
|
||||
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
|
||||
obs = game.obstacles[0]
|
||||
obs.current_value = 5
|
||||
# Helper card guaranteed to match the obstacle's color
|
||||
helper_card = "9D" if cards.parse_card(obs.original_card)["color"] == "red" else "9S"
|
||||
p3.hand_cards = json.dumps([helper_card])
|
||||
session.add_all([obs, p3])
|
||||
session.commit()
|
||||
|
||||
ok, msg = crud.create_challenge(session, game.id, deep.id, p2.id, [obs.id])
|
||||
assert ok
|
||||
session.refresh(game)
|
||||
challenge = game.challenges[0]
|
||||
|
||||
# p3 assists: plays a color-matching card but must NOT draw back
|
||||
ok, msg, res = crud.play_challenge_card(session, p3.id, obs.id, helper_card)
|
||||
assert ok
|
||||
assert res["success"]
|
||||
assert res["drew_card"] is None
|
||||
session.refresh(p3)
|
||||
assert crud.get_player_hand(p3) == []
|
||||
|
||||
def test_secret_technique_auto_success(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1")
|
||||
p1.role = "pirat"
|
||||
p1.rank = 2
|
||||
game, deep, (p1,) = make_scene_game(session)
|
||||
obs = game.obstacles[0]
|
||||
p1.tech_jack = "Pocket Sand"
|
||||
p1.hand_cards = json.dumps(["JS"])
|
||||
session.add(p1)
|
||||
|
||||
obs = Obstacle(
|
||||
game_id=game.id,
|
||||
original_card="10C",
|
||||
suit="C",
|
||||
title="Knights",
|
||||
current_value=10
|
||||
)
|
||||
session.add(obs)
|
||||
obs.current_value = 10
|
||||
session.add_all([p1, obs])
|
||||
session.commit()
|
||||
|
||||
|
||||
ok, msg = crud.create_challenge(session, game.id, deep.id, p1.id, [obs.id])
|
||||
assert ok
|
||||
# Play Jack. J is a face card -> auto success!
|
||||
ok, msg, res = crud.play_card_on_obstacle(session, p1.id, obs.id, "JS")
|
||||
ok, msg, res = crud.play_challenge_card(session, p1.id, obs.id, "JS")
|
||||
assert ok
|
||||
assert res["success"]
|
||||
assert res["is_technique"]
|
||||
@@ -204,12 +239,12 @@ def test_joker_play(session):
|
||||
deck.remove("10C")
|
||||
game.deck_cards = json.dumps(deck)
|
||||
session.add(game)
|
||||
|
||||
|
||||
p1 = crud.add_player(session, game.id, "P1")
|
||||
p1.role = "pirat"
|
||||
p1.hand_cards = json.dumps(["Joker1"])
|
||||
session.add(p1)
|
||||
|
||||
|
||||
obs = Obstacle(
|
||||
game_id=game.id,
|
||||
original_card="10C",
|
||||
@@ -219,16 +254,18 @@ def test_joker_play(session):
|
||||
)
|
||||
session.add(obs)
|
||||
session.commit()
|
||||
|
||||
# Play Joker
|
||||
ok, msg, res = crud.play_card_on_obstacle(session, p1.id, obs.id, "Joker1")
|
||||
|
||||
# Play Joker: discards the obstacle and draws a replacement
|
||||
ok, msg = crud.play_joker(session, p1.id, obs.id, "Joker1")
|
||||
assert ok
|
||||
assert res["is_joker"]
|
||||
|
||||
|
||||
# Verify obstacle was deleted and replaced
|
||||
session.refresh(game)
|
||||
assert len(game.obstacles) == 1
|
||||
assert game.obstacles[0].original_card != "10C"
|
||||
# Joker left the player's hand
|
||||
session.refresh(p1)
|
||||
assert crud.get_player_hand(p1) == []
|
||||
|
||||
def test_obstacle_success_count(session):
|
||||
game = crud.create_game(session)
|
||||
@@ -249,23 +286,77 @@ def test_obstacle_success_count(session):
|
||||
|
||||
assert obs.success_count == 2
|
||||
|
||||
def test_non_deep_player_treatment(session):
|
||||
def test_captaincy_and_hand_sizes(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "Captain Barnaby")
|
||||
p2 = crud.add_player(session, game.id, "Crewmate Pip")
|
||||
|
||||
# p1 is deep, p2 is None (non-deep)
|
||||
p3 = crud.add_player(session, game.id, "Lowly Larry")
|
||||
|
||||
p1.role = "deep"
|
||||
p2.role = None
|
||||
session.add_all([p1, p2])
|
||||
p1.rank = 2
|
||||
p2.role = "pirat"
|
||||
p2.rank = 2
|
||||
p3.role = "pirat"
|
||||
p3.rank = 1
|
||||
session.add_all([p1, p2, p3])
|
||||
session.commit()
|
||||
|
||||
# Check captain status: p2 should be captain because p2 is the only non-deep player
|
||||
assert crud.is_player_captain(p2, game.players)
|
||||
assert not crud.is_player_captain(p1, game.players)
|
||||
|
||||
# Check hand size: p2 should participate in hand size calculations and get Captain privileges (+1)
|
||||
assert crud.calculate_max_hand_size(p2, game.players) == 4
|
||||
|
||||
# No captain until the crew controls a ship
|
||||
assert game.captain_player_id is None
|
||||
assert not crud.is_player_captain(p2, game)
|
||||
|
||||
# Highest pirat (p2) -> 4 cards, lowest (p3) -> 2 cards
|
||||
assert crud.calculate_max_hand_size(p2, game.players, game.captain_player_id) == 4
|
||||
assert crud.calculate_max_hand_size(p3, game.players, game.captain_player_id) == 2
|
||||
|
||||
# If everyone shares a Rank, they are all 'highest' -> 4 cards
|
||||
p3.rank = 2
|
||||
session.add(p3)
|
||||
session.commit()
|
||||
assert crud.calculate_max_hand_size(p3, game.players, game.captain_player_id) == 4
|
||||
|
||||
# Completing Crew Objective 1 (Steal a Ship) makes the highest-ranked Pi-Rat the Captain
|
||||
crud.toggle_objective(session, game.id, p1.id, "crew_1", True)
|
||||
session.refresh(game)
|
||||
assert game.captain_player_id in (p2.id, p3.id)
|
||||
captain = crud.get_player(session, game.captain_player_id)
|
||||
assert crud.is_player_captain(captain, game)
|
||||
# Captain privilege: +1 hand size
|
||||
assert crud.calculate_max_hand_size(captain, game.players, game.captain_player_id) == 5
|
||||
|
||||
# Losing the ship vacates the Captaincy
|
||||
crud.toggle_objective(session, game.id, p1.id, "crew_1", False)
|
||||
session.refresh(game)
|
||||
assert game.captain_player_id is None
|
||||
|
||||
def test_captain_tax_on_failed_challenge(session):
|
||||
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
|
||||
# p3 (rank 2) becomes captain
|
||||
crud.set_captain(session, game, p3.id)
|
||||
session.refresh(game)
|
||||
assert game.captain_player_id == p3.id
|
||||
start_rank = p3.rank
|
||||
|
||||
obs = game.obstacles[0]
|
||||
obs.current_value = 13
|
||||
p3.hand_cards = json.dumps(["2C"])
|
||||
session.add_all([obs, p3])
|
||||
session.commit()
|
||||
|
||||
ok, msg = crud.create_challenge(session, game.id, deep.id, p3.id, [obs.id])
|
||||
assert ok
|
||||
session.refresh(game)
|
||||
challenge = [c for c in game.challenges if c.status == "open"][0]
|
||||
|
||||
ok, msg, res = crud.play_challenge_card(session, p3.id, obs.id, "2C")
|
||||
assert ok
|
||||
assert not res["success"]
|
||||
|
||||
ok, msg = crud.resolve_challenge(session, challenge.id, deep.id)
|
||||
assert ok
|
||||
session.refresh(p3)
|
||||
# Captain Tax: lost a rank for personally failing a Challenge
|
||||
assert p3.rank == start_rank - 1
|
||||
|
||||
def test_set_role_endpoint():
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -389,22 +480,24 @@ def test_confirm_deep_refresh(session):
|
||||
|
||||
session.refresh(p1)
|
||||
session.refresh(game)
|
||||
|
||||
# Hand should not contain 2C and 3C, and should have drawn 5H and 6H
|
||||
|
||||
# Hand should not contain 2C and 3C, and should have drawn back up to max
|
||||
# hand size (4: the only player counts as highest rank)
|
||||
hand = json.loads(p1.hand_cards)
|
||||
assert "2C" not in hand
|
||||
assert "3C" not in hand
|
||||
assert "4C" in hand
|
||||
assert "5H" in hand
|
||||
assert "6H" in hand
|
||||
assert len(hand) == 3
|
||||
|
||||
assert "7H" in hand
|
||||
assert len(hand) == 4
|
||||
|
||||
# Discarded cards should be at the end of the deck
|
||||
deck = json.loads(game.deck_cards)
|
||||
assert "2C" in deck
|
||||
assert "3C" in deck
|
||||
assert deck == ["7H", "8H", "2C", "3C"]
|
||||
|
||||
assert deck == ["8H", "2C", "3C"]
|
||||
|
||||
# Since P1 was the only resting deep player and is now ready, phase should have advanced to scene_setup!
|
||||
assert game.phase == "scene_setup"
|
||||
|
||||
@@ -564,8 +657,8 @@ def test_roll_new_character(session):
|
||||
assert p1.first_words == "No fear!"
|
||||
assert p1.good_at_math == "Yes"
|
||||
|
||||
# Verify rank randomized between 1-3
|
||||
assert p1.rank in [1, 2, 3]
|
||||
# New recruits always start at Rank 1
|
||||
assert p1.rank == 1
|
||||
|
||||
# Verify name is "Recruit Spiced Rum"
|
||||
assert p1.name == "Recruit Spiced Rum"
|
||||
@@ -588,4 +681,331 @@ def test_roll_new_character(session):
|
||||
assert "3D" in deck or "3D" in hand
|
||||
assert "4H" in deck or "4H" in hand
|
||||
|
||||
def test_gat_tax_accept(session):
|
||||
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
|
||||
obs = game.obstacles[0]
|
||||
# p2 is gatless; p3 has a gat
|
||||
p3.completed_personal_1 = True
|
||||
p2.hand_cards = json.dumps(["2C", "3C"])
|
||||
p3.hand_cards = json.dumps(["10C"])
|
||||
session.add_all([p2, p3])
|
||||
session.commit()
|
||||
|
||||
ok, msg = crud.create_challenge(session, game.id, deep.id, p2.id, [obs.id])
|
||||
assert ok
|
||||
session.refresh(game)
|
||||
challenge = game.challenges[0]
|
||||
|
||||
ok, msg = crud.request_tax(session, challenge.id, p2.id, p3.id)
|
||||
assert ok, msg
|
||||
session.refresh(challenge)
|
||||
assert challenge.tax_state == "requested"
|
||||
assert challenge.tax_type == "gat"
|
||||
|
||||
# Plays are blocked while the tax is pending
|
||||
ok, msg, res = crud.play_challenge_card(session, p2.id, obs.id, "2C")
|
||||
assert not ok
|
||||
|
||||
ok, msg = crud.respond_tax(session, challenge.id, p3.id, accept=True)
|
||||
assert ok
|
||||
session.refresh(challenge)
|
||||
session.refresh(p2)
|
||||
session.refresh(p3)
|
||||
# The gatted pi-rat takes over and received one random card from the requester
|
||||
assert challenge.acting_player_id == p3.id
|
||||
assert len(crud.get_player_hand(p2)) == 1
|
||||
assert len(crud.get_player_hand(p3)) == 2
|
||||
|
||||
def test_gat_tax_refuse_and_fail(session):
|
||||
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
|
||||
obs = game.obstacles[0]
|
||||
obs.current_value = 13
|
||||
p3.completed_personal_1 = True
|
||||
p2.hand_cards = json.dumps(["2C"])
|
||||
session.add_all([obs, p2, p3])
|
||||
session.commit()
|
||||
p2_start_rank = p2.rank
|
||||
|
||||
ok, msg = crud.create_challenge(session, game.id, deep.id, p2.id, [obs.id])
|
||||
assert ok
|
||||
session.refresh(game)
|
||||
challenge = game.challenges[0]
|
||||
|
||||
ok, msg = crud.request_tax(session, challenge.id, p2.id, p3.id)
|
||||
assert ok
|
||||
ok, msg = crud.respond_tax(session, challenge.id, p3.id, accept=False)
|
||||
assert ok
|
||||
|
||||
session.refresh(p2)
|
||||
session.refresh(p3)
|
||||
# Refusal: the gat changes paws immediately, requester completes Objective 1 (+1 Rank)
|
||||
assert p2.completed_personal_1
|
||||
assert not p3.completed_personal_1
|
||||
assert p2.rank == p2_start_rank + 1
|
||||
|
||||
# The requester attempts the challenge themselves... and fails
|
||||
ok, msg, res = crud.play_challenge_card(session, p2.id, obs.id, "2C")
|
||||
assert ok
|
||||
assert not res["success"]
|
||||
ok, msg = crud.resolve_challenge(session, challenge.id, deep.id)
|
||||
assert ok
|
||||
|
||||
session.refresh(p2)
|
||||
session.refresh(p3)
|
||||
# Failure: the gat goes back, the rank reverts, and no more taxes this scene
|
||||
assert not p2.completed_personal_1
|
||||
assert p3.completed_personal_1
|
||||
assert p2.rank == p2_start_rank
|
||||
assert p2.tax_banned
|
||||
|
||||
def test_pvp_challenge(session):
|
||||
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
|
||||
p2.hand_cards = json.dumps(["7C"])
|
||||
p3.hand_cards = json.dumps(["9C"])
|
||||
session.add_all([p2, p3])
|
||||
session.commit()
|
||||
|
||||
ok, msg = crud.create_pvp_challenge(session, game.id, p2.id, p3.id, "7C")
|
||||
assert ok, msg
|
||||
session.refresh(p2)
|
||||
assert crud.get_player_hand(p2) == [] # temp obstacle card left the hand
|
||||
|
||||
session.refresh(game)
|
||||
duel = [c for c in game.challenges if c.challenge_type == "pvp"][0]
|
||||
assert duel.temp_card == "7C"
|
||||
|
||||
# Defender beats 7 with 9; matching color (both black) -> draws a card back
|
||||
ok, msg, res = crud.play_pvp_defense(session, duel.id, p3.id, "9C")
|
||||
assert ok
|
||||
assert res["success"]
|
||||
assert res["drew_card"] is not None
|
||||
session.refresh(duel)
|
||||
assert duel.status == "succeeded"
|
||||
|
||||
def test_obstacles_persist_across_scenes(session):
|
||||
game, deep, (p2,) = make_scene_game(session)
|
||||
assert len(game.obstacles) == 2
|
||||
surviving_ids = {o.id for o in game.obstacles}
|
||||
|
||||
# End the scene, vote, and set up scene 2 with swapped roles
|
||||
crud.end_scene_and_transition(session, game.id)
|
||||
crud.transition_to_deep_upkeep(session, game.id)
|
||||
crud.confirm_deep_refresh(session, deep.id, [])
|
||||
session.refresh(game)
|
||||
assert game.phase == "scene_setup"
|
||||
assert game.current_scene_number == 2
|
||||
|
||||
deep.role = "pirat"
|
||||
p2.role = "deep"
|
||||
session.add_all([deep, p2])
|
||||
session.commit()
|
||||
|
||||
success, msg = crud.confirm_scene_setup(session, game.id)
|
||||
assert success, msg
|
||||
session.refresh(game)
|
||||
# Unbeaten obstacles remain; the list is already at Deep(1)+1=2, so nothing new is drawn
|
||||
assert {o.id for o in game.obstacles} == surviving_ids
|
||||
|
||||
def test_joker_obstacle_increase_is_permanent(session):
|
||||
game = crud.create_game(session)
|
||||
deep = crud.add_player(session, game.id, "Deep1")
|
||||
deep.rank = 3
|
||||
deep.role = "deep"
|
||||
p2 = crud.add_player(session, game.id, "Rat1")
|
||||
p2.rank = 1
|
||||
p2.role = "pirat"
|
||||
game.extra_obstacles = 1 # a Joker was drawn as an Obstacle earlier
|
||||
session.add_all([game, deep, p2])
|
||||
session.commit()
|
||||
|
||||
# Stub shuffle so the rebuilt deck keeps its construction order (Jokers last)
|
||||
# and no Joker can be drawn as an Obstacle here
|
||||
import random
|
||||
orig_shuffle = random.shuffle
|
||||
random.shuffle = lambda x: None
|
||||
|
||||
try:
|
||||
success, msg = crud.confirm_scene_setup(session, game.id)
|
||||
finally:
|
||||
random.shuffle = orig_shuffle
|
||||
|
||||
assert success, msg
|
||||
session.refresh(game)
|
||||
# Deep(1) + 1 + permanent increase(1) = 3 obstacles
|
||||
assert len(game.obstacles) == 3
|
||||
# The increase is permanent, not consumed
|
||||
assert game.extra_obstacles == 1
|
||||
|
||||
def test_first_scene_role_choice(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1")
|
||||
p2 = crud.add_player(session, game.id, "P2")
|
||||
p3 = crud.add_player(session, game.id, "P3")
|
||||
p1.rank = 3
|
||||
p2.rank = 1
|
||||
p3.rank = 2
|
||||
session.add_all([p1, p2, p3])
|
||||
session.commit()
|
||||
|
||||
# Rank 3 must play the Deep, Rank 1 must play their Pi-Rat, Rank 2 may choose either
|
||||
assert crud.get_allowed_roles(session, game.id, p1.id) == ["deep"]
|
||||
assert crud.get_allowed_roles(session, game.id, p2.id) == ["pirat"]
|
||||
assert set(crud.get_allowed_roles(session, game.id, p3.id)) == {"pirat", "deep"}
|
||||
|
||||
# A Rank 2 player choosing Deep alongside the Rank 3 player is legal
|
||||
p1.role = "deep"
|
||||
p2.role = "pirat"
|
||||
p3.role = "deep"
|
||||
session.add_all([p1, p2, p3])
|
||||
session.commit()
|
||||
success, msg = crud.confirm_scene_setup(session, game.id)
|
||||
assert success, msg
|
||||
|
||||
def test_vote_validation(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1")
|
||||
p2 = crud.add_player(session, game.id, "P2")
|
||||
p3 = crud.add_player(session, game.id, "P3")
|
||||
p1.role = "deep"
|
||||
p2.role = "pirat"
|
||||
p3.role = "pirat"
|
||||
session.add_all([p1, p2, p3])
|
||||
session.commit()
|
||||
|
||||
# Self-nomination is rejected
|
||||
ok, msg = crud.submit_rank_vote(session, game.id, p2.id, p2.id)
|
||||
assert not ok
|
||||
|
||||
# Nominating a previous-scene Deep player is rejected
|
||||
ok, msg = crud.submit_rank_vote(session, game.id, p2.id, p1.id)
|
||||
assert not ok
|
||||
|
||||
# But the Deep player may vote
|
||||
ok, msg = crud.submit_rank_vote(session, game.id, p1.id, p2.id)
|
||||
assert ok
|
||||
|
||||
def test_rank_up_draws_immediately(session):
|
||||
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
|
||||
# p2 is rank 1 (lowest -> max 2), p3 is rank 2 (highest -> max 4)
|
||||
hand_before = len(crud.get_player_hand(p2))
|
||||
crud.toggle_objective(session, game.id, p2.id, "personal_1", True)
|
||||
session.refresh(p2)
|
||||
assert p2.rank == 2
|
||||
# p2 jumped from lowest (2 cards) to highest-tied (4 cards): draws 2 immediately
|
||||
assert len(crud.get_player_hand(p2)) == hand_before + 2
|
||||
|
||||
|
||||
|
||||
def test_auto_delegate_all(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1", is_creator=True)
|
||||
p2 = crud.add_player(session, game.id, "P2")
|
||||
p3 = crud.add_player(session, game.id, "P3")
|
||||
session.refresh(game)
|
||||
|
||||
crud.auto_delegate_all(session, game)
|
||||
|
||||
players = [p1, p2, p3]
|
||||
for p in players:
|
||||
session.refresh(p)
|
||||
assert p.other_like_from_player_id and p.other_like_from_player_id != p.id
|
||||
assert p.other_hate_from_player_id and p.other_hate_from_player_id != p.id
|
||||
# With 3+ players, like and hate go to different crewmates
|
||||
assert p.other_like_from_player_id != p.other_hate_from_player_id
|
||||
|
||||
# Workload is even: each player answers exactly one like and one hate
|
||||
like_answerers = sorted(p.other_like_from_player_id for p in players)
|
||||
hate_answerers = sorted(p.other_hate_from_player_id for p in players)
|
||||
assert like_answerers == sorted(p.id for p in players)
|
||||
assert hate_answerers == sorted(p.id for p in players)
|
||||
|
||||
def test_auto_delegate_all_two_players(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1", is_creator=True)
|
||||
p2 = crud.add_player(session, game.id, "P2")
|
||||
session.refresh(game)
|
||||
|
||||
crud.auto_delegate_all(session, game)
|
||||
session.refresh(p1)
|
||||
session.refresh(p2)
|
||||
assert p1.other_like_from_player_id == p2.id
|
||||
assert p1.other_hate_from_player_id == p2.id
|
||||
assert p2.other_like_from_player_id == p1.id
|
||||
assert p2.other_hate_from_player_id == p1.id
|
||||
|
||||
def test_submit_techniques_rejects_collisions(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1", is_creator=True)
|
||||
p2 = crud.add_player(session, game.id, "P2")
|
||||
|
||||
# Duplicates within a single submission are rejected
|
||||
err = crud.submit_techniques(session, p1.id, "Tail Whip", "tail whip", "Cheese Decoy")
|
||||
assert err is not None
|
||||
session.refresh(p1)
|
||||
assert json.loads(p1.created_techniques) == []
|
||||
|
||||
# Valid submission goes through
|
||||
err = crud.submit_techniques(session, p1.id, "Tail Whip", "Pocket Sand", "Cheese Decoy")
|
||||
assert err is None
|
||||
|
||||
# A second player colliding (case-insensitively) with the pool is rejected
|
||||
err = crud.submit_techniques(session, p2.id, "POCKET SAND", "Bilge Bomb", "Rat King Roar")
|
||||
assert err is not None
|
||||
session.refresh(p2)
|
||||
assert json.loads(p2.created_techniques) == []
|
||||
|
||||
# And a unique set is accepted, triggering the swap
|
||||
err = crud.submit_techniques(session, p2.id, "Plank Pirouette", "Bilge Bomb", "Rat King Roar")
|
||||
assert err is None
|
||||
session.refresh(game)
|
||||
assert game.phase == "swap_techniques"
|
||||
|
||||
def test_roll_new_character_smell_name_normalization(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1")
|
||||
p2 = crud.add_player(session, game.id, "P2")
|
||||
p1.is_dead = True
|
||||
session.add(p1)
|
||||
session.commit()
|
||||
|
||||
crud.roll_new_character(
|
||||
session,
|
||||
game.id,
|
||||
p1.id,
|
||||
avatar_look="Shiny eyes",
|
||||
avatar_smell="suspiciously smelling of ink and sour lemons",
|
||||
first_words="No fear!",
|
||||
good_at_math="Yes"
|
||||
)
|
||||
session.refresh(p1)
|
||||
assert p1.name == "Recruit Ink and sour lemons"
|
||||
|
||||
def test_roll_new_character_avoids_technique_collisions(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1")
|
||||
p2 = crud.add_player(session, game.id, "P2")
|
||||
|
||||
# Give the surviving player techniques straight from the suggestion pool
|
||||
p2.tech_jack = cards.TECHNIQUE_SUGGESTIONS[0]
|
||||
p2.tech_queen = cards.TECHNIQUE_SUGGESTIONS[1]
|
||||
p2.tech_king = cards.TECHNIQUE_SUGGESTIONS[2]
|
||||
p1.is_dead = True
|
||||
session.add_all([p1, p2])
|
||||
session.commit()
|
||||
|
||||
# Shrink the pool so a collision would be guaranteed without the exclusion logic
|
||||
import pirats.crud_character # noqa: F401 (module uses cards.TECHNIQUE_SUGGESTIONS)
|
||||
original = cards.TECHNIQUE_SUGGESTIONS
|
||||
cards.TECHNIQUE_SUGGESTIONS = original[:6]
|
||||
try:
|
||||
crud.roll_new_character(
|
||||
session, game.id, p1.id,
|
||||
avatar_look="Shiny eyes", avatar_smell="Spiced Rum",
|
||||
first_words="No fear!", good_at_math="Yes"
|
||||
)
|
||||
finally:
|
||||
cards.TECHNIQUE_SUGGESTIONS = original
|
||||
|
||||
session.refresh(p1)
|
||||
recruit_techs = {p1.tech_jack, p1.tech_queen, p1.tech_king}
|
||||
assert recruit_techs == set(original[3:6])
|
||||
|
||||
Reference in New Issue
Block a user