File size: 7,082 Bytes
9f14abf 86ff9a7 9f14abf 86ff9a7 9f14abf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | import asyncio
import pytest
from app import Card, GameError, Player, Room, create_deck_for_tests, rooms, run_auto_draw
def make_room(player_count=3):
players = [
Player(id=f"p{i}", name=f"P{i}", token=f"t{i}", connected=True)
for i in range(player_count)
]
return Room(code="TEST", host_player_id="p0", total_players=player_count, players=players)
def test_deck_has_108_cards():
deck = create_deck_for_tests()
assert len(deck) == 108
assert sum(1 for c in deck if c.color == "wild" and c.value == "wild") == 4
assert sum(1 for c in deck if c.color == "wild" and c.value == "wild_draw4") == 4
def test_start_game_with_15_players_is_legal():
room = make_room(15)
event = room.start_game("p0")
assert event["type"] == "game_started"
assert room.phase == "playing"
assert all(len(p.hand) == 7 for p in room.players)
assert len(room.discard_pile) == 1
assert room.discard_pile[-1].color != "wild"
assert len(room.deck) == 2
def test_reshuffle_keeps_top_discard():
room = make_room()
top = Card("top", "red", "5")
old = Card("old", "blue", "7")
room.discard_pile = [old, top]
room.deck = []
room.reshuffle_deck()
assert room.discard_pile == [top]
assert room.deck == [old]
def test_normal_play_rules_and_draw_stacking():
room = make_room()
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "3")]
assert room.is_valid_play(Card("same_color", "red", "9"))
assert room.is_valid_play(Card("same_value", "blue", "3"))
assert room.is_valid_play(Card("wild", "wild", "wild"))
assert not room.is_valid_play(Card("bad", "green", "8"))
room.pending_draw = 2
room.discard_pile = [Card("draw_top", "red", "draw2")]
assert room.is_valid_play(Card("stack2", "blue", "draw2"))
assert room.is_valid_play(Card("stack4", "wild", "wild_draw4"))
assert not room.is_valid_play(Card("number", "red", "3"))
def test_strict_jump_in_requires_color_and_value():
room = make_room()
room.phase = "playing"
room.discard_pile = [Card("top", "red", "7")]
assert room.is_strict_jump_in(Card("same", "red", "7"))
assert not room.is_strict_jump_in(Card("value_only", "blue", "7"))
assert not room.is_strict_jump_in(Card("color_only", "red", "8"))
room.discard_pile = [Card("wild_top", "wild", "wild_draw4")]
assert room.is_strict_jump_in(Card("wild_same", "wild", "wild_draw4"))
assert not room.is_strict_jump_in(Card("wild_other", "wild", "wild"))
def test_jump_in_interrupts_and_continues_from_jumper():
room = make_room(4)
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
room.players[2].hand = [Card("jump", "red", "5")]
room.play_card("p2", "jump", expected_top_card_id="top", expected_version=0)
assert room.current_player_id == "p3"
assert room.discard_pile[-1].id == "jump"
def test_jump_in_with_reverse_uses_jumper_as_turn_source():
room = make_room(4)
room.phase = "playing"
room.current_player_id = "p0"
room.direction = 1
room.current_color = "green"
room.discard_pile = [Card("top", "green", "reverse")]
room.players[2].hand = [Card("jump", "green", "reverse")]
room.play_card("p2", "jump", expected_top_card_id="top", expected_version=0)
assert room.direction == -1
assert room.current_player_id == "p1"
def test_second_simultaneous_jump_in_revalidates_top_card():
room = make_room(4)
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
room.players[2].hand = [Card("jump1", "red", "5")]
room.players[3].hand = [Card("jump2", "red", "5")]
room.play_card("p2", "jump1", expected_top_card_id="top", expected_version=0)
with pytest.raises(GameError):
room.version += 1
room.play_card("p3", "jump2", expected_top_card_id="top", expected_version=0)
def test_bot_jump_in_revalidation_shape():
room = make_room(4)
room.players[2].is_bot = True
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
bot_card = Card("bot_jump", "red", "5")
room.players[2].hand = [bot_card]
assert any(room.is_strict_jump_in(card) for card in room.players[2].hand)
room.discard_pile.append(Card("changed", "blue", "9"))
assert not any(room.is_strict_jump_in(card) for card in room.players[2].hand)
def test_auto_draw_no_playable_human_draws_and_advances():
room = make_room()
room.code = "AUTO1"
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
room.players[0].hand = [Card("bad", "green", "8")]
room.players[1].hand = [Card("p1_playable", "red", "1")]
room.deck = [Card("drawn", "blue", "9")]
rooms[room.code] = room
try:
asyncio.run(run_auto_draw(room.code, "p0", room.version, "top", "red", 0, delay=0))
assert [card.id for card in room.players[0].hand] == ["bad", "drawn"]
assert room.current_player_id == "p1"
assert room.version == 1
finally:
rooms.pop(room.code, None)
def test_auto_draw_takes_pending_penalty_and_clears_it():
room = make_room()
room.code = "AUTO2"
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.pending_draw = 2
room.discard_pile = [Card("top", "red", "draw2")]
room.players[0].hand = [Card("bad", "green", "8")]
room.players[1].hand = [Card("p1_playable", "red", "1")]
room.deck = [Card("drawn1", "blue", "9"), Card("drawn2", "yellow", "1")]
rooms[room.code] = room
try:
asyncio.run(run_auto_draw(room.code, "p0", room.version, "top", "red", 2, delay=0))
assert len(room.players[0].hand) == 3
assert room.pending_draw == 0
assert room.current_player_id == "p1"
finally:
rooms.pop(room.code, None)
def test_auto_draw_revalidates_before_drawing():
room = make_room()
room.code = "AUTO3"
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
room.players[0].hand = [Card("playable", "red", "8")]
room.deck = [Card("drawn", "blue", "9")]
rooms[room.code] = room
try:
asyncio.run(run_auto_draw(room.code, "p0", room.version, "top", "red", 0, delay=0))
assert [card.id for card in room.players[0].hand] == ["playable"]
assert room.current_player_id == "p0"
room.players[0].hand = [Card("bad", "green", "8")]
room.version += 1
asyncio.run(run_auto_draw(room.code, "p0", 0, "top", "red", 0, delay=0))
assert [card.id for card in room.players[0].hand] == ["bad"]
assert room.current_player_id == "p0"
finally:
rooms.pop(room.code, None)
|