Exemple #1
0
def agent(obs, conf):
    global directions

    obs = Observation(obs)
    conf = Configuration(conf)
    board = np.zeros((7, 11), dtype=int)
    
    #Obstacle-ize your opponents
    for ind, goose in enumerate(obs.geese):
        if ind == obs.index or len(goose) == 0:
            continue
        for direction in range(4):
            moved = move(goose, direction)
            for part in moved:
                board[part//11][part%11] -= 1
    
    #Obstacle-ize your body, except the last part
    if len(obs.geese[obs.index]) > 1:
        for k in obs.geese[obs.index][:-1]:
            board[k//11][k%11] -= 1
            
    #Count food only if there's no chance an opponent will meet you there
    for f in obs.food: 
        board[f//11][f%11] += (board[f//11][f%11] == 0)
        
    return directions[greedy_choose(obs.geese[obs.index][0], board)]
Exemple #2
0
    def rule_based_action(self, player):
        from kaggle_environments.envs.hungry_geese.hungry_geese import (
            Observation,
            Configuration,
            Action,
            GreedyAgent,
        )

        action_map = {
            "N": Action.NORTH,
            "S": Action.SOUTH,
            "W": Action.WEST,
            "E": Action.EAST,
        }

        agent = GreedyAgent(Configuration({"rows": 7, "columns": 11}))
        agent.last_action = (
            action_map[self.ACTION[self.last_actions[player]][0]]
            if player in self.last_actions
            else None
        )
        obs = {
            **self.obs_list[-1][0]["observation"],
            **self.obs_list[-1][player]["observation"],
        }
        action = agent(Observation(obs))
        return self.ACTION.index(action)
Exemple #3
0
 def process_state(self, obs_dict, config_dict):
     observation = Observation(obs_dict)
     configuration = Configuration(config_dict)
     player_index = observation.index
     b_state={
         'blank': 0, # Empty cell
         'x': -1, # Head of opponent player
         'h': 1, # Head of current player
         'b': -2, # Body cell
         'f': 2, # Food
     }
     # строим доску
     board = b_state['blank'] * np.zeros(N_CELL).astype(int).reshape((N_ROWS, N_COLS))
     # гуси
     geese = observation.geese
     for goose in geese:
         for i, cell in enumerate(goose):
             row, col = row_col(cell, configuration.columns)
             board[row][col] = b_state['b']
     for i in range(len(geese)):
         if(len(geese[i]) <= 0):
             continue
         head_row, head_col = row_col(geese[i][0], configuration.columns)
         if player_index == i:
             board[head_row][head_col] = b_state['h']
         else:
             board[head_row][head_col] = b_state['x']
     # food
     foods = observation.food
     for food in foods:
         food_row, food_col = row_col(food, configuration.columns)
         board[food_row][food_col] = b_state['f']
     board = self.centroid_agent(board, geese[player_index][0], configuration)
     return board
    def local(self, observation, configuration):
        observation = observation if isinstance(
            observation, Observation) else Observation(observation)
        player = self._world.player(observation.index)

        actionsMask, actionsMapping = player.validMoves()
        return player.raw, actionsMask, actionsMapping
def agent(obs_dict, config_dict):
    global ralph_last_action

    #np.set_printoptions(precision=2)
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)

    astar = AStar(observation, configuration)
    path = astar.search()
    choice = "WEST"
    if (path is not None):
        cam = []
        if (len(path.caminho) > 1):
            cam = path.caminho[1]
        else:
            cam = path.next_p
        choice = get_choice(cam, astar.start, astar.rows, astar.cols)
        print(choice)
        '''for line in astar.heu_map:
            print(line)'''
    else:
        if (ralph_last_action[observation.index] is not None):
            choice = opposite(
                get_choice(ralph_last_action[observation.index], astar.start,
                           astar.rows, astar.cols))
        else:
            coice = "WEST"

        print()
        print()
        print()
    ralph_last_action[observation.index] = astar.start
    return choice
Exemple #6
0
 def getNextBoard(self, actions):
     self.prev_actions = actions
     obs, _, done, info = self.env.step(actions)
     obs = Observation(obs)
     reward = self.getReward(obs) if done else None
     board = self.getBoard(obs, 0)
     return board, reward, done, info
Exemple #7
0
    def rule_based_action(self, player):
        from kaggle_environments.envs.hungry_geese.hungry_geese import Observation, Configuration, Action, GreedyAgent
        action_map = {'N': Action.NORTH, 'S': Action.SOUTH, 'W': Action.WEST, 'E': Action.EAST}

        agent = GreedyAgent(Configuration({'rows': 7, 'columns': 11}))
        agent.last_action = action_map[self.ACTION[self.last_actions[player]][0]] if player in self.last_actions else None
        obs = {**self.obs_list[-1][0]['observation'], **self.obs_list[-1][player]['observation']}
        action = agent(Observation(obs))
        return self.ACTION.index(action)
Exemple #8
0
def agent(obs_dict, config_dict):
    observation = Observation(obs_dict)
    player_index = observation.index
    geese = observation.geese
    food = observation.food
    step = observation.step

    current_node = Node(geese=geese, food=food, step=step, max_depth=4)

    return manager(current_node=current_node, max_time=.25, player_index=player_index)
Exemple #9
0
def agent(observation, configuration):
    observation = Observation(observation)
    configuration = Configuration(configuration)
    rows, columns = configuration.rows, configuration.columns

    food = observation.food
    geese = observation.geese
    opponents = [
        goose
        for index, goose in enumerate(geese)
        if index != observation.index and len(goose) > 0
    ]

    # Don't move adjacent to any heads
    head_adjacent_positions = {
        opponent_head_adjacent
        for opponent in opponents
        for opponent_head in [opponent[0]]
        for opponent_head_adjacent in adjacent_positions(opponent_head, rows, columns)
    }
    # Don't move into any bodies
    bodies = {position for goose in geese for position in goose[0:-1]}
    # Don't move into tails of heads that are adjacent to food
    tails = {
        opponent[-1]
        for opponent in opponents
        for opponent_head in [opponent[0]]
        if any(
            adjacent_position in food
            # Head of opponent is adjacent to food so tail is not safe
            for adjacent_position in adjacent_positions(opponent_head, rows, columns)
        )
    }

    # Move to the closest food
    position = geese[observation.index][0]
    actions = {
        action: min_distance(new_position, food, columns)
        for action in Action
        for new_position in [translate(position, action, columns, rows)]
        if (
            new_position not in head_adjacent_positions and
            new_position not in bodies and
            new_position not in tails
        )
    }

    if any(actions):
        return min(actions, key=actions.get).name

    return random_agent()
    def encodedStates():
        for env, world in zip(environments, worlds):
            if not env.done:
                world.update(Observation(env.state[0]['observation']))

        states = []
        observations = []
        for env, agents in EA:
            for obs, agent in zip(env.state, agents):
                state = agent.processObservations(obs['observation'],
                                                  env.configuration,
                                                  obs['alive'])
                states.append(state)
                observations.append(obs)
        return states, observations
def agent_ORIGINAL(obs_dict, config_dict):
    """This agent always moves toward observation.food[0] but does not take advantage of board wrapping"""
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)
    player_index = observation.index
    player_goose = observation.geese[player_index]
    player_head = player_goose[0]
    player_row, player_column = row_col(player_head, configuration.columns)
    food = observation.food[0]
    food_row, food_column = row_col(food, configuration.columns)

    if food_row > player_row:
        return Action.SOUTH.name
    if food_row < player_row:
        return Action.NORTH.name
    if food_column > player_column:
        return Action.EAST.name
    return Action.WEST.name
Exemple #12
0
  def processObservations(self, obs_dict, config_dict, alive=True):
    if not alive: return self._state.EmptyObservations
    
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)
    
    if self._agent is None:
      self._agent = GreedyAgent(configuration)

    state, validAct, actionsMapping = self._state.local(observation, configuration)
    
    self._agent.last_action = Action[self._prevAction] # sync
    self._actName = self._agent(observation)
    if self._actName not in actionsMapping:
      self._actName = next(
        (act for i, act in enumerate(actionsMapping) if 0 < validAct[i]),
        actionsMapping[1]
      )
    self._actID = actionsMapping.index(self._actName)
    return state
