diff --git a/battlecode25/engine/container/runner.py b/battlecode25/engine/container/runner.py index 9330210..833fb02 100644 --- a/battlecode25/engine/container/runner.py +++ b/battlecode25/engine/container/runner.py @@ -167,13 +167,13 @@ def instrument_call(self): self.bytecode -= 1 if self.bytecode <= 0: self.error_method(f'Ran out of bytecode. Remaining bytecode: {self.bytecode}') - self.thread.wait() + self.pause() def multinstrument_call(self, n): self.bytecode -= n if self.bytecode <= 0: self.error_method(f'Ran out of bytecode. Remaining bytecode: {self.bytecode}') - self.thread.wait() + self.pause() def import_call(self, name, globals=None, locals=None, fromlist=(), level=0, caller='robot'): if not isinstance(name, str) or not (isinstance(fromlist, tuple) or fromlist is None): @@ -259,6 +259,9 @@ def run(self): self.thread.finished_event.wait() self.thread.finished_event.clear() + def pause(self): + self.thread.wait() + def kill(self): self.thread.kill() self.thread.join() diff --git a/battlecode25/engine/game/constants.py b/battlecode25/engine/game/constants.py index 00ae5b1..e417eab 100644 --- a/battlecode25/engine/game/constants.py +++ b/battlecode25/engine/game/constants.py @@ -28,6 +28,10 @@ class GameConstants: TOWER_BYTECODE_LIMIT = 20000 + # The maximum execution time that can be spent on a team in one match. If the total time spent executing a team's bots + # exceeds this limit, the team will immediately lose the game. Execution time is measured in ns. + MAX_TEAM_EXECUTION_TIME = 1200000000000 + # The maximum length of indicator strings that a player can associate with a robot. INDICATOR_STRING_MAX_LENGTH = 64 diff --git a/battlecode25/engine/game/game.py b/battlecode25/engine/game/game.py index 3d69122..8e7c77d 100644 --- a/battlecode25/engine/game/game.py +++ b/battlecode25/engine/game/game.py @@ -1,3 +1,5 @@ +import math +import time import random from enum import Enum from .robot import Robot @@ -19,8 +21,6 @@ from typing import Generator from .message import Message -import math - class Game: def __init__(self, code, initial_map: InitialMap, game_fb: GameFB, game_args): @@ -66,12 +66,20 @@ def __init__(self, code, initial_map: InitialMap, game_fb: GameFB, game_args): self.update_defense_towers(robot.team, new_type) def run_round(self): + def run_turn(robot: Robot): + start_time = time.time_ns() + robot.turn() + run_time = time.time_ns() - start_time + self.team_info.add_execution_time(robot.team, run_time) + if self.team_info.get_execution_time(robot.team) >= GameConstants.MAX_TEAM_EXECUTION_TIME: + self.resign(robot.team) + if self.running: self.round += 1 self.game_fb.start_round(self.round) self.update_resource_patterns() self.each_robot(lambda robot: robot.process_beginning_of_round()) - self.each_robot_update(lambda robot: robot.turn()) + self.each_robot_update(run_turn) self.serialize_team_info() self.team_info.process_end_of_round() self.game_fb.end_round() @@ -250,6 +258,9 @@ def set_winner_arbitrary(self): self.set_winner(Team.A if random.random() < 0.5 else Team.B, DominationFactor.WON_BY_DUBIOUS_REASONS) return True + def resign(self, team: Team): + self.set_winner(team.opponent(), DominationFactor.RESIGNATION) + def run_tiebreakers(self): if self.set_winner_if_more_area(): return if self.set_winner_if_more_allied_towers(): return @@ -584,12 +595,17 @@ def create_methods(self, rc: RobotController): 'upgrade_tower': rc.upgrade_tower, 'resign': rc.resign, 'disintegrate': rc.disintegrate, + 'yield_turn': (rc.yield_turn, 0), + 'get_bytecode_num': (rc.get_bytecode_num, 0), + 'get_bytecodes_left': (rc.get_bytecodes_left, 0), + 'get_time_elapsed': (rc.get_time_elapsed, 0), + 'get_time_left': (rc.get_time_left, 0), 'set_indicator_string': rc.set_indicator_string, 'set_indicator_dot': rc.set_indicator_dot, 'set_indicator_line': rc.set_indicator_line, - 'set_timeline_marker': rc.set_timeline_marker, + 'set_timeline_marker': rc.set_timeline_marker } - + def create_patterns(self): resource_pattern = [ [True, True, False, True, True], diff --git a/battlecode25/engine/game/play.py b/battlecode25/engine/game/play.py index 026384c..93a9cca 100644 --- a/battlecode25/engine/game/play.py +++ b/battlecode25/engine/game/play.py @@ -93,7 +93,7 @@ def run_game(args: RunGameArgs): b_wins += 1 game_fb.make_match_footer(game.winner, game.domination_factor, game.round) except Exception as e: - print("[server:error] An internal engine error has occurred. Please report this to the devs. This match has been terminated.") + print(f"[server:error] An internal engine error has occurred. Please report this to the devs. This match has been terminated : {e}") game.set_winner_arbitrary() game.stop() # Internal engine occurred, we have to throw away this replay diff --git a/battlecode25/engine/game/robot.py b/battlecode25/engine/game/robot.py index 2df2141..43af111 100644 --- a/battlecode25/engine/game/robot.py +++ b/battlecode25/engine/game/robot.py @@ -25,7 +25,6 @@ def __init__(self, game: Game, id, team: Team, type: UnitType, loc: MapLocation) self.paint = round(self.type.paint_capacity * GameConstants.INITIAL_ROBOT_PAINT_PERCENTAGE / 100) else: self.paint = GameConstants.INITIAL_TOWER_PAINT_AMOUNT - self.bytecodes_used = 0 self.rounds_alive = 0 self.action_cooldown = type.action_cooldown self.movement_cooldown = GameConstants.COOLDOWN_LIMIT @@ -52,12 +51,12 @@ def calc_paint_cooldown_multiplier(self): if paint_percent < 0.5: return 2 - 2 * paint_percent return 1 - + def add_action_cooldown(self, cooldown=-1): if cooldown == -1: cooldown = self.type.action_cooldown self.action_cooldown += round(cooldown * self.calc_paint_cooldown_multiplier()) - + def add_movement_cooldown(self): self.movement_cooldown += round(GameConstants.MOVEMENT_COOLDOWN * self.calc_paint_cooldown_multiplier()) @@ -68,7 +67,7 @@ def upgrade_tower(self): def log(self, msg): self.logs.append(msg) - + def error(self, msg): self.logs.append(msg) @@ -80,6 +79,15 @@ def animate(self, code, methods, debug=False): def kill(self): self.runner.kill() + def get_bytecode_limit(self): + return self.runner.bytecode_limit + + def get_bytecodes_left(self): + return self.runner.bytecode + + def get_bytecodes_used(self): + return max(self.runner.bytecode_limit - self.runner.bytecode, 0) + def turn(self): self.process_beginning_of_turn() self.logs.clear() @@ -104,7 +112,7 @@ def process_beginning_of_turn(self): self.action_cooldown = max(0, self.action_cooldown - GameConstants.COOLDOWNS_PER_TURN) self.movement_cooldown = max(0, self.movement_cooldown - GameConstants.COOLDOWNS_PER_TURN) self.game.game_fb.start_turn(self.id) - + def process_end_of_turn(self): loc_idx = self.game.loc_to_index(self.loc) paint_status = self.game.paint[loc_idx] @@ -135,7 +143,7 @@ def process_end_of_turn(self): if self.type.is_robot_type() and self.paint == 0: self.add_health(-GameConstants.NO_PAINT_DAMAGE) - self.game.game_fb.end_turn(self.id, self.health, self.paint, self.movement_cooldown, self.action_cooldown, self.bytecodes_used, self.loc) + self.game.game_fb.end_turn(self.id, self.health, self.paint, self.movement_cooldown, self.action_cooldown, self.get_bytecodes_used(), self.loc) self.rounds_alive += 1 def get_robot_info(self) -> RobotInfo: diff --git a/battlecode25/engine/game/robot_controller.py b/battlecode25/engine/game/robot_controller.py index 091e348..6397970 100644 --- a/battlecode25/engine/game/robot_controller.py +++ b/battlecode25/engine/game/robot_controller.py @@ -792,11 +792,28 @@ def set_timeline_marker(self, label: str, red: int, green: int, blue: int) -> No self.game.game_fb.add_timeline_marker(self.robot.team, label, red, green, blue) def resign(self) -> None: - self.game.set_winner(self.robot.team.opponent(), DominationFactor.RESIGNATION) + self.game.resign(self.robot.team) def disintegrate(self) -> None: self.game.destroy_robot(self.robot.id) - + + # CLOCK METHODS + + def yield_turn(self) -> None: + self.robot.runner.pause() + + def get_bytecode_num(self) -> int: + return self.robot.get_bytecodes_used() + + def get_bytecodes_left(self) -> int: + return self.robot.get_bytecodes_left() + + def get_time_elapsed(self) -> int: + return self.game.team_info.get_execution_time(self.robot.team) + + def get_time_left(self) -> int: + return max(GameConstants.MAX_TEAM_EXECUTION_TIME - self.get_time_elapsed(), 0) + class RobotError(Exception): """Raised for illegal robot inputs""" pass diff --git a/battlecode25/engine/game/team_info.py b/battlecode25/engine/game/team_info.py index 2fc000e..6c6aee3 100644 --- a/battlecode25/engine/game/team_info.py +++ b/battlecode25/engine/game/team_info.py @@ -10,6 +10,7 @@ def __init__(self, game_world): self.old_coin_counts = [0] * 2 self.unit_counts = [0] * 2 self.defense_damage_increase = [0] * 2 + self.execution_time = [0] * 2 # ***** GETTER METHODS ***** @@ -33,6 +34,9 @@ def get_unit_count(self, team): def get_defense_damage_increase(self, team): return self.defense_damage_increase[team.value] + + def get_execution_time(self, team): + return self.execution_time[team.value] # ***** UPDATE METHODS ***** @@ -56,3 +60,6 @@ def add_unit_count(self, team, amount): def add_defense_damage_increase(self, team, amount): self.defense_damage_increase[team.value] += amount + + def add_execution_time(self, team, amount): + self.execution_time[team.value] += amount diff --git a/battlecode25/stubs.py b/battlecode25/stubs.py index 4263316..bb452f3 100644 --- a/battlecode25/stubs.py +++ b/battlecode25/stubs.py @@ -476,3 +476,37 @@ def disintegrate() -> None: Destroys this robot. """ pass + +# CLOCK METHODS + +def yield_turn() -> None: + """ + Ends the processing of this robot during the current round. + """ + pass + +def get_bytecode_num() -> int: + """ + Returns the number of bytecodes the current robot has executed since the + beginning of the current round. + """ + pass + +def get_bytecodes_left() -> int: + """ + Returns the number of bytecodes this robot has left in this round. + """ + pass + +def get_time_elapsed() -> int: + """ + Returns the total amount of time, in nanoseconds, that this team's robots have collectively spent executing + since the beginning of the match. + """ + pass + +def get_time_left() -> int: + """ + Returns the total amount of execution time, in nanoseconds, left this team has before they timeout + """ + pass