Minimax method in an AI TicTacToe game - python

The following code describes an AI TicTacToe game(the main file is game.py)
**player.py:**
import math
import random
class Player():
def __init__(self, letter):
self.letter = letter
def get_move(self, game):
pass
class HumanPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
valid_square = False
val = None
while not valid_square:
square = input(self.letter + '\'s turn. Input move (0-9): ')
try:
val = int(square)
if val not in game.available_moves():
raise ValueError
valid_square = True
except ValueError:
print('Invalid square. Try again.')
return val
class RandomComputerPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
square = random.choice(game.available_moves())
return square
class SmartComputerPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
if len(game.available_moves()) == 9:
square = random.choice(game.available_moves())
else:
square = self.minimax(game, self.letter)['position']
return square
def minimax(self, state, player):
max_player = self.letter # yourself
other_player = 'O' if player == 'X' else 'X'
# first we want to check if the previous move is a winner
if state.current_winner == other_player:
return {'position': None, 'score': 1 * (state.num_empty_squares() + 1) if other_player == max_player else -1 * (
state.num_empty_squares() + 1)}
elif not state.empty_squares():
return {'position': None, 'score': 0}
if player == max_player:
best = {'position': None, 'score': -math.inf} # each score should maximize
else:
best = {'position': None, 'score': math.inf} # each score should minimize
for possible_move in state.available_moves():
state.make_move(possible_move, player)
sim_score = self.minimax(state, other_player) # simulate a game after making that move
# undo move
state.board[possible_move] = ' '
state.current_winner = None
sim_score['position'] = possible_move # this represents the move optimal next move
if player == max_player: # X is max player
if sim_score['score'] > best['score']:
best = sim_score
else:
if sim_score['score'] < best['score']:
best = sim_score
return best
**game.py:**
import math
import time
from player import HumanPlayer, RandomComputerPlayer, SmartComputerPlayer
class TicTacToe():
def __init__(self):
self.board = self.make_board()
self.current_winner = None
#staticmethod
def make_board():
return [' ' for _ in range(9)]
def print_board(self):
for row in [self.board[i*3:(i+1) * 3] for i in range(3)]:
print('| ' + ' | '.join(row) + ' |')
#staticmethod
def print_board_nums():
# 0 | 1 | 2
number_board = [[str(i) for i in range(j*3, (j+1)*3)] for j in range(3)]
for row in number_board:
print('| ' + ' | '.join(row) + ' |')
def make_move(self, square, letter):
if self.board[square] == ' ':
self.board[square] = letter
if self.winner(square, letter):
self.current_winner = letter
return True
return False
def winner(self, square, letter):
# check the row
row_ind = math.floor(square / 3)
row = self.board[row_ind*3:(row_ind+1)*3]
# print('row', row)
if all([s == letter for s in row]):
return True
col_ind = square % 3
column = [self.board[col_ind+i*3] for i in range(3)]
# print('col', column)
if all([s == letter for s in column]):
return True
if square % 2 == 0:
diagonal1 = [self.board[i] for i in [0, 4, 8]]
# print('diag1', diagonal1)
if all([s == letter for s in diagonal1]):
return True
diagonal2 = [self.board[i] for i in [2, 4, 6]]
# print('diag2', diagonal2)
if all([s == letter for s in diagonal2]):
return True
return False
def empty_squares(self):
return ' ' in self.board
def num_empty_squares(self):
return self.board.count(' ')
def available_moves(self):
return [i for i, x in enumerate(self.board) if x == " "]
def play(game, x_player, o_player, print_game=True):
if print_game:
game.print_board_nums()
letter = 'X'
while game.empty_squares():
if letter == 'O':
square = o_player.get_move(game)
else:
square = x_player.get_move(game)
if game.make_move(square, letter):
if print_game:
print(letter + ' makes a move to square {}'.format(square))
game.print_board()
print('')
if game.current_winner:
if print_game:
print(letter + ' wins!')
return letter # ends the loop and exits the game
letter = 'O' if letter == 'X' else 'X' # switches player
time.sleep(.8)
if print_game:
print('It\'s a tie!')
if __name__ == '__main__':
x_player = SmartComputerPlayer('X')
o_player = HumanPlayer('O')
t = TicTacToe()
play(t, x_player, o_player, print_game=True)
As you can see, the programmer used an minimx algorithmus to minimize the possibility to lose and to maximize the possibility to win. Now after several days of trying to understand how this minimax method works, I can't help but ask you guys to explain this to me.
1.what is the reasoning behind adding this code to player.py and how does the method return a score that will be greater than the initial value of negative infinity?:
if player == max_player:
best = {'position': None, 'score': -math.inf}
else:
best = {'position': None, 'score': math.inf}
2.in our simscore variabel we added the otherplayer parameters into our minimax function. Why did we do that? And doesnt we need to add the variable max_player as a parameter to minimax() to simulate a game?
3. How does recursion in this specific case work?