Exemple #13
0
def agent(obs_dict, config_dict):
    global ralph_last_action
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)

    #print("Player")
    # Define my player
    player_index = observation.index
    my_goose = Goose(observation.geese[player_index], configuration)
    player_row, player_column = my_goose.get_head_pos()

    #print("Enemies")
    # Define the enemies players
    enemies_indexes = []
    for i in range(0, len(observation.geese)):
        if i != player_index:
            enemies_indexes.append(i)

    enemies_geese = []
    for enemy in enemies_indexes:
        enemies_geese.append(Goose(observation.geese[enemy], configuration))

#print("Food")
# Get the nearest food position
    food_row, food_column = get_min_distance_food_pos(my_goose.get_head(),
                                                      observation.food,
                                                      configuration.columns)

    #print("Possible Moves")
    # List the possible moves
    possible_moves = ['SOUTH', 'NORTH', 'WEST', 'EAST']
    if (ralph_last_action[player_index] in possible_moves):
        possible_moves.remove(ralph_last_action[player_index])

#print("Possible Moves and colisions")
    moves = []
    for i in range(0, len(possible_moves)):
        move = possible_moves[i]
        n_r, n_c = my_goose.get_next_position(move)
        if (not is_any_enemy_there(enemies_geese, n_r, n_c)
                and not my_goose.am_i_there(n_r, n_c)
                and not will_be_any_enemy_there(enemies_geese, n_r, n_c)):
            moves.append(move)

#print("Possible Moves by food")
# From the remaining possible moves, wich one get me nearest to the food?
    min_food_move = 0
    if (len(moves) > 0):
        for i in range(0, len(moves)):
            n_r1, n_c1 = my_goose.get_next_position(moves[i])
            n_r2, n_c2 = my_goose.get_next_position(moves[min_food_move])
            if (get_distance(food_row, food_column, n_r1, n_c1) < get_distance(
                    food_row, food_column, n_r2, n_c2)):
                min_food_move = i

#print("Select Move")
    if (len(moves) > 0 and min_food_move is not None):
        return_action = moves[min_food_move]
    else:
        return_action = 'NORTH'

#print("Last Move")
# Refresh the last move
    ralph_last_action[player_index] = opposite(return_action)
    #print(return_action)
    #print()
    return return_action
Exemple #14
0
 def get_init_obs(self):
     return Observation(self.env.reset(self.num_agents))
Exemple #15
0
    def preprocess_env(self, observation, configuration):
        observation = Observation(observation)
        configuration = Configuration(configuration)

        self.rows, self.columns = configuration.rows, configuration.columns
        self.my_index = observation.index
        self.hunger_rate = configuration.hunger_rate
        self.min_food = configuration.min_food

        self.my_head, self.my_tail = observation.geese[
            self.my_index][0], observation.geese[self.my_index][-1]
        self.my_body = [pos for pos in observation.geese[self.my_index][1:-1]]

        self.geese = [
            g for i, g in enumerate(observation.geese)
            if i != self.my_index and len(g) > 0
        ]
        self.geese_cells = [pos for g in self.geese for pos in g if len(g) > 0]

        self.occupied = [p for p in self.geese_cells]
        self.occupied.extend([p for p in observation.geese[self.my_index]])

        self.heads = [
            g[0] for i, g in enumerate(observation.geese)
            if i != self.my_index and len(g) > 0
        ]
        self.bodies = [
            pos for i, g in enumerate(observation.geese) for pos in g[1:-1]
            if i != self.my_index and len(g) > 2
        ]
        self.tails = [
            g[-1] for i, g in enumerate(observation.geese)
            if i != self.my_index and len(g) > 1
        ]
        self.food = [f for f in observation.food]

        self.adjacent_to_heads = [
            pos for head in self.heads
            for pos in self._adjacent_positions(head)
        ]
        self.adjacent_to_bodies = [
            pos for body in self.bodies
            for pos in self._adjacent_positions(body)
        ]
        self.adjacent_to_tails = [
            pos for tail in self.tails
            for pos in self._adjacent_positions(tail)
        ]
        self.adjacent_to_geese = self.adjacent_to_heads + self.adjacent_to_bodies
        self.danger_zone = self.adjacent_to_geese

        #Cell occupation
        self.cell_states = [
            CellState.EMPTY.value for _ in range(self.rows * self.columns)
        ]
        for g in self.geese:
            for pos in g:
                self.cell_states[pos] = CellState.GOOSE.value
        for pos in self.heads:
            self.cell_states[pos] = CellState.GOOSE.value
        for pos in self.my_body:
            self.cell_states[pos] = CellState.GOOSE.value

        #detect dead-ends
        self.dead_ends = []
        for pos_i, _ in enumerate(self.cell_states):
            if self.cell_states[pos_i] != CellState.EMPTY.value:
                continue
            adjacent = self._adjacent_positions(pos_i)
            adjacent_states = [
                self.cell_states[adj_pos] for adj_pos in adjacent
                if adj_pos != self.my_head
            ]
            num_blocked = sum(adjacent_states)
            if num_blocked >= (CellState.GOOSE.value * 3):
                self.dead_ends.append(pos_i)

        #check for extended dead-ends
        new_dead_ends = [pos for pos in self.dead_ends]
        while new_dead_ends != []:
            for pos in new_dead_ends:
                self.cell_states[pos] = CellState.GOOSE.value
                self.dead_ends.append(pos)

            new_dead_ends = []
            for pos_i, _ in enumerate(self.cell_states):
                if self.cell_states[pos_i] != CellState.EMPTY.value:
                    continue
                adjacent = self._adjacent_positions(pos_i)
                adjacent_states = [
                    self.cell_states[adj_pos] for adj_pos in adjacent
                    if adj_pos != self.my_head
                ]
                num_blocked = sum(adjacent_states)
                if num_blocked >= (CellState.GOOSE.value * 3):
                    new_dead_ends.append(pos_i)
