Skip to content

Commit 6e65014

Browse files
committed
Enable position shifts for CoreWar
1 parent f60b391 commit 6e65014

File tree

1 file changed

+57
-30
lines changed

1 file changed

+57
-30
lines changed

codeclash/arenas/corewar/corewar.py

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import re
22
import shlex
3+
from collections import defaultdict
4+
from concurrent.futures import ThreadPoolExecutor, as_completed
35

46
from codeclash.agents.player import Player
57
from codeclash.arenas.arena import CodeArena, RoundStats
8+
from codeclash.constants import RESULT_TIE
69

7-
COREWAR_LOG = "sim.log"
10+
COREWAR_LOG = "sim_{idx}.log"
811

912

1013
class CoreWarArena(CodeArena):
@@ -23,49 +26,73 @@ def __init__(self, config, **kwargs):
2326
else:
2427
self.run_cmd_round += f" -{arg} {val}"
2528

26-
def execute_round(self, agents: list[Player]):
29+
def _run_single_simulation(self, agents: list[Player], idx: int):
30+
# Shift agents by idx to vary starting positions
31+
agents = agents[idx:] + agents[:idx]
2732
args = [f"/{agent.name}/{self.submission}" for agent in agents]
2833
cmd = (
2934
f"{self.run_cmd_round} {shlex.join(args)} "
3035
f"-r {self.game_config['sims_per_round']} "
31-
f"> {self.log_env / COREWAR_LOG};"
36+
f"> {self.log_env / COREWAR_LOG.format(idx=idx)};"
3237
)
3338
self.logger.info(f"Running game: {cmd}")
3439
response = self.environment.execute(cmd)
3540
assert response["returncode"] == 0, response
3641

42+
def execute_round(self, agents: list[Player]):
43+
with ThreadPoolExecutor(4) as executor:
44+
futures = [executor.submit(self._run_single_simulation, agents, idx) for idx in range(len(agents))]
45+
for future in as_completed(futures):
46+
future.result()
47+
3748
def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
38-
with open(self.log_round(round_num) / COREWAR_LOG) as f:
39-
result_output = f.read()
40-
self.logger.debug(f"Determining winner from result output: {result_output}")
41-
scores = []
42-
n = len(agents) * 2
43-
lines = result_output.strip().split("\n")
49+
scores, wins = defaultdict(int), defaultdict(int)
50+
for idx in range(len(agents)):
51+
shift = agents[idx:] + agents[:idx] # Shift agents by idx to match simulation order
52+
with open(self.log_round(round_num) / COREWAR_LOG.format(idx=idx)) as f:
53+
result_output = f.read()
4454

45-
# Get the last n lines which contain the scores (closer to original)
46-
relevant_lines = lines[-n:] if len(lines) >= n else lines
47-
relevant_lines = [l for l in relevant_lines if len(l.strip()) > 0]
48-
self.logger.debug(f"Relevant lines for scoring: {relevant_lines}")
55+
# Get the last n lines which contain the scores (closer to original)
56+
lines = result_output.strip().split("\n")
57+
relevant_lines = lines[-len(shift) * 2 :] if len(lines) >= len(shift) * 2 else lines
58+
relevant_lines = [l for l in relevant_lines if len(l.strip()) > 0]
4959

50-
# Go through each line; we assume score position is correlated with agent index
51-
for line in relevant_lines:
52-
match = re.search(r".*\sby\s.*\sscores\s(\d+)", line)
53-
if match:
54-
score = int(match.group(1))
55-
scores.append(score)
60+
# Go through each line; score position is correlated with agent index
61+
for i, line in enumerate(relevant_lines):
62+
match = re.search(r".*\sby\s.*\sscores\s(\d+)", line)
63+
if match:
64+
scores[shift[i].name] += int(match.group(1))
5665

57-
if scores:
58-
if len(scores) != len(agents):
59-
self.logger.error(f"Have {len(scores)} scores but {len(agents)} agents")
60-
stats.winner = agents[scores.index(max(scores))].name
61-
stats.scores = {agent.name: score for agent, score in zip(agents, scores)}
62-
else:
63-
self.logger.debug("No scores found, returning unknown")
64-
stats.winner = "unknown"
65-
stats.scores = {agent.name: 0 for agent in agents}
66+
# Last line corresponds to absolute number of wins
67+
last = relevant_lines[-1][len("Results:") :].strip()
68+
for i, w in enumerate(last.split()[:-1]): # NOTE: Omitting ties (last entry)
69+
wins[shift[i].name] += int(w)
6670

67-
for player, score in stats.scores.items():
68-
stats.player_stats[player].score = score
71+
if len(wins) != len(agents):
72+
# Should not happen
73+
self.logger.error(f"Have {len(wins)} wins but {len(agents)} agents")
74+
75+
# Bookkeeping
76+
stats.scores = {a.name: wins[a.name] for a in agents}
77+
for a in agents:
78+
stats.player_stats[a.name].score = wins[a.name]
79+
80+
# Determine overall winner by highest wins, then highest score
81+
max_wins = max(wins.values(), default=0)
82+
potential_winners = [name for name, w in wins.items() if w == max_wins]
83+
if len(potential_winners) == 1:
84+
stats.winner = potential_winners[0]
85+
else:
86+
# Tie-break by score
87+
max_score = -1
88+
winner = RESULT_TIE
89+
for name in potential_winners:
90+
if scores[name] > max_score:
91+
max_score = scores[name]
92+
winner = name
93+
elif scores[name] == max_score:
94+
winner = RESULT_TIE
95+
stats.winner = winner
6996

7097
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
7198
if self.submission not in agent.environment.execute("ls")["output"]:

0 commit comments

Comments
 (0)