Esempio n. 1
0
def move_player_out_of_square(game, player, x, y, p_used=None, p_down=None):
    # places the player at a random position that is not in the given square. 
    
    xx = x if isinstance(x, Iterable) else (x,x)
    yy = y if isinstance(y, Iterable) else (y,y)
    
    x_min = xx[0]
    x_max = xx[1]
    y_min = yy[0]
    y_max = yy[1]
    
    board_x_max = len(game.state.pitch.board[0]) -2  
    board_y_max = len(game.state.pitch.board) -2
    
    i = 0
    
    while True: 
        x = randint(1, board_x_max)
        y = randint(1, board_y_max)
        
        if x_min <= x <= x_max and y_min <= y <= y_max: 
            i += 1 
            assert i<5000
            continue 
        
        
        if game.state.pitch.board[y][x] is None: 
            break 
    
    game.move(player, Square(x,y)) 
    set_player_state(player, p_used=p_used, p_down=p_down)
Esempio n. 2
0
def get_boundary_square(game, steps, from_position): 
    steps = int(steps)
    
    if steps == 0: 
        if game.state.pitch.board[from_position.y][from_position.x] is None: 
            return from_position
        else: 
            steps += 1 
    
    # return a position that is 'steps' away from 'from_position'
    # checks are done so it's square is available 
    board_x_max = len(game.state.pitch.board[0]) -2  
    board_y_max = len(game.state.pitch.board) -2
    
    assert steps > 0 

    avail_squares = steps*8 
    
    squares_per_side = 2*steps 
    
    i = 0 
    while True: 
        i +=1
        assert i<5000
        
        sq_index = randint(0, avail_squares -1)
        steps_along_side = sq_index % squares_per_side
   
        # up, including left corner 
        if sq_index // squares_per_side == 0: 
            dx = - steps + steps_along_side
            dy = - steps
        # right, including upper corner
        elif sq_index // squares_per_side == 1: 
            dx = + steps 
            dy = - steps + steps_along_side
        # down, including right corner
        elif sq_index // squares_per_side == 2: 
            dx = + steps - steps_along_side
            dy = + steps 
        # left, including lower corner    
        elif sq_index // squares_per_side == 3: 
            dx = - steps
            dy = + steps - steps_along_side
        else: 
            assert False 
    
        position = Square(from_position.x + dx, from_position.y + dy)
        x = position.x
        y = position.y 
        
        if x < 1 or x > board_x_max or  y < 1 or y > board_y_max: 
            continue 
    
        if game.state.pitch.board[y][x] is None: #it should y first, don't ask. 
            break  
            
    return position 
