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 6fa04b6..db1c34b 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: @@ -276,10 +275,128 @@ 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 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.MONEY_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): + ''' + 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 + + if not self.is_valid_pattern_center(center): + 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 = self.pattern[shape] + 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) 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 95525ee..fe4c90f 100644 --- a/battlehack20/engine/game/robot_controller.py +++ b/battlehack20/engine/game/robot_controller.py @@ -39,37 +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 + 2 >= game.board_width or loc.x - 2 < 0 or loc.y + 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) - 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]) + 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) + self.mark(loc, pattern_array[dx+offset][dy+offset]) def sense(game, robot): #TODO adapt this method for new sensing methods @@ -97,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): @@ -339,6 +339,68 @@ 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 + 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) #TODO: implement mark_resource_pattern in game.py + # SPAWN METHODS def assert_spawn(game, robot, robot_type, map_location): """ @@ -482,8 +544,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): @@ -518,11 +578,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)