Exemple #16
0
def straightforward_bfs(obs_dict, config_dict):
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)
    player_index = observation.index

    player_goose = observation.geese[player_index]
    player_head = player_goose[0]
    start_row, start_col = row_col(player_head, configuration.columns)

    mask = np.zeros((configuration.rows, configuration.columns))
    for current_id in range(4):
        current_goose = observation.geese[current_id]
        for block in current_goose:
            current_row, current_col = row_col(block, configuration.columns)
            mask[current_row, current_col] = -1

    food_coords = []

    for food_id in range(configuration.min_food):
        food = observation.food[food_id]
        current_row, current_col = row_col(food, configuration.columns)
        mask[current_row, current_col] = 2
        food_coords.append((current_row, current_col))

    last_action = bfs(start_row, start_col, mask, food_coords)

    global LAST_ACTION
    up_x = start_row + 1 if start_row != 6 else 0
    down_x = start_row - 1 if start_row != 0 else 6
    left_y = start_col - 1 if start_col != 0 else 10
    right_y = start_col + 1 if start_col != 10 else 0

    step = Action.NORTH.name
    if last_action == 0:
        step = Action.SOUTH.name
        if LAST_ACTION == Action.NORTH.name:
            if mask[down_x, start_col] != -1:
                step = Action.NORTH.name
            elif mask[start_row, left_y] != -1:
                step = Action.WEST.name
            elif mask[start_row, right_y] != -1:
                step = Action.EAST.name
    if last_action == 1:
        step = Action.NORTH.name
        if LAST_ACTION == Action.SOUTH.name:
            if mask[up_x, start_col] != -1:
                step = Action.SOUTH.name
            elif mask[start_row, left_y] != -1:
                step = Action.WEST.name
            elif mask[start_row, right_y] != -1:
                step = Action.EAST.name
    if last_action == 2:
        step = Action.WEST.name
        if LAST_ACTION == Action.EAST.name:
            if mask[up_x, start_col] != -1:
                step = Action.SOUTH.name
            elif mask[down_x, start_col] != -1:
                step = Action.NORTH.name
            elif mask[start_row, right_y] != -1:
                step = Action.EAST.name
    if last_action == 3:
        step = Action.EAST.name
        if LAST_ACTION == Action.WEST.name:
            if mask[up_x, start_col] != -1:
                step = Action.SOUTH.name
            elif mask[down_x, start_col] != -1:
                step = Action.NORTH.name
            elif mask[start_row, left_y] != -1:
                step = Action.WEST.name
    LAST_ACTION = step

    return step
