I am currently trying to solve an exercise in CS50 AI, where I am supposed to create a tictactoe using a minimax algorithm. While doing this, I have to also create a function that generates possible options as well a function that generates a new state of the board as soon as one of the possible actions is chosen. However, I am having an error in my code.
This is the error:
File "c:\Users\Melisa\OneDrive\Desktop\tictactoe\tictactoe.py", line 40, in result
kopja[i][j] = player(board)
TypeError: list indices must be integers or slices, not tuple`
This is my code for the whole problem:
import copy
import math
X = "X"
O = "O"
EMPTY = None
def initial_state():
return [[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY],
[EMPTY, EMPTY, EMPTY]]
def player(board):
Xc= 0
Oc= 0
for i in board:
for j in i:
if j == X:
Xc+=1
if j == O:
Oc+=1
# since we start with x
if Xc>Oc:
return O
else:
return X
def actions(board):
possible= set()
for i in range(0,len(board)):
for j in range(len(board[0])):
if board[i][j]== EMPTY:
possible.add((i,j))
return possible
def result(board, action):
kopja = copy.deepcopy(board)
i,j = action[0],action[1]
kopja[i][j] = player(board)
return kopja
def winner(board):
# present all of the winning outlays in a manual way:
# horisontally; only i changes ,j is contantly 0,1,2
for e in range(3):
if board[e][0]==board[e][1]==board[e][2] and board[e][0] != EMPTY:
winneri = board[0][e]
# diagonals are purely fixed
if (board[0][0]==board[1][1]==board[2][2] or board[0][2]==board[1][1]==board[2][0])and board[1][1]!=EMPTY:
winneri= board[1][1]
else:
winneri= None
return winneri
def terminal(board):
if winner(board) == X or winner(board) == O:
return True
for i in range(3):
for j in range(3):
if board[i][j] == EMPTY:
return False
return True
def utility(board):
if winner(board)== X:
return 1
if winner(board)== O:
return -1
else:
return 0
def maxval(board):
if terminal(board):
return utility(board)
else:
v = float('-inf')
for action in actions(board):
v = max(v,minval(result(board,action)))
return v
def minval(board):
if terminal(board):
return utility(board)
else:
v = float('inf')
for action in actions(board):
v = min(v,maxval(result(board,action)))
return v
def minimax(board):
if terminal(board):
return None
else:
listx=[]
if player(board) == X:
for action in actions(board):
listx.append((minval(result(board,action)),action))
listx.reverse()
listi=listx
return listi[0]
if player(board) == O:
listo=[]
for action in actions(board):
listo.append((maxval(result(board,action)),action))
listo.reverse()
lista=listo
return lista[0]
Here is the code that CS50 uses to run the program:
import pygame
import sys
import time
import tictactoe as ttt
pygame.init()
size = width, height = 600, 400
black = (0, 0, 0)
white = (255, 255, 255)
screen = pygame.display.set_mode(size)
mediumFont = pygame.font.Font("OpenSans-Regular.ttf", 28)
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 = ((width / 2), 50)
screen.blit(title, titleRect)
# Draw buttons
playXButton = pygame.Rect((width / 8), (height / 2), 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 * (width / 8), (height / 2), 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()
if playXButton.collidepoint(mouse):
time.sleep(0.2)
user = ttt.X
elif playOButton.collidepoint(mouse):
time.sleep(0.2)
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(
tile_origin[0] + j * tile_size,
tile_origin[1] + i * tile_size,
tile_size, 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"Computer thinking..."
title = largeFont.render(title, True, white)
titleRect = title.get_rect()
titleRect.center = ((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(width / 3, height - 65, 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()
I tried to use the tuples inside the set as indexes for the array(board) by assigning them :
i,j = action[0],action[1]
and expected this solution to work, but instead it generated an error.
After some digging thru your code, I think I figured it out. Did you check the value of action
before the call to result()
? If so, I think you will find it is not a what you think it is. (In other words, it does not look like (1,1)
).
The cause is complicated...it begins near the end of minimax()
then propagates thru your code. The code segment of interest is repeated below for easy reference:
if player(board) == X:
for action in actions(board):
listx.append((minval(result(board,action)),action))
listx.reverse()
listi=listx
return listi[0]
If I understand your code, inside this for loop you are creating listx
as a list of tuples with (value, action)
pairs. Then, when you exit the loop, you return listi[0]
which is the 1st (value, action)
tuple in the list. You want to return the action from the first tuple, which is listi[0][1]
.
Once you get that fixed, review the logic in the loop. You are creating a list and reversing it each time thru the loop. Its not clear to me why are you reversing it. Do you want to sort the tuples based on the value? If so, you should do that after you exit the loop, AND use the value in the tuple as the sort key. Also, you really don't need 2 lists.