import subprocess as sp
plyr_x, plyr_y = (9,5)
plyr_char = 'o'
def create_board():
''' Creates a 10x10 grid of cells'''
board = []
for _ in range(10):
board.append(['-']*10)
board[plyr_x][plyr_y] = plyr_char
return board
def print_board(board):
'''Clears the screen prints or re-print all changes'''
tmp = sp.call('clear')
for row in board:
print ' '.join(row)
board = create_board()
print_board(board)
while True:
'''Main loop'''
choice = raw_input('What do you want to do? ')
print_board(board)
if choice == 'move':
'''Moves the player. Empties the cell previously occupid,
adds/subtracts 1 from the player location,
and then reprints the board'''
direction = raw_input('Which way do you want to move? ')
if direction == 'up':
board[plyr_x][plyr_y] = ' '
plyr_x += -1
board[plyr_x][plyr_y] = 'o'
print_board(board)
elif direction == 'down':
board[plyr_x][plyr_y] = ' '
plyr_x += 1
board[plyr_x][plyr_y] = 'o'
print_board(board)
elif direction == 'right':
board[plyr_x][plyr_y] = ' '
plyr_y += 1
board[plyr_x][plyr_y] = 'o'
print_board(board)
elif direction == 'left':
board[plyr_x][plyr_y] = ' '
plyr_y += -1
board[plyr_x][plyr_y] = 'o'
print_board(board)
elif choice == 'attack':
print 'There is no one to attack right now.'
elif choice == 'die':
plyr_char = '%'
print_board(board)
quit()
Everything works fine but I want to get rid of the global variables at the top, if that's possible, and write all of the movement code into it's own move() function so I can get rid of the huge if in the main loop. How could you do this?
Here's how I would proceed:
Use a main function
Move global args there
Now you must pass your args to functions, rather than rely on globals
Start to add classes which can contain both behaviour and state!
I only implemented a Player() class, but I would also create a board class. I will leave that one up to you!
For example, updating the chars in the player position (and his last known position) could be a method of a the Board class. Similarly, you could have a method that displays the board, def print(self). And of course, create_board() function would become something like def __init__(self, player, size).
I think you'll find this code is quite easy to understand:
import subprocess as sp
class Player(object):
def __init__(self, x, y, char):
self.x = x
self.y = y
self.char = char
def move(self, direction):
""" update our position. """
if direction.lower() == 'left':
self.y -= 1
elif direction.lower() == 'right':
self.y += 1
elif direction.lower() == 'up':
self.x -= 1
elif direction.lower() == 'down':
self.x += 1
def create_board(player):
''' Creates a 10x10 grid of cells'''
board = []
for _ in range(10):
board.append(['-']*10)
board[player.x][player.y] = player.char
return board
def print_board(board):
'''Clears the screen prints or re-print all changes'''
tmp = sp.call('clear')
for row in board:
print ' '.join(row)
def main():
player = Player(9, 5, 'o')
board = create_board(player)
print_board(board)
while True:
'''Main loop'''
choice = raw_input('What do you want to do? ')
print_board(board)
if choice == 'move':
'''Moves the player. Empties the cell previously occupid,
adds/subtracts 1 from the player location,
and then reprints the board'''
direction = raw_input('Which way do you want to move? ')
if direction in ('up', 'down', 'left', 'right'):
board[player.x][player.y] = ' ' # Erase
player.move(direction)
board[player.x][player.y] = player.char
print_board(board)
elif choice == 'attack':
print 'There is no one to attack right now.'
elif choice == 'die':
player.char = '%'
print_board(board)
quit()
if __name__ == '__main__':
main()
I hope that gives you some ideas!
This should hopefully work. I think the concept that might be confusing you is passing things to functions, as that is essentially all that I did.
import subprocess as sp
def move(plyr_x, plyr_y, board):
'''Moves the player. Empties the cell previously occupid,
adds/subtracts 1 from the player location,
and then reprints the board'''
direction = raw_input('Which way do you want to move? ')
if direction == 'up':
board[plyr_x][plyr_y] = ' '
plyr_x += -1
board[plyr_x][plyr_y] = 'o'
print_board(board)
elif direction == 'down':
board[plyr_x][plyr_y] = ' '
plyr_x += 1
board[plyr_x][plyr_y] = 'o'
print_board(board)
elif direction == 'right':
board[plyr_x][plyr_y] = ' '
plyr_y += 1
board[plyr_x][plyr_y] = 'o'
print_board(board)
elif direction == 'left':
board[plyr_x][plyr_y] = ' '
plyr_y += -1
board[plyr_x][plyr_y] = 'o'
print_board(board)
return (plyr_x, plyr_y, board)
def create_board(plyr_x, plyr_y, plyr_char):
''' Creates a 10x10 grid of cells'''
board = []
for _ in range(10):
board.append(['-']*10)
board[plyr_x][plyr_y] = plyr_char
return board
def print_board(board):
'''Clears the screen prints or re-print all changes'''
tmp = sp.call('clear')
for row in board:
print ' '.join(row)
def main():
plyr_x, plyr_y = (9,5)
plyr_char = 'o'
board = create_board(plyr_x, plyr_y, plyr_char)
print_board(board)
while True:
'''Main loop'''
choice = raw_input('What do you want to do? ')
print_board(board)
if choice == 'move':
plyr_x, plyr_y, board = move(plyr_x, plyr_y, board)
elif choice == 'attack':
print 'There is no one to attack right now.'
elif choice == 'die':
plyr_char = '%'
print_board(board)
quit()
Related
As the title suggests, I'm trying to create a Tic-Tac-Toe game using the Minimax algorithm.
My problem occurs when the computer is supposed to counter my movements. Instead of doing so, it plays in what seems to be random yet repetitive patterns.
This is my first time using the Minimax algorithm, so any help or advice would be welcomed.
Thanks in advance!
Here is my code:
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
import numpy as np
import random
class Board(GridLayout):
def __init__(self):
GridLayout.__init__(self)
self.cols = 3
self.level = 8
self.listBoard = []
array = ([0, 0, 0],
[0, 0, 0],
[0, 0, 0])
self.array = np.array(array)
for i in range(self.cols):
listLine = []
for j in range(self.cols):
cell = Button(font_size=50, on_press=self.click)
cell.i = i
cell.j = j
self.add_widget(cell)
listLine.append(cell)
self.listBoard.append(listLine)
self.check = False
start = random.randint(0, 1)
self.player = ''
self.computer = ''
# player is 1, computer is 10
if start == 1:
self.player = 'X'
self.computer = 'O'
else:
self.computer = 'X'
self.player = 'O'
move = self.minimax(self.array.copy(), self.level)
self.listBoard[move[0]][move[1]].text = self.computer
self.array[move[0]][move[1]] = 10
print(self.array)
def click(self, button):
if button.text == '' and not self.check:
button.text = self.player
self.array[button.i][button.j] = 1
print(self.array)
if self.check_victory(self.array) == 'Player':
print('Player Wins!')
self.check = True
moves = self.find_moves(self.array)
if moves.any() and not self.check:
move = self.minimax(self.array.copy(), self.level)
self.listBoard[move[0]][move[1]].text = self.computer
self.array[move[0]][move[1]] = 10
print(self.array)
elif self.check_victory(self.array) == 'None':
print('Tie!')
self.check = True
if self.check_victory(self.array) == 'Computer' and not self.check:
print('Computer Wins!')
self.check = True
moves = self.find_moves(self.array)
if not moves.any() and self.check_victory(self.array) == 'None' and not self.check:
print('Tie!')
def find_moves(self, array):
moves = np.argwhere(array == 0)
return moves
def check_victory(self, array):
temp1 = array
temp2 = np.rot90(array)
column1 = np.sum(temp1, axis=0)
column2 = np.sum(temp2, axis=0)
diagonal1 = np.diag(temp1).sum()
diagonal2 = np.diag(temp2).sum()
for i in range(3):
if column1[i] % 10 == 3 or column2[i] % 10 == 3:
return 'Player'
if column1[i] / 10 == 3 or column2[i] / 10 == 3:
return 'Computer'
if diagonal1 % 10 == 3 or diagonal2 % 10 == 3:
return 'Player'
if diagonal1 / 10 == 3 or diagonal2 / 10 == 3:
return 'Computer'
return 'None'
def minimax(self, game_state, level):
moves = self.find_moves(game_state)
best_move = moves[0]
best_score = float('-inf')
for move in moves:
clone = self.next_state(game_state.copy(), move[0], move[1], 'Computer')
score = self.min_play(clone, level - 1)
if score > best_score:
best_move = move
best_score = score
return best_move
def min_play(self, game_state, level):
if self.check_victory(game_state) == 'Player' or level == 0:
return self.evaluate(game_state)
moves = self.find_moves(game_state)
best_score = float('inf')
for move in moves:
clone = self.next_state(game_state.copy(), move[0], move[1], 'Player')
score = self.max_play(clone, level - 1)
if score < best_score:
# best_move = move
best_score = score
return best_score
def max_play(self, game_state, level):
if self.check_victory(game_state) == 'Computer' or level == 0:
return self.evaluate(game_state)
moves = self.find_moves(game_state)
best_score = float('-inf')
for move in moves:
clone = self.next_state(game_state, move[0], move[1], 'Computer')
score = self.min_play(clone, level - 1)
if score > best_score:
# best_move = move
best_score = score
return best_score
def next_state(self, array, i, j, who):
if who == 'Player':
array[i][j] = 1
else:
array[i][j] = 10
return array
def evaluate(self, array):
if self.check_victory(array) == 'Player':
return -1
if self.check_victory(array) == 'Computer':
return 1
return 0
class TestApp(App):
def build(self):
self.title = 'based graphics'
return Board()
TestApp().run()
In this question I have to create a TicTacToe game in python which will run in terminal. This is a 2player game and if any player wins by the rules of TicTacToe, Winner = player 1/2 has to be printed.
I'm getting a winner statement when I have three 0's or 1's diagonally but I'm not getting winner in other two cases (horizontally and vertically)
Please help find the error in my code
import numpy as np
def create_board():
return (np.array([[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]))
def coordinates(board, player):
i, j, cn = (-1, -1, 0)
while (i > 3 or i < 0 or j < 0 or j > 3) or (board[i][j] != 0):
if cn > 0:
print("Wrong Input! Try Again")
print("Player {}'s turn".format(player))
i = int(input("x-coordinates: "))
j = int(input("y-coordinates: "))
i = i - 1
j = j - 1
cn = cn + 1
board[i][j] = player
return board
def row_win(board, player):
for x in range(len(board)):
win = True
for y in range(len(board)):
if board[x, y] != player:
win = False
continue
return win
def col_win(board, player):
for x in range(len(board)):
win = True
for y in range(len(board)):
if board[y][x] != player:
win = False
continue
return win
def diag_win(board, player):
win = True
y = 0
for x in range(len(board)):
if board[x][x] != player:
win = False
if win:
return win
win = True
if win:
for x in range(len(board)):
y = len(board) - 1 - x
if board[x][y] != player:
win = False
return win
def evaluate(board):
winner = 0
for player in [1, 2]:
if (row_win(board, player) or
col_win(board, player) or
diag_win(board, player)):
winner = player
if np.all(board != 0) and winner == 0:
winner = -1
return winner
def play_game():
board, winner, counter = create_board(), 0, 1
print(board)
while winner == 0:
for player in [1, 2]:
board = coordinates(board, player)
print("Board after " + str(counter) + " move")
print(board)
counter += 1
winner = evaluate(board)
if winner != 0:
break
return winner
print("Winner is: " + str(play_game()))
Let's say this is the board:
x x x
o o x
x o o
Your row_win checks then first row, win remains True.
It then proceeds to check the second row, win is set to False. After the last row the function ends up reporting that the player has not won, even though it has.
To fix this change your row_win and col_win to something like this:
def row_win(board, player):
for row in board:
if all([cell == player for cell in row]):
return True
return False
If all the cells of a row are equal to the player, then the player has won.
If none of the rows have all cells equal to the player, then the player has not won.
I am trying to make a Tic-Tac-Toe game in Python using PyGame and the MiniMax algorithm. The AI plays really well when given the first chance (playing as 'X'), but becomes dumb enough to help the user win when not given the first chance (playing as 'O'). I think I know what the problem is but changing it is messing with the whole program and is not going by the given docstrings.
I've made two python files - one for the GUI (runner.py) and the other for the logic behind the game and the AI (tictactoe.py).
This is the logic behind the game:
# Import module `copy` for function `deepcopy` to deeply copy an
# original (mutable) object to save the object from mutations
import copy
X = 'X'
O = 'O'
EMPTY = None
def initial_state():
"""Returns starting state of the board
"""
return [
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]
]
def display(board, autoprint=False):
"""Displays the board nested list as
a 3x3 matrix for board visualization
"""
vis_board = ''
for row in board:
for playr in row:
if playr is None:
playr = ' '
playr += ' '
vis_board += playr
vis_board += '\n'
if autoprint:
print(vis_board)
return vis_board
def player(board):
"""Returns player who has the next turn on a board
"""
global X, O
# Initial values for every call of the function
X_count = 0
O_count = 0
for row in board:
for playr in row:
if playr == X:
X_count += 1
elif playr == O:
O_count += 1
# `X` always starts first
if O_count < X_count:
return O
return X
def actions(board):
"""Returns set of all possible actions
(i, j) available on the board
"""
global EMPTY
action_set = set()
for i, row in enumerate(board):
for j, playr in enumerate(row):
if playr is EMPTY:
action_set.add((i, j))
return action_set
def result(board, action):
"""Returns the board that results from
making move (i, j) on the board.
"""
global EMPTY
if type(action) is not tuple or len(action) != 2:
raise Exception('invalid action taken')
# Using `deepcopy` to make a deepcopy of *board*
# as duplication by slicing entire list and by
# type conversion is not working poperly
dup_board = copy.deepcopy(board)
# Unpack the coordinates as `I` and `J`
I, J = action
# Check if place has not already been used
if dup_board[I][J] is EMPTY:
dup_board[I][J] = player(dup_board)
else:
raise Exception('invalid action taken')
return dup_board
def is_full(board):
"""Returns True if all places have been occupied, else returns False
"""
global EMPTY
for row in board:
for playr in row:
if playr is EMPTY:
return False
return True
def winner(board):
"""Returns the winner of the game, if there is one.
"""
winr = None # Initial declaration to avoid errors if no winner found
# Check diagonally
if (board[1][1] == board[0][0] and board[0][0] == board[2][2])\
or (board[1][1] == board[0][2] and board[0][2] == board[2][0]):
winr = board[1][1]
return winr
for i in range(3):
# Check each row for three-in-a-row
if board[i][0] == board[i][1] and board[i][1] == board[i][2]:
winr = board[i][1]
break
# Check each column for three-in-a-column
elif board[0][i] == board[1][i] and board[1][i] == board[2][i]:
winr = board[1][i]
break
return winr
def terminal(board):
"""Returns True if game is over, False otherwise.
"""
if winner(board) is None and not is_full(board):
return False
return True
def utility(board):
"""Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
"""
global X, O
if terminal(board):
winr = winner(board)
if winr == X:
util = 1
elif winr == O:
util = -1
else:
util = 0
return util
return None
def get_best_score(board, is_max_turn):
"""Returns the best value of values of all possible moves
"""
if utility(board) is not None:
return utility(board)
scores = []
# Recursively help `minimax` choose the best action
# in `actions` of *board* by returning the best value
for action in actions(board):
rslt = result(board, action)
scores.append(get_best_score(rslt, not is_max_turn))
return max(scores) if is_max_turn else min(scores)
def minimax(board):
"""Returns the optimal action for the current player on the board.
"""
if terminal(board):
return None
best_score = -float('inf') # Least possible score
best_action = None
for action in actions(board):
rslt = result(board, action)
score = get_best_score(rslt, False)
if score > best_score:
best_score = score
best_action = action
return best_action
The GUI code file:
# Import module `PyGame` for a GUI
import pygame
import sys
import time
# Import module `tictactoe` (from the same folder as
# this file `__file__`) for the logic of the game's AI
import tictactoe as ttt
pygame.init()
size = width, height = 600, 400
# Colors
black = (0, 0, 0)
white = (255, 255, 255)
screen = pygame.display.set_mode(size)
mediumFont = pygame.font.Font('OpenSans-Regular.ttf', 24)
largeFont = pygame.font.Font('OpenSans-Regular.ttf', 40)
moveFont = pygame.font.Font('OpenSans-Regular.ttf', 60)
user = None
board = ttt.initial_state()
ai_turn = False
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(black)
# Let user choose a player.
if user is None:
# Draw title
title = largeFont.render('Play Tic-Tac-Toe', True, white)
titleRect = title.get_rect()
titleRect.center = (round(width/2), 50)
screen.blit(title, titleRect)
# Draw buttons
playXButton = pygame.Rect(round(width/8), round(height/2), round(width/4), 50)
playX = mediumFont.render('Play as X', True, black)
playXRect = playX.get_rect()
playXRect.center = playXButton.center
pygame.draw.rect(screen, white, playXButton)
screen.blit(playX, playXRect)
playOButton = pygame.Rect(5*round(width/8), round(height/2), round(width/4), 50)
playO = mediumFont.render('Play as O', True, black)
playORect = playO.get_rect()
playORect.center = playOButton.center
pygame.draw.rect(screen, white, playOButton)
screen.blit(playO, playORect)
# Check if button is clicked
click, _, _ = pygame.mouse.get_pressed()
if click == 1:
mouse = pygame.mouse.get_pos()
time.sleep(0.5)
if playXButton.collidepoint(mouse):
user = ttt.X
elif playOButton.collidepoint(mouse):
user = ttt.O
else:
# Draw game board
tile_size = 80
tile_origin = (width / 2 - (1.5 * tile_size),
height / 2 - (1.5 * tile_size))
tiles = []
for i in range(3):
row = []
for j in range(3):
rect = pygame.Rect(
round(tile_origin[0]+j*tile_size),
round(tile_origin[1]+i*tile_size),
round(tile_size), round(tile_size)
)
pygame.draw.rect(screen, white, rect, 3)
if board[i][j] != ttt.EMPTY:
move = moveFont.render(board[i][j], True, white)
moveRect = move.get_rect()
moveRect.center = rect.center
screen.blit(move, moveRect)
row.append(rect)
tiles.append(row)
game_over = ttt.terminal(board)
player = ttt.player(board)
# Show title
if game_over:
winner = ttt.winner(board)
if winner is None:
title = f'Game Over: Tie.'
else:
title = f'Game Over: {winner} wins.'
elif user == player:
title = f'Play as {user}'
else:
title = f'AI thinking...'
title = largeFont.render(title, True, white)
titleRect = title.get_rect()
titleRect.center = (round(width/2), 30)
screen.blit(title, titleRect)
# Check for AI move
if user != player and not game_over:
if ai_turn:
time.sleep(0.5)
move = ttt.minimax(board)
board = ttt.result(board, move)
ai_turn = False
else:
ai_turn = True
# Check for a user move
click, _, _ = pygame.mouse.get_pressed()
if click == 1 and user == player and not game_over:
mouse = pygame.mouse.get_pos()
for i in range(3):
for j in range(3):
if (board[i][j] == ttt.EMPTY and tiles[i][j].collidepoint(mouse)):
board = ttt.result(board, (i, j))
if game_over:
againButton = pygame.Rect(round(width/3), round(height-65), round(width/3), 50)
again = mediumFont.render('Play Again', True, black)
againRect = again.get_rect()
againRect.center = againButton.center
pygame.draw.rect(screen, white, againButton)
screen.blit(again, againRect)
click, _, _ = pygame.mouse.get_pressed()
if click == 1:
mouse = pygame.mouse.get_pos()
if againButton.collidepoint(mouse):
time.sleep(0.2)
user = None
board = ttt.initial_state()
ai_turn = False
pygame.display.flip()
These are the sidenotes for the answers given by the organization that gave these questions:
No changing the no. of parameters or the parameters themselves in any functions.
Follow the docstrings written in all functions
New functions may be defined as you wish
Please let me know if there are any bugs/errors which are causing the AI to be dumb when playing as 'O'. I believe the bug is in utility, but I can't change the code because it is not allowed (written in the docstrings).
Thank you!
Edit: The problem has been ALMOST solved, but the AI becomes dumb sometimes, like not trying to block the user's move with the opposite symbol, etc.
best_score = -float('inf') # Least possible score
you need to vary this according to the player for which you calculate the move. I think because of this the negative player is choosing random/first plausible move.
I have implemented minimax and related heuristics like 2 times, and always found that using the "negamax" approach worked best, since you don't need to worry about when to apply max and when min based on the player.
I am trying to make a Tic-Tac-Toe game in Python using PyGame and the MiniMax algorithm. The AI plays really well when given the first chance (playing as 'X'), but becomes dumb enough to help the user win when not given the first chance (playing as 'O'). I think I know what the problem is but changing it is messing with the whole program and is not going by the given docstrings.
I've made two python files - one for the GUI (runner.py) and the other for the logic behind the game and the AI (tictactoe.py).
This is the logic behind the game:
# Import module `copy` for function `deepcopy` to deeply copy an
# original (mutable) object to save the object from mutations
import copy
X = 'X'
O = 'O'
EMPTY = None
def initial_state():
"""Returns starting state of the board
"""
return [
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]
]
def display(board, autoprint=False):
"""Displays the board nested list as
a 3x3 matrix for board visualization
"""
vis_board = ''
for row in board:
for playr in row:
if playr is None:
playr = ' '
playr += ' '
vis_board += playr
vis_board += '\n'
if autoprint:
print(vis_board)
return vis_board
def player(board):
"""Returns player who has the next turn on a board
"""
global X, O
# Initial values for every call of the function
X_count = 0
O_count = 0
for row in board:
for playr in row:
if playr == X:
X_count += 1
elif playr == O:
O_count += 1
# `X` always starts first
if O_count < X_count:
return O
return X
def actions(board):
"""Returns set of all possible actions
(i, j) available on the board
"""
global EMPTY
action_set = set()
for i, row in enumerate(board):
for j, playr in enumerate(row):
if playr is EMPTY:
action_set.add((i, j))
return action_set
def result(board, action):
"""Returns the board that results from
making move (i, j) on the board.
"""
global EMPTY
if type(action) is not tuple or len(action) != 2:
raise Exception('invalid action taken')
# Using `deepcopy` to make a deepcopy of *board*
# as duplication by slicing entire list and by
# type conversion is not working poperly
dup_board = copy.deepcopy(board)
# Unpack the coordinates as `I` and `J`
I, J = action
# Check if place has not already been used
if dup_board[I][J] is EMPTY:
dup_board[I][J] = player(dup_board)
else:
raise Exception('invalid action taken')
return dup_board
def is_full(board):
"""Returns True if all places have been occupied, else returns False
"""
global EMPTY
for row in board:
for playr in row:
if playr is EMPTY:
return False
return True
def winner(board):
"""Returns the winner of the game, if there is one.
"""
winr = None # Initial declaration to avoid errors if no winner found
# Check diagonally
if (board[1][1] == board[0][0] and board[0][0] == board[2][2])\
or (board[1][1] == board[0][2] and board[0][2] == board[2][0]):
winr = board[1][1]
return winr
for i in range(3):
# Check each row for three-in-a-row
if board[i][0] == board[i][1] and board[i][1] == board[i][2]:
winr = board[i][1]
break
# Check each column for three-in-a-column
elif board[0][i] == board[1][i] and board[1][i] == board[2][i]:
winr = board[1][i]
break
return winr
def terminal(board):
"""Returns True if game is over, False otherwise.
"""
if winner(board) is None and not is_full(board):
return False
return True
def utility(board):
"""Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
"""
global X, O
if terminal(board):
winr = winner(board)
if winr == X:
util = 1
elif winr == O:
util = -1
else:
util = 0
return util
return None
def get_best_score(board, is_max_turn):
"""Returns the best value of values of all possible moves
"""
if utility(board) is not None:
return utility(board)
scores = []
# Recursively help `minimax` choose the best action
# in `actions` of *board* by returning the best value
for action in actions(board):
rslt = result(board, action)
scores.append(get_best_score(rslt, not is_max_turn))
return max(scores) if is_max_turn else min(scores)
def minimax(board):
"""Returns the optimal action for the current player on the board.
"""
if terminal(board):
return None
best_score = -float('inf') # Least possible score
best_action = None
for action in actions(board):
rslt = result(board, action)
score = get_best_score(rslt, False)
if score > best_score:
best_score = score
best_action = action
return best_action
The GUI code file:
# Import module `PyGame` for a GUI
import pygame
import sys
import time
# Import module `tictactoe` (from the same folder as
# this file `__file__`) for the logic of the game's AI
import tictactoe as ttt
pygame.init()
size = width, height = 600, 400
# Colors
black = (0, 0, 0)
white = (255, 255, 255)
screen = pygame.display.set_mode(size)
mediumFont = pygame.font.Font('OpenSans-Regular.ttf', 24)
largeFont = pygame.font.Font('OpenSans-Regular.ttf', 40)
moveFont = pygame.font.Font('OpenSans-Regular.ttf', 60)
user = None
board = ttt.initial_state()
ai_turn = False
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(black)
# Let user choose a player.
if user is None:
# Draw title
title = largeFont.render('Play Tic-Tac-Toe', True, white)
titleRect = title.get_rect()
titleRect.center = (round(width/2), 50)
screen.blit(title, titleRect)
# Draw buttons
playXButton = pygame.Rect(round(width/8), round(height/2), round(width/4), 50)
playX = mediumFont.render('Play as X', True, black)
playXRect = playX.get_rect()
playXRect.center = playXButton.center
pygame.draw.rect(screen, white, playXButton)
screen.blit(playX, playXRect)
playOButton = pygame.Rect(5*round(width/8), round(height/2), round(width/4), 50)
playO = mediumFont.render('Play as O', True, black)
playORect = playO.get_rect()
playORect.center = playOButton.center
pygame.draw.rect(screen, white, playOButton)
screen.blit(playO, playORect)
# Check if button is clicked
click, _, _ = pygame.mouse.get_pressed()
if click == 1:
mouse = pygame.mouse.get_pos()
time.sleep(0.5)
if playXButton.collidepoint(mouse):
user = ttt.X
elif playOButton.collidepoint(mouse):
user = ttt.O
else:
# Draw game board
tile_size = 80
tile_origin = (width / 2 - (1.5 * tile_size),
height / 2 - (1.5 * tile_size))
tiles = []
for i in range(3):
row = []
for j in range(3):
rect = pygame.Rect(
round(tile_origin[0]+j*tile_size),
round(tile_origin[1]+i*tile_size),
round(tile_size), round(tile_size)
)
pygame.draw.rect(screen, white, rect, 3)
if board[i][j] != ttt.EMPTY:
move = moveFont.render(board[i][j], True, white)
moveRect = move.get_rect()
moveRect.center = rect.center
screen.blit(move, moveRect)
row.append(rect)
tiles.append(row)
game_over = ttt.terminal(board)
player = ttt.player(board)
# Show title
if game_over:
winner = ttt.winner(board)
if winner is None:
title = f'Game Over: Tie.'
else:
title = f'Game Over: {winner} wins.'
elif user == player:
title = f'Play as {user}'
else:
title = f'AI thinking...'
title = largeFont.render(title, True, white)
titleRect = title.get_rect()
titleRect.center = (round(width/2), 30)
screen.blit(title, titleRect)
# Check for AI move
if user != player and not game_over:
if ai_turn:
time.sleep(0.5)
move = ttt.minimax(board)
board = ttt.result(board, move)
ai_turn = False
else:
ai_turn = True
# Check for a user move
click, _, _ = pygame.mouse.get_pressed()
if click == 1 and user == player and not game_over:
mouse = pygame.mouse.get_pos()
for i in range(3):
for j in range(3):
if (board[i][j] == ttt.EMPTY and tiles[i][j].collidepoint(mouse)):
board = ttt.result(board, (i, j))
if game_over:
againButton = pygame.Rect(round(width/3), round(height-65), round(width/3), 50)
again = mediumFont.render('Play Again', True, black)
againRect = again.get_rect()
againRect.center = againButton.center
pygame.draw.rect(screen, white, againButton)
screen.blit(again, againRect)
click, _, _ = pygame.mouse.get_pressed()
if click == 1:
mouse = pygame.mouse.get_pos()
if againButton.collidepoint(mouse):
time.sleep(0.2)
user = None
board = ttt.initial_state()
ai_turn = False
pygame.display.flip()
These are the sidenotes for the answers given by the organization that gave these questions:
No changing the no. of parameters or the parameters themselves in any functions.
Follow the docstrings written in all functions
New functions may be defined as you wish
Please let me know if there are any bugs/errors which are causing the AI to be dumb when playing as 'O'. I believe the bug is in utility, but I can't change the code because it is not allowed (written in the docstrings).
Thank you!
Edit: The problem has been ALMOST solved, but the AI becomes dumb sometimes, like not trying to block the user's move with the opposite symbol, etc.
best_score = -float('inf') # Least possible score
you need to vary this according to the player for which you calculate the move. I think because of this the negative player is choosing random/first plausible move.
I have implemented minimax and related heuristics like 2 times, and always found that using the "negamax" approach worked best, since you don't need to worry about when to apply max and when min based on the player.
I have written a Tic Tac Toe program that is working, but I am stuck on how to go about writing the chekDraw function (checking the game for a tie). All other functions are working, and I can play the game. My thought is that a for in range loop will go through the board indices, counting the x's and o's until the whole board is full. In main, I have the condition that if it is not a win (checkWin) then it is a tie. I have been staring at this so for so long, I would love fresh eyes on my code. Any insight/advice would be appreciated!
Edit: specifically what is happening with checkDraw is nothing- if the board is full and no winner, the game continually asks for a move, but any move at that point is illegal because all spots are taken (validation from getMove function).
# display instructions
def displayInstructions():
print()
print("This is a game of Tic-Tac-Toe. You will select an empty location")
print("and enter its index to select a move. The first player will be X")
print("and the second player will be O.")
print()
# display current state
# pass in board
# does not return anything
def showBoard(board):
for i in range(len(board)):
print("[" + str(board[i]) + "]", end="")
if i == 2 or i == 5 or i == 8:
print(end="\n")
# pass in board
# return updated board
# must validate move (in range, unoccupied square)
def getMove(board, player):
validMove = False
while not validMove:
move = input("{0}, what is your move: ".format(player))
position = int(move) - 1 # cast input as an integer, and puts player move in correct index
# in range
if position < 0 or position > 8:
print("That is an illegal move.")
# unoccupied square
if board[position] == "X" or board[position] == "O":
print("That is an illegal move.")
else:
# if valid move, put player on board
board[position] = player
return board
def checkWin(board, player):
if (board[0] == player and board[1] == player and board[2] == player) or \
(board[3] == player and board[4] == player and board[5] == player) or \
(board[6] == player and board[7] == player and board[8] == player) or \
(board[0] == player and board[3] == player and board[6] == player) or \
(board[1] == player and board[4] == player and board[7] == player) or \
(board[2] == player and board[5] == player and board[8] == player) or \
(board[0] == player and board[4] == player and board[8] == player) or \
(board[2] == player and board[4] == player and board[6] == player):
return True
else:
return False
def checkDraw(board, player):
count = 0
for i in range(len(board)):
if board[i] == player:
# if board[i] == "X" or board[i) == "O"
count += 1
if count == len(board):
return True
def main():
# Repeat play loop
playGame = True
while playGame:
# output instructions
displayInstructions()
# initialize board
board = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(len(board))
# initialize first player to X
player = "X"
# play until win or draw
while not checkWin(board, player) or checkDraw(board, player):
showBoard(board)
getMove(board, player)
checkWin(board, player)
checkDraw(board, player)
# if the game is in play (not a win or draw)
if not checkWin(board, player) or checkDraw(board, player):
# swap player
if player == "X":
player = "O"
else:
player = "X"
# if win
if checkWin(board, player):
showBoard(board)
print("{0} wins.".format(player))
playGame = False
# if draw
elif checkDraw(board, player):
showBoard(board)
print("It's a draw")
playGame = False
# Ask if want to play another game
playAgain = input("Would you like to play again? (y/n): ").lower()
if playAgain == "y":
main()
else:
print("Goodbye.")
if __name__ == "__main__":
main()
instead of these long series of and/or, you should build a list of board positions that form segments (lines) and use it as indirection to get all patterns on the board:
For example:
segments = [ (0,1,2),(3,4,5),(6,7,8),(0,3,6),(1,4,7),(2,5,8),(0,4,8),(2,4,6) ]
boardMarks = [ c if c in "XO" else "." for c in board ]
patterns = [ "".join(sorted(boardMarks[i] for i in s)) for s in segments ]
playerWins = (player*3) in patterns
xWins = "XXX" in patterns
oWins = "OOO" in patterns
draw = not xWins and not oWins and "." not in boardMarks
or, if you want to be more predictive and call out a draw early when there are no more moves that can win:
draw = not any(canWin in patterns for canWin in ("...","..X","..O",".XX",".OO"))
Because the patterns are sorted, there are only 5 combinations that represent a winnable line
If you want to make the computer play, you can use this 'autoPlay' function to select a position based on a mini-max rating system:
skill = 4 # 1 to 4 (novice to master)
def rating(position,player,board,level=skill+5):
if board[position] in "XO" or not level: return 0
newBoard = board[:position]+[player]+board[position+1:]
isWin = player*3 in ( "".join(newBoard[i] for i in s) for s in segments )
if isWin: return 3**level * 2
nextRatings = [rating(p,"XO"[player=="X"],newBoard,level-1) for p in range(9)]
return 3**level - max(nextRatings,key=abs)
from random import sample
def autoPlay(board,player):
return max(sample(range(9),9),key=lambda p:rating(p,player,board))
Example use:
position = autoPlay(board,player)
print("computer plays ",player," at position ",position+1)
board[position] = player