import pytest from fastapi.testclient import TestClient from app.main import app from app.game import game_engine @pytest.fixture(autouse=True) def reset_board_state(): import asyncio asyncio.run(game_engine.reset()) yield asyncio.run(game_engine.reset()) def test_bot_location_memory_and_radar(): client = TestClient(app) p1 = client.post("/api/players", json={"name": "MemoryBot", "color": "#00FFAA"}).json() p2 = client.post("/api/players", json={"name": "NearbyBot", "color": "#FF00AA"}).json() import asyncio async def place_bots(): b1 = await game_engine.get_player(p1["id"]) b1.x, b1.y = 15, 15 b1.visited_locations = [{"x": 15, "y": 15}] b2 = await game_engine.get_player(p2["id"]) b2.x, b2.y = 16, 16 b2.visited_locations = [{"x": 16, "y": 16}] asyncio.run(place_bots()) # Check memory of p1 mem = client.get(f"/api/players/{p1['id']}/memory").json() assert mem["player_id"] == p1["id"] assert mem["current_x"] == 15 assert mem["current_y"] == 15 assert mem["visited_count"] == 1 assert mem["visited_history"][0] == {"x": 15, "y": 15} # Check radar radar = client.get(f"/api/players/{p1['id']}/radar").json() assert radar["player_id"] == p1["id"] assert len(radar["targets"]) == 1 assert radar["targets"][0]["id"] == p2["id"] assert radar["targets"][0]["distance"] == 1 # Move p1 res_move = client.post(f"/api/players/{p1['id']}/move", json={"direction": "UP"}) assert res_move.status_code == 200 # Memory should now have 2 visited locations mem2 = client.get(f"/api/players/{p1['id']}/memory").json() assert mem2["visited_count"] == 2 assert mem2["visited_history"][-1] == {"x": 15, "y": 14} def test_3bout_d20_battle_with_defeated_members_joining_winner(): client = TestClient(app) # Party 1: Leader + 2 Followers (Total 3 bots, strength 3) p1 = client.post("/api/players", json={"name": "SquadLeader1", "color": "#111111"}).json() p2 = client.post("/api/players", json={"name": "WingmanA", "color": "#222222"}).json() p3 = client.post("/api/players", json={"name": "WingmanB", "color": "#333333"}).json() # Party 2: Leader + 1 Follower (Total 2 bots, strength 2) p4 = client.post("/api/players", json={"name": "SquadLeader2", "color": "#444444"}).json() p5 = client.post("/api/players", json={"name": "WingmanC", "color": "#555555"}).json() import asyncio async def setup_parties(): b1 = await game_engine.get_player(p1["id"]) b1.x, b1.y = 20, 20 b2 = await game_engine.get_player(p2["id"]) b2.x, b2.y = 20, 21 b3 = await game_engine.get_player(p3["id"]) b3.x, b3.y = 21, 21 b4 = await game_engine.get_player(p4["id"]) b4.x, b4.y = 21, 20 # Adjacent to b1 b5 = await game_engine.get_player(p5["id"]) b5.x, b5.y = 22, 20 asyncio.run(setup_parties()) # Form Party 1 party1 = client.post("/api/parties", json={ "member_ids": [p1["id"], p2["id"], p3["id"]], "leader_id": p1["id"], "name": "Titans", }).json() # Form Party 2 party2 = client.post("/api/parties", json={ "member_ids": [p4["id"], p5["id"]], "leader_id": p4["id"], "name": "Phantoms", }).json() # Fight battle between the adjacent parties res = client.post("/api/battles/fight", json={ "challenger_id": p1["id"], "defender_id": p4["id"], }) assert res.status_code == 200 battle = res.json() # Verify 3 bouts were fought assert len(battle["bouts"]) == 3 for bout in battle["bouts"]: assert 1 <= bout["party1_roll"] <= 20 assert 1 <= bout["party2_roll"] <= 20 assert bout["party1_score"] == bout["party1_strength"] * bout["party1_roll"] assert bout["party2_score"] == bout["party2_strength"] * bout["party2_roll"] # Winner was crowned winner_id = battle["winner_party_id"] defeated_id = battle["defeated_party_id"] assert winner_id in [party1["id"], party2["id"]] assert defeated_id in [party1["id"], party2["id"]] assert winner_id != defeated_id # Defeated leader was killed and received -1 point killed_leader_id = battle["killed_leader_id"] dead_leader = client.get(f"/api/players/{killed_leader_id}").json() assert dead_leader["score"] == -1 assert dead_leader["party_id"] is None assert dead_leader["is_party_leader"] is False # Winning leader received +2 points winning_leader_id = battle["winner_leader_id"] winning_leader = client.get(f"/api/players/{winning_leader_id}").json() assert winning_leader["score"] == 2 # Winning original squad members received +1 point # Defeated party members lost 0 points (score remains 0) if winner_id == party1["id"]: # Titans won: p2, p3 were in winning party -> score 1 # p5 was in defeated party -> score 0 assert client.get(f"/api/players/{p2['id']}").json()["score"] == 1 assert client.get(f"/api/players/{p3['id']}").json()["score"] == 1 assert client.get(f"/api/players/{p5['id']}").json()["score"] == 0 else: # Phantoms won: p5 was in winning party -> score 1 # p2, p3 were in defeated party -> score 0 assert client.get(f"/api/players/{p5['id']}").json()["score"] == 1 assert client.get(f"/api/players/{p2['id']}").json()["score"] == 0 assert client.get(f"/api/players/{p3['id']}").json()["score"] == 0 # The remainder of the defeated party joined the winning party winning_party = client.get(f"/api/parties/{winner_id}").json() assert battle["new_party_size"] == len(winning_party["member_ids"]) # Absorbed bots are now members of winning_party for absorbed_id in battle["absorbed_members"]: absorbed_player = client.get(f"/api/players/{absorbed_id}").json() assert absorbed_player["party_id"] == winner_id assert absorbed_id in winning_party["member_ids"] def test_party_formation_and_group_movement(): client = TestClient(app) p1 = client.post("/api/players", json={"name": "LeaderBot", "color": "#00FFAA"}).json() p2 = client.post("/api/players", json={"name": "FollowerBot", "color": "#FF00AA"}).json() import asyncio async def place_adjacent(): b1 = await game_engine.get_player(p1["id"]) b1.x, b1.y = 10, 10 b2 = await game_engine.get_player(p2["id"]) b2.x, b2.y = 10, 11 asyncio.run(place_adjacent()) form_res = client.post("/api/parties", json={ "member_ids": [p1["id"], p2["id"]], "leader_id": p1["id"], "name": "AlphaSquad", }) assert form_res.status_code == 201 party = form_res.json() assert party["leader_id"] == p1["id"] assert len(party["member_ids"]) == 2 res_follow = client.post(f"/api/players/{p2['id']}/move", json={"direction": "UP"}) assert res_follow.status_code == 403 res_move = client.post(f"/api/players/{p1['id']}/move", json={"direction": "UP"}) assert res_move.status_code == 200 move_data = res_move.json() assert move_data["party_moved"] is True assert len(move_data["affected_players"]) == 2 def test_party_wall_collision_for_follower(): client = TestClient(app) p1 = client.post("/api/players", json={"name": "Leader", "color": "#111111"}).json() p2 = client.post("/api/players", json={"name": "Follower", "color": "#222222"}).json() import asyncio async def setup_near_wall(): b1 = await game_engine.get_player(p1["id"]) b1.x, b1.y = 5, 1 b2 = await game_engine.get_player(p2["id"]) b2.x, b2.y = 5, 0 asyncio.run(setup_near_wall()) client.post("/api/parties", json={ "member_ids": [p1["id"], p2["id"]], "leader_id": p1["id"], }) moves = client.get(f"/api/players/{p1['id']}/available-moves").json() assert moves["moves"]["UP"]["available"] is False assert "boundary wall" in moves["moves"]["UP"]["reason"]