def agent(obs_dict, config_dict):
    global last_move
    global last_eaten
    global last_size
    global step
    #print ("==============================================")
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)
    player_index = observation.index
    player_goose = observation.geese[player_index]
    player_head = player_goose[0]
    player_row, player_column = row_col(player_head, configuration.columns)

    if (len(player_goose) > last_size):
        last_size = len(player_goose)
        last_eaten = step
    step += 1

    moves = {1: 'SOUTH', 2: 'NORTH', 3: 'EAST', 4: 'WEST'}

    board = np.zeros((7, 11))

    # Adding food to board
    for food in observation.food:
        x, y = row_col(food, configuration.columns)
        #print ("Food cell on ({}, {})".format(x, y))
        board[x, y] = FOOD_CELL
    # Adding geese to the board
    for i in range(4):
        goose = observation.geese[i]
        # Skip if goose is dead
        if len(goose) == 0:
            continue
        # If it's an opponent
        if i != player_index:
            x, y = row_col(goose[0], configuration.columns)
            # Add possible head movements for it
            for px, py in get_nearest_cells(x, y):
                #print ("Head possible cell on ({}, {})".format(px, py))
                # If one of these head movements may lead the goose
                # to eat, add tail as BODY_CELL, because it won't move.
                if board[px, py] == FOOD_CELL:
                    x_tail, y_tail = row_col(goose[-1], configuration.columns)
                    #print ("Adding tail on ({}, {}) as the goose may eat".format(x_tail, y_tail))
                    board[x_tail, y_tail] = BODY_CELL
                board[px, py] = HEAD_POSSIBLE_CELL
        # Adds goose body without tail (tail is previously added only if goose may eat)
        for n in goose[:-1]:
            x, y = row_col(n, configuration.columns)
            #print ("Body cell on ({}, {})".format(x, y))
            board[x, y] = BODY_CELL

    # Adding my head to the board
    x, y = row_col(player_head, configuration.columns)
    #print ("My head is at ({}, {})".format(x, y))
    board[x, y] = MY_HEAD

    # Debug board
    #print (board)

    # Iterate over food and geese in order to compute distances for each one
    food_race = {}
    for food in observation.food:
        food_race[food] = {}
        for i in range(4):
            goose = observation.geese[i]
            if len(goose) == 0:
                continue
            food_race[food][i] = cell_distance(goose[0], food, configuration)

    # The best food is the least coveted
    best_food = None
    best_distance = float('inf')
    best_closest_geese = float('inf')
    for food in food_race:
        #print ("-> Food on {}".format(row_col(food, configuration.columns)))
        my_distance = food_race[food][player_index]
        #print (" - My distance is {}".format(my_distance))
        closest_geese = 0
        for goose_id in food_race[food]:
            if goose_id == player_index:
                continue
            if food_race[food][goose_id] <= my_distance:
                closest_geese += 1
        #print (" - There are {} closest geese".format(closest_geese))
        if (closest_geese < best_closest_geese):
            best_food = food
            best_distance = my_distance
            best_closest_geese = closest_geese
            #print ("  * This food is better")
        elif (closest_geese
              == best_closest_geese) and (my_distance <= best_distance):
            best_food = food
            best_distance = my_distance
            best_closest_geese = closest_geese
            #print ("  * This food is better")

    # Now that the best food has been found, check if the movement towards it is safe.
    # Computes every available move and then check for move priorities.
    if len(player_goose) > 1:
        food_movements = move_towards(player_head, player_goose[1], best_food,
                                      configuration)
    else:
        food_movements = move_towards(player_head, player_head, best_food,
                                      configuration)
    all_movements = get_all_movements(player_head, configuration)
    # Excluding last movement reverse
    food_movements = [
        move for move in food_movements if move[2] != REVERSE_MOVE[last_move]
    ]
    all_movements = [
        move for move in all_movements if move[2] != REVERSE_MOVE[last_move]
    ]
    #print ("-> Available food moves: {}".format(food_movements))
    #print ("-> All moves: {}".format(all_movements))

    # Trying to reach goal size of 4
    if (len(player_goose) < 4):
        # 1. Food movements that are safe and not closed
        for food_movement in food_movements:
            #print ("Food movement {}".format(food_movement))
            if is_safe(food_movement,
                       board) and not is_closed(food_movement, board):
                #print ("It's safe! Let's move {}!".format(moves[food_movement[2]]))
                last_move = food_movement[2]
                return moves[food_movement[2]]  # Move here

        # 2. Any movement safe and not closed
        for movement in all_movements:
            #print ("Movement {}".format(movement))
            if is_safe(movement, board) and not is_closed(movement, board):
                #print ("It's safe! Let's move {}!".format(moves[movement[2]]))
                last_move = movement[2]
                return moves[movement[2]]  # Move here

        # 3. Food movements half safe and not closed
        for food_movement in food_movements:
            if is_half_safe(food_movement,
                            board) and not is_closed(food_movement, board):
                #print ("Food movement {} is half safe, I'm going {}!".format(food_movement, moves[food_movement[2]]))
                last_move = food_movement[2]
                return moves[food_movement[2]]  # Move here

        # 4. Any movement half safe and not closed
        for movement in all_movements:
            if is_half_safe(movement,
                            board) and not is_closed(movement, board):
                #print ("Movement {} is half safe, I'm going {}!".format(movement, moves[movement[2]]))
                last_move = movement[2]
                return moves[movement[2]]  # Move here

        # 5. Food movements that are safe
        for food_movement in food_movements:
            #print ("Food movement {}".format(food_movement))
            if is_safe(food_movement, board):
                #print ("It's safe! Let's move {}!".format(moves[food_movement[2]]))
                last_move = food_movement[2]
                return moves[food_movement[2]]  # Move here

        # 6. Any movement safe
        for movement in all_movements:
            #print ("Movement {}".format(movement))
            if is_safe(movement, board):
                #print ("It's safe! Let's move {}!".format(moves[movement[2]]))
                last_move = movement[2]
                return moves[movement[2]]  # Move here

        # 7. Food movements half safe
        for food_movement in food_movements:
            if is_half_safe(food_movement, board):
                #print ("Food movement {} is half safe, I'm going {}!".format(food_movement, moves[food_movement[2]]))
                last_move = food_movement[2]
                return moves[food_movement[2]]  # Move here

        # 8. Any movement half safe
        for movement in all_movements:
            if is_half_safe(movement, board):
                #print ("Movement {} is half safe, I'm going {}!".format(movement, moves[movement[2]]))
                last_move = movement[2]
                return moves[movement[2]]  # Move here

    # Just trying to walk in circles
    else:

        # Delete food moves
        food_coordinates = []
        for food in food_race:
            x_food, y_food = row_col(food, configuration.columns)
            food_coordinates.append((x_food, y_food))
        available_moves = []
        for move in all_movements:
            for (x_food, y_food) in food_coordinates:
                if (move[0] != x_food) or (move[1] != y_food):
                    available_moves.append(move)

        # 1. Run in circles if you can
        circle_move = CIRCLE_MOVE[last_move]
        for move in available_moves:
            if (move[2] == circle_move) and (is_safe(
                    move, board)) and not (is_closed(move, board)):
                last_move = move[2]
                return moves[move[2]]

        # 2. Any movement safe and not closed
        for movement in all_movements:
            #print ("Movement {}".format(movement))
            if is_safe(movement, board) and not is_closed(movement, board):
                #print ("It's safe! Let's move {}!".format(moves[movement[2]]))
                last_move = movement[2]
                return moves[movement[2]]  # Move here

        # 3. Any movement half safe and not closed
        for movement in all_movements:
            if is_half_safe(movement,
                            board) and not is_closed(movement, board):
                #print ("Movement {} is half safe, I'm going {}!".format(movement, moves[movement[2]]))
                last_move = movement[2]
                return moves[movement[2]]  # Move here

        # 4. Any movement safe
        for movement in all_movements:
            #print ("Movement {}".format(movement))
            if is_safe(movement, board):
                #print ("It's safe! Let's move {}!".format(moves[movement[2]]))
                last_move = movement[2]
                return moves[movement[2]]  # Move here

        # 5. Any movement half safe
        for movement in all_movements:
            if is_half_safe(movement, board):
                #print ("Movement {} is half safe, I'm going {}!".format(movement, moves[movement[2]]))
                last_move = movement[2]
                return moves[movement[2]]  # Move here

    # Finally, if all moves are unsafe, randomly pick one
    rand_pick = np.random.randint(4) + 1
    last_move = rand_pick
    #print ("Yeah whatever, I'm going {}".format(moves[rand_pick]))
    return moves[rand_pick]