1.what is the reasoning behind adding this code to player.py and how does the method return a score that will be greater than the initial
value of negative infinity?:
if state.current_winner == other_player:
return {
'position': None,
'score': 1 * (state.num_empty_squares() + 1) if other_player == max_player else -1 * (state.num_empty_squares() + 1)}
or I think alternately:
if state.current_winner == other_player:
position = None
score = state.num_empty_squares() + 1
if other_player == max_player:
score = -score
return {'position': None, 'score': score}
If the player that moved last at this move in the tree of all moves "won" then that is ultimately good for that player and bad for the other player. Wins with more empty spaces are better.
2.in our simscore variabel we added the otherplayer parameters into our minimax function. Why did we do that? And doesnt we need to add
the variable max_player as a parameter to minimax() to simulate a
game?
minimax() is called recursively simulating a game. When called it switches players via other_player = 'O' if player == 'X' else 'X' when we pass the "current" other_player into the next move, minimax() can determine who moves in this call.
How does recursion in this specific case work?
Each call of minimax() allows for a simulated move by one of the two players. inside this method, the current moving player is other_player. A move is selected based on the current game state and minimax() is called to simulate the next player moving.

Related

AttributeError: 'TicTacToe' object has no attribute 'get_move'

I am trying to do tictactoe but I keep getting this error
AttributeError: 'TicTacToe' object has no attribute 'get_move'
Here is the first part of my code:
from player import HumanPlayer, RandomComputerPlayer
class TicTacToe:
def __init__(self):
self.board = self.make_board()# we will use a single list t0 rep 3x3 board
self.current_winner = None # keep track of current winner
#staticmethod
def make_board():
return [' ' for _ in range(9)]
def print_board(self):
for row in [self.board[i*3:(i+1)*3] for i in range(3)]:
# the header goes like 0-3, 3-6, 6-9 and tells us which \
# row we are at where 0, 1 , 2 first row; 3, 4, 5 second and so on
print('|'+'|'.join(row)+'|')
#staticmethod
def print_board_nums():
# 0 | 1 | 2 etc, tells us which number corresponds to which box
number_board = [[str(i) for i in range(j*3, (j+1)*3)] for j in range(3)]
for row in number_board:
print('|'+'|'.join(row)+'|')
def available_moves(self):
'''
# return [], a list of available moves
moves = []
for (i, x) in enumerate(self, board):
# enumerate creates a list and assigns value 0, 1, .. to each
# value from the list. eg. 'hi' -> [(0, h), (1, i)]
# enumerate l1 = ['hello', 'world'] as enumerate(2, l1]
# would go as [(2, 'hello'), (3, 'world')]
# so here it would go as ['x','x','o'] -> [(0, 'x'), (1, 'x'), (2, 'o')]
if spot = ' ': moves.append(i)
return moves
'''
# or just do
return [i for i, spot in enumerate(self, self.board) if spot == ' ']
def empty_squares(self):
return ' ' in self.board
def num_empty_squares(self):
return len(self.available_moves())
# return self.board.count(' ')
def make_move(self, square, letter):
# if the move is valid then return true else false
if self.board[square] == ' ':
self.board[square] = letter
# check for winner
if self.winner(square, letter):
self.current_winner = letter
return True
return False
def winner(self, square, letter):
row_ind = square // 3 # checking row [specifies which row we are on]
row = self.board[row_ind*3 : (row_ind+1)*3]
if all([spot == letter for spot in row]):
return True
# check column if above was false
col_ind = square % 3
col = [self.board[col_ind+i*3] for i in range(3)]
if all([spot == letter for spot in col]):
return True
# check diagonal
# for this we have to place a move to a diagonal sq
# i.e. [0,2,4,6,8] even number
if square % 2 == 0:
# first diagonal [0,4,8] or second [2,4,6]
diagonal1 = [self.board[i] for i in [0, 4, 8]]
diagonal2 = [self.board[i] for i in [2, 4, 6]]
if all([spot == letter for spot in diagonal1]):
return True
if all([spot == letter for spot in diagonal2]):
return True
return False
def play(game, x_player, o_player, print_game = True):
# return winner if there is one else return none which is tie
if print_game:
game.print_board_nums() # see which number correspond to which spot
letter = 'x' # starting letter
# iterate while the game has empty squares
while game.empty_squares():
if letter == 'o':
square = o_player.get_move(game)
if letter == 'x':
square = x_player.get_move(game)
if game.make_move(square, letter):
if print_game:
print(letter + f'makes a move to square {square}')
game.print_board()
print('') # just an empty line
if game.current_winner:
if print_game:
print(letter + 'wins!')
return letter
# next move/ switch letter
'''if letter == 'x': letter = 'o'
else letter = 'x'
can be re written as '''
letter = 'o' if letter == 'x' else 'x'
if print_game:
print("It's a tie.")
if __name__ == '__main__':
x_player = HumanPlayer('x')
o_player = RandomComputerPlayer('o')
t = TicTacToe()
t.play(t, x_player, o_player)
and I tried importing these two classes
class RandomComputerPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
square = random.choice(game.available_moves())
return square
class HumanPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
valid_square = False
val = None
while not valid_square:
square = input(self.letter + "'s turn. Input move (0-8): ")
# checking if value if valid by casting it into an integer
try:
val = int(square)
if val not in game.available_moves():
raise ValueError
valid_square = True # valid value obtained
except ValueError:
print("Invalid value, try again.")
return val
Why can't it get the import from the player file / read its method? any clues?
Should I just cut and paste the imported part directly instead?
All class instance functions must include self as the first parameter. Under the hood, it takes an instance by itself:
class TicTacToe:
def play(self, game, x_player, o_player, print_game=True)
# ^note the 'self' parameter
if __name__ == '__main__':
TicTacToe().play(t, x_player, o_player)
You shouldn't pass the self parameter. Python will do it by itself automatically.

