"""
 tictactoe.py

 a Tic Tac Toe game in Python,
 using classes as a demo for an intro programming demo.

 --- API ---

         letter2point(letter)
         point2letter(point)
         ask(question, legal_responses)

         Person(symbol)
               .symbol
               .get_symbol()
               .get_move(board)

         Computer(symbol)
                 .symbol
                 .get_symbol()
                 .get_move(board)

         Board()
              .directions
              .empty
              .grid
              .row2string(row)
              .get_empty_count()
              .get_symb_at(point)
              .get_nth_empty_move(n)
              .make_move(move, symbol)
              .get_winner()

         Game()
             .whose_turn
             .players
             .board
             .make_player(symbol)
             .play()

         Referee()
                .run()

 --- discussion questions ---

  * Do you think that all of this should be done with objects?
  * How about Referee?

  *  Why are the parens different in the following :
        import math
        x = math.sqrt(10.0)
     vs
        Referee().run()
     ?

  *  What's going on with this line :
        self.player_X = player_X or self.get_player('X')

  *  What are these special methods doing ?
        object.__init__() ?
        object.__str__()  ?

  * Does the Game object really need to have (whose_move, board)
    as data within it?  Or could those just be local variables
    in the Game.play() function?  What are the advantages / disadvantages
    of each?

 Below is a summary of the functions, objects, data, and methods.
 Some of these are intended to be 'public' (what I'd call the 'API')
 and some are 'private' (only for use within that class).

  * Which of these are data?  Which methods?
  * Where are they defined in the code above?
  * Is everything obvious from just the names?
  * Or are some properties implied?
  * Did I use getter's and setter's consistently everywhere?
  * Where do you think I cut corners?

 Is there any object inheritance here?  Could there be?
 What is the relationships between the objects?


 --- other remarks ---

 * There isn't a a good test suite here yet.
   Several bugs persisted for a while:
     * It wasn't finding 'O' wins.
     * It was calling anything with 9 moves a tie.
     * The random thing worked some of the time ... counting logic was wrong.
     * The user could mark over an already place mark and cheat.  (Oops).
     * Hitting return without typing crashed when answer[0] was examined.

 * So ... what do you think some of the tests should be?

 * All in all this was one full evening's worth of coding for me,
   about six hours - and I've written tic-tac-toe programs
   before in other languages.  So you should be aware that
   doing anything of this scope (which isn't all that big
   as programming project go) takes time and will have bugs.

 * Suppose we want to make the computer *win* .
   How do we go about writing that?
   A conversation for another time ... or an algorithms class. :)

 == sample output =====================================================

 $ python tictactoe.py
  === Tic Tac Toe === 
  Inputs may be abbreviated by their first letter. 
  Type 'quit' at any prompt to quit. 
  X always goes first.
  So there.
 
  == Playing a new game. ==
  Player X is person or computer ? person
  Player O is person or computer ? computer
 
      |   |            a | b | c 
   ---+---+---        ---+---+---
      |   |            d | e | f 
   ---+---+---        ---+---+---
      |   |            g | h | i 
 
  X: Where do you want to move? a
 
    X |   |            a | b | c 
   ---+---+---        ---+---+---
      |   |            d | e | f 
   ---+---+---        ---+---+---
      |   |            g | h | i 
 
  O: the computer chooses 'b'.
 
    X | O |            a | b | c 
   ---+---+---        ---+---+---
      |   |            d | e | f 
   ---+---+---        ---+---+---
      |   |            g | h | i 
 
  X: Where do you want to move? e
 
    X | O |            a | b | c 
   ---+---+---        ---+---+---
      | X |            d | e | f 
   ---+---+---        ---+---+---
      |   |            g | h | i 
 
  O: the computer chooses 'c'.
 
    X | O | O          a | b | c 
   ---+---+---        ---+---+---
      | X |            d | e | f 
   ---+---+---        ---+---+---
      |   |            g | h | i 
 
  X: Where do you want to move? i
 
    X | O | O          a | b | c 
   ---+---+---        ---+---+---
      | X |            d | e | f 
   ---+---+---        ---+---+---
      |   | X          g | h | i 
 
   X  wins!
 
  == Playing a new game. ==
  Player X is person or computer ? c
  Player O is person or computer ? c
 
      |   |            a | b | c 
   ---+---+---        ---+---+---
      |   |            d | e | f 
   ---+---+---        ---+---+---
      |   |            g | h | i 
 
  X: the computer chooses 'f'.
 
      |   |            a | b | c 
   ---+---+---        ---+---+---
      |   | X          d | e | f 
   ---+---+---        ---+---+---
      |   |            g | h | i 
 
  O: the computer chooses 'd'.
 
      |   |            a | b | c 
   ---+---+---        ---+---+---
    O |   | X          d | e | f 
   ---+---+---        ---+---+---
      |   |            g | h | i 
 
  X: the computer chooses 'i'.
 
      |   |            a | b | c 
   ---+---+---        ---+---+---
    O |   | X          d | e | f 
   ---+---+---        ---+---+---
      |   | X          g | h | i 
 
  O: the computer chooses 'g'.
 
      |   |            a | b | c 
   ---+---+---        ---+---+---
    O |   | X          d | e | f 
   ---+---+---        ---+---+---
    O |   | X          g | h | i 
 
  X: the computer chooses 'e'.
 
      |   |            a | b | c 
   ---+---+---        ---+---+---
    O | X | X          d | e | f 
   ---+---+---        ---+---+---
    O |   | X          g | h | i 
 
  O: the computer chooses 'a'.
 
    O |   |            a | b | c 
   ---+---+---        ---+---+---
    O | X | X          d | e | f 
   ---+---+---        ---+---+---
    O |   | X          g | h | i 
 
  X: the computer chooses 'h'.
 
    O |   |            a | b | c 
   ---+---+---        ---+---+---
    O | X | X          d | e | f 
   ---+---+---        ---+---+---
    O | X | X          g | h | i 
 
  O: the computer chooses 'c'.
 
    O |   | O          a | b | c 
   ---+---+---        ---+---+---
    O | X | X          d | e | f 
   ---+---+---        ---+---+---
    O | X | X          g | h | i 
 
  X: the computer chooses 'b'.
 
    O | X | O          a | b | c 
   ---+---+---        ---+---+---
    O | X | X          d | e | f 
   ---+---+---        ---+---+---
    O | X | X          g | h | i 
 
  Tie game.
 
  == Playing a new game. ==
  Player X is person or computer ? q
 
  Bye. 

 -- versions --
   original              Oct 2006
   updated for python3   Nov 2019

 Jim Mahoney | cs.marlboro.college | MIT License
"""
import sys           # sys.exit()
import random        # random.randint(low,high)

