Mac Chapman :: lines.py

This bot has played 740 games (275 wins / 40 draws / 425 losses).

Play against this bot as X or as O.

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

Bot P W D L Points Size From beginner One hit wonder Goldfish
Robert Seaman : : not_minimax.py (v25) 10 10 0 0 10 3501 False False True
Séverin Hatt : : romuald_no_last_check.py (v1) 10 10 0 0 10 1598 False False True
Jordan Banting : : trololololol.py (v1) 10 10 0 0 10 402 False False True
Martijn Pieters : : zopatista.py (v9) 10 10 0 0 10 2649 False False False
Sergey Kolosov : : sergeykolosov_0004.py 10 8 0 2 6 146 False True True

...and these bots have done worst

Bot P W D L Points Size From beginner One hit wonder Goldfish
Ian Ozsvald : : ian12.py 10 0 0 10 -10 943 False False True
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

All standings against lines.py

The code

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


def get_logger():
    logging.basicConfig(
         filename='bot_star.log',
         level=logging.WARNING,
         format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s',  # noqa
         datefmt='%H:%M:%S',
    )
    return logging.getLogger('bot')


def first_empty(l):
    for i, c in enumerate(l):
        if c == '.':
            return i


def highest_position(l):
    highest = 0
    for i, c in enumerate(l):
        if c > l[highest]:
            highest = i
    return highest


def preference_for_cell(cell, token, other_token):
        if cell == token:
            return 1
        if cell == other_token:
            return -1
        return 0


def get_preference(log, board, token, other_token, move):
    log.info('Calculating pref for move option %s', move)
    initial_x = move
    initial_y = first_empty(board[move])
    directions = [
        (-1, 1),  # left/up
        (-1, 0),  # left
        (-1, -1),  # left/down
        (0, -1),  # down
        (1, -1),  # right/down
        (1, 0),  # right
        (1, 1),  # right/up
    ]
    opposites = {
        (-1, 1): (1, -1),
        (-1, 0): (1, 0),
        (-1, -1): (1, 1),
        (0, -1): None,
        (1, -1): (-1, 1),
        (1, 0): (-1, 0),
        (1, 1): (-1, -1),
    }
    prefs_for_direction = {}
    # Look through the possible directions.
    # For each, move 3 cells out and see what's there.
    # Count how many we have and how many the opponent has in that direction
    # TODO properly stop our calculations when we hit an opposing token
    for direction in directions:
        x, y = initial_x, initial_y
        ours_blocked = False
        theirs_blocked = False
        log.info('Direction %s, [%s, %s]', direction, x, y)
        prefs_for_direction[direction] = {'ours': 0, 'theirs': 0}
        for _ in range(3):
            try:
                x = x + direction[0]
                y = y + direction[1]
                if x < 0 or y < 0:
                    break
                cell = board[x][y]
                log.info('cell: %s (%s, %s)', cell, x, y)
                if cell == token and not ours_blocked:
                    prefs_for_direction[direction]['ours'] += 1
                    theirs_blocked = True
                if cell == other_token and not theirs_blocked:
                    prefs_for_direction[direction]['theirs'] += 1
                    ours_blocked = True
            except Exception:
                continue
        log.info("Final prefs: %s", prefs_for_direction[direction])
    log.info(prefs_for_direction)
    total = 0
    for direction, prefs in prefs_for_direction.items():
        # If we're in a row of 3+, this is a great choice - we can win!
        if prefs_for_direction.get(opposites[direction], {'ours': 0, 'theirs': 0})['ours'] + prefs['ours'] > 2:
            total += 200
        # If our opponent has a row of 3+, we've got to stop them!
        if prefs_for_direction.get(opposites[direction], {'ours': 0, 'theirs': 0})['theirs'] + prefs['theirs'] > 2:
            total += 100
        total += prefs['ours']
        total += prefs['theirs']
    return total


def get_next_move(board, move_list, token, state):
    """

    https://docs.google.com/document/d/1Wu-cMAn-dxUnz-zaeOQKUirS7_Onq8Bi2gm7jpbQwkE/edit#

    Numberphile has a video:
    https://www.youtube.com/watch?v=yDWPi1pZ0Po

    https://www.quora.com/What-is-the-winning-strategy-for-the-first-player-in-Connect-Four-games

    State is passed from move to move.
    """
    log = get_logger()
    log.info('Opcodes so far: %s', get_opcode_count())

    available_moves = game.available_moves(board)
    if token == 'X':
        other_token = 'O'
    else:
        other_token = 'X'

    log.info('Board:')
    for y in range(5, -1, -1):
        log.info(' '.join([board[x][y] for x in range(7)]))
    log.info("move_list: %s", move_list)
    log.info("token: %s", token)
    log.info("state: %s", state)
    log.info("available_moves: %s", available_moves)
    log.info('Opcodes so far: %s', get_opcode_count())

    # If I go first, go in the middle
    if not move_list:
        return 3

    preferences = []
    for col in available_moves:
        # for each move, calculate a preference value
        preferences.append(get_preference(log, board, token, other_token, col))

    if all(map(lambda p: p == 0, preferences)):
        for i in range(int(len(preferences) / 2)):
            preferences[i] += i

    # Move to the one that scored highest
    move = available_moves[highest_position(preferences)]

    log.info('Prefs: %s', preferences)

    new_state = {}
    log.info("I'm going to go %s", move)
    log.info('Opcodes so far: %s', get_opcode_count())
    return (move, new_state)