List value being overwritten even though I am checking for it

I'm new to python and writing my first project. I'm trying to implement a check that if a space is already occupied, not to move there. I can't seem to figure out why my move_player method overwrites the index value of the board even though I am explicitly checking it (If it is O's turn and X has already been placed in the index O is trying to move to, it just overwrites it). I have tried hard coding the check for 'X' and 'O' instead of player.player as well but can't seem to figure it out. Does it have to do something with how Python works or am I implementing it wrong?
class Player:
def __init__(self, player):
self.player = player
class Board:
def __init__(self):
self.board = [[' ' for i in range(3)] for j in range(3)]
def display_board(self):
print('---------')
for row in self.board:
print('| ', end='')
for col in row:
print(f'{col} ', end='')
print('|')
print('---------')
def move_player(self, player):
try:
p1 = Player('X')
p2 = Player('O')
coordinates = [int(i) for i in input("Enter coordinates for move: ").split()]
xCoordinate = coordinates[0]
yCoordinate = coordinates[1]
if ((self.board[xCoordinate][yCoordinate] == p1.player) or
(self.board[xCoordinate][yCoordinate] == p2.player)):
print("That space is occupied, please choose another one.")
self.move_player(player)
else:
self.board[xCoordinate - 1][yCoordinate - 1] = player.player
except (ValueError, IndexError):
print("Please only enter numbers between 1 and 3.")
self.move_player(player)
def has_won(self, player):
if self.check_diagonal(player):
return True
elif self.check_across(player):
return True
elif self.check_down(player):
return True
return False
if __name__ == '__main__':
board = Board()
player1 = Player('X')
player2 = Player('O')
player = player1
while True:
board.display_board()
board.move_player(player)
if board.has_won(player):
board.display_board()
print(f'{player.player} wins!!!')
break
if player == player1:
player = player2
else:
player = player1
The code is very convoluted but from what I can see:
if ((self.board[xCoordinate][yCoordinate] == p1.player) or
(self.board[xCoordinate][yCoordinate] == p2.player)):
...
self.board[xCoordinate - 1][yCoordinate - 1] = player.player
You are checking [x,y] but assigning to [x-1,y-1].