Exemple #18
0
def agent(obs_dict, config_dict):
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)
    player_index = observation.index
    player_goose = observation.geese[player_index]
    player_head = player_goose[0]
    player_tail = player_goose[-1]
    my_X, my_Y = row_col(player_head, configuration.columns)
    my_tail_X, my_tail_Y = row_col(player_tail, configuration.columns)
    mask = np.zeros((configuration.rows, configuration.columns))
    
    global last_direction
    global iteration_number

    print("**iteration**: {}".format(iteration_number))
    print("my head at {} - {}".format([my_X, my_Y], last_direction))
    iteration_number+=1
    
    food_location = []
    available_steps = {}
    other_heads = [] # head of competitor geese
    body_cells = []
    other_tails = []
    last_direction_updated = False

    # add all directions
    possible_positions = get_nearest_cells(my_X, my_Y)
    for x, y in possible_positions:
        direct = '-'
        if((x-1 == my_X) or (x==0 and my_X==6)) and last_direction != 'NORTH':
            direct = 'SOUTH'
        elif((x+1 == my_X) or (x==6 and my_X==0)) and last_direction != 'SOUTH':
            direct = 'NORTH'
        elif((y-1 == my_Y) or (y==0 and my_Y==10)) and last_direction != 'WEST':
            direct = 'EAST'
        elif((y+1 == my_Y) or (y==10 and my_Y==0)) and last_direction != 'EAST':
            direct = 'WEST'
        if direct != '-':
            available_steps[(x,y)] = [direct,0]

    for food in observation.food:
        x,y = row_col(food, configuration.columns)
        mask[x, y] = 2
        food_location.append([x,y])
    print("food locations are {}".format(food_location))
  
    #   where other goose can come
    danger_area = {}
    for i in range(len(observation.geese)):
        opp_goose = observation.geese[i]
        if len(opp_goose) == 0:
            continue
        for ind, goose in enumerate(opp_goose):
            x, y = row_col(goose, configuration.columns)
            mask[x, y] = -1
            # and mark the occupied cells if not head or tail of goose
            if ind != 0 and ind != len(opp_goose) - 1:
                body_cells.append([x,y])
            if ind != 0 and ind == len(opp_goose) - 1:
                other_tails.append([x,y])
            if (x, y) in available_steps.keys():
                # let's remove all cells that are not available from available steps
                available_steps.pop((x,y))
            if (x, y) in danger_area.keys():
                # let's remove all cells that are not available from danger steps
                danger_area.pop((x,y))
            if not len(available_steps) and not len(danger_area): #if steps is empty kill by NORTH direction
                print("kill")
                if player_head!=player_tail and (my_tail_X, my_tail_Y) in possible_positions:
                    last_direction = get_direction((my_X, my_Y), (my_tail_X, my_tail_Y))
                else:
                    last_direction = 'NORTH'
                last_direction_updated = True
                break
        if last_direction_updated:
            break
        if i != player_index:
            x, y = row_col(opp_goose[0], configuration.columns)
            # add competition goose head
            other_heads.append([x, y])
            possible_moves = get_nearest_cells(x, y) # head can move anywhere
            for [x, y] in possible_moves:
                if (x,y) in available_steps.keys() and [x,y] in food_location:
                    danger_area[(x,y)] = available_steps[(x,y)]
                    available_steps.pop((x, y)) #only safe moves
                    food_location.remove([x,y]) #don't consider this food
                elif (x,y) in available_steps.keys() and len(available_steps)+len(danger_area) > 1:
                    danger_area[(x,y)] = available_steps[(x,y)]
                    available_steps.pop((x, y)) #only safe moves
        if last_direction_updated:
            break
    
    print("body cells are {}".format(body_cells))
    print("available steps are {}".format(available_steps))
    print("other heads at {}".format(other_heads))
    print("other tails at {}".format(other_tails))
    print("danger area at {}".format(danger_area))

    my_head = [my_X, my_Y]
    if not last_direction_updated:
            last_direction = my_move(food_location, available_steps, other_heads, my_head, last_direction, body_cells, danger_area, mask, other_tails)
            last_direction_updated = True

    return last_direction
Exemple #19
0
 def encodeObservations(self, obs_dict, config_dict):
     observation = Observation(obs_dict)
     configuration = Configuration(config_dict)
     return self._state.local(observation, configuration)
def agent(obs_dict, config_dict):
    """This agent always moves toward observation.food[0] but does not take advantage of board wrapping"""
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)
    maze_col = configuration.columns
    maze_rows = configuration.rows
    maze = np.zeros([maze_rows, maze_col])

    print("Player")
    # Define my player

    player_index = observation.index
    player_goose = observation.geese[player_index]
    player_head = player_goose[0]
    player_row, player_column = row_col(player_head, configuration.columns)
    start = (player_row, player_column)
    print(start)
    #Define Food

    hipotenuse_low = 14
    closest_food = observation.food[0]
    for food in observation.food:
        food_row, food_column = row_col(food, configuration.columns)
        hipotenuse = sqrt((player_row - food_row)**2 +
                          (player_column - food_column)**2)
        if hipotenuse < hipotenuse_low:
            hipotenuse_low = hipotenuse
            closest_food = food

    end = row_col(closest_food, configuration.columns)

    # Define the enemies players
    enemies_indexes = observation.geese
    del enemies_indexes[player_index]

    for enemies in enemies_indexes:
        for enemies_pos in enemies:
            enemie_row, enemie_col = row_col(enemies_pos,
                                             configuration.columns)
            maze[enemie_row, enemie_col] = 1

    for player_pos in player_goose[1:]:
        player_rw, player_col = row_col(player_pos, configuration.columns)
        maze[player_rw, player_col] = 1

    path = astar(maze, start, end, False)
    next_move = path[1]
    print(next_move)
    print(player_column)
    print(player_row)
    x = int(next_move[1]) - int(player_column)
    y = int(next_move[0]) - int(player_row)
    print(x, y)
    print(maze)
    if x == -1 and y == 0:
        print("WEST")
        return Action.WEST.name
    if x == 1 and y == 0:
        print("EAST")
        return Action.EAST.name
    if x == 0 and y == -1:
        print("NORTH")
        return Action.NORTH.name
    if x == 0 and y == 1:
        print("SOUTH")
        return Action.SOUTH.name
def agent(obs_dict, config_dict):
    
    global which_turn
    which_turn += 1
    
    t0= time.clock()
    f = open("./myfile1.txt", "a")
    f.write("\n")
    f.write("Starting file" + "\n")
    
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)
    player_index = observation.index
    player_goose = observation.geese[player_index]
    #player_goose is list of all bodypart positions
    player_head = player_goose[0]
    player_row, player_column = row_col(player_head, configuration.columns)
    start = (player_row, player_column)
    
    
    f.write("Commencing turn: " + str(which_turn))
    with open('./myfile2.txt', 'a') as testfile:
        testfile.write("Player_index: " + str(player_index) + " [0: White, 1: Blue, 2: Green, 3: Red], " + "\n")
    f.write("Player_index: " + str(player_index) + " [0: White, 1: Blue, 2: Green, 3: Red], " + "\n")
    """This agent always moves toward observation.food[0] but does not take advantage of board wrapping"""
    

    Matrix, MatrixNoFood, MatrixHeadAvoid1, MatrixHeadAvoid2 = fill_matrix(observation, configuration)
    
    f.write("Matrix filled" + "\n")
    
    
    
    
    
    [MatrixNoFood, MatrixHeadAvoid1, MatrixHeadAvoid2], start, shift = shift_matrix([MatrixNoFood, MatrixHeadAvoid1, MatrixHeadAvoid2], start)
    f.write("Matrix shifted" + "\n")
    f.write("Start coords " + str(start) + "\n")

    
    
    
    #MatrixToUse = MatrixNoFood
    #MatrixToUse = MatrixHeadAvoid2
    #MatrixToUse = MatrixHeadAvoid1
    #MatrixBackup1 = MatrixHeadAvoid1
    #MatrixBackup2 = MatrixNoFood
    
    #MatrixsToUse = [MatrixHeadAvoid2, MatrixHeadAvoid1, MatrixNoFood]
    
    MatrixsToUse = [MatrixHeadAvoid1, MatrixNoFood]
    
    
    #will replace player_row, player_column with 2d array that has all positions filled with all player information
    path = path_to_closest_food(observation, configuration, MatrixsToUse, start, shift)
    if path == "no path":
        player_index = observation.index
        player_goose = observation.geese[player_index]
        player_end = player_goose[len(player_goose)-1]
        player_end_row, player_end_column = row_col(player_end, configuration.columns)
        for MatrixToUse in MatrixsToUse:
            if path == "no path":
                if start == (player_end_row, player_end_column):
                    path = astar(MatrixToUse, start, (player_end_row+3, player_end_column+5))
                else:
                    path = astar(MatrixToUse, start, (player_end_row, player_end_column))
    f.write("Path: " + str(path) + "\n")
        
    whichMove =  which_direction(path)
    
    t1 = time.clock() - t0
    f.write("This is turn: " + str(int(which_turn)) + " , chosen movement: " )
    f.write(str(whichMove))
    f.write(" which took " + str(t1) + " seconds")
    f.close()
    return whichMove
