Séverin Hatt :: romuald_no_last_check.py (v1)

This bot has played 280 games (239 wins / 5 draws / 36 losses).

Play against this bot as X or as O.

These bots have done best aganist romuald_no_last_check.py...

Bot P W D L Points Size From beginner One hit wonder Goldfish
Jon Bannister : : anybot_r282.py (v5) 10 5 5 0 5 974 False False True
Daniel Patrick : : error_unable_to_connect4.py (v2) 10 5 0 5 0 757 False False True
Ian Ozsvald : : ian12.py 10 5 0 5 0 943 False False True
Robert Seaman : : not_minimax.py (v25) 10 5 0 5 0 3501 False False True
Dilwoar Hussain : : dee-bot.py (v12) 10 3 0 7 -4 306 False False False

...and these bots have done worst

Bot P W D L Points Size From beginner One hit wonder Goldfish
Sergey Kolosov : : sergeykolosov_0004.py 10 0 0 10 -10 146 False True True
Robert Howlett : : smart11.py (v1) 10 0 0 10 -10 191 False False True
Matt Wheeler : : smol.py 10 0 0 10 -10 77 False True True
Jordan Banting : : trololololol.py (v1) 10 0 0 10 -10 402 False False True
Martijn Pieters : : zopatista.py (v9) 10 0 0 10 -10 2649 False False False

All standings against romuald_no_last_check.py

The code

      
# This bot looks one move ahead, and if possible it will make a move to
# block its opponent winning.  Otherwise, it picks a move at random.

import copy
import random
from botany_connectfour import game
from collections import defaultdict

def cut_fn(x):
    return (-x[2],x[3],x[4],x[5])

def remaining_to_win(board,line,token):
    line_string = "".join([board[l][r] for l,r in line])
    if 'X' in line_string and 'O' in line_string:
        return (5,token)
    else:
        letters = line_string.replace('.','')
        letter = token
        if len(letters)> 0:
            letter = letters[0]
        return (line_string.count('.') or 5,letter)

def get_letter_of_pos(position,board):
    col, row = position
    return board[col][row]


def get_traps(board,the_lines,best,token):
    dict_traps = defaultdict(list)
    for line in the_lines:
        dict_traps[remaining_to_win(board,line,token)[0]].append(line)
    if min(dict_traps.keys()) == 1:
        return dict_traps[1]
    output = list()
    for i in [key for key in dict_traps.keys() if key<best]:
        output += dict_traps[i]
    return output

def get_his_deadly_traps(board,the_lines,best,the_toten):
    traps = list()
    for line in the_lines:
        remaining, letter = remaining_to_win(board,line,the_toten)

        if remaining == 1 and letter != the_toten:
            traps.append(line)
    return traps


def select_for_victory(board,outcome_assign,proposals,token):
    for column,_,_,_,_,_ in proposals:
        if outcome_assign.get(column) == token:
            return column
    return column

def get_next_move(board, token):
    available_moves = game.available_moves(board)
    available_position = [(a,board[a].index('.')) for a in available_moves]
    available_play = available_position

    if token == 'X':
        other_token = 'O'
    else:
        other_token = 'X'

    possible = [line for line in game.LINES_OF_4 if not set(available_position).isdisjoint(line)]

    outcome = dict()

    best = 5

    for line in possible:
        remaining, letter = remaining_to_win(board,line,token)
        if remaining == 5:
            continue

        if best > remaining:
            outcome = defaultdict(int)
            outcome_assign = defaultdict()
            best = remaining

        if best == remaining:
            the_moves = list(set(line) & set(available_play))
            for move in the_moves:
                outcome[move[0]] += 1
                if letter == token:
                    outcome_assign[move[0]] = letter

                
    #tuple number of line_4, row, cols
    proposals = [(i,board[i].index('.'),9,0,0,j) for i,j in outcome.items()]

    if best == 1:
        return select_for_victory(board,outcome_assign,proposals,token)        
    
    traps = get_traps(board,game.LINES_OF_4,best,token)
       #Do not fall in a trap.
    proposals_minus_traps = [prop for prop in proposals if not any((prop[0],prop[1]+1) in trap for trap in traps)]
    friendly_traps = get_his_deadly_traps(board,game.LINES_OF_4,best,other_token)
    available_traps = set(pos for trap in friendly_traps for pos in trap if get_letter_of_pos(pos,board) == ".")
    enemy_traps = get_his_deadly_traps(board,game.LINES_OF_4,best,token)
    for move in available_moves:
        sequence_play = sorted([a[1] - board[a[0]].index('.') for a in available_traps if a[0] == move])
        if len(sequence_play) > 1 and sequence_play[0:1]== [2,3]:
            return move

    
    #We have a choice, let's see if we can set a trap
    if len(traps) > 1:
        
        
        available_enemy_traps = set(pos for trap in enemy_traps for pos in trap if get_letter_of_pos(pos,board) == ".")
        board1 = copy.deepcopy(board)
        improved_proposals = list()
        only_him = set((row,col) for row,col in available_play if not any((row,col+1) in trap for trap in traps)) - set((row,col) for row,col,_,_,_,_ in proposals)

        if len(enemy_traps)>0 and len(friendly_traps) == 0:
            for col,row in only_him:
                board1[col][row] = other_token
                new_enemy_traps = get_his_deadly_traps(board1,(enemy_traps+possible),best,token)
                all_new_traps = set(pos for trap in new_enemy_traps for pos in trap if get_letter_of_pos(pos,board1) == ".")
                new_traps = all_new_traps - available_enemy_traps
                if len(new_traps) > 0:
                    potential_traps = (all_new_traps.union(available_enemy_traps))
                    
                    sequence_play = [a[1] - board[a[0]].index('.') for a in potential_traps]
                    if len(potential_traps)>0:
                        next_one = min(sequence_play)
                    else:
                        next_one = 9
                    order = len(set(a%2 for a in sequence_play))
                    improved_proposals.append((col,row,next_one,order,len(new_traps),0))
                board1[col][row] = '.'

        for col,row,_,_,_,remaining in proposals_minus_traps:
            board1[col][row] = token
            new_friendly_traps = get_his_deadly_traps(board1,(traps+possible),best,other_token)
            all_new_traps = set(pos for trap in new_friendly_traps for pos in trap if get_letter_of_pos(pos,board1) == ".")
            new_traps = all_new_traps - available_traps
            potential_traps = (all_new_traps.union(available_traps))
            
            sequence_play = [a[1] - board[a[0]].index('.') for a in potential_traps]
            if len(potential_traps)>0:
                next_one = min(sequence_play)
            else:
                next_one = 9
            order = len(set(a%2 for a in sequence_play))
            improved_proposals.append((col,row,next_one,order,len(new_traps),remaining))
            board1[col][row] = '.'
        if len(improved_proposals):
            proposals_minus_traps = improved_proposals

    outputs = list()
    if len(proposals_minus_traps):
        outputs = [e[0] for e in proposals_minus_traps if e == max(proposals_minus_traps,key=cut_fn)]
    outputs = outputs or [avail[0] for avail in available_play if not any((avail[0],avail[1]+1) in trap for trap in traps)]
    if len(outputs) == 0:
        deadly_traps = enemy_traps
        outputs = [avail[0] for avail in available_play if not any((avail[0],avail[1]+1) in trap for trap in deadly_traps)] 
    outputs = outputs or available_moves
    try:
        output = random.choice(outputs)
    except:
        output = random.choice(available_moves)
    print(output,outputs)
    return output