Skip to content

RoboCode

Java-based robot programming game where robots battle in an arena.

Overview

RoboCode is a programming game where players develop tanks in Java to battle each other in a graphical arena.

Game Rules

  • Robots are programmed in Java
  • Battle in 2D arena
  • Scan for enemies, move, and fire
  • Destroy opponents to win

Configuration Example

game:
  name: RoboCode
  rounds: 10
  sims_per_round: 5
  timeout: 300

players:
  - name: Tank1
    model: gpt-4
  - name: Tank2
    model: claude-3

Resources

Implementation

codeclash.arenas.robocode.robocode.RoboCodeArena

RoboCodeArena(config, **kwargs)

Bases: CodeArena

Source code in codeclash/arenas/robocode/robocode.py
31
32
33
34
35
36
37
38
39
def __init__(self, config, **kwargs):
    super().__init__(config, **kwargs)
    self.run_cmd_round: str = "./robocode.sh"
    for arg, val in self.game_config.get("args", self.default_args).items():
        if isinstance(val, bool):
            if val:
                self.run_cmd_round += f" -{arg}"
        else:
            self.run_cmd_round += f" -{arg} {val}"

name class-attribute instance-attribute

name: str = 'RoboCode'

description class-attribute instance-attribute

description: str = f'Robocode (Tank Royale) is a programming game where your code is the tank: each turn your bot sends intents—speed plus body/gun/radar turn rates and firepower—based on the game state it perceives via radar.
Your program decides how to move, aim, and fire in a deterministic, turn-based arena to outlast other bots.
Your bot logic must be written in Java and located in the `robots/custom/` directory.
Keep the main bot class named `{str(RC_FILE)}`, but you can include additional Java files if you'd like.'

default_args class-attribute instance-attribute

default_args: dict = {'nodisplay': True, 'nosound': True}

submission class-attribute instance-attribute

submission: str = 'robots/custom/'

run_cmd_round instance-attribute

run_cmd_round: str = './robocode.sh'

execute_round

execute_round(agents: list[Player])
Source code in codeclash/arenas/robocode/robocode.py
 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
    def execute_round(self, agents: list[Player]):
        for agent in agents:
            # Copy the agent codebase into the game codebase and compile it
            for cmd in [
                f"mkdir -p robots/{agent.name}",
                f"cp -r /{agent.name}/robots/custom/* robots/{agent.name}/",
                f"find robots/{agent.name}/ -name '*.java' -exec sed -i 's/custom/{agent.name}/g' {{}} +",
                f'javac -cp "libs/robocode.jar" robots/{agent.name}/*.java',
            ]:
                self.environment.execute(cmd)

        # Create .battle file
        selected_robots = ",".join([f"{agent.name}.{RC_FILE.stem}*" for agent in agents])
        # Use timestamp for unique battle file name since rounds are managed by tournament
        battle_file = f"{self.game_id}-battle{int(time.time())}.battle"
        battle_content = f"""#Battle Properties
{self._get_battle_config()}
robocode.battle.selectedRobots={selected_robots}
"""
        create_file_in_container(self.environment, content=battle_content, dest_path=f"battles/{battle_file}")

        # Run battle with results output to file
        cmd = f"{self.run_cmd_round} -battle {battle_file}"
        self.logger.info(f"Running game: {cmd}")
        with ThreadPoolExecutor(5) as executor:
            # Submit all simulations to the thread pool
            futures = [
                executor.submit(self._run_single_simulation, agents, idx, cmd)
                for idx in range(self.game_config.get("sims_per_round", 100) // SIMS_PER_RUN)
            ]

            # Collect results as they complete
            for future in tqdm(as_completed(futures), total=len(futures)):
                future.result()

get_results

get_results(agents: list[Player], round_num: int, stats: RoundStats)
Source code in codeclash/arenas/robocode/robocode.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def get_results(self, agents: list[Player], round_num: int, stats: RoundStats):
    scores = defaultdict(int)
    for idx in range(self.game_config.get("sims_per_round", 100) // SIMS_PER_RUN):
        with open(self.log_round(round_num) / f"results_{idx}.txt") as f:
            result_output = f.read()
        lines = result_output.strip().split("\n")

        for line in lines:
            line = line.strip()
            if not re.match(r"^\d", line):
                continue
            match = re.search(r"(\d+)\S+\:\s(\S+)\s+(\d+)", line)
            if match:
                player = match.group(2).rsplit(".", 1)[0]
                scores[player] += int(match.group(3))

    stats.winner = max(scores, key=scores.get)
    stats.scores = scores
    for player, score in scores.items():
        stats.player_stats[player].score = score

validate_code

validate_code(agent: Player) -> tuple[bool, str | None]
Source code in codeclash/arenas/robocode/robocode.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
def validate_code(self, agent: Player) -> tuple[bool, str | None]:
    if "robots" not in agent.environment.execute("ls")["output"]:
        return False, "There should be a `robots/` directory"
    if "custom" not in agent.environment.execute("ls robots")["output"]:
        return False, "There should be a `robots/custom/` directory"
    if str(RC_FILE) not in agent.environment.execute("ls robots/custom")["output"]:
        return False, (
            f"There should be a `robots/custom/{RC_FILE}` file. "
            f"You can include additional files, but the primary tank logic must be in `robots/custom/{RC_FILE}`"
        )
    response = agent.environment.execute('javac -cp "libs/robocode.jar" robots/custom/*.java')
    if response["returncode"] != 0:
        return False, f"Compilation error:\n{response['output']}"
    if f"{RC_FILE.stem}.class" not in agent.environment.execute("ls robots/custom")["output"]:
        return False, f"`{RC_FILE.stem}.class` not found after compilation"
    return True, None