Exemple #22
0
def agent(obs_dict, config_dict, gindex):
    #################################################
    # State retrieval
    #################################################
    #print("-----------")
    global last_action
    conf = Configuration(config_dict)
    obs = Observation(obs_dict)
    step = obs.step + 1
    my_idx = gindex
    my_goose = obs.geese[my_idx]
    my_head = my_goose[0]
    my_row, my_col = row_col(position=my_head, columns=conf.columns)
    if DEBUG:
        print("---------- Step #" + str(step), "- Player #" + str(obs.index))

    #################################################
    # Map update
    #################################################
    board = np.zeros((7, 11), dtype=int)

    # Add food to board
    for food in obs.food:
        food_row, food_col = row_col(position=food, columns=conf.columns)
        board[food_row, food_col] += FOOD_REWARD
        '''if DEBUG:
            print("food", food_row, food_col)'''

    # Iterate over geese to add geese data to board
    nb_geese = len(obs.geese)
    geese_lengths = []
    for i in range(nb_geese):
        '''if DEBUG:
            print("--- Goose #" + str(i))'''
        goose = obs.geese[i]
        potential_food_head = None

        # Iterate over cells of current goose
        goose_len = len(goose)
        geese_lengths.append(goose_len)
        '''if DEBUG:
            print("--- Goose #" + str(i) + " len " + str(goose_len))'''
        for j in range(goose_len):
            '''if DEBUG:
                print("--- Goose #" + str(i) + " cell " + str(j))'''
            goose_cell = goose[j]
            goose_row, goose_col = row_col(position=goose_cell,
                                           columns=conf.columns)

            # Check for food on neighbour cells when handling head
            if j == 0:
                potential_heads = get_neighbours(goose_row, goose_col)
                for potential_head in potential_heads:
                    for food in obs.food:
                        food_row, food_col = row_col(position=food,
                                                     columns=conf.columns)
                        if potential_head == (food_row, food_col):
                            potential_food_head = potential_head

            # Update rewards linked to body/tail
            if j < goose_len - 1:
                # Body or head
                board[goose_row, goose_col] += BODY_REWARD
                '''if DEBUG:
                    print("--- Goose #" + str(i) + " cell " + str(j) + " add BODY_REWARD")'''
            else:
                # Tail : may not move if goose eats
                if potential_food_head is not None:
                    board[goose_row, goose_col] += TAIL_REWARD
                    '''if DEBUG:
                        print("--- Goose #" + str(i) + " cell " + str(j) + " add TAIL_REWARD")'''

        # Update potential villain head positions
        if (i != my_idx) & (goose_len > 0):
            if potential_food_head is not None:
                # Head will prolly go to the food
                for potential_head in potential_heads:
                    if potential_head == potential_food_head:
                        if (board[potential_head[0], potential_head[1]] != BODY_REWARD) & \
                           (board[potential_head[0], potential_head[1]] != TAIL_REWARD):
                            board[
                                potential_head[0],
                                potential_head[1]] += PROBABLE_HEAD_FOOD_REWARD
                            '''if DEBUG:
                                print("--- Goose #" + str(i) + " cell " + str(j) + " add PROBABLE_HEAD_FOOD_REWARD")'''
                    else:
                        if (board[potential_head[0], potential_head[1]] != BODY_REWARD) & \
                           (board[potential_head[0], potential_head[1]] != TAIL_REWARD):
                            board[potential_head[0], potential_head[
                                1]] += IMPROBABLE_HEAD_FOOD_REWARD
                            '''if DEBUG:
                                print("--- Goose #" + str(i) + " cell " + str(j) + " add IMPROBABLE_HEAD_FOOD_REWARD")'''
            else:
                # Standard potential head reward
                for potential_head in potential_heads:
                    if (board[potential_head[0], potential_head[1]] != BODY_REWARD) & \
                       (board[potential_head[0], potential_head[1]] != TAIL_REWARD):
                        board[potential_head[0],
                              potential_head[1]] += POTENTIAL_HEAD_STD_REWARD
                        '''if DEBUG:
                            print("--- Goose #" + str(i) + " cell " + str(j) + " add POTENTIAL_HEAD_STD_REWARD")'''

    # Check if I'm the current longest Goose
    if (len(my_goose) >= max(geese_lengths) - 3) & (step > 8):
        # Chasing my tail as a defensive action makes sense
        my_tail_row, my_tail_col = row_col(position=my_goose[-1],
                                           columns=conf.columns)
        board[my_tail_row, my_tail_col] += TAIL_CHASE_REWARD
        '''if DEBUG:
            print("Adding TAIL_CHASE_REWARD for me")'''

    # Diffuse values in adjacent cells
    if DEBUG:
        print("Initial board :")
        print(board)
    new_board = board.copy()
    for i in range(7):
        for j in range(11):
            value = board[i, j]
            if value > DIFFUSE_START:
                # Should diffuse positive value
                neighbours = get_neighbours(i, j)
                for neighbour in neighbours:
                    # Level 1
                    new_board[neighbour] += (2 * DIFFUSE_POS_REWARD)

                    # Level 2
                    neighbours_lvl2 = get_neighbours(neighbour[0],
                                                     neighbour[1])
                    for neighbour_lvl2 in neighbours_lvl2:
                        new_board[neighbour_lvl2] += DIFFUSE_POS_REWARD
            elif value < -DIFFUSE_START:
                # Should diffuse negative value
                neighbours = get_neighbours(i, j)
                for neighbour in neighbours:
                    # Level 1
                    new_board[neighbour] += (2 * DIFFUSE_NEG_REWARD)

                    # Level 2
                    neighbours_lvl2 = get_neighbours(neighbour[0],
                                                     neighbour[1])
                    for neighbour_lvl2 in neighbours_lvl2:
                        new_board[neighbour_lvl2] += DIFFUSE_NEG_REWARD
    board = new_board

    # Add last_action data to board
    if last_action is not None:
        if last_action == Action.SOUTH.name:
            board[(my_row + 6) % 7, my_col] += REVERSE_LAST_REWARD
        elif last_action == Action.NORTH.name:
            board[(my_row + 8) % 7, my_col] += REVERSE_LAST_REWARD
        elif last_action == Action.EAST.name:
            board[my_row, (my_col + 10) % 11] += REVERSE_LAST_REWARD
        elif last_action == Action.WEST.name:
            board[my_row, (my_col + 12) % 11] += REVERSE_LAST_REWARD
        '''if DEBUG:
            print("Adding REVERSE_LAST_REWARD for me")'''

    if DEBUG:
        print("Final board :")
        print(board)

    #################################################
    # Choose best action
    #################################################
    chosen_action = None
    rewards = []
    potential_next = get_neighbours(my_row, my_col)
    for cell in potential_next:
        rewards.append(board[cell])
    choice = np.argmax(rewards)
    if choice == 0:
        chosen_action = Action.NORTH.name
    elif choice == 1:
        chosen_action = Action.WEST.name
    elif choice == 2:
        chosen_action = Action.SOUTH.name
    else:
        chosen_action = Action.EAST.name
    if DEBUG:
        print("chosen_action", chosen_action)
    last_action = chosen_action
    return chosen_action
