Dead Pi-Rat reroll rework
This commit is contained in:
@@ -616,68 +616,102 @@ def test_become_ghost_flow(session):
|
||||
finally:
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
def test_roll_new_character(session):
|
||||
def test_recruit_creation_full_flow(session):
|
||||
"""A dead Pi-Rat re-rolls: queued in between_scenes, created cooperatively
|
||||
in the recruit_creation phase, then the game advances to scene_setup."""
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1")
|
||||
p2 = crud.add_player(session, game.id, "P2") # helper player for delegation target
|
||||
|
||||
p2 = crud.add_player(session, game.id, "P2")
|
||||
p3 = crud.add_player(session, game.id, "P3")
|
||||
|
||||
game.phase = "between_scenes"
|
||||
game.current_scene_number = 2
|
||||
p1.is_dead = True
|
||||
p1.rank = 3
|
||||
p1.hand_cards = json.dumps(["2C", "3D", "4H"])
|
||||
p1.completed_personal_1 = True
|
||||
p1.completed_personal_2 = True
|
||||
p1.completed_personal_3 = True
|
||||
|
||||
session.add_all([p1, p2])
|
||||
p1.name = "Dread Pirate Roberts"
|
||||
session.add_all([game, p1, p2, p3])
|
||||
session.commit()
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
|
||||
# 1. The dead player opts to re-roll (a living player can't)
|
||||
ok, msg = crud.choose_reroll(session, p2.id)
|
||||
assert not ok
|
||||
ok, msg = crud.choose_reroll(session, p1.id)
|
||||
assert ok, msg
|
||||
session.refresh(p1)
|
||||
assert p1.needs_reroll
|
||||
assert p1.is_dead # still dead until the recruit is created
|
||||
|
||||
# 2. Upkeep ends (no resting Deep) -> detours into recruit_creation
|
||||
crud.transition_to_deep_upkeep(session, game.id)
|
||||
session.refresh(game)
|
||||
session.refresh(p1)
|
||||
assert game.phase == "recruit_creation"
|
||||
assert game.current_scene_number == 2 # not incremented yet
|
||||
|
||||
# Activation reset the sheet and returned the old hand to the deck
|
||||
assert p1.rank == 1
|
||||
assert not p1.is_dead
|
||||
assert not p1.completed_personal_1
|
||||
assert p1.avatar_look == ""
|
||||
assert p1.tech_jack == ""
|
||||
assert json.loads(p1.hand_cards) == []
|
||||
deck = json.loads(game.deck_cards)
|
||||
assert all(c in deck for c in ["2C", "3D", "4H"])
|
||||
|
||||
# Crewmates were assigned to answer Like/Hate and write 3 techniques
|
||||
assert p1.other_like_from_player_id in (p2.id, p3.id)
|
||||
assert p1.other_hate_from_player_id in (p2.id, p3.id)
|
||||
assert p1.other_like_from_player_id != p1.other_hate_from_player_id
|
||||
slots = json.loads(p1.incoming_techniques)
|
||||
assert len(slots) == 3
|
||||
assert all(s["from_id"] in (p2.id, p3.id) for s in slots)
|
||||
assert all(s["text"] == "" for s in slots)
|
||||
|
||||
# 3. Can't finalize before the crew has done its part
|
||||
ok, msg = crud.finalize_recruit(session, p1.id, "a", "b", "c")
|
||||
assert not ok
|
||||
|
||||
# 4. The recruit fills in their basic records
|
||||
p1.avatar_look = "Shiny eyes"
|
||||
p1.avatar_smell = "Spiced Rum"
|
||||
p1.first_words = "No fear!"
|
||||
p1.good_at_math = "Yes"
|
||||
p1.name = crud.recruit_name(p1)
|
||||
session.add(p1)
|
||||
session.commit()
|
||||
assert p1.name == "Recruit Spiced Rum"
|
||||
|
||||
# 5. Crewmates answer Like/Hate and write the techniques
|
||||
crud.submit_delegated_answer(session, p1.id, "like", "Great hat", p1.other_like_from_player_id)
|
||||
crud.submit_delegated_answer(session, p1.id, "hate", "Bad jokes", p1.other_hate_from_player_id)
|
||||
techs = ["Plank Pirouette", "Bilge Bomb", "Rat King Roar"]
|
||||
for i, s in enumerate(slots):
|
||||
# The wrong crewmate can't fill someone else's slot
|
||||
wrong = p3.id if s["from_id"] == p2.id else p2.id
|
||||
ok, msg = crud.contribute_recruit_technique(session, wrong, p1.id, i, techs[i])
|
||||
assert not ok
|
||||
ok, msg = crud.contribute_recruit_technique(session, s["from_id"], p1.id, i, techs[i])
|
||||
assert ok, msg
|
||||
|
||||
# 6. The recruit assigns J/Q/K and joins the crew
|
||||
ok, msg = crud.finalize_recruit(session, p1.id, techs[1], techs[0], techs[2])
|
||||
assert ok, msg
|
||||
session.refresh(p1)
|
||||
session.refresh(game)
|
||||
|
||||
# Verify properties
|
||||
assert not p1.is_dead
|
||||
assert not p1.is_ghost
|
||||
assert not p1.completed_personal_1
|
||||
assert not p1.completed_personal_2
|
||||
assert not p1.completed_personal_3
|
||||
assert p1.avatar_look == "Shiny eyes"
|
||||
assert p1.avatar_smell == "Spiced Rum"
|
||||
assert p1.first_words == "No fear!"
|
||||
assert p1.good_at_math == "Yes"
|
||||
|
||||
# New recruits always start at Rank 1
|
||||
assert p1.rank == 1
|
||||
|
||||
# Verify name is "Recruit Spiced Rum"
|
||||
assert p1.name == "Recruit Spiced Rum"
|
||||
|
||||
# Verify techniques auto-assigned
|
||||
assert p1.tech_jack != ""
|
||||
assert p1.tech_queen != ""
|
||||
assert p1.tech_king != ""
|
||||
|
||||
# Verify delegated targets exist (p2 is the only other player)
|
||||
assert p1.other_like_from_player_id == p2.id
|
||||
assert p1.other_hate_from_player_id == p2.id
|
||||
|
||||
# Verify hand was refreshed up to new max hand size
|
||||
hand = json.loads(p1.hand_cards)
|
||||
assert len(hand) > 0
|
||||
# Old cards returned to deck
|
||||
deck = json.loads(game.deck_cards)
|
||||
assert "2C" in deck or "2C" in hand
|
||||
assert "3D" in deck or "3D" in hand
|
||||
assert "4H" in deck or "4H" in hand
|
||||
assert not p1.needs_reroll
|
||||
assert p1.tech_jack == techs[1]
|
||||
assert p1.tech_queen == techs[0]
|
||||
assert p1.tech_king == techs[2]
|
||||
assert json.loads(p1.swapped_techniques) == techs
|
||||
assert len(json.loads(p1.hand_cards)) > 0 # starting hand dealt
|
||||
|
||||
# Last recruit done -> next scene's setup begins
|
||||
assert game.phase == "scene_setup"
|
||||
assert game.current_scene_number == 3
|
||||
|
||||
def test_gat_tax_accept(session):
|
||||
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
|
||||
@@ -1049,55 +1083,77 @@ def test_submit_techniques_rejects_collisions(session):
|
||||
session.refresh(game)
|
||||
assert game.phase == "swap_techniques"
|
||||
|
||||
def test_roll_new_character_smell_name_normalization(session):
|
||||
def test_recruit_smell_name_normalization(session):
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1")
|
||||
crud.add_player(session, game.id, "P2")
|
||||
p1.is_dead = True
|
||||
session.add(p1)
|
||||
session.commit()
|
||||
p1.avatar_smell = "suspiciously smelling of ink and sour lemons"
|
||||
assert crud.recruit_name(p1) == "Recruit Ink and sour lemons"
|
||||
|
||||
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):
|
||||
def test_contribute_technique_rejects_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]
|
||||
p2.tech_jack = "Parabolic Boarding Bounce"
|
||||
p1.is_dead = True
|
||||
p1.needs_reroll = 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
|
||||
|
||||
crud.start_recruit_creation(session, game)
|
||||
session.refresh(p1)
|
||||
recruit_techs = {p1.tech_jack, p1.tech_queen, p1.tech_king}
|
||||
assert recruit_techs == set(original[3:6])
|
||||
slots = json.loads(p1.incoming_techniques)
|
||||
assert all(s["from_id"] == p2.id for s in slots)
|
||||
|
||||
# A name already on another sheet is rejected (case-insensitively)
|
||||
ok, msg = crud.contribute_recruit_technique(session, p2.id, p1.id, 0, "parabolic boarding bounce")
|
||||
assert not ok
|
||||
ok, msg = crud.contribute_recruit_technique(session, p2.id, p1.id, 0, "Bilge Bomb")
|
||||
assert ok, msg
|
||||
# A name already written into another of the recruit's slots is rejected too
|
||||
ok, msg = crud.contribute_recruit_technique(session, p2.id, p1.id, 1, "Bilge Bomb")
|
||||
assert not ok
|
||||
|
||||
def test_solo_recruit_gets_pool_techniques(session):
|
||||
"""With no crewmates to ask, the recruit's techniques come from the pool."""
|
||||
game = crud.create_game(session)
|
||||
p1 = crud.add_player(session, game.id, "P1")
|
||||
p1.is_dead = True
|
||||
p1.needs_reroll = True
|
||||
session.add(p1)
|
||||
session.commit()
|
||||
|
||||
crud.start_recruit_creation(session, game)
|
||||
session.refresh(p1)
|
||||
slots = json.loads(p1.incoming_techniques)
|
||||
assert len(slots) == 3
|
||||
assert all(s["from_id"] is None for s in slots)
|
||||
assert all(s["text"] in cards.TECHNIQUE_SUGGESTIONS for s in slots)
|
||||
assert p1.other_like_from_player_id is None
|
||||
|
||||
def test_late_joiner_queued_for_recruit_creation(session):
|
||||
game, deep, (p2,) = make_scene_game(session)
|
||||
assert game.phase == "scene"
|
||||
|
||||
newbie = crud.add_player(session, game.id, "Latecomer")
|
||||
assert newbie.needs_reroll
|
||||
assert newbie.role is None
|
||||
assert json.loads(newbie.hand_cards) == []
|
||||
|
||||
# Scene ends, everyone votes/readies, resting Deep refreshes...
|
||||
crud.end_scene_and_transition(session, game.id)
|
||||
crud.transition_to_deep_upkeep(session, game.id)
|
||||
session.refresh(game)
|
||||
assert game.phase == "deep_upkeep"
|
||||
crud.confirm_deep_refresh(session, deep.id, [])
|
||||
session.refresh(game)
|
||||
|
||||
# ...and instead of scene_setup we land in recruit_creation
|
||||
assert game.phase == "recruit_creation"
|
||||
session.refresh(newbie)
|
||||
slots = json.loads(newbie.incoming_techniques)
|
||||
assert len(slots) == 3
|
||||
assert all(s["from_id"] in (deep.id, p2.id) for s in slots)
|
||||
|
||||
def test_reshuffle_keeps_open_pvp_temp_card_out_of_deck(session):
|
||||
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
|
||||
@@ -1123,17 +1179,6 @@ def test_reshuffle_keeps_open_pvp_temp_card_out_of_deck(session):
|
||||
crud.reshuffle_discard_pile(session, game)
|
||||
assert "7C" in crud.get_game_deck(game)
|
||||
|
||||
def test_late_joiner_is_dealt_a_hand(session):
|
||||
game, deep, (p2,) = make_scene_game(session)
|
||||
assert game.phase == "scene"
|
||||
|
||||
newbie = crud.add_player(session, game.id, "Stowaway")
|
||||
assert newbie.role == "pirat"
|
||||
session.refresh(game)
|
||||
expected = crud.calculate_max_hand_size(newbie, game.players, game.captain_player_id)
|
||||
assert expected > 0
|
||||
assert len(crud.get_player_hand(newbie)) == expected
|
||||
|
||||
def test_technique_suggestions_endpoint():
|
||||
from fastapi.testclient import TestClient
|
||||
from pirats.main import app
|
||||
|
||||
Reference in New Issue
Block a user