# coding: utf-8 """ A simple TicTacToe game with AI using alpha-beta pruning (c) Julian Habrock , 9/2009 bytemuehle.de """ import pygame import pygame.locals as loc ######### # ai part # TicTacToe AI # # A ``field`` is a list of length 9 representing a tictactoe field. At the # beginning its [0,0,0,0,0,0,0,0,0]. 1 is used to show the first players draws, # -1 for the second players ones. def printfield(field): """ prints a field (helper function for debugging) """ out = "" chars = {1:"X", -1:"O", 0:"."} for i, c in enumerate(field): out += chars[c] if not (i+1) % 3: out += "\n" print out def score(field): """ Score a field for player 1. If a player wins, return 10 (or -10). Otherwise return the difference between the possible ways to win for each player. """ lines = [field[0:9:4],field[2:7:2]] possible_win_positions = [0, 0] for i in xrange(3): lines.append(field[i*3:i*3+3]) lines.append(field[i:i+7:3]) for line in lines: if sum(line) == 3: return 10 elif sum(line) == -3: return -10 elif not -1 in line: possible_win_positions[0] += 1 elif not 1 in line: possible_win_positions[1] += 1 if not any(possible_win_positions): return False return (possible_win_positions[0]-possible_win_positions[1]) def max(deep, alpha, beta, field, first=True): """ The max-function for the minimax algorithm. """ field_score = score(field) if deep == 0 or abs(field_score) == 10 or 0 not in field: # give earlier wins a higher score if field_score == 10: return 10 + deep elif field_score == -10: return -10 - deep return field_score best_i = None for i in xrange(len(field)): if not field[i]: s = min(deep-1, alpha, beta, field[:i]+[1]+field[i+1:], False) if s >= beta: if first: return i else: return beta if s > alpha: alpha = s best_i = i if first: return best_i else: return alpha def min(deep, alpha, beta, field, first=True): """ The min-function for the minimax algorithm. Since this is nearly the same as ``max`` both methods should be replaced with one negamax function. """ field_score = score(field) if deep == 0 or abs(field_score) == -10 or 0 not in field: if field_score == -10: return -10 - deep elif field_score == 10: return 10 + deep return field_score best_i = None for i in xrange(len(field)): if not field[i]: s = max(deep-1, alpha, beta, field[:i]+[-1]+field[i+1:], False) if s <= alpha: if first: return i else: return alpha if s < beta: beta = s best_i = i if first: return best_i else: return beta def ai(field): """ wrap the max function call to make it even easier to call it from the game part """ return max(6, -100, 100, field) ########## # gamepart def main(): pygame.init() screen = pygame.display.set_mode((500, 500)) pygame.display.set_caption('T1(T4(T41 - by jug') def init(): screen.fill((0,0,0), (100,100,300,300)) for i in xrange(1, 3): a = 100+i*100 pygame.draw.line(screen, (255,255,255), (a, 100), (a, 400)) pygame.draw.line(screen, (255,255,255), (100, a), (400, a)) pygame.display.flip() return [0 for i in xrange(9)] if pygame.font: font = pygame.font.Font(None, 36) text = font.render("TicTacTai", 1, (255,255,255), (0,0,0)) textpos = text.get_rect(centerx=screen.get_width()/2, top=20) screen.blit(text, textpos) font = pygame.font.Font(None, 20) font.set_italic(True) text = font.render("press space to restart", 1, (255,255,255), (0,0,0)) textpos = text.get_rect(centerx=screen.get_width()/2, top=450) screen.blit(text, textpos) clock = pygame.time.Clock() drects = [] field = init() status = "" stati = {0:"draw", -10:"you win", 10:"you lose"} while 1: clock.tick(100) for event in pygame.event.get(): if event.type == loc.QUIT: return elif event.type == loc.KEYDOWN: if event.key == loc.K_ESCAPE: return elif event.key == loc.K_SPACE: field = init() status = "" if event.type == loc.MOUSEBUTTONDOWN and not status: x, y = event.pos if 100 < x < 400 and 100 < y < 400: index = ((y-100)/100)*3+(x-100)/100 if not field[index]: field[index] = -1 pos = (x-x%100+50, y-y%100+50) drects.append(pygame.draw.circle(screen, (200, 0, 0), pos, 40)) if not score(field) == -10 and 0 in field: ai_chip = ai(field) field[ai_chip] = 1 pos = (ai_chip%3)*100+150, (ai_chip/3)*100+150 drects.append(pygame.draw.circle(screen, (0, 0, 200), pos, 40)) s = score(field) if not 0 in field or s is False or abs(s) == 10: status = stati[s] font = pygame.font.Font(None, 25) text = font.render(status, 1, (255,255,255)) textpos = text.get_rect( center=screen.get_rect().center) drects.append(screen.blit(text, textpos)) pygame.display.update(drects) drects = [] if __name__ == '__main__': main()