grid-royale / backend /engine.py
LokeshReddy001's picture
suchith's changes (#2)
d7e2b77
Raw
History Blame Contribute Delete
7.24 kB
from __future__ import annotations
import asyncio
import json
import random
import time
from .environment import Environment, GameConfig
from .environment.tile import Tile
from .environment.loot_tables import generate_chest_loot
from .agent import AgentState
from .agent.agent import Agent
from .agent.state import AbilityInstance
from .tools import ALL_TOOLS, TOOL_SCHEMAS
class Engine:
def __init__(self, config: GameConfig | None = None):
self.env = Environment(config=config or GameConfig())
self.agents: dict[str, Agent] = {}
self.last_turn_log: dict | None = None
def generate_grid(self):
gs = self.env.config.grid_size
for x in range(gs):
for y in range(gs):
self.env.grid[(x, y)] = Tile(terrain="grass")
def scatter_chests(self, count: int | None = None):
count = count or self.env.config.num_chests
gs = self.env.config.grid_size
positions = random.sample(
[(x, y) for x in range(gs) for y in range(gs)],
count,
)
for pos in positions:
if self.env.is_empty(pos[0], pos[1]):
tile = self.env.get_tile(pos[0], pos[1])
tile.loot = generate_chest_loot()
tile.loot_spawn_turn = self.env.turn
def spawn_agent(
self,
agent_id: str,
name: str,
model: str | None = None,
provider: str | None = None,
system_prompt: str | None = None,
):
gs = self.env.config.grid_size
while True:
x = random.randint(0, gs - 1)
y = random.randint(0, gs - 1)
if self.env.is_empty(x, y):
break
state = AgentState(
id=agent_id,
name=name,
pos=[x, y],
hp=self.env.config.base_hp,
)
import os
default_provider = os.getenv("LLM_PROVIDER", "modal")
default_model = os.getenv(
"LLM_MODEL",
"qwen2.5:7b" if default_provider == "local" else "google/gemma-4-26B-A4B-it",
)
brain = Agent(
agent_id=agent_id,
model=model or default_model,
provider=provider or default_provider,
system_prompt=system_prompt,
config=self.env.config,
)
state.messages.append({"role": "system", "content": brain.system_prompt})
self.env.agents[agent_id] = state
self.agents[agent_id] = brain
self._send_start_message(agent_id)
return state
async def step(self):
t0 = time.time()
alive = self.env.alive_agents()
if len(alive) <= 1:
if len(alive) == 1:
winner = alive[0]
winner.score += self.env.config.win_bonus
return
decisions: dict[str, dict] = {}
turn_log: dict[str, list[dict]] = {}
tasks = []
for aid, state in self.env.agents.items():
if not state.alive:
decisions[aid] = []
turn_log[aid] = []
continue
brain = self.agents.get(aid)
if brain:
tasks.append((aid, brain.decide(state.messages, TOOL_SCHEMAS)))
else:
decisions[aid] = []
turn_log[aid] = []
if tasks:
results = await asyncio.gather(*[t for _, t in tasks])
for (aid, _), result in zip(tasks, results):
calls = result["calls"]
decisions[aid] = calls
turn_log[aid] = [{
"phase": "llm",
"turn": self.env.turn,
"time_ms": result["time_ms"],
"usage": result.get("raw"),
}]
if calls:
state = self.env.agents[aid]
state.messages.append({
"role": "assistant",
"content": None,
"tool_calls": [
{"id": f"call_{i}", "type": "function",
"function": {"name": c["name"], "arguments": json.dumps(c.get("args", {}))}}
for i, c in enumerate(calls)
],
})
for rnd in range(3):
for aid in list(self.env.agents.keys()):
state = self.env.agents[aid]
if not state.alive:
continue
calls = decisions.get(aid, [])
if rnd < len(calls):
call = calls[rnd]
exec_result = self.env.execute(aid, call["name"], call["args"])
state.messages.append({
"role": "tool",
"content": exec_result["text"],
"tool_call_id": f"call_{rnd}",
})
turn_log[aid].append({
"phase": "exec",
"round": rnd,
"tool": call["name"],
"args": call["args"],
"result": exec_result["text"],
"time_ms": exec_result["time_ms"],
})
self.env.turn += 1
step_total = round((time.time() - t0) * 1000)
for state in self.env.agents.values():
if state.alive:
state.score += self.env.config.survival_score_per_turn
# Decay old loot
for tile in list(self.env.grid.values()):
if tile.loot is not None and tile.loot_spawn_turn is not None:
if self.env.turn - tile.loot_spawn_turn >= self.env.config.chest_lifetime:
tile.loot = None
tile.loot_spawn_turn = None
if self.env.turn % self.env.config.chest_respawn_interval == 0:
self.scatter_chests(count=max(1, self.env.config.num_chests // 3))
self.last_turn_log = {
"time_ms": step_total,
"agents": turn_log,
}
def _send_start_message(self, aid: str):
agent = self.env.agents[aid]
agent.messages.append({
"role": "user",
"content": (
f"You are {agent.name}. You have been dropped into a battle royale on a "
f"{self.env.config.grid_size}x{self.env.config.grid_size} grid. "
f"Your HP is {agent.hp}. There are {len(self.env.alive_agents())} agents alive. "
"Use observe() to see your surroundings, move() to explore, "
"and activate_ability() once you find abilities in loot chests. "
"Good luck."
),
})
async def run_game(self) -> dict[str, AgentState]:
self.generate_grid()
self.scatter_chests()
while self.env.turn < self.env.config.max_turns:
alive = self.env.alive_agents()
if len(alive) <= 1:
break
await self.step()
if len(self.env.alive_agents()) == 1:
winner = self.env.alive_agents()[0]
winner.score += self.env.config.win_bonus
return self.env.agents