2026-09-05 21:10:18 +00:00
|
|
|
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())
|
|
|
|
|
|
|
|
|
|
|
2026-09-05 22:09:18 +00:00
|
|
|
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"]
|
|
|
|
|
|
|
|
|
|
|
2026-09-05 21:55:07 +00:00
|
|
|
def test_party_formation_and_group_movement():
|
2026-09-05 21:10:18 +00:00
|
|
|
client = TestClient(app)
|
2026-09-05 21:55:07 +00:00
|
|
|
p1 = client.post("/api/players", json={"name": "LeaderBot", "color": "#00FFAA"}).json()
|
|
|
|
|
p2 = client.post("/api/players", json={"name": "FollowerBot", "color": "#FF00AA"}).json()
|
2026-09-05 21:20:39 +00:00
|
|
|
|
|
|
|
|
import asyncio
|
2026-09-05 21:55:07 +00:00
|
|
|
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
|
|
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
|
2026-09-05 21:55:07 +00:00
|
|
|
def test_party_wall_collision_for_follower():
|
2026-09-05 21:10:18 +00:00
|
|
|
client = TestClient(app)
|
2026-09-05 21:55:07 +00:00
|
|
|
p1 = client.post("/api/players", json={"name": "Leader", "color": "#111111"}).json()
|
|
|
|
|
p2 = client.post("/api/players", json={"name": "Follower", "color": "#222222"}).json()
|
2026-09-05 21:20:39 +00:00
|
|
|
|
|
|
|
|
import asyncio
|
2026-09-05 21:55:07 +00:00
|
|
|
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"]
|