import random

'''
Gioco del tris (filetto). Mette a disposizione le seguenti classi:

Board: schema di gioco

Player: giocatore stupido (esegue prima mossa possibile)
Human: giocatore umano (chiede la mossa da eseguire)
Random: giocatore casuale (esegue una mossa a caso)

Game: partita tra due giocatori
'''

class Board(object):
    '''
    rappresentazione del gioco Tris
    scacchiera 3x3: ogni cella contiene X, O oppure .
    inoltre mantiene informazione sul turno di gioco
    Le caselle sono identificate da una cifra da 1-9 come nello schema:
     
    123
    456
    789
    
    ''' 
    def __init__(self,board=None):
        'nuovo schema di gioco vuoto oppure copia di @board'
        if board is None:
            self.s = '.........' # '.'*9
            self.turn = 'O'
        else:
            self.s = board.s
            self.turn = board.turn
        self._state = 'not ended'
       
    def rows(self):
       'restituisce le tre righe di tre caratteri'
       return [self.s[n*3:(n+1)*3] for n in range(3)]
       
    def cols(self):
       'restituisce le tre colonne di tre caratteri'
       return [self.s[n] + self.s[n+3] + self.s[n+6] for n in range(3)]
       
    def diags(self):
       'restituisce le due diagonali di tre caratteri'
       return [self.s[0]+self.s[4]+self.s[8], self.s[2]+self.s[4]+self.s[6]]
       
    def __str__(self):
        'rappresentazione dello schema in una stringa multi-linea'
        r = '\n'.join(self.rows())+'\n'+'turn: '+self.turn+'\n'
        s = self.state()
        if s != 'not ended':
            if s == 'draw':
                r += 'DRAW!\n'
            else:
                r += s + ' WINS!'
        return r

    def is_valid(self,move):
        '''la mossa @move e' valida?'''
        return move in range(1,10) and self.s[move-1] == '.'

    def valid_moves(self):
        'lista di tutte le mosse valide'
        return [x for x in range(1,10) if self.is_valid(x)]
    
    def state(self):
        '''stato corrente: 'O', 'X', 'draw', 'not ended' '''
        return self._state

    def compute_state(self):
        for r in self.rows() + self.cols() + self.diags():
            if r == 'OOO':
                self._state = 'O'
                return
            if r == 'XXX':
                self._state = 'X'
                return
        if self.s.find('.') == -1:
            self._state = 'draw'
            return
        return 'not ended'

    def play(self, move):
        'effettua una mossa nella posizione @move: 1,2,3, 4,5,6, 7,8,9'
        if self.is_valid(move):
            self.s = self.s[0:move-1] + self.turn + self.s[move:9] 
            if self.turn == 'O':
                self.turn = 'X'
            else:
                self.turn = 'O'
            self.compute_state()
        else:
            # print 'mossa non valida!'
            if move in range(1,10):
                self.s = self.s[0:move-1] + (self.turn.lower()) + self.s[move:9]
            else:
                self.s = self.turn.lower()*9
            if self.turn == 'O':
                self._state = 'X'
            else:
                self._state = 'O'

class Player(object):
    ''' Giocatore. Puo' essere inizializzato con un nome. 
        Gioca la prima mossa valida
    '''

    def __init__(self,name='no-name'):
        self.name = name
       
    def move(self,board):
        return board.valid_moves()[0]
 
    def __str__(self):
        return '{0}: {1}'.format(type(self).__name__, self.name)

class Human(Player):
    def move(self,board):
        while True:
            move = raw_input('%s: che mossa vuoi fare? ' % self.name)
            try: 
                move = int(move)
                if board.is_valid(move):
                    return move
                else:
                    print 'mossa non valida: %s' % move
                    print board
            except ValueError:
                print 'la mossa deve essere un numero tra 1 e 9'

class Random(Player):
    def move(self,board):
        return random.choice(board.valid_moves())
        
class Smart(Player):
    def think(self,board):
        ''' restituisce una coppia (esito,move) con la mossa migliore e relativo esito '''

        # controlla se abbiamo gia' esaminato questa posizione
        if not hasattr(self,'cache'):
            self.cache = {}
        hash = board.turn + board.s
        if hash in self.cache:
            return self.cache[hash]

        me = board.turn
        draw_move = None
        for m in board.valid_moves():
            b = Board(board)
            b.play(m)
            s = b.state()
            if s == me:
                self.cache[hash] = (me,m)
                return (me,m) # mossa vincente!
            if s == 'draw': # unica mossa possibile!
                self.cache[hash] = ('draw',m)
                return ('draw',m)
            assert s == 'not ended'
            (s,mm) = self.think(b)
            if s == me:
                self.cache[hash] = (me,m)
                return (me,m) # mossa vincente!
            if s == 'draw':
                draw_move = m # mossa pattante... c'e' di meglio?
        # nessuna mossa vincente trovata. puntiamo alla patta
        if draw_move:
            self.cache[hash] = ('draw',draw_move)
            return ('draw',draw_move)
        # vince avversario :( scegliamo una mossa a caso
        self.cache[hash] = (b.turn,random.choice(board.valid_moves()))
        return self.cache[hash]

    def move(self,board):
        return self.think(board)[1]

class Game(object):
    def __init__(self,player_O,player_X,verbose=2):
        self.players = {'O': player_O,
                        'X': player_X}
        self.board = Board()
        self.verbose = verbose
        if self.verbose > 0:
            print 'O={0}, X={1}'.format(player_O.name, player_X.name)
        
    def play(self):
        while self.board.state() == 'not ended':
            # visualizza schema di gioco:
            if self.verbose>1:
                print self.board

            # chiede al giocatore di turno la mossa da fare:
            current_player = self.players[self.board.turn]
            move = current_player.move(Board(self.board))
            if self.verbose>1:
                print "{0} sceglie la mossa: {1}".format(current_player, move)

            # esegue mossa sullo schema:
            self.board.play(move)

        # visualizza posizione finale 
        state = self.board.state()
        if self.verbose > 1:
            print self.board
            if state in self.players:
                print 'Ha vinto: {0}'.format(self.players[state])
        return self.board

if __name__ == '__main__':
    # esecuzione diretta del codice
    name = raw_input('Come ti chiami? ')
    game = Game(Human(name),Smart('computer'))
    game.play()
