diff --git a/roborally.py b/roborally.py index 4f71c21..60e09e9 100644 --- a/roborally.py +++ b/roborally.py @@ -1,7 +1,7 @@ import random +import sys random.seed(0) - class Card: possible_moves = ['forward', 'forward x2', 'forward x3', 'backward', 'turn left', 'turn right', 'turn around'] card_counter = 0 @@ -70,18 +70,57 @@ class Robot: self.board = board + # mark the tile on the board as occupied + self.board[(x,y)].occupier = self + + def get_accessed_tiles(self, count): + # create a list of all tiles the robot would enter if it drives steps forward tiles = [] - if self.orientation == '>': - tiles = [self.board.get((self.x + i, self.y)) for i in range(1, count + 1)] - elif self.orientation == '<': - tiles = [self.board.get((self.x - i, self.y)) for i in range(1, count + 1)] - elif self.orientation == '^': - tiles = [self.board.get((self.x, self.y - i)) for i in range(1, count + 1)] - elif self.orientation == 'v': - tiles = [self.board.get((self.x, self.y + i)) for i in range(1, count + 1)] + current_tile = self.board[(self.x, self.y)] + for i in range(1, count + 1): + current_tile = self.board.get(current_tile.get_neighbor_coordinates(self.orientation)) + tiles.append(current_tile) return tiles + def is_pushable(self, direction): + # check if the robot can be pushed in the given direction + # this is the case if there is a non-blocking tile next to the robot or if there is another robot that is pushable + robot_tile = self.board[(self.x, self.y)] + neighbor_tile = self.board.get(robot_tile.get_neighbor_coordinates(direction)) + if neighbor_tile is None: # neighbor tile could not be found -> robot would be pushed out of the board + return False + else: + if neighbor_tile.is_empty(): + return True + elif neighbor_tile.modifier == '#': # if there's a wall on the neighbor tile the robot cannot be pushed there + return False + else: + # this means there's another robot on the neighbor tile -> check if it can be pushed away + return neighbor_tile.occupant.is_pushable(direction) + + def has_opposite_orientation(self, direction): + opposites = [('^', 'v'), ('>', '<'), ('v', '^'), ('<', '>')] + 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)] + def move(self, type): pass @@ -100,8 +139,23 @@ class Tile: self.x = x self.y = y + def get_neighbor_coordinates(self, direction): + # get the coordinates of the neighboring tile in the given direction + if direction == '^': + return (self.x, self.y - 1) + elif direction == '>': + return (self.x + 1, self.y) + elif direction == 'v': + return (self.x, self.y + 1) + elif direction == '<': + return (self.x - 1, self.y) + else: + print("error: unknown direction") + sys.exit(1) + def is_empty(self): - return self.occupant is None + # check if the tile is non-empty and does not contain a wall + return self.occupant is None and self.modifier != '#' def __str__(self): if self.is_empty(): @@ -119,16 +173,20 @@ class Board: def __init__(self): self.board = {} - for x in range(Board.x_dims): - for y in range(Board.y_dims): - if x == 0 and (y >= 1) and (y <= 4): + for x in range(Board.x_dims + 2): + for y in range(Board.y_dims + 2): + if (x == 0) or (x == Board.x_dims + 1) or (y == 0) or (y == Board.y_dims + 1): + # place walls around the board + self.board[(x, y)] = Tile(x, y, '#') + elif x == 1 and (y >= 1) and (y <= 4): self.board[(x, y)] = Tile(x, y, 'v') else: - self.board[(x, y)] = Tile(x, y) + self.board[(x,y)] = Tile(x,y) self.robots = {} - self.robots[0] = Robot(0, 0, '<', 0, self.board) - self.robots[1] = Robot(2, 0, '^', 1, self.board) + self.robots[0] = Robot(3, 1, '<', 0, self.board) + self.robots[1] = Robot(1, 1, 'v', 1, self.board) + def handle_single_action(self, action, robot): if 'forward' in action: # driving forward @@ -141,19 +199,45 @@ class Board: accessed_tiles = robot.get_accessed_tiles(move_count) for tile in accessed_tiles: - if tile is None: # robot tries to access a tile outside of the board - # TODO take robot out of the game + if tile.modifier == '#': # robot hits a wall -> stop the robot + pass + elif tile.modifier == 'X': # robot drives into a pit -> take damage pass elif any([(tile.x, tile.y) == (r.x, r.y) for r in - self.robots]): # robots hits a tile occupied by another robot - # TODO move "pushed" robot by one tile: - # -> get current orientation - # -> turn the "pushed" robot to face in the same direction as the "pushing" robot - # -> move the "pushed" robot one step forward (while handling the move action of the robot recursively and pushing other robots as required) - # -> turn the "pushed" robot back to the original orientation - pass + 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 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 elif action == 'backward': @@ -183,26 +267,27 @@ class Board: self.handle_single_action(action, robot) + # apply the actions caused by board elements at the end of the phase pass def __str__(self): - output = '#' * (Board.x_dims + 2) + '\n' - for y in range(Board.y_dims): - output += '#' - for x in range(Board.x_dims): + #output = '#' * (Board.x_dims + 2) + '\n' + output = '' + for y in range(Board.y_dims+2): + for x in range(Board.x_dims+2): if any((r.x, r.y) == (x,y) for r in self.robots.values()): r = list(filter(lambda r: (r.x,r.y) == (x,y), self.robots.values()))[0] output += str(r.id) else: output += str(self.board[(x, y)]) - output += '#\n' - output += '#' * (Board.x_dims + 2) + output += '\n' + #output += '#' * (Board.x_dims + 2) return output if __name__ == "__main__": n = 5 - player_1_cards = deck.draw_cards(3) - player_2_cards = deck.draw_cards(3) + 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) cards_1 = [(0, c) for c in player_1_cards] cards_2 = [(1, c) for c in player_2_cards]