diff --git a/engine/src/crossplay_python/battlecode26/classes.py b/engine/src/crossplay_python/battlecode26/classes.py index fa683e9a..5c24e589 100644 --- a/engine/src/crossplay_python/battlecode26/classes.py +++ b/engine/src/crossplay_python/battlecode26/classes.py @@ -216,18 +216,6 @@ def __repr__(self) -> str: _trap_to_index = {trap: index for index, trap in enumerate(_trap_order)} -class MapInfo: - def __init__(self, location: MapLocation, is_passable: bool, is_wall: bool, is_dirt: bool, - cheese_amount: int, trap: TrapType, has_cheese_mine: bool): - self.location = location - self.is_passable = is_passable - self.is_wall = is_wall - self.is_dirt = is_dirt - self.cheese_amount = cheese_amount - self.trap = trap - self.has_cheese_mine = has_cheese_mine - - class RobotInfo: def __init__(self, id: int, team: Team, unit_type: UnitType, health: int, location: MapLocation, direction: Direction, @@ -243,6 +231,19 @@ def __init__(self, id: int, team: Team, unit_type: UnitType, health: int, self.carrying_robot = carrying_robot +class MapInfo: + def __init__(self, location: MapLocation, is_passable: bool, flying_robot: RobotInfo, is_wall: bool, + is_dirt: bool, cheese_amount: int, trap: TrapType, has_cheese_mine: bool): + self.location = location + self.is_passable = is_passable + self.flying_robot = flying_robot + self.is_wall = is_wall + self.is_dirt = is_dirt + self.cheese_amount = cheese_amount + self.trap = trap + self.has_cheese_mine = has_cheese_mine + + class Message: def __init__(self, message_bytes: int, sender_id: int, round: int, source_loc: MapLocation): self.bytes = message_bytes diff --git a/engine/src/crossplay_python/battlecode26/crossplay.py b/engine/src/crossplay_python/battlecode26/crossplay.py index c23ba31c..e15e3635 100644 --- a/engine/src/crossplay_python/battlecode26/crossplay.py +++ b/engine/src/crossplay_python/battlecode26/crossplay.py @@ -23,109 +23,112 @@ def __init__(self, message): class CrossPlayMethod(Enum): INVALID = 0 END_TURN = 1 - RC_GET_ROUND_NUM = 2 - RC_GET_MAP_WIDTH = 3 - RC_GET_MAP_HEIGHT = 4 - RC_IS_COOPERATION = 5 - RC_GET_ID = 6 - RC_GET_TEAM = 7 - RC_GET_LOCATION = 8 - RC_GET_ALL_PART_LOCATIONS = 9 - RC_GET_DIRECTION = 10 - RC_GET_HEALTH = 11 - RC_GET_RAW_CHEESE = 12 - RC_GET_GLOBAL_CHEESE = 13 - RC_GET_ALL_CHEESE = 14 - RC_GET_DIRT = 15 - RC_GET_TYPE = 16 - RC_GET_CARRYING = 17 - RC_IS_BEING_THROWN = 18 - RC_IS_BEING_CARRIED = 19 - RC_ON_THE_MAP = 20 - RC_CAN_SENSE_LOCATION = 21 - RC_IS_LOCATION_OCCUPIED = 22 - RC_CAN_SENSE_ROBOT_AT_LOCATION = 23 - RC_SENSE_ROBOT_AT_LOCATION = 24 - RC_CAN_SENSE_ROBOT = 25 - RC_SENSE_ROBOT = 26 - RC_SENSE_NEARBY_ROBOTS = 27 - RC_SENSE_NEARBY_ROBOTS__INT = 28 - RC_SENSE_NEARBY_ROBOTS__INT_TEAM = 29 - RC_SENSE_NEARBY_ROBOTS__LOC_INT_TEAM = 30 - RC_SENSE_PASSABILITY = 31 - RC_SENSE_MAP_INFO = 32 - RC_SENSE_NEARBY_MAP_INFOS = 33 - RC_SENSE_NEARBY_MAP_INFOS__INT = 34 - RC_SENSE_NEARBY_MAP_INFOS__LOC = 35 - RC_SENSE_NEARBY_MAP_INFOS__LOC_INT = 36 - RC_ADJACENT_LOCATION = 37 - RC_GET_ALL_LOCATIONS_WITHIN_RADIUS_SQUARED = 38 - RC_IS_ACTION_READY = 39 - RC_GET_ACTION_COOLDOWN_TURNS = 40 - RC_IS_MOVEMENT_READY = 41 - RC_IS_TURNING_READY = 42 - RC_GET_MOVEMENT_COOLDOWN_TURNS = 43 - RC_GET_TURNING_COOLDOWN_TURNS = 44 - RC_CAN_MOVE_FORWARD = 45 - RC_CAN_MOVE = 46 - RC_MOVE_FORWARD = 47 - RC_MOVE = 48 - RC_CAN_TURN = 49 - RC_TURN = 50 - RC_GET_CURRENT_RAT_COST = 51 - RC_CAN_BUILD_RAT = 52 - RC_BUILD_RAT = 53 - RC_CAN_BECOME_RAT_KING = 54 - RC_BECOME_RAT_KING = 55 - RC_CAN_PLACE_DIRT = 56 - RC_PLACE_DIRT = 57 - RC_CAN_REMOVE_DIRT = 58 - RC_REMOVE_DIRT = 59 - RC_CAN_PLACE_RAT_TRAP = 60 - RC_PLACE_RAT_TRAP = 61 - RC_CAN_REMOVE_RAT_TRAP = 62 - RC_REMOVE_RAT_TRAP = 63 - RC_CAN_PLACE_CAT_TRAP = 64 - RC_PLACE_CAT_TRAP = 65 - RC_CAN_REMOVE_CAT_TRAP = 66 - RC_REMOVE_CAT_TRAP = 67 - RC_CAN_PICK_UP_CHEESE = 68 - RC_PICK_UP_CHEESE = 69 - RC_PICK_UP_CHEESE__LOC_INT = 70 - RC_CAN_ATTACK = 71 - RC_CAN_ATTACK__LOC_INT = 72 - RC_ATTACK = 73 - RC_ATTACK__LOC_INT = 74 - RC_SQUEAK = 75 - RC_READ_SQUEAKS = 76 - RC_WRITE_SHARED_ARRAY = 77 - RC_READ_SHARED_ARRAY = 78 - RC_CAN_TRANSFER_CHEESE = 79 - RC_TRANSFER_CHEESE = 80 - RC_THROW_RAT = 81 - RC_CAN_THROW_RAT = 82 - RC_DROP_RAT = 83 - RC_CAN_DROP_RAT = 84 - RC_CAN_CARRY_RAT = 85 - RC_CARRY_RAT = 86 - RC_DISINTEGRATE = 87 - RC_RESIGN = 88 - RC_SET_INDICATOR_STRING = 89 - RC_SET_INDICATOR_DOT = 90 - RC_SET_INDICATOR_LINE = 91 - RC_SET_TIMELINE_MARKER = 92 - RC_CAN_TURN__DIR = 93 - ML_DISTANCE_SQUARED_TO = 94 - ML_BOTTOM_LEFT_DISTANCE_SQUARED_TO = 95 - ML_IS_WITHIN_DISTANCE_SQUARED = 96 - ML_IS_WITHIN_DISTANCE_SQUARED__LOC_INT_DIR_DOUBLE = 97 - ML_IS_WITHIN_DISTANCE_SQUARED__LOC_INT_DIR_DOUBLE_BOOLEAN = 98 - ML_IS_ADJACENT_TO = 99 - ML_DIRECTION_TO = 100 - UT_GET_ALL_TYPE_LOCATIONS = 101 - LOG = 102 - THROW_GAME_ACTION_EXCEPTION = 103 - THROW_EXCEPTION = 104 + ML_DISTANCE_SQUARED_TO = 2 + ML_BOTTOM_LEFT_DISTANCE_SQUARED_TO = 3 + ML_IS_WITHIN_DISTANCE_SQUARED = 4 + ML_IS_WITHIN_DISTANCE_SQUARED__LOC_INT_DIR_DOUBLE = 5 + ML_IS_WITHIN_DISTANCE_SQUARED__LOC_INT_DIR_DOUBLE_BOOLEAN = 6 + ML_IS_ADJACENT_TO = 7 + ML_DIRECTION_TO = 8 + UT_GET_ALL_TYPE_LOCATIONS = 9 + LOG = 10 + THROW_GAME_ACTION_EXCEPTION = 11 + THROW_EXCEPTION = 12 + RC_GET_ROUND_NUM = 13 + RC_GET_MAP_WIDTH = 14 + RC_GET_MAP_HEIGHT = 15 + RC_IS_COOPERATION = 16 + RC_GET_ID = 17 + RC_GET_TEAM = 18 + RC_GET_LOCATION = 19 + RC_GET_ALL_PART_LOCATIONS = 20 + RC_GET_DIRECTION = 21 + RC_GET_HEALTH = 22 + RC_GET_RAW_CHEESE = 23 + RC_GET_GLOBAL_CHEESE = 24 + RC_GET_ALL_CHEESE = 25 + RC_GET_DIRT = 26 + RC_GET_TYPE = 27 + RC_GET_CARRYING = 28 + RC_IS_BEING_THROWN = 29 + RC_IS_BEING_CARRIED = 30 + RC_ON_THE_MAP = 31 + RC_CAN_SENSE_LOCATION = 32 + RC_IS_LOCATION_OCCUPIED = 33 + RC_CAN_SENSE_ROBOT_AT_LOCATION = 34 + RC_SENSE_ROBOT_AT_LOCATION = 35 + RC_CAN_SENSE_ROBOT = 36 + RC_SENSE_ROBOT = 37 + RC_SENSE_NEARBY_ROBOTS = 38 + RC_SENSE_NEARBY_ROBOTS__INT = 39 + RC_SENSE_NEARBY_ROBOTS__INT_TEAM = 40 + RC_SENSE_NEARBY_ROBOTS__LOC_INT_TEAM = 41 + RC_SENSE_PASSABILITY = 42 + RC_SENSE_MAP_INFO = 43 + RC_SENSE_NEARBY_MAP_INFOS = 44 + RC_SENSE_NEARBY_MAP_INFOS__INT = 45 + RC_SENSE_NEARBY_MAP_INFOS__LOC = 46 + RC_SENSE_NEARBY_MAP_INFOS__LOC_INT = 47 + RC_ADJACENT_LOCATION = 48 + RC_GET_ALL_LOCATIONS_WITHIN_RADIUS_SQUARED = 49 + RC_IS_ACTION_READY = 50 + RC_GET_ACTION_COOLDOWN_TURNS = 51 + RC_IS_MOVEMENT_READY = 52 + RC_IS_TURNING_READY = 53 + RC_GET_MOVEMENT_COOLDOWN_TURNS = 54 + RC_GET_TURNING_COOLDOWN_TURNS = 55 + RC_CAN_MOVE_FORWARD = 56 + RC_CAN_MOVE = 57 + RC_MOVE_FORWARD = 58 + RC_MOVE = 59 + RC_CAN_TURN = 60 + RC_TURN = 61 + RC_GET_CURRENT_RAT_COST = 62 + RC_CAN_BUILD_RAT = 63 + RC_BUILD_RAT = 64 + RC_CAN_BECOME_RAT_KING = 65 + RC_BECOME_RAT_KING = 66 + RC_CAN_PLACE_DIRT = 67 + RC_PLACE_DIRT = 68 + RC_CAN_REMOVE_DIRT = 69 + RC_REMOVE_DIRT = 70 + RC_CAN_PLACE_RAT_TRAP = 71 + RC_PLACE_RAT_TRAP = 72 + RC_CAN_REMOVE_RAT_TRAP = 73 + RC_REMOVE_RAT_TRAP = 74 + RC_CAN_PLACE_CAT_TRAP = 75 + RC_PLACE_CAT_TRAP = 76 + RC_CAN_REMOVE_CAT_TRAP = 77 + RC_REMOVE_CAT_TRAP = 78 + RC_CAN_PICK_UP_CHEESE = 79 + RC_PICK_UP_CHEESE = 80 + RC_PICK_UP_CHEESE__LOC_INT = 81 + RC_CAN_ATTACK = 82 + RC_CAN_ATTACK__LOC_INT = 83 + RC_ATTACK = 84 + RC_ATTACK__LOC_INT = 85 + RC_SQUEAK = 86 + RC_READ_SQUEAKS = 87 + RC_WRITE_SHARED_ARRAY = 88 + RC_READ_SHARED_ARRAY = 89 + RC_CAN_TRANSFER_CHEESE = 90 + RC_TRANSFER_CHEESE = 91 + RC_THROW_RAT = 92 + RC_CAN_THROW_RAT = 93 + RC_DROP_RAT = 94 + RC_CAN_DROP_RAT = 95 + RC_CAN_CARRY_RAT = 96 + RC_CARRY_RAT = 97 + RC_DISINTEGRATE = 98 + RC_RESIGN = 99 + RC_SET_INDICATOR_STRING = 100 + RC_SET_INDICATOR_DOT = 101 + RC_SET_INDICATOR_LINE = 102 + RC_SET_TIMELINE_MARKER = 103 + RC_CAN_TURN__DIR = 104 + RC_GET_BACKSTABBING_TEAM = 105 + RC_GET_NUMBER_RAT_TRAPS = 106 + RC_GET_NUMBER_CAT_TRAPS = 107 class CrossPlayObjectType(Enum): @@ -318,6 +321,7 @@ def parse(json): return MapInfo( parse(json["loc"]), json["pass"], + parse(json["fly"]), json["wall"], json["dirt"], json["ch"], diff --git a/engine/src/crossplay_python/battlecode26/wrappers.py b/engine/src/crossplay_python/battlecode26/wrappers.py index 6cda478c..b3ca8fc7 100644 --- a/engine/src/crossplay_python/battlecode26/wrappers.py +++ b/engine/src/crossplay_python/battlecode26/wrappers.py @@ -149,7 +149,11 @@ def get_all_locations_within_radius_squared(center: MapLocation, radius_squared: @staticmethod def get_all_part_locations() -> list[MapLocation]: return _wait(_m.RC_GET_ALL_PART_LOCATIONS) - + + @staticmethod + def get_backstabbing_team() -> Team: + return _wait(_m.RC_GET_BACKSTABBING_TEAM) + @staticmethod def get_carrying() -> RobotInfo: return _wait(_m.RC_GET_CARRYING) @@ -185,7 +189,15 @@ def get_location() -> MapLocation: @staticmethod def get_movement_cooldown_turns() -> int: return _wait(_m.RC_GET_MOVEMENT_COOLDOWN_TURNS) - + + @staticmethod + def get_number_rat_traps() -> int: + return _wait(_m.RC_GET_NUMBER_RAT_TRAPS) + + @staticmethod + def get_number_cat_traps() -> int: + return _wait(_m.RC_GET_NUMBER_CAT_TRAPS) + @staticmethod def get_raw_cheese() -> int: return _wait(_m.RC_GET_RAW_CHEESE) diff --git a/engine/src/crossplay_python/python_docs.md b/engine/src/crossplay_python/python_docs.md index 89b76e5c..69cbde2f 100644 --- a/engine/src/crossplay_python/python_docs.md +++ b/engine/src/crossplay_python/python_docs.md @@ -1,5 +1,5 @@ # Battlecode 2026 Python Documentation -v1.2.1 +v1.2.2 ## Getting Started @@ -159,6 +159,9 @@ class RobotController: def get_all_part_locations() -> list[MapLocation]: pass + def get_backstabbing_team() -> Team: + pass + def get_carrying() -> RobotInfo: pass @@ -186,6 +189,12 @@ class RobotController: def get_movement_cooldown_turns() -> int: pass + def get_number_rat_traps() -> int: + pass + + def get_number_cat_traps() -> int: + pass + def get_raw_cheese() -> int: pass @@ -488,6 +497,7 @@ class MapInfo: Fields: - location: MapLocation - is_passable: bool + - flying_robot: RobotInfo - is_wall: bool - is_dirt: bool - cheese_amount: int diff --git a/engine/src/crossplay_python/setup.py b/engine/src/crossplay_python/setup.py index a78bd0da..38e82569 100644 --- a/engine/src/crossplay_python/setup.py +++ b/engine/src/crossplay_python/setup.py @@ -26,5 +26,5 @@ ], python_requires='>=3.12, <3.13', zip_safe=False, - version='1.2.1', + version='1.2.2', ) diff --git a/engine/src/main/battlecode/common/MapInfo.java b/engine/src/main/battlecode/common/MapInfo.java index 333fddc4..7de0495f 100644 --- a/engine/src/main/battlecode/common/MapInfo.java +++ b/engine/src/main/battlecode/common/MapInfo.java @@ -6,6 +6,8 @@ public class MapInfo { private boolean isPassable; + private RobotInfo flyingRobot; + private boolean isWall; private boolean isDirt; @@ -17,7 +19,7 @@ public class MapInfo { private boolean hasCheeseMine; - public MapInfo(MapLocation loc, boolean isPassable, boolean isWall, boolean isDirt, int cheeseAmount, TrapType trap, boolean hasCheeseMine) { + public MapInfo(MapLocation loc, boolean isPassable, RobotInfo flyingRobot, boolean isWall, boolean isDirt, int cheeseAmount, TrapType trap, boolean hasCheeseMine) { if (loc == null) { throw new IllegalArgumentException("MapLocation in MapInfo constructor cannot be null"); } else if (trap == null) { @@ -26,6 +28,7 @@ public MapInfo(MapLocation loc, boolean isPassable, boolean isWall, boolean isDi this.loc = loc; this.isPassable = isPassable; + this.flyingRobot = flyingRobot; this.isWall = isWall; this.isDirt = isDirt; this.cheeseAmount = cheeseAmount; @@ -44,6 +47,17 @@ public boolean isPassable() { return isPassable; } + /** + * Returns info of a flying robot at this location + * + * @return RobotInfo for the flying robot at this location (or null if no flying robot is present) + * + * @battlecode.doc.costlymethod + */ + public RobotInfo flyingRobot() { + return flyingRobot; + } + /** * Returns if this square is a wall. * diff --git a/engine/src/main/battlecode/common/RobotController.java b/engine/src/main/battlecode/common/RobotController.java index ab46c2c0..65e2dc51 100644 --- a/engine/src/main/battlecode/common/RobotController.java +++ b/engine/src/main/battlecode/common/RobotController.java @@ -53,6 +53,33 @@ public interface RobotController { */ boolean isCooperation(); + /** + * Returns the backstabbing team, or null if still in cooperation mode. + * + * @return the team that performed the backstab, or null if still in cooperation mode. + * + * @battlecode.doc.costlymethod + */ + Team getBackstabbingTeam(); + + /** + * Returns the number of active cat traps for the team. + * + * @return the number of active cat traps this team has. + * + * @battlecode.doc.costlymethod + */ + int getNumberCatTraps(); + + /** + * Returns the number of active rat traps for the team. + * + * @return the number of active rat traps this team has. + * + * @battlecode.doc.costlymethod + */ + int getNumberRatTraps(); + // ********************************* // ****** UNIT QUERY METHODS ******* // ********************************* @@ -357,7 +384,7 @@ public interface RobotController { /** * Senses the map info at a location. - * MapInfo includes walls, dirt, cheese mines, and cheese. + * MapInfo includes passability, flying robots, walls, dirt, traps, cheese mines, and cheese. * * @param loc to sense map at * @return MapInfo describing map at location @@ -369,7 +396,7 @@ public interface RobotController { /** * Return map info for all senseable locations. - * MapInfo includes walls, dirt, cheese mines, and cheese. + * MapInfo includes passability, flying robots, walls, dirt, traps, cheese mines, and cheese. * * @return MapInfo about all locations within vision radius * @@ -382,7 +409,7 @@ public interface RobotController { * If radiusSquared is larger than the robot's vision radius, uses the robot's * vision radius instead. If -1 is passed, all locations within vision radius * are returned. - * MapInfo includes walls, dirt, cheese mines, and cheese. + * MapInfo includes passability, flying robots, walls, dirt, traps, cheese mines, and cheese. * * @param radiusSquared the squared radius of all locations to be returned * @return MapInfo about all locations within vision radius @@ -395,7 +422,7 @@ public interface RobotController { /** * Return map info for all senseable locations within vision radius of a center * location. - * MapInfo includes walls, dirt, cheese mines, and cheese. + * MapInfo includes passability, flying robots, walls, dirt, traps, cheese mines, and cheese. * * @param center the center of the search area * @return MapInfo about all locations within vision radius @@ -411,7 +438,7 @@ public interface RobotController { * If radiusSquared is larger than the robot's vision radius, uses the robot's * vision radius instead. If -1 is passed, all locations within vision radius * are returned. - * MapInfo includes walls, dirt, cheese mines, and cheese. + * MapInfo includes passability, flying robots, walls, dirt, traps, cheese mines, and cheese. * * @param center the center of the search area * @param radiusSquared the squared radius of all locations to be returned @@ -739,7 +766,7 @@ public interface RobotController { * Tests whether this robot can place a cat trap at the given location. * * @param loc the location to place cat trap - * @return whether the robot can remove a cat trap at the given location + * @return whether the robot can place a cat trap at the given location * * @battlecode.doc.costlymethod */ diff --git a/engine/src/main/battlecode/crossplay/CrossPlay.java b/engine/src/main/battlecode/crossplay/CrossPlay.java index db3df6d0..5dc017c4 100644 --- a/engine/src/main/battlecode/crossplay/CrossPlay.java +++ b/engine/src/main/battlecode/crossplay/CrossPlay.java @@ -736,6 +736,22 @@ private JsonNode processMessage(CrossPlayMessage message, boolean init) throws G return nodeFactory.nullNode(); } + case RC_GET_BACKSTABBING_TEAM: { + checkParams(message, 0); + Team backstabTeam = this.processingRobot.getBackstabbingTeam(); + return makeTeamNode(nodeFactory, backstabTeam); + } + + case RC_GET_NUMBER_RAT_TRAPS: { + checkParams(message, 0); + return nodeFactory.numberNode(this.processingRobot.getNumberRatTraps()); + } + + case RC_GET_NUMBER_CAT_TRAPS: { + checkParams(message, 0); + return nodeFactory.numberNode(this.processingRobot.getNumberCatTraps()); + } + case ML_BOTTOM_LEFT_DISTANCE_SQUARED_TO: { checkParams(message, 2); MapLocation loc1 = parseLocNode(message.params().get(0)); diff --git a/engine/src/main/battlecode/crossplay/CrossPlayHelpers.java b/engine/src/main/battlecode/crossplay/CrossPlayHelpers.java index 92da0379..cffd4367 100644 --- a/engine/src/main/battlecode/crossplay/CrossPlayHelpers.java +++ b/engine/src/main/battlecode/crossplay/CrossPlayHelpers.java @@ -176,6 +176,7 @@ public static JsonNode makeMapInfoNode(JsonNodeFactory nodeFactory, MapInfo mapI mapNode.put("type", MAP_INFO.ordinal()); mapNode.set("loc", makeLocNode(nodeFactory, mapInfo.getMapLocation())); mapNode.put("pass", mapInfo.isPassable()); + mapNode.set("fly", makeRobotInfoNode(nodeFactory, mapInfo.flyingRobot())); mapNode.put("wall", mapInfo.isWall()); mapNode.put("dirt", mapInfo.isDirt()); mapNode.set("trap", makeTrapTypeNode(nodeFactory, mapInfo.getTrap())); @@ -191,12 +192,13 @@ public static MapInfo parseMapInfoNode(JsonNode node) { MapLocation loc = parseLocNode(node.get("loc")); boolean passable = node.get("pass").asBoolean(); + RobotInfo flyingRobot = parseRobotInfoNode(node.get("fly")); boolean wall = node.get("wall").asBoolean(); boolean dirt = node.get("dirt").asBoolean(); TrapType trap = trapTypes[node.get("trap").asInt()]; boolean hasCheeseMine = node.get("cm").asBoolean(); int cheeseAmount = node.get("ch").asInt(); - return new MapInfo(loc, passable, wall, dirt, cheeseAmount, trap, hasCheeseMine); + return new MapInfo(loc, passable, flyingRobot, wall, dirt, cheeseAmount, trap, hasCheeseMine); } public static JsonNode makeMessageNode(JsonNodeFactory nodeFactory, Message message) { diff --git a/engine/src/main/battlecode/crossplay/CrossPlayMethod.java b/engine/src/main/battlecode/crossplay/CrossPlayMethod.java index 8e082207..683915a0 100644 --- a/engine/src/main/battlecode/crossplay/CrossPlayMethod.java +++ b/engine/src/main/battlecode/crossplay/CrossPlayMethod.java @@ -8,6 +8,17 @@ public enum CrossPlayMethod { INVALID, END_TURN, + ML_DISTANCE_SQUARED_TO, + ML_BOTTOM_LEFT_DISTANCE_SQUARED_TO, + ML_IS_WITHIN_DISTANCE_SQUARED, + ML_IS_WITHIN_DISTANCE_SQUARED__LOC_INT_DIR_DOUBLE, + ML_IS_WITHIN_DISTANCE_SQUARED__LOC_INT_DIR_DOUBLE_BOOLEAN, + ML_IS_ADJACENT_TO, + ML_DIRECTION_TO, + UT_GET_ALL_TYPE_LOCATIONS, + LOG, + THROW_GAME_ACTION_EXCEPTION, + THROW_EXCEPTION, RC_GET_ROUND_NUM, RC_GET_MAP_WIDTH, RC_GET_MAP_HEIGHT, @@ -100,17 +111,9 @@ public enum CrossPlayMethod { RC_SET_INDICATOR_LINE, RC_SET_TIMELINE_MARKER, RC_CAN_TURN__DIR, - ML_DISTANCE_SQUARED_TO, - ML_BOTTOM_LEFT_DISTANCE_SQUARED_TO, - ML_IS_WITHIN_DISTANCE_SQUARED, - ML_IS_WITHIN_DISTANCE_SQUARED__LOC_INT_DIR_DOUBLE, - ML_IS_WITHIN_DISTANCE_SQUARED__LOC_INT_DIR_DOUBLE_BOOLEAN, - ML_IS_ADJACENT_TO, - ML_DIRECTION_TO, - UT_GET_ALL_TYPE_LOCATIONS, - LOG, - THROW_GAME_ACTION_EXCEPTION, - THROW_EXCEPTION, + RC_GET_BACKSTABBING_TEAM, + RC_GET_NUMBER_RAT_TRAPS, + RC_GET_NUMBER_CAT_TRAPS, ; public static final CrossPlayMethod[] values = values(); diff --git a/engine/src/main/battlecode/world/GameWorld.java b/engine/src/main/battlecode/world/GameWorld.java index 38c294b4..b59c50de 100644 --- a/engine/src/main/battlecode/world/GameWorld.java +++ b/engine/src/main/battlecode/world/GameWorld.java @@ -35,6 +35,7 @@ public class GameWorld { private int[] cheeseAmounts; private InternalRobot[][] robots; + private InternalRobot[][] flyingRobots; private Trap[][] trapLocations; private ArrayList[] trapTriggers; private HashMap trapCounts; // maps trap type to counts for each team @@ -143,6 +144,7 @@ public GameWorld(LiveMap gm, RobotControlProvider cp, GameMaker.MatchMaker match this.trapLocations = new Trap[2][numSquares]; // We guarantee that no maps will contain traps at t = 0 this.robots = new InternalRobot[width][height]; // if represented in cartesian, should be height-width, but this // should allow us to index x-y + this.flyingRobots = new InternalRobot[width][height]; this.hasRunCheeseMinesThisRound = false; this.currentRound = 0; this.idGenerator = new IDGenerator(gm.getSeed()); @@ -403,6 +405,10 @@ public boolean isCooperation() { return this.isCooperation; } + public Team getBackStabbingTeam(){ + return this.backstabber; + } + public int getRoundsSinceBackstab() { if (this.isCooperation) { return 0; @@ -417,9 +423,11 @@ public boolean catTrapsAllowed(Team team) { } public void backstab(Team backstabber) { - this.isCooperation = false; - this.backstabRound = this.currentRound; - this.backstabber = backstabber; + if (this.isCooperation){ + this.isCooperation = false; + this.backstabRound = this.currentRound; + this.backstabber = backstabber; + } } public boolean getWall(MapLocation loc) { @@ -469,7 +477,8 @@ public int getNumCats() { public boolean isPassable(MapLocation loc) { return !(this.walls[locationToIndex(loc)] - || this.dirt[locationToIndex(loc)]); + || this.dirt[locationToIndex(loc)] + || (this.getFlyingRobot(loc) != null)); } /** @@ -623,7 +632,7 @@ public void triggerTrap(Trap trap, InternalRobot robot) { robot.setMovementCooldownTurns(type.stunTime); - if (type == TrapType.CAT_TRAP && robot.getType().isCatType()) { + if (type == TrapType.CAT_TRAP && robot.getType().isCatType() && robot.getHealth() > 0) { this.teamInfo.addDamageToCats(trap.getTeam(), Math.min(type.damage, robot.getHealth())); } @@ -649,6 +658,10 @@ public InternalRobot getRobot(MapLocation loc) { return this.robots[loc.x - this.gameMap.getOrigin().x][loc.y - this.gameMap.getOrigin().y]; } + public InternalRobot getFlyingRobot(MapLocation loc) { + return this.flyingRobots[loc.x - this.gameMap.getOrigin().x][loc.y - this.gameMap.getOrigin().y]; + } + public void moveRobot(MapLocation start, MapLocation end) { addRobot(end, getRobot(start)); removeRobot(start); @@ -658,10 +671,18 @@ public void addRobot(MapLocation loc, InternalRobot robot) { this.robots[loc.x - this.gameMap.getOrigin().x][loc.y - this.gameMap.getOrigin().y] = robot; } + public void addFlyingRobot(MapLocation loc, InternalRobot robot) { + this.flyingRobots[loc.x - this.gameMap.getOrigin().x][loc.y - this.gameMap.getOrigin().y] = robot; + } + public void removeRobot(MapLocation loc) { this.robots[loc.x - this.gameMap.getOrigin().x][loc.y - this.gameMap.getOrigin().y] = null; } + public void removeFlyingRobot(MapLocation loc) { + this.flyingRobots[loc.x - this.gameMap.getOrigin().x][loc.y - this.gameMap.getOrigin().y] = null; + } + public InternalRobot[] getAllRobotsWithinRadiusSquared(MapLocation center, int radiusSquared, int chirality) { return getAllRobotsWithinRadiusSquared(center, radiusSquared, null, chirality); } @@ -1118,6 +1139,7 @@ public void destroyRobot(int id, boolean fromException, boolean fromDamage) { for (MapLocation robotLoc : robot.getAllPartLocations()) { removeRobot(robotLoc); + removeFlyingRobot(robotLoc); } if (robot.isCarryingRobot()) { diff --git a/engine/src/main/battlecode/world/InternalRobot.java b/engine/src/main/battlecode/world/InternalRobot.java index a4f42386..f43a9f3d 100644 --- a/engine/src/main/battlecode/world/InternalRobot.java +++ b/engine/src/main/battlecode/world/InternalRobot.java @@ -724,8 +724,8 @@ private void getThrown(Direction dir) { this.setInternalLocationOnly(this.getLocation()); - this.travelFlying(true); this.travelFlying(false); + this.travelFlying(true); } public void getDropped(MapLocation loc) { @@ -761,6 +761,7 @@ public void hitGround() { this.gameWorld.getMatchMaker().addDamageAction(this.ID, damage); this.gameWorld.getMatchMaker().addRatNapAction(this.getID()); + this.gameWorld.removeFlyingRobot(this.location); if (this.health > 0) { this.gameWorld.addRobot(this.location, this); @@ -785,10 +786,16 @@ public void hitTarget(boolean isSecondMove) { InternalRobot robot = this.gameWorld.getRobot(this.getLocation().add(this.thrownDir)); robot.addHealth(-damage); } + else if (this.gameWorld.getFlyingRobot(this.getLocation().add(this.thrownDir)) != null){ + InternalRobot robot = this.gameWorld.getFlyingRobot(this.getLocation().add(this.thrownDir)); + robot.remainingThrowDuration = 1; // force other robot to drop to ground as well on next turn + } this.thrownDir = null; this.remainingThrowDuration = 0; this.addHealth(-damage); + + this.gameWorld.removeFlyingRobot(this.location); if (this.health > 0) { this.gameWorld.addRobot(this.location, this); this.controller.processTrapsAtLocation(this.location); @@ -818,6 +825,7 @@ public void travelFlying(boolean isSecondMove) { } else if (this.gameWorld.getRobot(newLoc) != null && this.gameWorld.getRobot(newLoc).getType() == UnitType.CAT) { // cat feeding! + this.gameWorld.removeFlyingRobot(this.location); this.addHealth(-this.getHealth()); // rat dies :( // put cat to sleep this.gameWorld.getRobot(newLoc).sleepTimeRemaining = GameConstants.CAT_SLEEP_TIME; @@ -825,6 +833,9 @@ public void travelFlying(boolean isSecondMove) { } else if (this.gameWorld.getRobot(newLoc) != null || !this.gameWorld.isPassable(newLoc)) { this.hitTarget(isSecondMove); return; + } else{ + this.gameWorld.removeFlyingRobot(this.location); + this.gameWorld.addFlyingRobot(newLoc, this); } this.setInternalLocationOnly(newLoc); @@ -1182,14 +1193,15 @@ public void processEndOfTurn() { this.gameWorld.getMatchMaker().addCatFeedAction(this.getID()); this.sleepTimeRemaining -= 1; } else if (this.type == UnitType.CAT) { + Direction[] nonCenterDirections = {Direction.WEST, Direction.NORTHWEST, Direction.NORTH, Direction.NORTHEAST, Direction.EAST, Direction.SOUTHEAST, Direction.SOUTH, Direction.SOUTHWEST}; switch (this.catState) { case EXPLORE: if (this.catTurnsStuck >= 4) { // cat has been unable to move or dig or attack for 4+ turns // start turning and then trying to dig or attack again - Direction[] directions = Direction.values(); - Direction random = directions[rand.nextInt(directions.length)]; + + Direction random = nonCenterDirections[rand.nextInt(nonCenterDirections.length)]; if (this.controller.canTurn()) { try { @@ -1291,7 +1303,7 @@ public void processEndOfTurn() { clearAllMessages(); RobotInfo[] nearbyRobots = this.controller.senseNearbyRobots(); - if (squeak != null) { + if (squeak != null && this.getLocation().directionTo(squeak.getSource()) != Direction.CENTER) { // get distracted and turn towards squeak this.dir = this.getLocation().directionTo(squeak.getSource()); } diff --git a/engine/src/main/battlecode/world/RobotControllerImpl.java b/engine/src/main/battlecode/world/RobotControllerImpl.java index 6fe20669..0419c614 100644 --- a/engine/src/main/battlecode/world/RobotControllerImpl.java +++ b/engine/src/main/battlecode/world/RobotControllerImpl.java @@ -76,7 +76,8 @@ private MapInfo getMapInfo(MapLocation loc) throws GameActionException { GameWorld gw = this.gameWorld; Trap trap = gw.getTrap(loc, team); TrapType trapType = (trap != null) ? trap.getType() : TrapType.NONE; - MapInfo currentLocInfo = new MapInfo(loc, gw.isPassable(loc), gw.getWall(loc), gw.getDirt(loc), + RobotInfo flyingRobot = gw.getFlyingRobot(loc)!=null ? gw.getFlyingRobot(loc).getRobotInfo() : null; + MapInfo currentLocInfo = new MapInfo(loc, gw.isPassable(loc), flyingRobot, gw.getWall(loc), gw.getDirt(loc), gw.getCheeseAmount(loc), trapType, gw.hasCheeseMine(loc)); return currentLocInfo; @@ -106,6 +107,11 @@ public boolean isCooperation() { return this.gameWorld.isCooperation(); } + @Override + public Team getBackstabbingTeam() { + return this.gameWorld.getBackStabbingTeam(); + } + // ********************************* // ****** UNIT QUERY METHODS ******* // ********************************* @@ -160,6 +166,16 @@ public int getDirt() { return this.gameWorld.getTeamInfo().getDirt(getTeam()); } + @Override + public int getNumberCatTraps(){ + return this.gameWorld.getTrapCount(TrapType.CAT_TRAP, this.getTeam()); + } + + @Override + public int getNumberRatTraps(){ + return this.gameWorld.getTrapCount(TrapType.RAT_TRAP, this.getTeam()); + } + @Override public UnitType getType() { return this.robot.getType(); @@ -454,6 +470,10 @@ private void assertCanPickUpCheese(MapLocation loc) throws GameActionException { if (myType != UnitType.BABY_RAT && myType != UnitType.RAT_KING) { throw new GameActionException(CANT_DO_THAT, "Only rats can pick up cheese"); } + + if (this.robot.isBeingThrown()){ + throw new GameActionException(CANT_DO_THAT, "Flying rats cannot pick up cheese"); + } } @Override @@ -536,7 +556,8 @@ public RobotInfo senseRobotAtLocation(MapLocation loc) throws GameActionExceptio @Override public boolean canSenseRobot(int id) { InternalRobot sensedRobot = getRobotByID(id); - return sensedRobot != null && canSenseLocation(sensedRobot.getLocation()); + Boolean isFlying = sensedRobot.isBeingThrown(); + return sensedRobot != null && isFlying && canSenseLocation(sensedRobot.getLocation()); } @Override diff --git a/example-bots/src/crossplay_python/lectureplayer_py/bot.py b/example-bots/src/crossplay_python/lectureplayer_py/bot.py new file mode 100644 index 00000000..c5d73b71 --- /dev/null +++ b/example-bots/src/crossplay_python/lectureplayer_py/bot.py @@ -0,0 +1,373 @@ + +from battlecode26 import * +import random +from enum import Enum + +class State(Enum): + INITIALIZE = 1 + FIND_CHEESE = 2 + RETURN_TO_KING = 3 + BUILD_TRAPS = 4 + EXPLORE_AND_ATTACK = 5 + RETURN_TO_KING_THEN_EXPLORE = 6 + +rand = random.Random(1092) + +current_state = State.INITIALIZE + +num_rats_spawned = 0 +turns_since_carry = 1000 + +directions = list(Direction) + +mine_loc = None +num_mines = 0 +mine_locs = [] + +explore_when_finding_cheese = False +target_cheese_mine_loc = None + +class SqueakType(Enum): + INVALID = 0 + ENEMY_RAT_KING = 1 + ENEMY_COUNT = 2 + CHEESE_MINE = 3 + CAT_FOUND = 4 + +squeak_types = list(SqueakType) + + +def move_random(): + forward_loc = rc.adjacent_location(rc.get_direction()) + + if rc.can_remove_dirt(forward_loc): + rc.remove_dirt(forward_loc) + + if rc.can_move_forward(): + rc.move_forward() + else: + random_dir = directions[rand.randint(0, len(directions) - 1)] + + if rc.can_turn(random_dir): + rc.turn(random_dir) + + +def run_rat_king(): + global num_rats_spawned, num_mines + + current_cost = rc.get_current_rat_cost() + + potential_spawn_locations = rc.get_all_locations_within_radius_squared(rc.get_location(), 8) + spawn = current_cost <= 10 or rc.get_all_cheese() > current_cost + 2500 + + for loc in potential_spawn_locations: + if spawn and rc.can_build_rat(loc): + rc.build_rat(loc) + num_rats_spawned += 1 + break + + if rc.can_pick_up_cheese(loc): + rc.pick_up_cheese(loc) + break + + squeaks = rc.read_squeaks(rc.get_round_num()) + + for msg in squeaks: + raw_squeak = msg.bytes + + if get_squeak_type(raw_squeak) != SqueakType.CHEESE_MINE: + continue + + encoded_loc = get_squeak_value(raw_squeak) + + if encoded_loc in mine_locs: + continue + + mine_locs.append(encoded_loc) + first_int = get_first_int(encoded_loc) + last_int = get_last_int(encoded_loc) + + rc.write_shared_array(2 * num_mines + 2, first_int) + rc.write_shared_array(2 * num_mines + 3, last_int) + log("Writing to shared array:", first_int, last_int) + log("Cheese mine located at:", get_x(encoded_loc), get_y(encoded_loc)) + + num_mines += 1 + + move_random() + + # update king location in shared array + rc.write_shared_array(0, rc.get_location().x) + rc.write_shared_array(1, rc.get_location().y) + + +def run_find_cheese(): + global explore_when_finding_cheese, target_cheese_mine_loc, current_state + + if not explore_when_finding_cheese and num_mines == 0: + explore_when_finding_cheese = True + + if target_cheese_mine_loc is None and not explore_when_finding_cheese and num_mines > 0: + cheese_mine_index = rand.randint(0, num_mines - 1) + x = rc.read_shared_array(2 * cheese_mine_index + 2) + y = rc.read_shared_array(2 * cheese_mine_index + 3) + encoded_loc = 1024 * y + x + target_cheese_mine_loc = MapLocation(get_x(encoded_loc), get_y(encoded_loc)) + + nearby_infos = rc.sense_nearby_map_infos() + + for info in nearby_infos: + if info.cheese_amount > 0: + to_cheese = direction_to(rc.get_location(), info.location) + + if rc.can_turn(to_cheese): + rc.turn(to_cheese) + break + elif info.has_cheese_mine: + global mine_loc + mine_loc = info.location + log("Found cheese mine at", mine_loc) + + for dir in directions: + loc = rc.get_location().add(dir) + + if rc.can_pick_up_cheese(loc): + rc.pick_up_cheese(loc) + + if rc.get_raw_cheese() >= 10: + current_state = State.RETURN_TO_KING + + if explore_when_finding_cheese: + rc.set_indicator_string("Exploring!") + move_random() + elif target_cheese_mine_loc is not None: + rc.set_indicator_string(f"Going to cheese mine at {target_cheese_mine_loc}") + to_target = direction_to(rc.get_location(), target_cheese_mine_loc) + next_loc = rc.get_location().add(to_target) + + if rc.can_turn(to_target): + rc.turn(to_target) + + if rc.can_remove_dirt(next_loc): + rc.remove_dirt(next_loc) + + if rc.can_move(to_target): + rc.move(to_target) + + target_cheese_mine_loc = None + + +def run_return_to_king(): + global current_state, explore_when_finding_cheese + + king_loc = MapLocation(rc.read_shared_array(0), rc.read_shared_array(1)) + to_king = direction_to(rc.get_location(), king_loc) + next_loc = rc.get_location().add(to_king) + + if rc.can_turn(to_king): + rc.turn(to_king) + + if rc.can_remove_dirt(next_loc): + rc.remove_dirt(next_loc) + + if rc.can_move(to_king): + rc.move(to_king) + + raw_cheese = rc.get_raw_cheese() + + if raw_cheese == 0: + current_state = State.FIND_CHEESE + explore_when_finding_cheese = rand.choice([True, False]) and rand.choice([True, False]) + + if rc.can_sense_location(king_loc): + if distance_squared_to(king_loc, rc.get_location()) <= 16 and mine_loc is not None: + rc.squeak(get_squeak(SqueakType.CHEESE_MINE, to_integer(mine_loc))) + + king_locations: list[RobotInfo] = rc.sense_nearby_robots(king_loc, 8, rc.get_team()) + + for robot_info in king_locations: + if robot_info.type.is_rat_king_type(): + actual_king_loc = robot_info.location + + if rc.can_transfer_cheese(actual_king_loc, raw_cheese): + log(f"Transferred {raw_cheese} cheese to king at {king_loc}: I'm at {rc.get_location()}") + rc.transfer_cheese(actual_king_loc, raw_cheese) + current_state = State.FIND_CHEESE + explore_when_finding_cheese = rand.choice([True, False]) and rand.choice([True, False]) + + break + + +def run_build_traps(): + global current_state + + for dir in directions: + loc = rc.get_location().add(dir) + cat_traps = rand.choice([True, False]) + + if cat_traps and rc.can_place_cat_trap(loc): + log("Built cat trap at", loc) + rc.place_cat_trap(loc) + elif rc.can_place_rat_trap(loc): + log("Built rat trap at", loc) + rc.place_rat_trap(loc) + + if rand.random() < 0.1: + current_state = State.EXPLORE_AND_ATTACK + + move_random() + + +def run_explore_and_attack(): + global current_state, turns_since_carry + + squeaks = rc.read_squeaks(rc.get_round_num()) + + for msg in squeaks: + raw_squeak = msg.bytes + + if get_squeak_type(raw_squeak) != SqueakType.CAT_FOUND: + continue + + dir_ordinal = get_squeak_value(raw_squeak) + to_cat = directions[dir_ordinal] + away = to_cat.opposite() + + if rc.can_turn(away): + rc.turn(away) + break + + if rc.can_remove_dirt(rc.get_location().add(away)): + rc.remove_dirt(rc.get_location().add(away)) + + if rc.can_move(away): + rc.move(away) + break + + move_random() + + if rc.can_throw_rat() and turns_since_carry >= 3: + rc.throw_rat() + + for dir in directions: + loc = rc.get_location().add(dir) + + if rc.can_carry_rat(loc): + rc.carry_rat(loc) + turns_since_carry = 0 + + if rc.can_attack(loc): + rc.attack(loc) + + if rand.random() < 0.1: + current_state = State.BUILD_TRAPS + + nearby_enemies = rc.sense_nearby_robots(..., rc.get_type().vision_cone_radius_squared, rc.get_team().opponent()) + nearby_cats = rc.sense_nearby_robots(..., rc.get_type().vision_cone_radius_squared, Team.NEUTRAL) + + for enemy in nearby_enemies: + if enemy.type.is_rat_king_type(): + current_state = State.RETURN_TO_KING_THEN_EXPLORE + + num_enemies = len(nearby_enemies) + if num_enemies > 0: + rc.set_indicator_string(f"Nearby enemies: {num_enemies}") + rc.squeak(get_squeak(SqueakType.ENEMY_COUNT, num_enemies)) + + if len(nearby_cats) > 0: + if distance_squared_to(rc.get_location(), nearby_cats[0].location) >= 17: + rc.set_indicator_string(f"Found a cat at {nearby_cats[0].location}") + to_cat = direction_to(rc.get_location(), nearby_cats[0].location) + rc.squeak(get_squeak(SqueakType.CAT_FOUND, to_cat.ordinal())) + else: + rc.set_indicator_string("Cat is too close! Running away!") + away = direction_to(rc.get_location(), nearby_cats[0].location).opposite() + if rc.can_turn(away): + rc.turn(away) + + if rc.can_remove_dirt(rc.get_location().add(away)): + rc.remove_dirt(rc.get_location().add(away)) + + if rc.can_move(away): + rc.move(away) + + +def to_integer(loc: MapLocation) -> int: + return (loc.x << 6) | loc.y + + +def get_first_int(loc: int) -> int: + return loc % 1024 + + +def get_last_int(loc: int) -> int: + return loc >> 10 + + +def get_x(encoded_loc: int) -> int: + return encoded_loc >> 6 + + +def get_y(encoded_loc: int) -> int: + return encoded_loc % 64 + + +def get_squeak(type_: SqueakType, value: int) -> int: + if type_ == SqueakType.ENEMY_RAT_KING: + return (1 << 12) | value + if type_ == SqueakType.ENEMY_COUNT: + return (2 << 12) | value + if type_ == SqueakType.CHEESE_MINE: + return (3 << 12) | value + if type_ == SqueakType.CAT_FOUND: + return (4 << 12) | value + return value + + +def get_squeak_type(raw_squeak: int): + return squeak_types[raw_squeak >> 12] + + +def get_squeak_value(raw_squeak: int): + return raw_squeak % 4096 + + +def turn(): + global rand, current_state, num_rats_spawned, turns_since_carry, \ + directions, mine_loc, num_mines, mine_locs, \ + explore_when_finding_cheese, target_cheese_mine_loc + + try: + if rc.get_type().is_rat_king_type(): + run_rat_king() + else: + turns_since_carry += 1 + + if current_state == State.INITIALIZE: + if rc.get_round_num() < 30 or rc.get_current_rat_cost() <= 10: + current_state = State.FIND_CHEESE + # replicate Java randomness: two coin flips + explore_when_finding_cheese = rand.choice([True, False]) and rand.choice([True, False]) + else: + current_state = State.EXPLORE_AND_ATTACK + + elif current_state == State.FIND_CHEESE: + run_find_cheese() + elif current_state == State.RETURN_TO_KING: + run_return_to_king() + elif current_state == State.BUILD_TRAPS: + run_build_traps() + elif current_state == State.EXPLORE_AND_ATTACK: + run_explore_and_attack() + elif current_state == State.RETURN_TO_KING_THEN_EXPLORE: + run_return_to_king() + + if current_state == State.FIND_CHEESE: + current_state = State.EXPLORE_AND_ATTACK + + except GameActionException as e: + log("GameActionException in bot:") + log(e) + except Exception as e: + log("Exception in bot:") + log(e) diff --git a/specs/specs.pdf b/specs/specs.pdf index 44f1e07e..b0cc964d 100644 Binary files a/specs/specs.pdf and b/specs/specs.pdf differ