Exemple #23
0
 def __call__(self, obs_dict, config_dict):
     self._world.update(Observation(obs_dict))
     return self.play(obs_dict, config_dict)[0]
def agent(obs_dict, config_dict):
    """This agent always moves toward observation.food[0] but does not take advantage of board wrapping"""
    global last_move
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)
    player_index = observation.index
    player_goose = observation.geese[player_index]
    player_head = player_goose[0]
    player_row, player_column = row_col(player_head, configuration.columns)
    possible_moves = [
        Action.SOUTH.name, Action.NORTH.name, Action.WEST.name,
        Action.EAST.name
    ]
    possible_move = None
    oldFoodDistanceToMe = 9999

    def getFoodsIndex():
        foods = []
        for i in range(len(observation.food)):
            food = observation.food[i]
            foods.append(row_col(food, configuration.columns))
        return foods

    def getGoose(index):
        goose = []
        for i in range(len(observation.geese[index])):
            goose.append(
                row_col(observation.geese[index][i], configuration.columns))
        return goose

    def getGeesesIndex():
        geeses = []
        for geese in observation.geese:
            geeses.append(row_col(geese[0], configuration.columns))
        return geeses

    def randomMove(moves):
        if len(moves) > 1:
            return moves[randint(0, len(moves) - 1)]
        return moves[0]

    def countMoves(x1, y1, x2, y2):
        return abs((x1 - x2) + (y1 - y2))

    def willCollide(x, y, action):
        #print(f'Goose[{player_index} in player_row: {player_row}, player_column: {player_column} move to {action} and can collide with x: {x}, y: {y}')
        if action == Action.WEST.name:
            if player_column - 1 == y and x == player_row:
                return True
            if player_column == 0 and y == 10 and x == player_row:
                return True
        if action == Action.EAST.name:
            if player_column + 1 == y and x == player_row:
                return True
            if player_column == 10 and y == 0 and x == player_row:
                return True
        if action == Action.SOUTH.name:
            if player_row + 1 == x and y == player_column:
                return True
            if player_row == 6 and x == 0 and y == player_column:
                return True
        if action == Action.NORTH.name:
            if player_row - 1 == x and y == player_column:
                return True
            if player_row == 0 and x == 6 and y == player_column:
                return True
        return False

    def opposite(action):
        if action == Action.NORTH.name:
            return Action.SOUTH.name
        if action == Action.SOUTH.name:
            return Action.NORTH.name
        if action == Action.EAST.name:
            return Action.WEST.name
        if action == Action.WEST.name:
            return Action.EAST.name

    possible_moves = [
        Action.SOUTH.name, Action.NORTH.name, Action.WEST.name,
        Action.EAST.name
    ]

    if last_move[player_index] is not None:
        print(
            f'Geese {player_index} removes opposite last move: {opposite(last_move[player_index])}'
        )
        possible_moves.remove(opposite(last_move[player_index]))

    for geese_row, geese_column in getGeesesIndex():
        for food_row, food_column in getFoodsIndex():
            foodDistanceToMe = countMoves(food_row, food_column, player_row,
                                          player_column)
            if oldFoodDistanceToMe > foodDistanceToMe:
                oldFoodDistanceToMe = foodDistanceToMe
            else:
                continue

            geeseDistanceToFood = countMoves(geese_row, geese_column, food_row,
                                             food_column)
            if foodDistanceToMe < geeseDistanceToFood:
                if food_row > player_row:
                    possible_move = Action.SOUTH.name

                if food_row < player_row:
                    possible_move = Action.NORTH.name

                if food_column > player_column:
                    possible_move = Action.EAST.name

                if food_column < player_column:
                    possible_move = Action.WEST.name

                print(
                    f'Geese {player_index} based by food, possible move: {possible_move}'
                )
    if possible_move not in possible_moves:
        print(
            f'Geese {player_index}, possible move: {possible_move} is banned.')
        possible_move = randomMove(possible_moves)

    for i in range(len(observation.geese)):
        geese_index = getGoose(i)
        j = 0
        while j < len(geese_index):
            collision = False
            x, y = geese_index[j]
            if i != player_index:
                print(f'geese {player_index} -> geese {i} part {j}')
                #print(f'goose[{player_index}] in {getGoose(player_index)[0]} with possible move: {possible_move} can colide with goose[{i}] in x: {x} and y: {y}')
                while willCollide(x, y, possible_move):
                    collision = True
                    print(
                        f'goose[{player_index}] in {getGoose(player_index)[0]} with possible move: {possible_move} will colide with goose[{i}] in x: {x} and y: {y}'
                    )
                    possible_moves.remove(possible_move)
                    possible_move = randomMove(possible_moves)
                    print(f'now goose[{player_index}] will to {possible_move}')
                    j = 0
            if not collision:
                j += 1

    for x, y in getGoose(player_index):
        while willCollide(x, y, possible_move):
            print(f'BODY HIT!!!')
            possible_moves.remove(possible_move)
            possible_move = randomMove(possible_moves)

    if len(possible_moves) == 1:
        print(
            f'Geese {player_index} just remain this move: {possible_moves[0]}')
        possible_move = possible_moves[0]

    last_move[player_index] = possible_move

    print(
        f'Geese {player_index}: {getGoose(player_index)} move to {possible_move}'
    )
    return possible_move
