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 def __init__(self): self.number = Card.card_counter Card.card_counter += 1 self.action = random.choice(Card.possible_moves) self.priority = random.randint(0, 100) def __str__(self): return "Card No. " + str(self.number) + " " + self.action + " " + str(self.priority) def __repr__(self): return self.action + " (" + str(self.priority) + ")" class CardDeck: def __init__(self, n=84): self.deck = {} # generate cards for i in range(0, n): self.deck[i] = Card() self.dealt = set() self.discard_pile = set() def draw_cards(self, n=1): available = set(self.deck.keys()).difference(self.dealt) # print("{} cards are available".format(len(available))) if len(available) < n: drawn = list(available) # give out remaining cards # print("drawing remaining {} cards".format(len(drawn))) self.dealt = self.dealt.union(drawn) # put the cards from the discard pile back into the game self.dealt = self.dealt - self.discard_pile self.discard_pile = set() # reset the discard pile # draw rest of cards available = set(self.deck.keys()).difference(self.dealt) # print("drawing another {} cards".format(n - len(drawn))) drawn += random.sample(available, n - len(drawn)) else: drawn = random.sample(available, n) # print("cards drawn: {}".format(drawn)) self.dealt = self.dealt.union(drawn) return [self.deck[i] for i in drawn] def return_cards(self, cards): self.discard_pile = self.discard_pile.union(set([c.number for c in cards])) pass deck = CardDeck() class Robot: def __init__(self, x, y, orientation, id, board): self.x = x self.y = y self.orientation = orientation self.id = id 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 = [] 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 def __str__(self): return str(self.id) class Tile: # possible modifiers: # conveyors: <, >, ^, v # repair station: r # flag: f def __init__(self, x, y, modifier=None): self.modifier = modifier self.occupant = None 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): # 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(): if self.modifier is None: return ' ' else: return self.modifier else: return str(self.occupant) class Board: x_dims = 12 # number of tiles in x direction y_dims = 6 # number of tiles in y direction def __init__(self): self.board = {} 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.robots = {} 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 if "x2" in action: move_count = 2 elif "x3" in action: move_count = 3 else: move_count = 1 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 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 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': # basically do the same as with forward pass elif action == 'turn left': pass elif action == 'turn right': pass elif action == 'turn around': pass def apply_actions(self, cards): # apply the actions to the board and generate a list of movement commands for i, phase in enumerate(cards): # process register phases print("processing phase {}".format(i+1)) # sort actions by priority sorted_actions = sorted(phase, key=lambda a: a[1].priority) for a in sorted_actions: robot_id = a[0] robot = self.robots[robot_id] action = a[1].action print("robot {} action {}".format(robot, action)) 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' 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) return output 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) cards_1 = [(0, c) for c in player_1_cards] cards_2 = [(1, c) for c in player_2_cards] chosen_cards = list(zip(cards_1, cards_2)) b = Board() print(b) b.apply_actions(chosen_cards)