Esempio n. 3
0
 def _reset_lecture(self, game): 
     # ### CONFIG ### # 
     board_x_max = len(game.state.pitch.board[0]) -2  
     board_y_max = len(game.state.pitch.board) -2
 
     #Level configuration 
     level = self.get_level()        
     self.dices    =  3-(level % self.dice_mod) 
     blocker_skill =    (level // self.dice_mod) % self.own_skills_mod 
     victim_skill  =    (level // self.dice_mod // self.own_skills_mod)  % self.opp_skills_mod 
     
     blocker_team    = get_home_players(game)
     victim_team     = get_away_players(game) 
     
     victim = victim_team.pop() 
     move_player_within_square(game, victim, x = [2,board_x_max-1], y = [2, board_y_max-1], give_ball=random.random() < 0.5)
     x = victim.position.x
     y = victim.position.y
     
     blocker = blocker_team.pop() 
     move_player_within_square(game, blocker, x = [x-1,x+1], y = [y-1, y+1])
     
     
     #Setup skills
     if random.random() < 0.8: 
         blocker.extra_skills = self.own_skills[blocker_skill]
     if random.random() < 0.8: 
         victim.extra_skills = self.opp_skills[victim_skill]
     
     #setup assists if needed for two die 
     target_str = victim.get_st()  + 1 + victim.get_st()*(self.dices==3)
     blocker_assists  = target_str - blocker.get_st() 
     for _ in range(blocker_assists): 
         move_player_within_square(game, blocker_team.pop(), x = [x-1,x+1], y = [y-1, y+1], p_used=1)
     
     #Setup rest of players: 
     move_players_out_of_square(game, blocker_team, [x-4, x+4], [y-4, y+4], p_used=1)
     move_players_out_of_square(game, victim_team, [x-4, x+4], [y-4, y+4])
     
     #Randomly place ball 
     ball_pos = Square( randint(1,board_x_max), randint(1,board_y_max)) 
     game.get_ball().move_to( ball_pos ) 
     game.get_ball().is_carried = game.get_player_at(ball_pos) is not None  
                  
     game.set_available_actions()
     a = Action(action_type=ActionType.START_BLOCK, position = blocker.position, player = blocker )
     game.step(a)
     a = Action(action_type=ActionType.BLOCK, position = victim.position, player = victim)
     game.step(a)
         
     self.actions = [a.action_type for a in game.get_available_actions() ] 
     
     assert True in [a in self.actions for a in ChooseBlockDie.action_types]
        
     self.victim = victim
     self.blocker = blocker 
     
     assert game.state.active_player ==  self.blocker
Esempio n. 4
0
def swap_game(game): 

    moved_players = []
    board_x_max = len(game.state.pitch.board[0]) -2      
    
    player_to_move  = get_home_players(game) + get_away_players(game) 
    for p in player_to_move: 
        
        if p in moved_players: 
            continue 
        
        old_x = p.position.x 
        new_x = 27  - old_x 
        
        potential_swap_p =  game.state.pitch.board[p.position.y][new_x]
        if potential_swap_p is not None: 
            game.move(potential_swap_p , Square(0,0) ) 
        
        game.move(p, Square(new_x, p.position.y) ) 
        
        if potential_swap_p is not None: 
            game.move(potential_swap_p , Square(old_x, p.position.y) ) 
            moved_players.append(potential_swap_p) 
Esempio n. 5
0
def move_player_within_square(game, player, x, y, give_ball=False, p_used=None, p_down=None): 
    # places the player at a random position within the given square. 
    
    assert isinstance(give_ball, bool)
    
    board_x_max = len(game.state.pitch.board[0]) -2  
    board_y_max = len(game.state.pitch.board) -2
    
     
    xx = sorted(x) if isinstance(x, Iterable) else (x,x)
    yy = sorted(y) if isinstance(y, Iterable) else (y,y)
    
    x_min = max(xx[0]  , 1 )
    x_max = min(xx[1]  , board_x_max ) 
    y_min = max(yy[0]  , 1 )
    y_max = min(yy[1]  , board_y_max ) 
    
    
    assert x_min <= x_max
    assert y_min <= y_max
    
    i = 0
    
    while True: 
        i += 1 
        assert i < 5000
        
        x = randint(x_min, x_max)
        y = randint(y_min, y_max)
        
        #if x < 1 or x > board_x_max or y < 1 or y > board_y_max:  
        #    continue 
        
        if game.state.pitch.board[y][x] is None: 
            break 
    
    game.move(player, Square(x,y)) 
    if give_ball == True: 
        game.get_ball().move_to( player.position ) 
        game.get_ball().is_carried = True 
    
    set_player_state(player, p_used=p_used, p_down=p_down)
Esempio n. 6
0
    def _reset_lecture(self, game): 
        
        if self.home_defence and not self.debug: 
            if game.away_agent.name.find("selfplay") < 0: 
                print(f"expected away agent name 'selfplay*', got '{game.away_agent.name}'")
            
                assert False 
        
        
        # ### CONFIG ### # 
        board_x_max = len(game.state.pitch.board[0]) -2  
        board_y_max = len(game.state.pitch.board) -2
    
        #Level configuration 
        level = self.get_level()        
        home_on_defence = self.home_defence # have to pick from argument to init() 
        noise           = level % self.noise_mod 
        scatter_1       = (level // self.noise_mod) % self.scatter1_mod
        scatter_2       = (level // self.noise_mod // self.scatter1_mod) % self.scatter2_mod
        offence_support = (level // self.noise_mod // self.scatter1_mod // self.scatter2_mod) % self.offence_support_mod
        
        if level%2==0:
            temp = scatter_1 
            scatter_1 = scatter_2 
            scatter_2 = temp 
        
        #get players 
        if not home_on_defence: 
            #0 - home team threatens score, 
            offence = get_home_players(game)
            defence = get_away_players(game)
             
        else: 
            #1 - away team threatens score 
            defence = get_home_players(game)
            offence = get_away_players(game)
            #swap needed 
            
        
        #setup ball carrier  
        p_carrier = offence.pop() 
        move_player_within_square(game, p_carrier, x=[3, 9], y=[2, board_y_max-1], give_ball=False)
        extra_ma = (p_carrier.position.x - 1) - p_carrier.get_ma() 
        p_carrier.extra_ma = max(min(extra_ma, 2), 0)
        
        ball_x = p_carrier.position.x
        ball_y = p_carrier.position.y 
        
        #setup players intended for action 
        
        marker_up_pos   = Square(ball_x-1, max(ball_y-1, 1) )        
        marker_down_pos = Square(ball_x-1, min(ball_y+1, board_y_max) )        
        
        guy1 = defence.pop()
        guy2 = defence.pop()
        
        game.move(guy1, get_boundary_square(game, scatter_1, marker_up_pos))
        game.move(guy2, get_boundary_square(game, scatter_2, marker_down_pos) )
        
        guy1.state.up = guy1.position.distance(p_carrier.position) > 2
        guy2.state.up = guy2.position.distance(p_carrier.position) > 2
        
        #setup rest of screen (in state used 
        p_used_defence = (1-level/ self.max_level) * home_on_defence
        p_used_offence = 0 

        upwards_y = ball_y - 4
        downwards_y = ball_y + 4
        
        while upwards_y > 0: #Potential special case at equal 0. 
            move_player_within_square(game, defence.pop(), x=[ball_x-2, ball_x+2], y = upwards_y, p_used=p_used_defence)
            upwards_y -= 4 
        
        while downwards_y < board_y_max+1: #Potential special case at equal to board_y_max +1 
            move_player_within_square(game, defence.pop(), x=[ball_x-2, ball_x+2], y = downwards_y, p_used=p_used_defence) 
            downwards_y += 4 
        
        #setup offensive support
        for _ in range(offence_support): 
            move_player_within_square(game, offence.pop(), x=[ball_x-3,ball_x+3], y=[ball_y-3,ball_y+3] )

        
        #setup other players randomly 
        move_players_out_of_square(game, defence, x=[0, ball_x + 5], y=[0,20], p_used = p_used_defence) #home 
        move_players_out_of_square(game, offence, x=[0, ball_x + 5], y=[0,20], p_used = p_used_offence) 
            
        
        if home_on_defence: 
            swap_game(game) #flips the board
            if level == 0 or (noise == 0 and random.random() < 0.5): 
                #set_trace() 
                game.set_available_actions() 
                action_type = ActionType.START_MOVE
                a = Action(action_type=action_type, position = guy1.position, player = guy1)
                game.step(a)
        else: 
            pass 
            
        game.get_ball().move_to(p_carrier.position)
        game.get_ball().is_carried = True 
        #Log turn 
        self.turn = deepcopy(game.state.home_team.state.turn)  
        self.opp_turn = deepcopy(game.state.away_team.state.turn)  
Esempio n. 7
0
 def _reset_lecture(self, game): 
     
     assert game.is_pass_available()
     # ### CONFIG ### # 
     board_x_max = len(game.state.pitch.board[0]) -2  
     board_y_max = len(game.state.pitch.board) -2
 
     #Level configuration 
     extra_pass_dist = 1 if self.handoff else 4
     
     level = self.get_level()        
     noise      = (level %  self.noise_mod)
     dist_pass  = (level // self.noise_mod) % self.pass_dist_mod + extra_pass_dist 
     dist_to_td = (level // self.noise_mod // self.pass_dist_mod) % self.score_dist_mod +1 #1 = start in td zone
     
     
     #get players 
     home_players = get_home_players(game)
     away_players = get_away_players(game)
     
     #setup scorer 
     p_score = home_players.pop() 
     p_score_x = dist_to_td
     p_score_y = randint(2, board_y_max -1 )
     p_score.extra_skills = [] if random.random() < 0.5 else [Skill.CATCH]
     game.move(p_score, Square( p_score_x, p_score_y) )
     
     #setup passer
     p_pass  = home_players.pop() 
     #p_pass.extra_skills = [] if random.random() < 0.5 else [Skill.PASS] #Pass skill not implemeted
     
     p_pass_x = p_score_x + dist_pass 
     dx = abs(p_pass_x - p_score_x) 
     move_player_within_square(game, p_pass, x=p_pass_x, y=[p_score_y-dx, p_score_y+dx], give_ball=True )
     
     #setup passer movement left 
     max_movement = p_pass.get_ma() + 2 #add two to remove GFIs
     p_pass.state.moves  = max_movement
     
     
     if self.handoff: 
         if dx > 1: 
             #make sure passer can't score but can reach scorer 
             to_not_score= p_pass_x -1 #double gfi to score is ok. 
             to_handoff = dx #double gfi to score is ok.
             assert to_handoff <= to_not_score 
             
             p_pass.state.moves -= randint(to_handoff, min(to_not_score, max_movement) ) 
         
     else: #PassAction  
         if noise > 0: 
             #make sure passer can't reach scorer: 
             to_not_handoff = dx-2 
             p_pass.state.moves  = p_pass.get_ma() + 2 #add two to remove GFIs 
             p_pass.state.moves -= randint(0, min(to_not_handoff, max_movement) ) 
             
     assert 0 <= p_pass.state.moves 
     
     
     if level == 0 or (noise == 0 and random.random() < 0.2) : 
         # Start the pass/handoff action 
         
         action_type = ActionType.START_HANDOFF if self.handoff else ActionType.START_PASS
         a = Action(action_type=action_type, position = p_pass.position, player = p_pass )
         game.step(a)
     
     if True: 
         x_min = 0
         x_max = max(p_score.position.x, p_pass.position.x)+1
         
         y_min = min(p_score.position.y, p_pass.position.y)-1
         y_max = max(p_score.position.y, p_pass.position.y)+1
         
         if noise <= 2: 
             p_used = 1-noise/2
             p_down = 1-noise/2
         else:
             p_used = 0
             p_down = 0
         
         move_players_out_of_square(game, away_players, [x_min, x_max], [y_min , y_max], p_down=p_down )
         move_players_out_of_square(game, home_players, [x_min, x_max], [y_min , y_max], p_used=p_used, p_down=p_down )
         
         self.turn = deepcopy(game.state.home_team.state.turn)  
         
     if False: 
         print("pass moves: {}/{}".format(p_pass.state.moves, p_pass.get_ma() ))
Esempio n. 8
0
    def _reset_lecture(self, game): 

        # ### CONFIG ### # 
        board_x_max = len(game.state.pitch.board[0]) -2  
        board_y_max = len(game.state.pitch.board) -2
    
        #Level configuration 
        level = self.get_level()        
        noise           = (level % self.noise_mod) 
        action_started  = (level // self.noise_mod) % self.action_started_mod
        distance        = (level // self.noise_mod // self.action_started_mod) % self.dst_mod
        
        home_players = get_home_players(game)
        away_players = get_away_players(game)
        
        #setup LoS oppenent
        for _ in range( randint(3,6)): 
            p = away_players.pop() 
            move_player_within_square(game, p, x=13, y=[5,11])
        
        #setup rest of opponents 
        move_players_out_of_square(game, away_players, x=[12, 50], y=[0, 20])
        
        
        #get ball carrier
        p_carrier = home_players.pop() 
        if level / self.max_level < random.random():  
            p_carrier.extra_skills = [Skill.SURE_HANDS]
        
        #setup LoS own team
        p_used = 1 - level / (self.max_level)
        p_used = max(1, p_used)
        
        for _ in range( randint(3,6)): 
            p = home_players.pop() 
            move_player_within_square(game, p, x=14, y=[5,11], p_used=p_used)
        
        #setup rest of team 
        for _ in range(len(home_players)): 
            p = home_players.pop() 
            move_player_within_square(game, p, x=[15,19], y=[1,board_y_max], p_used=p_used)
        
        #setup ball 
        center_square = Square(20,8)
        scatter_ball(game, max(1,noise), center_square)
        
        #setup ball carrier
        
        if noise == 0: 
            move_player_within_square(game, p_carrier, center_square.x, center_square.y) 
        else:    
            game.move(p_carrier, get_boundary_square(game, 1+distance, game.get_ball().position)) 
        
        if p_carrier.position.x < 15: 
            move_player_within_square(game, p_carrier, x=[15,18], y=[p_carrier.position.y-1, p_carrier.position.y+1] )
        
        if action_started == 0: 
            game.set_available_actions() 
            action_type = ActionType.START_MOVE
            a = Action(action_type=action_type, position = p_carrier.position, player = p_carrier )
            game.step(a)
        
        self.turn = deepcopy(game.state.home_team.state.turn)  
        self.carrier = p_carrier
Esempio n. 9
0
class Pathfinder:

    DIRECTIONS = [
        Square(-1, -1),
        Square(-1, 0),
        Square(-1, 1),
        Square(0, -1),
        Square(0, 1),
        Square(1, -1),
        Square(1, 0),
        Square(1, 1)
    ]

    def __init__(self,
                 game,
                 player,
                 trr=False,
                 directly_to_adjacent=False,
                 can_block=False,
                 can_handoff=False,
                 can_foul=False):
        self.game = game
        self.player = player
        self.trr = trr
        self.directly_to_adjacent = directly_to_adjacent
        self.can_block = can_block
        self.can_handoff = can_handoff
        self.can_foul = can_foul
        self.ma = player.get_ma() - player.state.moves
        self.gfis = 3 if player.has_skill(Skill.SPRINT) else 2
        self.locked_nodes = np.full((game.arena.height, game.arena.width),
                                    None)
        self.nodes = np.full((game.arena.height, game.arena.width), None)
        self.tzones = np.zeros((game.arena.height, game.arena.width),
                               dtype=np.uint8)
        self.current_prob = 1
        self.open_set = PriorityQueue()
        self.risky_sets = {}
        self.target_found = False
        for p in game.get_players_on_pitch():
            if p.team != player.team and p.has_tackle_zone():
                for square in game.get_adjacent_squares(p.position):
                    self.tzones[square.y][square.x] += 1

    def get_path(self, target):
        paths = self.get_paths(target)
        if len(paths) > 0:
            return paths[0]
        return None

    def get_paths(self, target=None):

        ma = self.player.get_ma() - self.player.state.moves
        self.ma = max(0, ma)
        gfis_used = 0 if ma >= 0 else -ma
        self.gfis = 3 - gfis_used if self.player.has_skill(
            Skill.SPRINT) else 2 - gfis_used

        if self.ma + self.gfis <= 0:
            return []

        can_dodge = self.player.has_skill(
            Skill.DODGE) and Skill.DODGE not in self.player.state.used_skills
        can_sure_feet = self.player.has_skill(
            Skill.SURE_FEET
        ) and Skill.SURE_FEET not in self.player.state.used_skills
        can_sure_hands = self.player.has_skill(Skill.SURE_HANDS)
        rr_states = {(self.trr, can_dodge, can_sure_feet, can_sure_hands): 1}
        node = Node(None,
                    self.player.position,
                    self.ma,
                    self.gfis,
                    euclidean_distance=0,
                    rr_states=rr_states)
        if not self.player.state.up:
            node = self._expand_stand_up(node)
            self.nodes[node.position.y][node.position.x] = node
        self.open_set.put((0, node))
        self._expansion(target)
        self._clear()

        while not self.target_found and len(self.risky_sets) > 0:
            self._prepare_nodes()
            self._expansion(target)
            self._clear()

        return self._collect_paths(target)

    def _get_pickup_target(self, to_pos):
        zones_to = self.tzones[to_pos.y][to_pos.x]
        modifiers = 1
        if not self.player.has_skill(Skill.BIG_HAND):
            modifiers -= int(zones_to)
        if self.game.state.weather == WeatherType.POURING_RAIN:
            if not self.player.has_skill(Skill.BIG_HAND):
                modifiers -= 1
        if self.player.has_skill(Skill.EXTRA_ARMS):
            modifiers += 1
        target = Rules.agility_table[self.player.get_ag()] - modifiers
        return min(6, max(2, target))

    def _get_handoff_target(self, catcher):
        modifiers = self.game.get_catch_modifiers(catcher, handoff=True)
        target = Rules.agility_table[catcher.get_ag()] - modifiers
        return min(6, max(2, target))

    def _get_dodge_target(self, from_pos, to_pos):
        zones_from = self.tzones[from_pos.y][from_pos.x]
        if zones_from == 0:
            return None
        zones_to = int(self.tzones[to_pos.y][to_pos.x])
        modifiers = 1

        ignore_opp_mods = False
        if self.player.has_skill(Skill.STUNTY):
            modifiers = 1
            ignore_opp_mods = True
        if self.player.has_skill(Skill.TITCHY):
            modifiers += 1
            ignore_opp_mods = True
        if self.player.has_skill(Skill.TWO_HEADS):
            modifiers += 1

        if not ignore_opp_mods:
            modifiers -= zones_to

        target = Rules.agility_table[self.player.get_ag()] - modifiers
        return min(6, max(2, target))

    def _expand(self, node: Node, target=None):
        if target is not None:
            # TODO: handoff?
            if type(target) == Square and target.distance(
                    node.position) > node.moves_left + node.gfis_left:
                return
            if type(target) == int and abs(
                    target -
                    node.position.x) > node.moves_left + node.gfis_left:
                return
            if type(target) == Square and node.position == target:
                self.target_found = True
                return
            if type(target) == int and node.position.x == target:
                self.target_found = True
                return

        if node.block_dice is not None or node.handoff_roll is not None:
            return

        out_of_moves = False
        if node.moves_left + node.gfis_left == 0:
            if not self.can_handoff:
                return
            out_of_moves = True

        for direction in self.DIRECTIONS:
            next_node = self._expand_node(node,
                                          direction,
                                          out_of_moves=out_of_moves)
            if next_node is None:
                continue
            rounded_p = round(next_node.prob, 6)
            if rounded_p < self.current_prob:
                self._add_risky_move(rounded_p, next_node)
            else:
                self.open_set.put((next_node.euclidean_distance, next_node))
                self.nodes[next_node.position.y][
                    next_node.position.x] = next_node

    def _expand_node(self, node, direction, out_of_moves=False):
        euclidean_distance = node.euclidean_distance + 1 if direction.x == 0 or direction.y == 0 else node.euclidean_distance + 1.41421
        to_pos = self.game.state.pitch.squares[node.position.y +
                                               direction.y][node.position.x +
                                                            direction.x]
        if not (1 <= to_pos.x < self.game.arena.width - 1
                and 1 <= to_pos.y < self.game.arena.height - 1):
            return None
        player_at = self.game.get_player_at(to_pos)
        if player_at is not None:
            if player_at.team == self.player.team and self.can_handoff and player_at.can_catch(
            ):
                return self._expand_handoff_node(node, to_pos)
            elif player_at.team != self.player.team and self.can_block and player_at.state.up:
                return self._expand_block_node(node, euclidean_distance,
                                               to_pos, player_at)
            elif player_at.team != self.player.team and self.can_foul and not player_at.state.up:
                return self._expand_foul_node(node, to_pos, player_at)
            return None
        if not out_of_moves:
            return self._expand_move_node(node, euclidean_distance, to_pos)
        return None

    def _expand_move_node(self, node, euclidean_distance, to_pos):
        best_node = self.nodes[to_pos.y][to_pos.x]
        best_before = self.locked_nodes[to_pos.y][to_pos.x]
        gfi = node.moves_left == 0
        moves_left_next = max(0, node.moves_left - 1)
        gfis_left_next = node.gfis_left - 1 if gfi else node.gfis_left
        total_moves_left = moves_left_next + gfis_left_next
        if best_node is not None:
            best_total_moves_left = best_node.moves_left + best_node.gfis_left
            if total_moves_left < best_total_moves_left:
                return None
            if total_moves_left == best_total_moves_left and euclidean_distance > best_node.euclidean_distance:
                return None
        next_node = Node(node, to_pos, moves_left_next, gfis_left_next,
                         euclidean_distance)
        if gfi:
            next_node.apply_gfi()
        if self.tzones[node.position.y][node.position.x] > 0:
            target = self._get_dodge_target(node.position, to_pos)
            next_node.apply_dodge(target)
        if self.game.get_ball_position() == to_pos:
            target = self._get_pickup_target(to_pos)
            next_node.apply_pickup(target)
        if best_before is not None and self._dominant(
                next_node, best_before) == best_before:
            return None
        return next_node

    def _expand_foul_node(self, node, to_pos, player_at):
        best_node = self.nodes[to_pos.y][to_pos.x]
        best_before = self.locked_nodes[to_pos.y][to_pos.x]
        assists_from, assists_to = self.game.num_assists_at(self.player,
                                                            player_at,
                                                            node.position,
                                                            foul=True)
        target = min(
            12, max(2,
                    player_at.get_av() + 1 - assists_from + assists_to))
        next_node = Node(node, to_pos, 0, 0, node.euclidean_distance)
        next_node.apply_foul(target)
        if best_node is not None and self._best(next_node,
                                                best_node) == best_node:
            return None
        if best_before is not None and self._dominant(
                next_node, best_before) == best_before:
            return None
        return next_node

    def _expand_handoff_node(self, node, to_pos):
        best_node = self.nodes[to_pos.y][to_pos.x]
        best_before = self.locked_nodes[to_pos.y][to_pos.x]
        player_at = self.game.get_player_at(to_pos)
        next_node = Node(node, to_pos, 0, 0, node.euclidean_distance)
        target = self._get_handoff_target(player_at)
        next_node.apply_handoff(target)
        if best_node is not None and self._best(next_node,
                                                best_node) == best_node:
            return None
        if best_before is not None and self._dominant(
                next_node, best_before) == best_before:
            return None
        return next_node

    def _expand_block_node(self, node, euclidean_distance, to_pos, player_at):
        best_node = self.nodes[to_pos.y][to_pos.x]
        best_before = self.locked_nodes[to_pos.y][to_pos.x]
        block_dice = self.game.num_block_dice_at(attacker=self.player,
                                                 defender=player_at,
                                                 position=node.position,
                                                 blitz=True)
        gfi = node.moves_left == 0
        moves_left_next = node.moves_left - 1 if not gfi else node.moves_left
        gfis_left_next = node.gfis_left - 1 if gfi else node.gfis_left
        next_node = Node(node,
                         to_pos,
                         moves_left_next,
                         gfis_left_next,
                         euclidean_distance,
                         block_dice=block_dice)
        if gfi:
            next_node.apply_gfi()
        if best_node is not None and self._best(next_node,
                                                best_node) == best_node:
            return None
        if best_before is not None and self._dominant(
                next_node, best_before) == best_before:
            return None
        return next_node

    def _add_risky_move(self, prob, node):
        if prob not in self.risky_sets:
            self.risky_sets[prob] = []
        self.risky_sets[prob].append(node)

    def _expand_stand_up(self, node):
        if self.player.has_skill(Skill.JUMP_UP):
            return Node(node,
                        self.player.position,
                        self.ma,
                        self.gfis,
                        euclidean_distance=0)
        elif self.ma < 3:
            target = max(
                2, min(6, 4 - self.game.get_stand_up_modifier(self.player)))
            next_node = Node(node,
                             self.player.position,
                             0,
                             self.gfis,
                             euclidean_distance=0)
            next_node.apply_stand_up(target)
            return next_node
        next_node = Node(node,
                         self.player.position,
                         self.ma - 3,
                         self.gfis,
                         euclidean_distance=0)
        return next_node

    def _best(self, a: Node, b: Node):
        if self.directly_to_adjacent and a.position.distance(
                self.player.position) == 1 and a.moves_left > b.moves_left:
            return a
        if self.directly_to_adjacent and b.position.distance(
                self.player.position) == 1 and b.moves_left > a.moves_left:
            return b
        a_moves_left = a.moves_left + a.gfis_left
        b_moves_left = b.moves_left + b.gfis_left
        block = a.block_dice is not None
        foul = a.foul_roll is not None
        if a.prob > b.prob:
            return a
        if b.prob > a.prob:
            return b
        if foul and a.foul_roll < b.foul_roll:
            return a
        if foul and b.foul_roll < a.foul_roll:
            return b
        if block and a.block_dice > b.block_dice:
            return a
        if block and b.block_dice > a.block_dice:
            return b
        if a_moves_left > b_moves_left:
            return a
        if b_moves_left > a_moves_left:
            return b
        if a.euclidean_distance < b.euclidean_distance:
            return a
        if b.euclidean_distance < a.euclidean_distance:
            return b
        return None

    def _dominant(self, a: Node, b: Node):
        if self.directly_to_adjacent and a.position.distance(
                self.player.position) == 1 and a.moves_left > b.moves_left:
            return a
        if self.directly_to_adjacent and b.position.distance(
                self.player.position) == 1 and b.moves_left > a.moves_left:
            return b
        a_moves_left = a.moves_left + a.gfis_left
        b_moves_left = b.moves_left + b.gfis_left
        # TODO: Write out as above
        if a.prob > b.prob and (
                a.foul_roll is None or a.foul_roll <= b.foul_roll) and (
                    a.block_dice is None or a.block_dice >= b.block_dice) and (
                        a_moves_left > b_moves_left or
                        (a_moves_left == b_moves_left
                         and a.euclidean_distance < b.euclidean_distance)):
            return a
        if b.prob > a.prob and (
                b.foul_roll is None or b.foul_roll <= a.foul_roll) and (
                    b.block_dice is None or b.block_dice >= a.block_dice) and (
                        b_moves_left > a_moves_left or
                        (b_moves_left == a_moves_left
                         and b.euclidean_distance < a.euclidean_distance)):
            return b
        return None

    def _clear(self):
        for y in range(self.game.arena.height):
            for x in range(self.game.arena.width):
                node = self.nodes[y][x]
                if node is not None:
                    before = self.locked_nodes[y][x]
                    if before is None or self._best(node, before) == node:
                        self.locked_nodes[y][x] = node
                    self.nodes[y][x] = None
        self.open_set = PriorityQueue()

    def _prepare_nodes(self):
        if len(self.risky_sets) > 0:
            probs = sorted(self.risky_sets.keys())
            self.current_prob = probs[-1]
            for node in self.risky_sets[probs[-1]]:
                best_before = self.locked_nodes[node.position.y][
                    node.position.x]
                if best_before is not None and self._dominant(
                        best_before, node) == best_before:
                    continue
                existing_node = self.nodes[node.position.y][node.position.x]
                if existing_node is None or self._best(existing_node,
                                                       node) == node:
                    self.open_set.put((node.euclidean_distance, node))
                    self.nodes[node.position.y][node.position.x] = node
            del self.risky_sets[probs[-1]]

    def _expansion(self, target=None):
        while not self.open_set.empty():
            _, best_node = self.open_set.get()
            self._expand(best_node, target)

    def _collect_paths(self, target=None):
        if type(target) == Square:
            node = self.locked_nodes[target.y][target.x]
            if node is not None:
                return [self._collect_path(node)]
            return []
        paths = []
        for y in range(self.game.arena.height):
            for x in range(self.game.arena.width):
                if self.player.position.x == x and self.player.position.y == y:
                    continue
                if type(target) == int and not target == x:
                    continue
                node = self.locked_nodes[y][x]
                if node is not None:
                    paths.append(self._collect_path(node))
        return paths

    def _collect_path(self, node):
        prob = node.prob
        steps = [node.position]
        rolls = [node.rolls]
        block_dice = node.block_dice
        foul_roll = node.foul_roll
        handoff_roll = node.handoff_roll
        node = node.parent
        while node is not None:
            steps.append(node.position)
            rolls.append(node.rolls)
            node = node.parent
        steps = list(reversed(steps))[1:]
        rolls = list(reversed(rolls))[1:]
        return Path(steps,
                    prob=prob,
                    rolls=rolls,
                    block_dice=block_dice,
                    foul_roll=foul_roll,
                    handoff_roll=handoff_roll)