Jon Bannister :: anybot_r282.py (v5)

This bot has played 370 games (262 wins / 80 draws / 28 losses).

Play against this bot as X or as O.

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

Bot P W D L Points Size From beginner One hit wonder Goldfish
Mac Chapman : : lines.py 10 5 5 0 5 893 False False False
Robert Seaman : : not_minimax.py (v25) 10 5 5 0 5 3501 False False True
Martijn Pieters : : zopatista.py (v9) 10 5 5 0 5 2649 False False False
Tom Campbell : : winblockprevent.py (v5) 10 1 5 4 -3 321 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
Ronan Murphy : : loser.py 10 0 0 10 -10 105 False False True
Luke Spademan : : lossingbot.py (v3) 10 0 0 10 -10 129 False False True
house : : opportunist.py 10 0 0 10 -10 None None None None
Peter Inglesby : : random.py 10 0 0 10 -10 26 False True True
Matt Wheeler : : smol.py 10 0 0 10 -10 77 False True True

All standings against anybot_r282.py

The code

      
import copy, random, sys
import math

from botany_connectfour import game

def POSSIBLE_MOVES():
    return [
        (0, 1), (1, 0), (1, 1), (1, -1)
    ]

def _other_token(token):
    return 'X' if token == 'O' else 'O'

def get_tokens(board, token):
    return {(col_i, row_i) for col_i, column in enumerate(board)
                 for row_i, row_val in enumerate(column)
                 if row_val == token}


def new_token_loc(board, col):
    for row in range(game.NROWS):
        if board[col][row] == game.EMPTY:
            return (col, row)


def cpy_board(board):
    return [[e for e in col] for col in board]


def move_ok(n_col, n_row):
    return 0 <= n_col < game.NCOLS and \
            0 <= n_row < game.NROWS


def get_bias(col, bias):
    vgood, okay, hiscore, midscore, lowscore = {3}, {1, 2, 4, 5}, 20, 12, -5
    if bias is not None:
        vgood = bias['vgood']
        okay = bias['okay']
        hiscore = bias['hiscore']
        midscore = bias['midscore']
        lowscore = bias['lowscore']
    if col in vgood:
        return hiscore
    if col in okay:
        return midscore
    return lowscore


def eval_tok(board, token, other_token, token_pos, potential_moves):
    t_col, t_row = token_pos
    value = 0
    for v_col, v_row in potential_moves:
        vector_value = 0
        curr_len = 0
        cutoffs = 0
        n_empty = 0
        potentials = [(v_col, v_row), (-v_col, -v_row)]
        for col, row in potentials:
            # print(f"Potential vector: {col},{row}",sys.gettrace().opcode_count)
            n_col, n_row = (t_col + col, t_row + row)
            if abs(row) >= 4:
                continue
            if move_ok(n_col, n_row):
                vect_col = int(col/abs(col)) if col else 0
                vect_row = int(row/abs(row)) if row else 0
                target_tok = board[n_col][n_row]
                if target_tok == token:
                    curr_len += 1
                    vector_value += 5**curr_len
                    potentials.append((col+vect_col, row+vect_row))
                    # print(f"Hit an {token} when looking at {col}, {row} - appending {potentials[-1]}")
                elif target_tok == other_token:
                    vector_value += 2  # It's a bit valuable to be next to other
                    cutoffs += 1
                # elif target_tok == game.EMPTY and n_empty == 0:
                #     potentials.append((col+vect_col, row+vect_row))
                #     n_empty += 1
            else:
                cutoffs += 1
            if cutoffs == 2:
                vector_value = vector_value/(5**(curr_len or 1)) if curr_len < 3 else 100000000
                break
        value += vector_value if curr_len < 3 else 100000000
    return value


def _get_next_move(board, token, recurse=True, bias=None):
    # n_moves = sum(len([x for x in col if x != game.EMPTY]) for col in board)

    other_token = _other_token(token)
    potential_moves = POSSIBLE_MOVES()
    moves = set(game.available_moves(board))
    # print(f'n_moves = {n_moves}')
    # if n_moves == 1:
    #     for col in moves:
    #         if board[col][0] == other_token:
    #             moves.remove(col)
    #             break
    for col in moves:
        new_tok = new_token_loc(board, col)
        if eval_tok(board, token, other_token, new_tok, potential_moves) > 100000:
            return col
        if eval_tok(board, other_token, token, new_tok, potential_moves) > 100000:
            return col

    possible_moves = []
    for col in moves:
        # print(f'MOVE {col}: ',sys.gettrace().opcode_count)
        new_token = new_token_loc(board, col)
        new_token_val = get_bias(col, bias) + eval_tok(board, token, other_token,  new_token, potential_moves)
        # print(f'Move {col} has at {new_token} value {new_token_val}',sys.gettrace().opcode_count)
        b1 = cpy_board(board)
        game.make_move(b1, col, token)
        next_cols = set(game.available_moves(b1))
        if len(next_cols) > 4:
            next_cols = next_cols.intersection({col, col-1, col+1})
        b2 = cpy_board(b1)
        max_next = 0
        for mv in next_cols:
            new_other_token = new_token_loc(b2, mv)
            nxt_other = eval_tok(b2, other_token, token, new_other_token, potential_moves)
            if nxt_other > max_next:
                max_next = nxt_other
        new_token_val -= max_next
        possible_moves.append((col, new_token_val))
        # print(f'MOVE {col} score was {new_token_val}')
    possible_moves_not_death = [(mv, v) for (mv, v) in possible_moves if v > -10000]
    if not possible_moves_not_death:
        # rip
        print(sys.gettrace().opcode_count, f'RIP: choosing move {possible_moves[0][0]} '
                                           f'with score {possible_moves[0][1]}')
        return possible_moves[0][0]

    best_move, best_value = max({(mv, v) for (mv, v) in possible_moves if v > -10000},
                                   key=lambda move: move[1])
    # print(f'choosing move {best_move} with score {best_value}')
    return best_move

def get_next_move(board, token):
    return _get_next_move(board, token)

def get_next_bias_move(board, token, bias):
    return _get_next_move(board, token, bias=bias)