from bppy import *

x = lambda row, col: BEvent('X' + str(row) + str(col))
o = lambda row, col: BEvent('O' + str(row) + str(col))

LINES = [[(i, j) for j in range(3)] for i in range(3)] + [[(i, j) for i in range(3)] for j in range(3)] + [
    [(i, i) for i in range(3)]] + [[(i, 3 - i - 1) for i in range(3)]]
x_lines = [[x(i, j) for (i, j) in line] for line in LINES]
o_lines = [[o(i, j) for (i, j) in line] for line in LINES]

any_x = [x(i, j) for i in range(3) for j in range(3)]
any_o = [o(i, j) for i in range(3) for j in range(3)]
move_events = EventSet(lambda e: e.name.startswith('X') or e.name.startswith('O'))

static_event = {
    'OWin': BEvent('OWin'),
    'XWin': BEvent('XWin'),
    'draw': BEvent('Draw')
}


@b_thread
def square_taken(row, col):
    yield {waitFor: [x(row, col), o(row, col)]}
    yield {block: [x(row, col), o(row, col)]}


@b_thread
def enforce_turns():
    while True:
        yield {waitFor: any_x, block: any_o}
        yield {waitFor: any_o, block: any_x}


@b_thread
def end_of_game():
    yield {waitFor: list(static_event.values())}
    yield {block: All()}


@b_thread
def detect_draw():
    for r in range(3):
        for c in range(3):
            yield {waitFor: move_events}
    yield {request: static_event['draw'], priority: 90}


@b_thread
def detect_x_win(line):
    for i in range(3):
        yield {waitFor: line}
    yield {request: static_event['XWin'], priority: 100}


@b_thread
def detect_o_win(line):
    for i in range(3):
        yield {waitFor: line}
    yield {request: static_event['OWin'], priority: 100}


# Preference to put O on the center
@b_thread
def center_preference():
    while True:
        yield {request: o(1, 1), priority: 35}


# Preference to put O on the corners
@b_thread
def corner_preference():
    while True:
        yield {request: [o(0, 0), o(0, 2), o(2, 0), o(2, 2)], priority: 20}


# Preference to put O on the sides
@b_thread
def side_preference():
    while True:
        yield {request: [o(0, 1), o(1, 0), o(1, 2), o(2, 1)], priority: 10}


# player O strategy to add a third O to win
@b_thread
def add_third_o(line):
    for i in range(2):
        yield {waitFor: line}
    yield {request: line, priority: 50}


# player O strategy to prevent a third X
@b_thread
def prevent_third_x(xline, oline):
    for i in range(2):
        yield {waitFor: xline}
    yield {request: oline, priority: 40}


@b_thread
def block_fork(xfork, ofork):
    for i in range(2):
        yield {waitFor: xfork}
    yield {request: ofork, priority: 30}


forks22 = [[x(1, 2), x(2, 0)], [x(2, 1), x(0, 2)], [x(1, 2), x(2, 1)]], [o(2, 2), o(0, 2), o(2, 0)]
forks02 = [[x(1, 2), x(0, 0)], [x(0, 1), x(2, 2)], [x(1, 2), x(0, 1)]], [o(0, 2), o(0, 0), o(2, 2)]
forks20 = [[x(1, 0), x(2, 2)], [x(2, 1), x(0, 0)], [x(2, 1), x(1, 0)]], [o(2, 0), o(0, 0), o(2, 2)]
forks00 = [[x(0, 1), x(2, 0)], [x(1, 0), x(0, 2)], [x(0, 1), x(1, 0)]], [o(0, 0), o(0, 2), o(2, 0)]
forks_diag = [[x(0, 2), x(2, 0)], [x(0, 0), x(2, 2)]], [o(0, 1), o(1, 0), o(1, 2), o(2, 1)]



# simulate player X
@b_thread
def player_x():
    while True:
        yield {request: any_x}


if __name__ == "__main__":
    bprog = BProgram(
        bthreads=[square_taken(i, j) for i in range(3) for j in range(3)] +
                 [enforce_turns(), end_of_game(), detect_draw()] +
                 [detect_x_win(line) for line in x_lines] +
                 [detect_o_win(line) for line in o_lines] +
                 [center_preference(), corner_preference(), side_preference()] +
                 [add_third_o(line) for line in o_lines] +
                 [prevent_third_x(xline, oline) for (xline, oline) in zip(x_lines, o_lines)] +
                 [block_fork(xfork, forks22[1]) for xfork in forks22[0]] +
                 [block_fork(xfork, forks02[1]) for xfork in forks02[0]] +
                 [block_fork(xfork, forks20[1]) for xfork in forks20[0]] +
                 [block_fork(xfork, forks00[1]) for xfork in forks00[0]] +
                 [block_fork(xfork, forks_diag[1]) for xfork in forks_diag[0]] +
                 [player_x()],
        event_selection_strategy=PriorityBasedEventSelectionStrategy(default_priority=0),
        listener=PrintBProgramRunnerListener()
    )
    bprog.run()
