From ca11f3476cc1d8792a1c997e6185b93ea5bb13fe Mon Sep 17 00:00:00 2001 From: spirkelmann Date: Sun, 20 Sep 2020 20:20:40 +0200 Subject: [PATCH] implemented core game logic with generated commands --- roborally.py | 201 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 136 insertions(+), 65 deletions(-) diff --git a/roborally.py b/roborally.py index 60e09e9..487cc83 100644 --- a/roborally.py +++ b/roborally.py @@ -62,6 +62,25 @@ deck = CardDeck() class Robot: + # dictionary mapping the current orientation and a turn command to the resulting orientation + resulting_orientation = { + '^': {'turn left': '<', 'turn right': '>', 'turn around': 'v'}, + '>': {'turn left': '^', 'turn right': 'v', 'turn around': '<'}, + 'v': {'turn left': '>', 'turn right': '<', 'turn around': '^'}, + '<': {'turn left': 'v', 'turn right': '^', 'turn around': '>'}, + } + + # dictionary mapping the current orientation and the target orientation to the necessary turn command + necessary_turn = { + '^': {'>': 'turn right', 'v': 'turn around', '<': 'turn left'}, + '>': {'v': 'turn right', '<': 'turn around', '^': 'turn left'}, + 'v': {'<': 'turn right', '^': 'turn around', '>': 'turn left'}, + '<': {'^': 'turn right', '>': 'turn around', 'v': 'turn left'}, + } + + # dictionary mapping an orientation to its opposite + opposites = {'^': 'v', '>': '<', 'v': '^', '<': '>'} + def __init__(self, x, y, orientation, id, board): self.x = x self.y = y @@ -71,7 +90,7 @@ class Robot: self.board = board # mark the tile on the board as occupied - self.board[(x,y)].occupier = self + self.board[(x,y)].occupant = self def get_accessed_tiles(self, count): @@ -104,25 +123,57 @@ class Robot: return (self.orientation, direction) in opposites def get_turn_direction(self, target_orienation): - # get the direction to turn to in order to face in the same direction as the given orientation - directions = { - ('^', '>'): 'turn right', - ('^', 'v'): 'turn around', - ('^', '<'): 'turn left', - ('>', 'v'): 'turn right', - ('>', '<'): 'turn around', - ('>', '^'): 'turn left', - ('v', '<'): 'turn right', - ('v', '^'): 'turn around', - ('v', '>'): 'turn left', - ('<', '^'): 'turn right', - ('<', '>'): 'turn around', - ('<', 'v'): 'turn left', - } - return directions[(self.orientation, target_orienation)] + return Robot.necessary_turn[self.orientation][target_orienation] + + def get_opposite_orientation(self): + return Robot.opposites[self.orientation] + + def turn(self, type): + # change the orientation of the robot + self.orientation = Robot.resulting_orientation[self.orientation][type] + + return "{}, {}".format(self.id, type) def move(self, type): - pass + # move the robot forward or backward + # this involves + tile = self.board[(self.x, self.y)] + if type == 'forward': + target_tile = self.board[tile.get_neighbor_coordinates(self.orientation)] + + if target_tile.occupant is not None: + print("error: target tile is not empty") + sys.exit(1) + + tile.occupant = None # delete the robot from the current tile + target_tile.occupant = self # place the robot in the next tile + self.x = target_tile.x + self.y = target_tile.y + + # return the move for sending to the controller + return "{}, forward".format(self.id) + elif type == 'backward': + opposite_orientation = self.get_opposite_orientation() + target_tile = tile.get_neighbor_coordinates(opposite_orientation) + + if target_tile.occupant is not None: + print("error: target tile is not empty") + sys.exit(1) + + tile.occupant = None # delete the robot from the current tile + target_tile.occupant = self # place the robot in the next tile + self.x = target_tile.x + self.y = target_tile.y + + # return the move for sending to the controller + return "{}, backward".format(self.id) + else: + print("error: invalid move") + sys.exit(1) + + def nop(self): + # do nothing command + return "{}, nop".format(self.id) def __str__(self): return str(self.id) @@ -164,7 +215,13 @@ class Tile: else: return self.modifier else: - return str(self.occupant) + if self.occupant is None: + return self.modifier + else: + return str(self.occupant) + + def __repr__(self): + return "({}, {}) occ: {} mod: {}".format(self.x, self.y, self.occupant, self.modifier) class Board: @@ -185,10 +242,42 @@ class Board: self.robots = {} self.robots[0] = Robot(3, 1, '<', 0, self.board) - self.robots[1] = Robot(1, 1, 'v', 1, self.board) + self.robots[1] = Robot(2, 1, 'v', 1, self.board) + def handle_push(self, pushing_robot, pushed_robot): + cmd_list = [] + # push robot out of the way + if pushed_robot.orientation == pushing_robot.orientation: + # the pushed robot can just drive forward + cmd_list += self.handle_single_action('forward', pushed_robot) + elif pushed_robot.has_opposite_orientation(pushing_robot.orientation): + # the pushed robot can drive backward + cmd_list += self.handle_single_action('backward', pushed_robot) + else: + # we first have to turn the pushed robot s.t. it faces in the same orientation as the + # pushing robot + turn_direction = pushed_robot.get_turn_direction(pushing_robot.orientation) + cmd_list += self.handle_single_action(turn_direction, pushed_robot) + + # then the pushed robot drives one step forward + cmd_list += self.handle_single_action('forward', pushed_robot) + + # afterwards we turn the robot back to the original orientation + if turn_direction == 'turn left': + turn_back_direction = 'turn right' + elif turn_direction == 'turn right': + turn_back_direction = 'turn left' + else: + print("error: invalid turn direction") + sys.exit(1) + cmd_list += self.handle_single_action(turn_back_direction, pushed_robot) + + # now the tile should be empty so the pushing robot can move into the tile + cmd_list.append(pushing_robot.move('forward')) + return cmd_list def handle_single_action(self, action, robot): + cmd_list = [] if 'forward' in action: # driving forward if "x2" in action: move_count = 2 @@ -199,58 +288,38 @@ class Board: accessed_tiles = robot.get_accessed_tiles(move_count) for tile in accessed_tiles: - if tile.modifier == '#': # robot hits a wall -> stop the robot - pass - elif tile.modifier == 'X': # robot drives into a pit -> take damage - pass + if tile is None: + # this case should not happen + print("error: unknown state occured") + sys.exit(1) + elif tile.is_empty(): + # if the tile is empty we can just move there + cmd_list.append(robot.move('forward')) + elif tile.modifier == '#': # robot hits a wall -> stop the robot + cmd_list.append(robot.nop()) + return cmd_list elif any([(tile.x, tile.y) == (r.x, r.y) for r in self.robots.values()]): # robots hits a tile occupied by another robot pushed_robot = next(filter(lambda r: (tile.x, tile.y) == (r.x, r.y), self.robots.values())) - if pushed_robot.is_pushable(robot.orientation): # check if robot is pushable in the given direction - if pushed_robot.orientation == robot.orientation: - # the pushed robot can just drive forward - self.handle_single_action('forward', pushed_robot) - elif pushed_robot.has_opposite_orientation(robot.orientation): - # the pushed robot can drive backward - self.handle_single_action('backward', pushed_robot) - else: - # we first have to turn the pushed robot s.t. it faces in the same orientation as the - # pushing robot - turn_direction = pushed_robot.get_turn_direction(robot.orientation) - self.handle_single_action(turn_direction, pushed_robot) - - # then the pushed robot drives one step forward - self.handle_single_action('forward', pushed_robot) - - # afterwards we turn the robot back to the original orientation - if turn_direction == 'turn left': - turn_back_direction = 'turn right' - elif turn_direction == 'turn right': - turn_back_direction = 'turn left' - else: - print("error: invalid turn direction") - sys.exit(1) - self.handle_single_action(turn_back_direction, pushed_robot) - else: # robot is not pushable -> do not move - pass + cmd_list += self.handle_push(pushing_robot=robot, pushed_robot=pushed_robot) + else: + cmd_list.append(robot.nop()) + return cmd_list else: - # now the tile should be empty so the robot can move into the tile - # TODO: possible problem: what happens when robot cannot be pushed out of the way (e.g. because it is - # blocked by a wall) -> check if robot is pushable beforehand - # -> register move action to process - pass + # this case should not happen + print("error: unknown state occured") + sys.exit(1) elif action == 'backward': # basically do the same as with forward pass - elif action == 'turn left': - pass - elif action == 'turn right': - pass - elif action == 'turn around': - pass + else: # this means we have a turn action + cmd_list.append(robot.turn(action)) + + return cmd_list def apply_actions(self, cards): + cmd_list = [] # apply the actions to the board and generate a list of movement commands for i, phase in enumerate(cards): # process register phases @@ -265,7 +334,9 @@ class Board: print("robot {} action {}".format(robot, action)) - self.handle_single_action(action, robot) + cmd_list += self.handle_single_action(action, robot) + print(cmd_list) + pass # apply the actions caused by board elements at the end of the phase pass @@ -286,8 +357,8 @@ class Board: if __name__ == "__main__": n = 5 - player_1_cards = random.sample(list(filter(lambda c: 'forward' in c.action, deck.deck.values())), 3) - player_2_cards = random.sample(list(filter(lambda c: 'turn around' in c.action, deck.deck.values())), 3) + player_1_cards = random.sample(list(filter(lambda c: 'forward' in c.action, deck.deck.values())), n) + player_2_cards = random.sample(list(filter(lambda c: 'turn around' in c.action, deck.deck.values())), n) cards_1 = [(0, c) for c in player_1_cards] cards_2 = [(1, c) for c in player_2_cards]