TypeError in tic-tac-toe game in python

I'm trying to make a tic-tac-toe game while trying to learn Python and keep getting TypeError and I have no idea why.
I have already looked at other questions similar to this (none with "NoneType") but I still don't understand where is my problem. I'm following a youtube tutorial and everything seems to check out, so I'm really lost here.
Here's the error:
File "game.py", line 93, in <module>
play(t, x_player, o_player, print_game=True)
File "game.py", line 71, in play
if game.make_move(square, letter):
File "game.py", line 29, in make_move
if self.board[square] == ' ':
TypeError: list indices must be integers or slices, not NoneType
And here is the code:
import time
from player import HumanPlayer, RandomComputerPlayer
class TicTacToe:
def __init__(self):
self.board = [' ' for _ in range(9)]
self.current_winner = None
def print_board(self):
for row in [self.board[i*3:(i+1)*3] for i in range(3)]:
print('| ' + ' | '.join(row) + ' |')
#staticmethod
def print_board_nums():
number_board = [[str(i) for i in range(j*3, (j+1)*3)] for j in range(3)]
for row in number_board:
print('| ' + ' | '.join(row) + ' |')
def available_moves(self):
return [i for i, spot in enumerate(self.board) if spot == ' ']
def empty_squares(self):
return ' ' in self.board
def num_empty_squares(self):
return self.board.count(' ')
def make_move(self, square, letter):
if self.board[square] == ' ':
self.board[square] = letter
if self.winner(square, letter):
self.current_winner = letter
return True
return False
def winner(self, square, letter):
row_ind = square // 3
row = self.board[row_ind*3:(row_ind+1)*3]
if all([spot == letter for spot in row]):
return True
col_ind = square % 3
column = self.board[col_ind*3:(col_ind+1)*3]
if all([spot == letter for spot in column]):
return True
if square % 2 == 0:
diag1 = [self.board[i] for i in [0, 4, 8]]
if all([spot == letter for spot in diag1]):
return True
diag2 = [self.board[i] for i in [2, 4, 6]]
if all([spot == letter for spot in diag2]):
return True
return False
def play(game, x_player, o_player, print_game=True):
if print_game:
game.print_board_nums()
letter = 'X'
while game.empty_squares():
if letter == 'O':
square = o_player.get_move(game)
else:
square = x_player.get_move(game)
if game.make_move(square, letter):
if print_game:
print(letter + f' makes a move to a square {square}')
game.print_board()
print('')
if game.current_winner:
if print_game:
print(letter + ' wins!')
return letter
letter = 'O' if letter == 'X' else 'X'
time.sleep(0.8)
if print_game:
print('It\'s a tie')
if __name__ == '__main__':
x_player = HumanPlayer('X')
o_player = RandomComputerPlayer('O')
t = TicTacToe()
play(t, x_player, o_player, print_game=True)
Edit: Here is the code from the player module:
import math
import random
class Player:
def __init__(self, letter):
self.letter = letter
def get_move(self, game):
pass
class RandomComputerPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
square = random.choice(game.available_moves())
return square
class HumanPlayer(Player):
def __init__(self, letter):
super().__init__(letter)
def get_move(self, game):
valid_square = False
val = None
while not valid_square:
square = input(self.letter + '\'s turn. Input move (0-8):')
try:
val = int(square)
if val not in game.available_moves():
raise ValueError
valid_square = True
except ValueError:
print('Invalid square. Try again')