def agent(obs_dict, config_dict):
    global last_step

    # State retrieval
    observation = Observation(obs_dict)
    configuration = Configuration(config_dict)
    player_index = observation.index
    player_goose = observation.geese[player_index]
    player_head = player_goose[0]
    player_row, player_column = row_col(player_head, configuration.columns)

    # Map creation
    # 0 - empty cells
    # -1 - obstacles
    # -4 - possible obstacles
    # -2 - food
    # -3 - head
    # 1,2,3,4 - reachable on the current step cell, number is the id of the first step direction
    table = np.zeros((7, 11))
    legend = {1: 'SOUTH', 2: 'NORTH', 3: 'EAST', 4: 'WEST'}

    # let's add food to the map
    for food in observation.food:
        x, y = row_col(food, configuration.columns)
        table[x, y] = -2  # food

    # let's add all cells that are forbidden
    for i in range(4):
        opp_goose = observation.geese[i]
        if len(opp_goose) == 0:
            continue

        is_close_to_food = False
        if i != player_index:
            x, y = row_col(opp_goose[0], configuration.columns)
            possible_moves = get_nearest_cells(x, y)  # head can move anywhere
            for x, y in possible_moves:
                if table[x, y] == -2:
                    is_close_to_food = True
                table[
                    x,
                    y] = -4  # Cells where opponent might possibly go next step

        # usually we ignore the last tail cell but there are exceptions
        tail_change = -1
        if obs_dict['step'] % 40 == 39:
            tail_change -= 1

        # we assume that the goose will eat the food
        if is_close_to_food:
            tail_change += 1
        if tail_change >= 0:
            tail_change = None

        for n in opp_goose[:tail_change]:
            x, y = row_col(n, configuration.columns)
            table[x, y] = -1  # forbidden cells

    # going back is forbidden according to the new rules
    x, y = row_col(player_head, configuration.columns)
    if last_step is not None:
        if last_step == 1:
            table[(x + 6) % 7, y] = -1
        elif last_step == 2:
            table[(x + 8) % 7, y] = -1
        elif last_step == 3:
            table[x, (y + 10) % 11] = -1
        elif last_step == 4:
            table[x, (y + 12) % 11] = -1

    # add head position
    table[x, y] = -3

    # the first step toward the nearest food
    step = int(find_closest_food(table))

    # if there is not available steps try to go to possibly dangerous cell
    if step not in [1, 2, 3, 4]:
        x, y = row_col(player_head, configuration.columns)
        if table[(x + 8) % 7, y] == -4:
            step = 1
        elif table[(x + 6) % 7, y] == -4:
            step = 2
        elif table[x, (y + 12) % 11] == -4:
            step = 3
        elif table[x, (y + 10) % 11] == -4:
            step = 4

    # else - do a random step and lose
        else:
            step = np.random.randint(4) + 1

    last_step = step
    return legend[step]
Exemple #26
0
def override_interpreter(state, env):
    configuration = Configuration(env.configuration)
    columns = configuration.columns
    rows = configuration.rows
    min_food = configuration.min_food
    state[0].observation = shared_observation = Observation(state[0].observation)
    hits = 0

    # Reset the environment.
    if env.done:
        agent_count = len(state)
        heads = sample(range(columns * rows), agent_count)
        shared_observation["geese"] = [[head] for head in heads]
        food_candidates = set(range(columns * rows)).difference(heads)
        # Ensure we only place as many food as there are open squares
        min_food = min(min_food, len(food_candidates))
        shared_observation["food"] = sample(food_candidates, min_food)
        return state

    geese = shared_observation.geese
    food = shared_observation.food

    # If there is no last state, reuse current state so that current action is never the opposite of the last action.
    last_state = env.steps[-1] if len(env.steps) > 1 else state
    # Apply the actions from active agents.
    for index, agent in enumerate(state):
        if agent.status != "ACTIVE":
            if agent.status != "INACTIVE" and agent.status != "DONE":
                # ERROR, INVALID, or TIMEOUT, remove the goose.
                geese[index] = []
            continue

        action = Action[agent.action]

        # Check action direction
        last_agent = last_state[index]
        last_action = Action[last_agent["action"]] if "action" in last_agent else action
        if last_action == action.opposite():
            env.debug_print(f"Opposite action: {agent.observation.index, action, last_action}")
            agent.status = "DONE"
            geese[index] = []
            hits +=1            
            continue

        goose = geese[index]
        head = translate(goose[0], action, columns, rows)

        # Consume food or drop a tail piece.
        if head in food:
            food.remove(head)
        else:
            goose.pop()

        # Self collision.
        if head in goose:
            env.debug_print(f"Body Hit: {agent.observation.index, action, head, goose}")
            agent.status = "DONE"
            geese[index] = []
            hits +=1
            continue

        while len(goose) >= configuration.max_length:
            # Free a spot for the new head if needed
            goose.pop()
        # Add New Head to the Goose.
        goose.insert(0, head)

        # If hunger strikes remove from the tail.
        if len(env.steps) % configuration.hunger_rate == 0:
            if len(goose) > 0:
                goose.pop()
            if len(goose) == 0:
                env.debug_print(f"Goose Starved: {action}")
                agent.status = "DONE"
                hits +=1
                continue

    goose_positions = histogram(
        position
        for goose in geese
        for position in goose
    )

    # Check for collisions.
    for index, agent in enumerate(state):
        goose = geese[index]
        if len(goose) > 0:
            head = geese[index][0]
            if goose_positions[head] > 1:
                env.debug_print(f"Goose Collision: {agent.action}")
                agent.status = "DONE"
                geese[index] = []
                hits +=1

    # Add food if min_food threshold reached.
    needed_food = min_food - len(food)
    if needed_food > 0:
        collisions = {
            position
            for goose in geese
            for position in goose
        }
        available_positions = set(range(rows * columns)).difference(collisions).difference(food)
        # Ensure we don't sample more food than available positions.
        needed_food = min(needed_food, len(available_positions))
        food.extend(sample(available_positions, needed_food))

    # Set rewards after deleting all geese to ensure that geese don't receive a reward on the turn they perish.
    for index, agent in enumerate(state):
        if agent.status == "ACTIVE":
            agent.reward = (configuration.max_length + 1)*(hits+1) + len(geese[index]) - sum((len(goose) for goose in geese))  

            # Adding 1 to len(env.steps) ensures that if an agent gets reward 4507, it died on turn 45 with length 7.

    # If only one ACTIVE agent left, set it to DONE.
    active_agents = [a for a in state if a.status == "ACTIVE"]
    if len(active_agents) == 1:
        agent = active_agents[0]
        agent.status = "DONE"

    return state