From ff8921502743a81e645c4bc1a05fc270375ca6b6 Mon Sep 17 00:00:00 2001 From: Hannah Date: Wed, 25 Dec 2024 17:38:05 -0500 Subject: [PATCH 1/4] check pattern transformations --- battlehack20/engine/game/game.py | 77 ++++++++++++++++++++ battlehack20/engine/game/robot_controller.py | 12 +-- 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/battlehack20/engine/game/game.py b/battlehack20/engine/game/game.py index 6fa04b6..3b92dd1 100644 --- a/battlehack20/engine/game/game.py +++ b/battlehack20/engine/game/game.py @@ -275,6 +275,83 @@ def get_all_locations_within_radius_squared(self, center: MapLocation, radius_sq def mark_location(self, loc, color): self.markers[self.loc_to_index(loc)] = color + + def detect_pattern(self, center, team): + ''' + Check if there is any transformation of a 5x5 pattern centered at "center" for a particular team + Returns detected pattern type (as Shape Enum), or None if there is no pattern + ''' + class Transformation(Enum): + ORIGINAL = 0 + FLIP_X = 1 + FLIP_Y = 2 + FLIP_D1 = 3 + FLIP_D2 = 4 + ROTATE_90 = 6 + ROTATE_180 = 7 + ROTATE_270 = 8 + + shape_out_of_bounds = (center.x + GameConstants.PATTERN_SIZE//2 >= self.width or center.x - GameConstants.PATTERN_SIZE//2 < 0 or center.y + GameConstants.PATTERN_SIZE//2 >= self.height or center.y < 0) + if shape_out_of_bounds: + return None + + patterns_list = list(Shape.__members__.values()) # list of Shape enum members + + def check_pattern(shape): + ''' + Check presence of a particular pattern type up to 8 symmetries + Returns True/False, whether pattern is present + ''' + pattern_array = get_pattern(shape) #TODO: get_pattern method in RobotController + valid_transformations = [True] * len(Transformation) # T/F for whether a transformation is valid + + offset = GameConstants.PATTERN_SIZE//2 + + for dx in range(-offset, offset + 1): + for dy in range(-offset, offset + 1): + for variant in list(Transformation.__members__.values()): + if(variant == Transformation.ORIGINAL): + dx_ = dx + dy_ = dy + elif(variant == Transformation.FLIP_X): + dx_ = -dx + dy_ = dy + elif(variant == Transformation.FLIP_Y): + dx_ = dx + dy_ = -dy + elif(variant == Transformation.FLIP_D1): + dx_ = dy + dy_ = dx + elif(variant == Transformation.FLIP_D2): + dx_ = -dy + dy_ = -dx + elif(variant == Transformation.ROTATE_90): + dx_ = -dy + dy_ = dx + elif(variant == Transformation.ROTATE_180): + dx_ = -dx + dy_ = -dy + elif(variant == Transformation.ROTATE_270): + dx_ = dy + dy_ = -dx + + map_loc = MapLocation(center.x + dx_, center.y + dy_) # location on map after transforming pattern + map_loc_idx = self.loc_to_index(map_loc) + + if(self.team_from_paint(pattern_array[dx + offset][dy + offset]) != team): # wrong team + valid_transformations[shape] = False + + #assumes pattern arrays have 1 as secondary, 0 as primary + match_primary = (pattern_array[dx + offset][dy + offset] == 0) and (self.paint[map_loc_idx] == self.get_primary_paint(team)) + match_secondary = (pattern_array[dx + offset][dy + offset] == 1) and (self.paint[map_loc_idx] == self.get_secondary_paint(team)) + if(not (match_primary or match_secondary)): + valid_transformations[variant] = False + return (any(valid_transformations)) + + for shape in patterns_list: + if(check_pattern(shape)): + return shape + return None def is_passable(self, loc): idx = self.loc_to_index(loc) diff --git a/battlehack20/engine/game/robot_controller.py b/battlehack20/engine/game/robot_controller.py index 95525ee..7446dcb 100644 --- a/battlehack20/engine/game/robot_controller.py +++ b/battlehack20/engine/game/robot_controller.py @@ -62,14 +62,16 @@ def mark_pattern(game, robot, center, shape): Marks the specified pattern centered at the location specified """ #check bounds - shape_out_of_bounds = (loc.x + 2 >= game.board_width or loc.x - 2 < 0 or loc.y + 2 >= game.board_height or loc.y < 0) + shape_out_of_bounds = (loc.x + GameConstants.PATTERN_SIZE//2 >= game.board_width or loc.x - GameConstants.PATTERN_SIZE//2 < 0 or loc.y + GameConstants.PATTERN_SIZE//2 >= game.board_height or loc.y < 0) assert(shape_out_of_bounds, "Shape out of bounds") pattern_array = get_pattern(shape) - for i in range(-2, +3): - for j in range(-2, +3): - loc = MapLocation(center.x + i, center.y + j) - mark(game, robot, loc, pattern_array[i+2][j+2]) + + offset = GameConstants.PATTERN_SIZE//2 + for dx in range(-offset, offset + 1): + for dy in range(-offset, offset + 1): + loc = MapLocation(center.x + dx, center.y + dy) + mark(game, robot, loc, pattern_array[dx+offset][dy+offset]) def sense(game, robot): #TODO adapt this method for new sensing methods From a2a2fb1ed7d7655cda6f6a2440dc16c75af899ec Mon Sep 17 00:00:00 2001 From: Hannah Date: Thu, 26 Dec 2024 16:22:55 -0500 Subject: [PATCH 2/4] more marking stuff --- battlehack20/engine/game/constants.py | 5 ++ battlehack20/engine/game/game.py | 24 +++-- battlehack20/engine/game/robot_controller.py | 93 ++++++++++++++++---- 3 files changed, 95 insertions(+), 27 deletions(-) diff --git a/battlehack20/engine/game/constants.py b/battlehack20/engine/game/constants.py index b4accc0..33e7b4c 100644 --- a/battlehack20/engine/game/constants.py +++ b/battlehack20/engine/game/constants.py @@ -62,6 +62,9 @@ class GameConstants: # The percent of the map which a team needs to paint to win. PAINT_PERCENT_TO_WIN = 70 + # Paint cost for marking a tower pattern. + MARK_PATTERN_COST = 25 + # ***************************** # ****** GAME MECHANICS ******* # ***************************** @@ -84,6 +87,8 @@ class GameConstants: # The maximum distance from a robot where information can be sensed VISION_RADIUS_SQUARED = 20 + # The maximum distance a robot can mark a pattern + MARK_RADIUS_SQUARED = 2 # The maximum distance for transferring paint from/to an ally robot or tower PAINT_TRANSFER_RADIUS_SQUARED = 2 diff --git a/battlehack20/engine/game/game.py b/battlehack20/engine/game/game.py index 3b92dd1..ed2ba63 100644 --- a/battlehack20/engine/game/game.py +++ b/battlehack20/engine/game/game.py @@ -17,11 +17,10 @@ import math class Shape(Enum): # marker shapes - STAR=0 - SMILE=1 - CIRCLE=2 - SQUARE=3 - DIAMOND=4 + RESOURCE=0 + DEFENSE_TOWER=1 + MONEY_TOWER=2 + PAINT_TOWER=3 class Game: @@ -275,6 +274,14 @@ def get_all_locations_within_radius_squared(self, center: MapLocation, radius_sq def mark_location(self, loc, color): self.markers[self.loc_to_index(loc)] = color + + def is_valid_pattern_center(self, center): + ''' + Checks if pattern centered at this location would be in the bounds of the map + ''' + + shape_out_of_bounds = (center.x + GameConstants.PATTERN_SIZE//2 >= self.width or center.x - GameConstants.PATTERN_SIZE//2 < 0 or center.y + GameConstants.PATTERN_SIZE//2 >= self.height or center.y < 0) + return shape_out_of_bounds def detect_pattern(self, center, team): ''' @@ -291,8 +298,7 @@ class Transformation(Enum): ROTATE_180 = 7 ROTATE_270 = 8 - shape_out_of_bounds = (center.x + GameConstants.PATTERN_SIZE//2 >= self.width or center.x - GameConstants.PATTERN_SIZE//2 < 0 or center.y + GameConstants.PATTERN_SIZE//2 >= self.height or center.y < 0) - if shape_out_of_bounds: + if not self.is_valid_pattern_center(center): return None patterns_list = list(Shape.__members__.values()) # list of Shape enum members @@ -302,7 +308,7 @@ def check_pattern(shape): Check presence of a particular pattern type up to 8 symmetries Returns True/False, whether pattern is present ''' - pattern_array = get_pattern(shape) #TODO: get_pattern method in RobotController + pattern_array = self.pattern[shape] valid_transformations = [True] * len(Transformation) # T/F for whether a transformation is valid offset = GameConstants.PATTERN_SIZE//2 @@ -356,7 +362,7 @@ def check_pattern(shape): def is_passable(self, loc): idx = self.loc_to_index(loc) return not self.walls[idx] and self.robots[idx] == None - + def get_map_info(self, loc): idx = self.loc_to_index(loc) return MapInfo(loc, self.is_passable(loc), self.walls[idx], self.paint[idx], self.markers[idx], self.ruins[idx]) diff --git a/battlehack20/engine/game/robot_controller.py b/battlehack20/engine/game/robot_controller.py index 7446dcb..11b2823 100644 --- a/battlehack20/engine/game/robot_controller.py +++ b/battlehack20/engine/game/robot_controller.py @@ -39,39 +39,37 @@ def get_team(self): def get_type(self): return self.robot.type - def mark(game, robot, loc, color): + def mark(self, loc, color): """ loc: MapLocation we want to mark color: Color enum specifying the color of the mark Marks the specified map location """ - game.mark_location(robot.team, loc, color) + self.game.mark_location(self.robot.team, loc, color) - def get_pattern(shape): + def get_pattern(self, shape): """ shape: Shape enum specifying the shape pattern to retrieve Returns a 5 x 5 array of the mark colors """ - #TODO: map shape enum to 5x5 array of colors - return + return self.game.pattern - def mark_pattern(game, robot, center, shape): + def mark_pattern(self, center, shape): """ center: MapLocation center of the 5x5 pattern shape: Shape enum to be marked Marks the specified pattern centered at the location specified """ #check bounds - shape_out_of_bounds = (loc.x + GameConstants.PATTERN_SIZE//2 >= game.board_width or loc.x - GameConstants.PATTERN_SIZE//2 < 0 or loc.y + GameConstants.PATTERN_SIZE//2 >= game.board_height or loc.y < 0) - assert(shape_out_of_bounds, "Shape out of bounds") + assert(not self.game.is_valid_pattern_center(center), "Shape out of bounds") - pattern_array = get_pattern(shape) + pattern_array = self.game.pattern[shape] offset = GameConstants.PATTERN_SIZE//2 for dx in range(-offset, offset + 1): for dy in range(-offset, offset + 1): loc = MapLocation(center.x + dx, center.y + dy) - mark(game, robot, loc, pattern_array[dx+offset][dy+offset]) + self.mark(loc, pattern_array[dx+offset][dy+offset]) def sense(game, robot): #TODO adapt this method for new sensing methods @@ -99,10 +97,10 @@ def sense(game, robot): return robots - def assert_can_sense_location(game, robot, loc): + def assert_can_sense_location(self, loc): if loc == None: raise RobotError("Not a valid location") - if not game.on_the_map(loc): + if not self.game.on_the_map(loc): raise RobotError("Target location is not on the map") def can_sense_location(game, robot, loc): @@ -341,6 +339,67 @@ def mop_swing(robot, game, direction): if self.team != robot.get_team(): robot.add_paint(-GameConstants.MOPPER_SWING_PAINT_DEPLETION) + # MARKING METHODS + def assert_can_mark_pattern(self, loc): + ''' + Asserts that a pattern can be marked at this location. + ''' + if self.robot.type.is_tower_type(): + raise RobotError("Marking unit is not a robot.") + if self.game.is_valid_pattern_center(loc): + raise RobotError(f"Pattern at ({loc.x}, {loc.y}) is out of the bounds of the map.") + if not loc.is_within_distance_squared(self.robot.loc, GameConstants.MARK_RADIUS_SQUARED): + raise RobotError(f"({loc.x}, {loc.y}) is not within the robot's pattern-marking range") + if self.robot.paint < GameConstants.MARK_PATTERN_COST: + raise RobotError("Robot does not have enough paint for mark the pattern.") + def assert_can_mark_tower_pattern(self, loc, tower_type): + ''' + Asserts that tower pattern can be marked at this location. + ''' + self.assert_can_mark_pattern(loc) + if tower_type.is_robot_type(): + raise RobotError("Pattern type is not a tower type.") + if not self.game.get_map_info(loc).has_ruin(): + raise RobotError(f"Cannot mark tower pattern at ({loc.x}, {loc.y}) because there is no ruin.") + def assert_can_mark_resource_pattern(self, loc): + ''' + Asserts that tower pattern can be marked at this location. + ''' + self.assert_can_mark_pattern(loc) + def can_mark_tower_pattern(self, loc, tower_type): + """ + Checks if specified tower pattern can be marked at location + """ + try: + self.assert_can_mark_tower_pattern(loc, tower_type) + return True + except: + return False + def can_mark_resource_pattern(self, loc): + """ + Checks if resource pattern can be marked at location + """ + try: + self.assert_can_mark_resource_pattern(loc) + return True + except: + return False + def mark_tower_pattern(self, loc, tower_type): + """ + Marks specified tower pattern at location if possible + """ + self.assert_can_mark_tower_pattern(loc) + self.robot.add_paint(-GameConstants.MARK_PATTERN_COST) + self.game.mark_tower_pattern(self.robot.team, loc, tower_type) #TODO: implement mark_tower_pattern in game.py + def mark_resource_pattern(self, loc): + """ + Marks resource pattern at location if possible + """ + self.assert_can_mark_resource_pattern(loc) + self.robot.add_paint(-GameConstants.MARK_PATTERN_COST) + self.game.mark_resource_pattern(self.robot.team, loc, tower_type) #TODO: implement mark_resource_pattern in game.py + + # SPAWN METHODS def assert_spawn(game, robot, robot_type, map_location): """ @@ -484,8 +543,6 @@ def withdraw_paint(game, robot, target_location, amount): target = game.get_robot(target_location) target.add_paint(amount) - - ## Upgrading tower def assert_can_upgrade_tower(game, team, tower_location): if not game.is_on_board(tower_location.x, tower_location.y): @@ -520,11 +577,11 @@ def upgrade_tower(game, team, tower_location): tower.type.upgradeTower(tower) ## Sensing other objects - def assert_can_sense_location(loc): - pass + # def assert_can_sense_location(loc): + # pass - def can_sense_location(loc): - pass + # def can_sense_location(loc): + # pass def sense_map_info(game, loc): assert_can_sense_location(loc) From 4ae3752e3164662fb2083d47830203769de6df90 Mon Sep 17 00:00:00 2001 From: Hannah Date: Mon, 30 Dec 2024 10:35:38 -0500 Subject: [PATCH 3/4] mark tower & resource patterns --- battlehack20/engine/game/game.py | 34 ++++++++++++++++++++ battlehack20/engine/game/robot_controller.py | 5 +-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/battlehack20/engine/game/game.py b/battlehack20/engine/game/game.py index ed2ba63..1f8e4ca 100644 --- a/battlehack20/engine/game/game.py +++ b/battlehack20/engine/game/game.py @@ -282,6 +282,40 @@ def is_valid_pattern_center(self, center): shape_out_of_bounds = (center.x + GameConstants.PATTERN_SIZE//2 >= self.width or center.x - GameConstants.PATTERN_SIZE//2 < 0 or center.y + GameConstants.PATTERN_SIZE//2 >= self.height or center.y < 0) return shape_out_of_bounds + + def mark_pattern(self, team, center, shape): + ''' + Marks pattern at center + ''' + pattern_array = self.pattern[shape] + + offset = GameConstants.PATTERN_SIZE//2 + + for dx in range(-offset, offset + 1): + for dy in range(-offset, offset + 1): + color_indicator = pattern_array[dx + offset, dy + offset] + mark_color = self.get_primary_paint(team) if (color_indicator == 0) else self.get_secondary_paint(team) + self.mark_location(MapLocation(center.x + dx, center.y + dy), mark_color) + + def mark_tower_pattern(self, team, center, tower_type): + ''' + Marks specified tower pattern at center + tower_type: tower_type: RobotType enum + ''' + if tower_type in {RobotType.LEVEL_ONE_PAINT_TOWER, RobotType.LEVEL_TWO_PAINT_TOWER, RobotType.LEVEL_THREE_PAINT_TOWER}: + shape = Shape.PAINT_TOWER + if tower_type in {RobotType.LEVEL_ONE_DEFENSE_TOWER, RobotType.LEVEL_TWO_DEFENSE_TOWER, RobotType.LEVEL_THREE_DEFENSE_TOWER}: + shape = Shape.DEFENSE_TOWER + if tower_type in {RobotType.LEVEL_ONE_MONEY_TOWER, RobotType.LEVEL_TWO_MONEY_TOWER, RobotType.LEVEL_THREE_MONEY_TOWER}: + shape = Shape.DEFENSE_TOWER + + self.mark_pattern(team, center, shape) + + def mark_resource_pattern(self, team, center): + ''' + Marks resource pattern at center + ''' + self.mark_pattern(team, center, Shape.RESOURCE) def detect_pattern(self, center, team): ''' diff --git a/battlehack20/engine/game/robot_controller.py b/battlehack20/engine/game/robot_controller.py index 11b2823..fe4c90f 100644 --- a/battlehack20/engine/game/robot_controller.py +++ b/battlehack20/engine/game/robot_controller.py @@ -387,18 +387,19 @@ def can_mark_resource_pattern(self, loc): def mark_tower_pattern(self, loc, tower_type): """ Marks specified tower pattern at location if possible + tower_type: RobotType enum """ self.assert_can_mark_tower_pattern(loc) self.robot.add_paint(-GameConstants.MARK_PATTERN_COST) self.game.mark_tower_pattern(self.robot.team, loc, tower_type) #TODO: implement mark_tower_pattern in game.py + def mark_resource_pattern(self, loc): """ Marks resource pattern at location if possible """ self.assert_can_mark_resource_pattern(loc) self.robot.add_paint(-GameConstants.MARK_PATTERN_COST) - self.game.mark_resource_pattern(self.robot.team, loc, tower_type) #TODO: implement mark_resource_pattern in game.py - + self.game.mark_resource_pattern(self.robot.team, loc) #TODO: implement mark_resource_pattern in game.py # SPAWN METHODS def assert_spawn(game, robot, robot_type, map_location): From 2dd73f163c2756f3819c1bd8f16e5f73cb66d987 Mon Sep 17 00:00:00 2001 From: Hannah Date: Mon, 30 Dec 2024 10:36:01 -0500 Subject: [PATCH 4/4] small fix --- battlehack20/engine/game/game.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/battlehack20/engine/game/game.py b/battlehack20/engine/game/game.py index 1f8e4ca..db1c34b 100644 --- a/battlehack20/engine/game/game.py +++ b/battlehack20/engine/game/game.py @@ -307,7 +307,7 @@ def mark_tower_pattern(self, team, center, tower_type): if tower_type in {RobotType.LEVEL_ONE_DEFENSE_TOWER, RobotType.LEVEL_TWO_DEFENSE_TOWER, RobotType.LEVEL_THREE_DEFENSE_TOWER}: shape = Shape.DEFENSE_TOWER if tower_type in {RobotType.LEVEL_ONE_MONEY_TOWER, RobotType.LEVEL_TWO_MONEY_TOWER, RobotType.LEVEL_THREE_MONEY_TOWER}: - shape = Shape.DEFENSE_TOWER + shape = Shape.MONEY_TOWER self.mark_pattern(team, center, shape)