Python - OOP Tic-Tac-Toe

I'm writing a tic-tac-toe game for an assignment. It needs to use object-oriented programming and it has to be relatively smart - it needs to block the player's success. I'm having a lot of trouble with this.
My trouble comes from my rowabouttowin method: I did a very convoluted list comprehension and I don't think that I did it correctly.
What I want is a method that checks if the player is about to win the game horizontally (O _ O, O O _, or _ O O if mark = O) and, if the player is, returns the position where the computer should play.
Any help or advice on how best to approach this?
from random import randint
class TTT:
board = [[' ' for row in range(3)] for col in range(3)]
currentgame = []
def print(self):
"""Displays the current board."""
print("\n-----\n".join("|".join(row) for row in self.board))
def mark(self,pos,mark):
"""Method that places designated mark at designated position on the board."""
x,y = pos
self.board[x][y] = mark
def win(self,mark):
"""Method that checks if someone has won the game."""
if mark == self.board[0][0] == self.board[1][1] == self.board[2][2]:
return True
if mark == self.board[2][0] == self.board[1][1] == self.board[0][2]:
return True
elif mark == self.board[0][0] == self.board[1][0] == self.board[2][0]:
return True
elif mark == self.board[1][0] == self.board[1][1] == self.board[1][2]:
return True
elif mark == self.board[0][1] == self.board[1][1] == self.board[2][1]:
return True
elif mark == self.board[0][2] == self.board[1][2] == self.board[2][2]:
return True
elif mark == self.board[0][0] == self.board[0][1] == self.board[0][2]:
return True
elif mark == self.board[2][0] == self.board[2][1] == self.board[2][2]:
return True
else:
return False
def choose(self,mark):
"""The computer chooses a place to play. If the player is not about to win,
plays randomly. Otherwise, does a series of checks to see if the player is about
to win horizontally, vertically, or diagonally. I only have horizontal done."""
spotx = randint(0,2)
spoty = randint(0,2)
if self.rowabouttowin(mark):
self.mark((self.rowabouttowin(mark)),mark)
elif self.legalspace(spotx,spoty):
self.mark((spotx,spoty),mark)
else:
self.choose(mark)
def legalspace(self,spotx,spoty):
"""Returns True if the provided spot is empty."""
if self.board[spotx][spoty] == ' ':
return True
else:
return False
def rowabouttowin(self,mark):
"""If the player is about to win via a horizontal 3-in-a-row,
returns location where the computer should play"""
for row in range(3):
if any(' ' == self.board[row][1] for i in range(3)) and any(self.board[row][i] == self.board[row][j] for i in range(3) for j in range(3)):
if self.board[row][i] == ' ' : yield(self.board[row][i % 3], self.board[row][i])
This currently gives this error message:
Traceback (most recent call last):
File "<pyshell#49>", line 1, in <module>
x.choose('x')
File "/Users/richiehoffman/Documents/Python Programs/Tic Tac Toe.py", line 40, in choose
self.mark((self.rowabouttowin(mark)),mark)
File "/Users/richiehoffman/Documents/Python Programs/Tic Tac Toe.py", line 11, in mark
x,y = pos
File "/Users/richiehoffman/Documents/Python Programs/Tic Tac Toe.py", line 61, in rowabouttowin
if self.board[row][i] == ' ' : yield(self.board[row][i % 3], self.board[row][i])
NameError: global name 'i' is not defined
A few tips:
You're using class variables, not instance variables, so look up the difference. I changed your class to use instance variables, as the variables that you set should belong in an instance.
Consider making things more readable.
Use __str__ to make the printable version of your class. That way, you can do print(class_instance) and it'll come out all nice.
Here's what I changed:
from random import randint
class TTT(object):
def __init__(self):
self.board = [[' ' for row in range(3)] for col in range(3)]
self.currentgame = []
def __str__(self):
"""Displays the current board."""
return "\n-----\n".join("|".join(row) for row in self.board)
def mark(self, pos, mark):
"""Method that places designated mark at designated position on the board."""
x, y = pos
self.board[x][y] = mark
def win(self, mark):
"""Method that checks if someone has won the game."""
for row in self.board:
if row[0] == row[1] == row[2]:
return True
for i in range(3):
if self.board[0][i] == self.board[1][i] == self.board[2][i]:
return True
if board[0][0] == board[1][1] == board[2][2]:
return True
elif board[0][2] == board[1][1] == board[2][0]:
return True
else:
return False
def choose(self, mark):
"""The computer chooses a place to play. If the player is not about to win,
plays randomly. Otherwise, does a series of checks to see if the player is about
to win horizontally, vertically, or diagonally. I only have horizontal done."""
spotx = randint(0, 2)
spoty = randint(0, 2)
if self.rowabouttowin(mark):
self.mark((self.rowabouttowin(mark)), mark)
elif self.legalspace(spotx, spoty):
self.mark((spotx, spoty), mark)
else:
self.choose(mark)
def legalspace(self, spotx, spoty):
"""Returns True if the provided spot is empty."""
return self.board[spotx][spoty] == ' '
def rowabouttowin(self, mark):
"""If the player is about to win via a horizontal 3-in-a-row,
returns location where the computer should play"""
for row in range(3):
check_one = any(' ' == self.board[row][1] for i in range(3))
check_two = any(self.board[row][i] == self.board[row][j] for i in range(3) for j in range(3))
# I have no idea what this code does
if self.board[row][i] == ' ' :
yield self.board[row][i % 3], self.board[row][i]

