Daniel Patrick :: error_unable_to_connect4.py (v2)

This bot has played 336 games (207 wins / 6 draws / 123 losses).

Play against this bot as X or as O.

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

Bot P W D L Points Size From beginner One hit wonder Goldfish
Mac Chapman : : lines.py 10 10 0 0 10 893 False False False
Robert Seaman : : not_minimax.py (v25) 10 10 0 0 10 3501 False False True
Tom Campbell : : winblockprevent.py (v5) 10 9 0 1 8 321 False False True
Jon Bannister : : anybot_r282.py (v5) 10 5 5 0 5 974 False False True
Dominik K : : lightgambler.py 10 7 0 3 4 145 False False True

...and these bots have done worst

Bot P W D L Points Size From beginner One hit wonder Goldfish
James Campbell : : horrible.py (v4) 10 0 0 10 -10 246 False False True
Joe Jordan : : jbot2.py (v4) 10 0 0 10 -10 719 False False False
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

All standings against error_unable_to_connect4.py

The code

      
import re
from botany_connectfour import game
from botany_core.tracer import get_opcode_count


def get_next_move(board, move_list, token):
    """
    board: [col][row]
    """

    def get_other_token(token):
        return "X" if token == "O" else "O"

    def score_position(board, move, their_move, token, other_token, full_check=False):
        board_string = "".join(["".join(row) for row in board])
        score = 0

        def winnable_regexes(token):
            return  [
                "(?=\.{}{{3}})".format(token),  # vertical
                "(?={}{{3}}\.)".format(token),  # vertical
                "(?=\.(?:.{{5}}{0}){{3}})".format(token),  # horizontal
                "(?=(?:{0}.{{5}}){{3}}\.)".format(token),  # horizontal
                "(?=\.(?:.{{6}}{0}){{3}})".format(token),  # diagonal1
                "(?=(?:{0}.{{6}}){{3}}\.)".format(token),  # diagonal1
                "(?=\.(?:.{{4}}{0}){{3}})".format(token),  # diagonal2
                "(?=(?:{0}.{{4}}){{3}}\.)".format(token),  # diagonal2
            ]

        def three_regexes(token):
            return [
                "({}{{3}})".format(token),  # vertical
                "((?:{0}.{{5}}){{2}}{0})".format(token),  # horizontal
                "((?:{0}.{{6}}){{2}}{0})".format(token),  # diagonal1
                "((?:{0}.{{4}}){{2}}{0})".format(token),  # diagonal2
            ]

        def two_regexes(token):
            return [
                "({}{{2}})".format(token),  # vertical
                "({0}.{{5}}{0})".format(token),  # horizontal
                "({0}.{{6}}{0})".format(token),  # diagonal1
                "({0}.{{4}}{0})".format(token),  # diagonal2
            ]

        starting_ops = get_opcode_count()

        if full_check:
            score -= len(tuple(re.finditer(
                "|".join(winnable_regexes(other_token)), board_string
            ))) * 1000
            # score -= len(tuple(re.finditer(
            #     "|".join(three_regexes(other_token)), board_string
            # ))) * 500
            """
            if (get_opcode_count() - starting_ops) < 1000:
                score += len(tuple(re.finditer(
                    "|".join(three_regexes(token)), board_string
                ))) * 25
            """

            if (get_opcode_count() - starting_ops) < 1000:
                score += len(tuple(re.finditer(
                    "|".join(two_regexes(token)), board_string
                ))) * 5
            """
            if (get_opcode_count() - starting_ops) < 1000:
                score -= len(tuple(re.finditer(
                    "|".join(two_regexes(other_token)), board_string
                ))) * 50
            """
        # score columns and rows - prioritise low, central
        score += [0, 1, 2, 3, 2, 1, 0][move[0]]
        score += [5, 4, 3, 2, 1, 0][move[1]]

        return score

    def is_won(board, token):
        winning_positions = "|".join([
            "{}{{4}}".format(token),
            "({0}.{{5}}){{3}}{0}".format(token),
            "({0}.{{6}}){{3}}{0}".format(token),
            "({0}.{{4}}){{3}}{0}".format(token)
        ])

        return re.search(winning_positions, "".join(["".join(row) for row in board]))

    def add_move_to_board(board, move, token):
        i = board[move].index(game.EMPTY)
        board[move][i] = token

    def remove_move_from_board(board, move, token):
        i = "".join(board[move]).rindex(token)
        board[move][i] = game.EMPTY

    possible_moves = []
    possible_move_scores = []

    # test if a move will win the game for me or them
    for move in [col for col in range(game.NCOLS) if game.EMPTY in board[col]]:
        pos = board[move].index(game.EMPTY)
        board[move][pos] = token
        if is_won(board, token):
            return move

        # test if a potentially available spot will win for them
        other_token = get_other_token(token)
        scores = []
        for move2 in [col for col in range(game.NCOLS) if game.EMPTY in board[col]]:
            pos2 = board[move2].index(game.EMPTY)
            board[move2][pos2] = other_token

            scores.append(
                score_position(
                    board,
                    (move, pos),
                    (move2, pos2),
                    token,
                    other_token,
                    len(move_list) >= 3
                )
            )

            board[move2][pos2] = game.EMPTY

        possible_moves.append(move)
        possible_move_scores.append(min(scores))

        board[move][pos] = game.EMPTY

    print(get_opcode_count())

    print(possible_move_scores)
    print(possible_moves)
    if possible_moves:
        best_move_index = possible_move_scores.index(max(possible_move_scores))
        return possible_moves[best_move_index]
    else:
        return [col for col in range(game.NCOLS) if game.EMPTY in board[col]][0]