def letter2point(letter):
    """ Convert a letter 'a'...'i' to a point (0,0) to (2,2). """
    zero_to_eight = ord(letter) - ord('a')
    row = zero_to_eight // 3       # 0, 0, 0, 1, 1, 1, 2, 2, 2
    column = zero_to_eight % 3     # 0, 1, 2, 0, 1, 2, 0, 1, 2
    return (row, column)

def point2letter(point):
    """ Convert a point (0,0) to (2,2) to a letter 'a' ... 'i'. """
    (row, column) = point
    return chr(ord('a') + row*3 + column)

def ask(question, legal_responses=()):
    """ Ask user a question. Return first character of what they type.
        If the first letter is 'q' (for 'quit'), quit the program.
        A list of legal responses (first letters) may be specified;
        if so, keep asking until one of those is seen. """
    while True:
        try:
            answer = input(question)
            if (answer):
                first_char = answer[0]
            else:
                first_char = ""
            if (first_char == 'q'):
                print("\n Bye. ")
                sys.exit()
            elif (first_char in legal_responses or not legal_responses):
                return answer
            else:
                print("  Oops: first letter isn't",
                      legal_responses + ('q',), ".")
                print("  Please try again.")
        except EOFError:
            print("\n Bye. ")
            sys.exit()

class Person:
    """ An interactive person player. """

    def __init__(self, symbol):
        self.symbol = symbol

    def get_symbol(self):
        return self.symbol

    def get_move(self, board):
        """ Return a (row,column) point where the person wants to move. """
        while True:
            letter = ask( " " + self.symbol + ": Where do you want to move? ",
                          ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'))
            move = letter2point(letter)
            if (board.get_symb_at(move) != ' '):
                print("  Oops: there's already a mark there.  Try again.")
            else:
                print()
                return move

class Computer:
    """ A (random) computer player. """

    def __init__(self, symbol):
        self.symbol = symbol

    def get_symbol(self):
        return self.symbol

    def get_move(self, board):
        """ Get the computer's next (random) move as a (row,column) point. """
        x = random.randint(0, board.get_empty_count()-1)
        move = board.get_nth_empty_move(x)
        letter = point2letter(move)
        print(" " + self.symbol + ": the computer chooses '"+letter+ "'.")
        print
        return move

class Board:
    """ The game board. """
                                                # All the ways to win :
    directions = ( ( (0,0), (0,1), (0,2) ),     #    horizontals,
                   ( (1,0), (1,1), (1,2) ),
                   ( (2,0), (2,1), (2,2) ),
                   ( (0,0), (1,0), (2,0) ),     #    verticals,
                   ( (0,1), (1,1), (2,1) ),
                   ( (0,2), (1,2), (2,2) ),
                   ( (0,0), (1,1), (2,2) ),     #    and diagonals.
                   ( (0,2), (1,1), (2,0) )  )

    def __init__(self):
        self.grid = [ [' ']*3, [' ']*3, [' ']*3 ]       # 3x3 matrix of spaces
        self.empty = 9                                  # remaining empty spots

    def get_empty_count(self):
        return self.empty

    def __str__(self):
        """ What a Board should look like when converted to a string. """
        return self.row2string(0) + "          a | b | c \n"  + \
                        "  ---+---+---        ---+---+---\n"  + \
               self.row2string(1) + "          d | e | f \n"  + \
                        "  ---+---+---        ---+---+---\n"  + \
               self.row2string(2) + "          g | h | i \n"

    def row2string(self, row):
        """ Return one row of the board as a string. """
        return "   " + self.grid[row][0] + \
               " | " + self.grid[row][1] + \
               " | " + self.grid[row][2]

    def make_move(self, move, symbol):
        """ Make a move on the board. """
        (row, column) = move
        self.grid[row][column] = symbol
        self.empty = self.empty - 1

    def get_symb_at(self, point):
        """ Given a (row,col) point, return the symbol on the grid there. """
        (row, col) = point
        return self.grid[row][col]

    def get_nth_empty_move(self, n_want):
        """ Return (row,col) point of the n'th (0,1,2,...) empty spot."""
        n_seen = 0
        for row in range(3):
            for col in range(3):
                if (self.grid[row][col] == ' '):
                    if (n_want == n_seen):
                        return (row,col)
                    else:
                        n_seen = n_seen +1
        print(" *** ")
        print(" OOPS - asked for empty ", n_want, " on board \n", self.grid)
        print(" *** ")
        return (-1,-1)  # error

    def get_winner(self):
        """ Return 'X', 'O', 'tie', or (if the game isn't done) just ''. """
        for line in self.directions:
            symbol = self.get_symb_at(line[0])
            if (symbol in ('X','O') and \
                symbol==self.get_symb_at(line[1])==self.get_symb_at(line[2])):
                return symbol
        if (self.empty == 0):
            return "tie"
        else:
            return ''

class Game:
    """ Choose players and play a game. """

    def __init__(self, players=[]):
        """ Start a new game with the given players,
            or prompt for the players if not provided."""
        print()
        print(" == Playing a new game. ==")
        if (players):
            self.players = players
        else:
            self.players = [ self.make_player('X'), self.make_player('O') ]
        self.whose_turn = 0
        self.board = Board()

    def make_player(self, symbol):
        """ Prompt user for a person or computer player for X or O. """
        answer = ask(" Player " + symbol + " is person or computer ? ",
                     legal_responses = ('p','c') )
        if (answer[0]=='p'):
            return Person(symbol)
        else :
            return Computer(symbol)

    def play(self):
        """ Play a game. """
        winner = ''
        print()
        while (not winner):
            print(self.board)
            player = self.players[self.whose_turn]
            next_move = player.get_move(self.board)
            self.board.make_move(next_move, player.get_symbol())
            winner = self.board.get_winner()
            self.whose_turn = (self.whose_turn + 1) % 2
        if (winner in ('X', 'O')):
            print(self.board)
            print(" ", winner, " wins!")
        else:
            print(self.board)
            print(" Tie game.")

class Referee:
    """ Manage a series of games. """
    def run(self):
        print(" === Tic Tac Toe === ")
        print(" Inputs may be abbreviated by their first letter. ")
        print(" Type 'quit' at any prompt to quit. ")
        print(" X always goes first.")
        print(" So there.")
        while True:
            Game().play()

Referee().run()