(Python) Why doesn't my method object execute when I call it after my class definition?

When I try to run my program in IDLE (the text editor i'm using at the moment, notepad ++ said indentation error and I don't know why) it only does the code in __init__, which shows it's been created into an object. But the line after that I tried to use the main method and it doesn't do anything. I also changed it to a different method but that didn't work either. Here's my program:
import sys
class Main:
def __init__(self):
WinX = False
WinO = False
Turn = 'X'
LastTurn = 'X'
a, b, c, d, e, f, g, h, i = ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
PosList = []
PosList.append(a)
PosList.append(b)
PosList.append(c)
PosList.append(d)
PosList.append(e)
PosList.append(f)
PosList.append(g)
PosList.append(h)
PosList.append(i)
self.board = '+---+---+---+\n| ' + PosList[0] +' | '+ PosList[1] +' | '+ PosList[2] +' |\n+---+---+---+\n| '+ PosList[3] +' | '+ PosList[4] +' | '+ PosList[5] +' |\n+---+---+---+\n| '+ PosList[6] +' | '+ PosList[7] +' | '+ PosList[8] +' |\n+---+---+---+'
print self.board
def UpdateTurn(self):
if LastTurn == 'X':
Turn == 'O'
elif LastTurn == 'O':
Turn == 'X'
LastTurn = Turn
def WinChecker(self):
if a and b and c == 'X' or a and d and g == 'X' or a and e and i == 'X' or g and e and c == 'X' or g and h and i == 'X' or c and f and i == 'X':
WinX = True
if a and b and c == 'O' or a and d and g == 'O' or a and e and i == 'O' or g and e and c == 'O' or g and h and i == 'O' or c and f and i == 'O':
WinO = True
def UpdateBoard(self):
print self.board
def Starter(self):
while True:
try:
i = int(input(''))
except TypeError:
print 'Not a Number, Try Again.'
continue
i -= 1
PosList[i] = Turn
self.UpdateBoard
self.WinChecker
if Winx == True:
print 'X Wins!'
sys.exit()
elif Wino == True:
print 'O Wins!'
sys.exit()
self.UpdateTurn
s = Main()
s.Starter
I just (4 days) finished python's own tutorial.
call Starter function like
s.Starter()
And also there might be an logical error The while loop below will be always true.
def Starter(self):
while True:
try:
i = int(input(''))
except TypeError:
print 'Not a Number, Try Again.'
continue
You haven't called the method.
s.Starter()
You are not actually calling the Starter method in the last line, just referencing it. Do this instead:
s.Starter()
That calls it.
For interest, here is a rewritten version. I've used some more advanced constructs - #classmethod and #property and 'magic methods' like __str__ and __repr__. Hope you find it useful!
def getInt(prompt='', lo=None, hi=None):
"""
Get integer value from user
If lo is given, value must be >= lo
If hi is given, value must be <= hi
"""
while True:
try:
val = int(raw_input(prompt))
if lo is not None and val < lo:
print("Must be at least {}".format(lo))
elif hi is not None and val > hi:
print("Must be no more than {}".format(hi))
else:
return val
except ValueError:
print("Must be an integer!")
class TicTacToe(object):
players = ('X', 'O') # player characters
blank = ' ' # no-player character
wins = ( # lines that give a win
(0,1,2),
(3,4,5),
(6,7,8),
(0,3,6),
(1,4,7),
(2,5,8),
(0,4,8),
(2,4,6)
)
board_string = '\n'.join([ # format string
"",
"+---+---+---+",
"| {} | {} | {} |",
"+---+---+---+",
"| {} | {} | {} |",
"+---+---+---+",
"| {} | {} | {} |",
"+---+---+---+"
])
#classmethod
def ch(cls, state):
"""
Given the state of a board square (None or turn number)
return the output character (blank or corresponding player char)
"""
if state is None:
return cls.blank
else:
return cls.players[state % len(cls.players)]
def __init__(self, bd=None):
"""
Set up a game of TicTacToe
If board is specified, resume playing (assume board state is valid),
otherwise set up a new game
"""
if bd is None:
# default constructor - new game
self.turn = 0
self.board = [None for i in range(9)]
elif hasattr(bd, "board"): # this is 'duck typing'
# copy constructor
self.turn = sum(1 for sq in bd if sq is not None)
self.board = bd.board[:]
else:
# given board state, resume game
self.turn = sum(1 for sq in bd if sq is not None)
self.board = list(bd)
#property
def board_chars(self):
"""
Return the output characters corresponding to the current board state
"""
getch = type(self).ch
return [getch(sq) for sq in self.board]
#property
def winner(self):
"""
If the game has been won, return winner char, else None
"""
bd = self.board_chars
cls = type(self)
for win in cls.wins:
c0, c1, c2 = (bd[sq] for sq in win)
if c0 != cls.blank and c0 == c1 and c0 == c2:
return c0
return None
def __str__(self):
"""
Return a string representation of the board state
"""
cls = type(self)
return cls.board_string.format(*self.board_chars)
def __repr__(self):
"""
Return a string representation of the object
"""
cls = type(self)
return "{}({})".format(cls.__name__, self.board)
def do_move(self):
"""
Get next player's move
"""
cls = type(self)
next_player = cls.ch(self.turn)
while True:
pos = getInt("Player {} next move? ".format(next_player), 1, 9) - 1
if self.board[pos] is None:
self.board[pos] = self.turn
self.turn += 1
break
else:
print("That square is already taken")
def play(self):
print("Welcome to Tic Tac Toe. Board squares are numbered 1-9 left-to-right, top-to-bottom.")
while True:
print(self) # show the board - calls self.__str__
w = self.winner
if w is None:
self.do_move()
else:
print("{} is the winner!".format(w))
break
def main():
TicTacToe().play()
if __name__=="__main__":
main()

Categories

Resources