3
\$\begingroup\$

I am looking for a review of my perfect TicTacToe algorithm. It will be split up into 3 parts:

  • The function
  • The GameState Class
  • The game (NOT well designed)

I am mainly looking for tips and comments on the first 2, but I will take comments / tips on the poorly designed game.

alphabeta.py

#Using wikipedia pseudocode without depth
def alphabeta(game_state, alpha=-2, beta=2, our_turn=True):
    if game_state.is_gameover():
        return game_state.score(), None
    if our_turn:
        score = -2 #worst non-possible score. A win, tie, or even a loss will change this
        for move in game_state.get_possible_moves():
            child = game_state.get_next_state(move, True)
            temp_max, _ = alphabeta(child, alpha, beta, False)
            if temp_max > score:
                score = temp_max
                best_move = move
            alpha = max(alpha, score)
            if beta <= alpha:
                break
        return score, best_move
    else:
        score = 2 #worst non-possible score. A win, tie, or even a loss will change this
        for move in game_state.get_possible_moves():
            child = game_state.get_next_state(move, False)
            temp_min, _ = alphabeta(child, alpha, beta, True)
            if temp_min < score:
                score = temp_min
                best_move = move
            beta = min(beta, score)
            if beta <= alpha:
                break
        return score, best_move

gamestate.py

class GameState:
    def __init__(self,board,char='X',oppchar='O'):
        self.char = char
        self.oppchar = oppchar
        self.board = board
        self.winning_combos = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]

    def is_gameover(self):
        '''returns if a game_state has been won or filled up'''
        if self.board.count(self.char) + self.board.count(self.oppchar) == 9:
            return True
        for combo in self.winning_combos:
            if self.board[combo[0]] == self.char and self.board[combo[1]] == self.char and self.board[combo[2]] == self.char:
                return True    
            elif self.board[combo[0]] == self.oppchar and self.board[combo[1]] == self.oppchar and self.board[combo[2]] == self.oppchar:
                return True       
        return False
    def score(self):
        '''returns 1, 0, or -1 corresponding to the score (WIN, TIE, LOSS) from the ai's point of view'''
        if self.board.count(self.char) + self.board.count(self.oppchar) == 9:
            return 0
        for combo in self.winning_combos:
            if self.board[combo[0]] == self.char and self.board[combo[1]] == self.char and self.board[combo[2]] == self.char:
                return 1   
            elif self.board[combo[0]] == self.oppchar and self.board[combo[1]] == self.oppchar and self.board[combo[2]] == self.oppchar:
                return -1       

    @staticmethod
    def return_state(score):
        '''Returns a word for each score'''
        if score == 0:
            return('TIED')
        elif score == 1:
            return('WON')
        elif score == -1:
            return('LOST')
        else:
            return('ERROR')

    def pretty_print(self):
        '''prints the board joined by spaces'''
        print(' '.join(self.board[:3]))
        print(' '.join(self.board[3:6]))
        print(' '.join(self.board[6:9]))

    def get_possible_moves(self):
        '''returns all possible squares to place a character'''
        return [index for index, square in enumerate(self.board) if square != self.char and square != self.oppchar]

    def get_next_state(self, move, our_turn):
        '''returns the gamestate with the move filled in'''
        copy = self.board[:]
        copy[move] = self.char if our_turn else self.oppchar
        return GameState(copy, char=self.char, oppchar=self.oppchar)

game

#!/usr/bin/env python3
import random
from time import sleep
import alphabeta as ab
import gamestate as gs

def get_p_move(game):
    while True:
        raw_move = input('Your move (1-9) > ')
        if raw_move.isdigit():
            p_move = int(raw_move) - 1
            if p_move > -1 and p_move < 9:
                if game.board[p_move] == '_':
                    break
    return p_move

def run_game(game, player_goes_first):
    if player_goes_first:
        while True:
            game.pretty_print()
            p_move = get_p_move(game)
            game = game.get_next_state(p_move, False)
            if game.is_gameover(): break
            score, ai_move = ab.alphabeta(game)
            game = game.get_next_state(ai_move, True)
            if game.is_gameover(): break
    else:
        while True:
            score, ai_move = ab.alphabeta(game)
            game = game.get_next_state(ai_move, True)
            if game.is_gameover(): break
            game.pretty_print()
            p_move = get_p_move(game)
            game = game.get_next_state(p_move, False)
            if game.is_gameover(): break
    game.pretty_print()
    print('The computer ' + game.return_state(score))

def get_symbols():
    human_char = input('Pick your symbol > ')
    if len(human_char) > 1 or human_char == '' or human_char == ' ' or human_char == '_':
        exit()
    elif human_char == 'X' or human_char == 'x':
        ai_char = 'O'
    else:
        ai_char = 'X'
    return human_char, ai_char
if __name__ == '__main__':
    try:
        human_char, ai_char = get_symbols()
        start_board = ['_'] * 9
        player_goes_first = bool(random.randint(0,2))

        input('You go first:{}\nenter to continue\n'.format(player_goes_first))
        game = gs.GameState(start_board,char=ai_char, oppchar=human_char)
        run_game(game, player_goes_first)
    except KeyboardInterrupt:
        print('\n')
        exit()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.