How to turn set items into indexes for an array - python

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.

Related

Optimizing Negamax Function with 5x5 Hexapawn

I need to improve the speed of this program, because at the moment it is pretty slow. I know that representing game states in binary can be very effective, however, I don't know how to do that. I have also tried using numba, however that seems to make it slower. I have attached the code below. Thank you to anyone who can help!
import pygame, sys, time, hashlib
from copy import deepcopy
pygame.init()
red = pygame.Color(255,0,0)
white = pygame.Color(255,255,255)
black = pygame.Color(0,0,0)
pygame.display.set_caption('Hexapawn AI')
width, height = 700,700
game_window = pygame.display.set_mode((width, height))
def set_pawns():
global game_window, board
for y in range(5):
for x in range(5):
if board[y][x] == 1:
game_window.blit( blue_pawn, ( (width/5)*x, (height/5)*(4-y) ))
if board[y][x] == -1:
game_window.blit( red_pawn, ( (width/5)*x , (height/5)*(4-y) ))
def build_lines():
global game_window
for x in range(1,5):
pygame.draw.line(game_window, black, (width/5 * x, 0), (width/5 * x, height), 7)
pygame.draw.line(game_window, black, (0, height/5 * x), (width, height/5 * x), 7)
def get_possible_moves(board, player):
possible_moves = []
forward = 1 if player == 1 else -1
opponent = -1 if player == 1 else 1
for y in range(5):
for x in range(5):
if board[y][x] != player:
continue
if x-1 >= 0 and y+forward < 5 and board[y+forward][x-1] == opponent:
possible_moves.append([x,y,x-1,y+forward])
if x+1 < 5 and y+forward < 5 and board[y+forward][x+1] == opponent:
possible_moves.append([x,y,x+1,y+forward])
if (y+1 < 5 and player == 1) or (y+1 > -1 and player == -1):
if board[y+forward][x] == " ":
possible_moves.append([x,y,x,y+forward])
return possible_moves
def make_move(board,move,player):
global game_window, width, height
game_window.fill(white)
build_lines()
board[move[1]][move[0]] = " "
board[move[3]][move[2]] = player
set_pawns()
def neg_make_move(board, move, player):
x1, y1, x2, y2 = move
board = deepcopy(board)
board[y1][x1] = " "
board[y2][x2] = player
return board
def check_for_win(board,player):
if player == -1:
if -1 in board[0]:
return True
if get_possible_moves(board,1) == []:
return True
elif player == 1:
if 1 in board[4]:
return True
if get_possible_moves(board,-1) == []:
return True
return False
TRANSPOSITION_TABLE = {}
def state_hash(board):
serialized = str(board).encode()
return hashlib.sha256(serialized).hexdigest()
def store(table, board, alpha, beta, best, depth):
state = state_hash(board)
if best[1] <= alpha:
flag = 'UPPERCASE'
elif best[1] >= beta:
flag = 'LOWERCASE'
else:
flag = 'EXACT'
table[state] = [best, flag, depth]
def negamax(board, depth, turn, alpha, beta):
alpha_org = alpha
state = state_hash(board)
if state in TRANSPOSITION_TABLE:
tt_entry = TRANSPOSITION_TABLE[state]
if tt_entry[2] >= depth:
if tt_entry[1] == 'EXACT':
return tt_entry[0]
elif tt_entry[1] == 'LOWERCASE':
alpha = max(alpha, tt_entry[0][1])
elif tt_entry[1] == 'UPPERCASE':
beta = min(beta, tt_entry[0][1])
if alpha >= beta:
return tt_entry[0]
if check_for_win(board, -turn):
return None, -(25+depth)
if depth == 0:
return get_possible_moves(board,turn)[0], (depth)
best_score = -200
for move in get_possible_moves(board,turn):
new_board = neg_make_move(board, move, turn)
score = -negamax(new_board, depth - 1, -turn, -beta, -alpha)[1]
alpha = max(alpha,score)
if score > best_score:
best_score, best_move = score, move
if alpha >= beta:
break
store(TRANSPOSITION_TABLE, board, alpha_org, beta, [best_move,best_score], depth)
return best_move, best_score
# Build board
board = [[1 for x in range(5)]]
for x in range(3):
board.append([" " for x in range(5)])
board.append([-1 for x in range(5)])
game_window.fill(white)
# Draw game board lines
build_lines()
# Load sprites with correct sizes
tile_size = (width/5,height/5)
blue_pawn = pygame.transform.scale(pygame.image.load("blue_pawn.png"), tile_size)
red_pawn = pygame.transform.scale(pygame.image.load("red_pawn.png"), tile_size)
# Draw the pawns to the board
set_pawns()
pygame.display.update()
while True:
for event in pygame.event.get():
# if user clicks the X or they type esc then the screen will close
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
start = time.time()
move = negamax(board,12,1,-10000,10000)[0]
print(f"Blue move took {time.time()-start} seconds to calculate.")
make_move(board,move,1)
pygame.display.update()
if check_for_win(board,1):
print("Blue Wins!")
pygame.quit()
sys.exit()
time.sleep(1)
start = time.time()
move = negamax(board,12,-1,-10000,10000)[0]
print(f"Red move took {time.time()-start} seconds to calculate.")
make_move(board,move,-1)
pygame.display.update()
if check_for_win(board,-1):
print("Red Wins!")
pygame.quit()
sys.exit()
pygame.display.update()
time.sleep(1)

Game AI works powerfully on one side and becomes dumb on the other in Tic-Tac-Toe

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.

How do I implement a minimax function in a Tic-Tac-Toe game in Python? [duplicate]

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.

How to add bubble sort to my boxes in the code?

So first here is my code:
import pygame, sys, random
from pygame.locals import *
# Create the constants (go ahead and experiment with different values)
BOARDWIDTH = 4 # number of columns in the board
BOARDHEIGHT = 4 # number of rows in the board
TILESIZE = 80
WINDOWWIDTH = 640
WINDOWHEIGHT = 480
FPS = 30
BLANK = None
# R G B
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
BRIGHTBLUE = ( 0, 50, 255)
DARKTURQUOISE = ( 3, 54, 73)
GREEN = ( 0, 204, 0)
BGCOLOR = DARKTURQUOISE
TILECOLOR = GREEN
TEXTCOLOR = WHITE
BORDERCOLOR = BRIGHTBLUE
BASICFONTSIZE = 20
BUTTONCOLOR = WHITE
BUTTONTEXTCOLOR = BLACK
MESSAGECOLOR = WHITE
XMARGIN = int((WINDOWWIDTH - (TILESIZE * BOARDWIDTH + (BOARDWIDTH - 1))) / 2)
YMARGIN = int((WINDOWHEIGHT - (TILESIZE * BOARDHEIGHT + (BOARDHEIGHT - 1))) / 2)
UP = 'up'
DOWN = 'down'
LEFT = 'left'
RIGHT = 'right'
def main():
global FPSCLOCK, DISPLAYSURF, BASICFONT, RESET_SURF, RESET_RECT, NEW_SURF, NEW_RECT, SOLVE_SURF, SOLVE_RECT
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
pygame.display.set_caption('Slide Puzzle')
BASICFONT = pygame.font.Font('freesansbold.ttf', BASICFONTSIZE)
# Store the option buttons and their rectangles in OPTIONS.
RESET_SURF, RESET_RECT = makeText('Reset', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 90)
NEW_SURF, NEW_RECT = makeText('New Game', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 60)
SOLVE_SURF, SOLVE_RECT = makeText('Solve', TEXTCOLOR, TILECOLOR, WINDOWWIDTH - 120, WINDOWHEIGHT - 30)
mainBoard, solutionSeq = generateNewPuzzle(80)
SOLVEDBOARD = getStartingBoard() # a solved board is the same as the board in a start state.
allMoves = [] # list of moves made from the solved configuration
while True: # main game loop
slideTo = None # the direction, if any, a tile should slide
msg = 'Click tile or press arrow keys to slide.' # contains the message to show in the upper left corner.
if mainBoard == SOLVEDBOARD:
msg = 'Solved!'
drawBoard(mainBoard, msg)
checkForQuit()
for event in pygame.event.get(): # event handling loop
if event.type == MOUSEBUTTONUP:
spotx, spoty = getSpotClicked(mainBoard, event.pos[0], event.pos[1])
if (spotx, spoty) == (None, None):
# check if the user clicked on an option button
if RESET_RECT.collidepoint(event.pos):
resetAnimation(mainBoard, allMoves) # clicked on Reset button
allMoves = []
elif NEW_RECT.collidepoint(event.pos):
mainBoard, solutionSeq = generateNewPuzzle(80) # clicked on New Game button
allMoves = []
elif SOLVE_RECT.collidepoint(event.pos):
resetAnimation(mainBoard, solutionSeq + allMoves) # clicked on Solve button
allMoves = []
else:
# check if the clicked tile was next to the blank spot
blankx, blanky = getBlankPosition(mainBoard)
if spotx == blankx + 1 and spoty == blanky:
slideTo = LEFT
elif spotx == blankx - 1 and spoty == blanky:
slideTo = RIGHT
elif spotx == blankx and spoty == blanky + 1:
slideTo = UP
elif spotx == blankx and spoty == blanky - 1:
slideTo = DOWN
elif event.type == KEYUP:
# check if the user pressed a key to slide a tile
if event.key in (K_LEFT, K_a) and isValidMove(mainBoard, LEFT):
slideTo = LEFT
elif event.key in (K_RIGHT, K_d) and isValidMove(mainBoard, RIGHT):
slideTo = RIGHT
elif event.key in (K_UP, K_w) and isValidMove(mainBoard, UP):
slideTo = UP
elif event.key in (K_DOWN, K_s) and isValidMove(mainBoard, DOWN):
slideTo = DOWN
if slideTo:
slideAnimation(mainBoard, slideTo, 'Click tile or press arrow keys to slide.', 8) # show slide on screen
makeMove(mainBoard, slideTo)
allMoves.append(slideTo) # record the slide
pygame.display.update()
FPSCLOCK.tick(FPS)
def terminate():
pygame.quit()
sys.exit()
def checkForQuit():
for event in pygame.event.get(QUIT): # get all the QUIT events
terminate() # terminate if any QUIT events are present
for event in pygame.event.get(KEYUP): # get all the KEYUP events
if event.key == K_ESCAPE:
terminate() # terminate if the KEYUP event was for the Esc key
pygame.event.post(event) # put the other KEYUP event objects back
def getStartingBoard():
# Return a board data structure with tiles in the solved state.
# For example, if BOARDWIDTH and BOARDHEIGHT are both 3, this function
# returns [[1, 4, 7], [2, 5, 8], [3, 6, BLANK]]
counter = 1
board = []
for x in range(BOARDWIDTH):
column = []
for y in range(BOARDHEIGHT):
column.append(counter)
counter += BOARDWIDTH
board.append(column)
counter -= BOARDWIDTH * (BOARDHEIGHT - 1) + BOARDWIDTH - 1
board[BOARDWIDTH-1][BOARDHEIGHT-1] = BLANK
return board
def getBlankPosition(board):
# Return the x and y of board coordinates of the blank space.
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if board[x][y] == BLANK:
return (x, y)
def makeMove(board, move):
# This function does not check if the move is valid.
blankx, blanky = getBlankPosition(board)
if move == UP:
board[blankx][blanky], board[blankx][blanky + 1] = board[blankx][blanky + 1], board[blankx][blanky]
elif move == DOWN:
board[blankx][blanky], board[blankx][blanky - 1] = board[blankx][blanky - 1], board[blankx][blanky]
elif move == LEFT:
board[blankx][blanky], board[blankx + 1][blanky] = board[blankx + 1][blanky], board[blankx][blanky]
elif move == RIGHT:
board[blankx][blanky], board[blankx - 1][blanky] = board[blankx - 1][blanky], board[blankx][blanky]
def isValidMove(board, move):
blankx, blanky = getBlankPosition(board)
return (move == UP and blanky != len(board[0]) - 1) or \
(move == DOWN and blanky != 0) or \
(move == LEFT and blankx != len(board) - 1) or \
(move == RIGHT and blankx != 0)
def getRandomMove(board, lastMove=None):
# start with a full list of all four moves
validMoves = [UP, DOWN, LEFT, RIGHT]
# remove moves from the list as they are disqualified
if lastMove == UP or not isValidMove(board, DOWN):
validMoves.remove(DOWN)
if lastMove == DOWN or not isValidMove(board, UP):
validMoves.remove(UP)
if lastMove == LEFT or not isValidMove(board, RIGHT):
validMoves.remove(RIGHT)
if lastMove == RIGHT or not isValidMove(board, LEFT):
validMoves.remove(LEFT)
# return a random move from the list of remaining moves
return random.choice(validMoves)
def getLeftTopOfTile(tileX, tileY):
left = XMARGIN + (tileX * TILESIZE) + (tileX - 1)
top = YMARGIN + (tileY * TILESIZE) + (tileY - 1)
return (left, top)
def getSpotClicked(board, x, y):
# from the x & y pixel coordinates, get the x & y board coordinates
for tileX in range(len(board)):
for tileY in range(len(board[0])):
left, top = getLeftTopOfTile(tileX, tileY)
tileRect = pygame.Rect(left, top, TILESIZE, TILESIZE)
if tileRect.collidepoint(x, y):
return (tileX, tileY)
return (None, None)
def drawTile(tilex, tiley, number, adjx=0, adjy=0):
# draw a tile at board coordinates tilex and tiley, optionally a few
# pixels over (determined by adjx and adjy)
left, top = getLeftTopOfTile(tilex, tiley)
pygame.draw.rect(DISPLAYSURF, TILECOLOR, (left + adjx, top + adjy, TILESIZE, TILESIZE))
textSurf = BASICFONT.render(str(number), True, TEXTCOLOR)
textRect = textSurf.get_rect()
textRect.center = left + int(TILESIZE / 2) + adjx, top + int(TILESIZE / 2) + adjy
DISPLAYSURF.blit(textSurf, textRect)
def makeText(text, color, bgcolor, top, left):
# create the Surface and Rect objects for some text.
textSurf = BASICFONT.render(text, True, color, bgcolor)
textRect = textSurf.get_rect()
textRect.topleft = (top, left)
return (textSurf, textRect)
def drawBoard(board, message):
DISPLAYSURF.fill(BGCOLOR)
if message:
textSurf, textRect = makeText(message, MESSAGECOLOR, BGCOLOR, 5, 5)
DISPLAYSURF.blit(textSurf, textRect)
for tilex in range(len(board)):
for tiley in range(len(board[0])):
if board[tilex][tiley]:
drawTile(tilex, tiley, board[tilex][tiley])
left, top = getLeftTopOfTile(0, 0)
width = BOARDWIDTH * TILESIZE
height = BOARDHEIGHT * TILESIZE
pygame.draw.rect(DISPLAYSURF, BORDERCOLOR, (left - 5, top - 5, width + 11, height + 11), 4)
DISPLAYSURF.blit(RESET_SURF, RESET_RECT)
DISPLAYSURF.blit(NEW_SURF, NEW_RECT)
DISPLAYSURF.blit(SOLVE_SURF, SOLVE_RECT)
def slideAnimation(board, direction, message, animationSpeed):
#This function does not check if the move is valid.
blankx, blanky = getBlankPosition(board)
if direction == UP:
movex = blankx
movey = blanky + 1
elif direction == DOWN:
movex = blankx
movey = blanky - 1
elif direction == LEFT:
movex = blankx + 1
movey = blanky
elif direction == RIGHT:
movex = blankx - 1
movey = blanky
# prepare the base surface
drawBoard(board, message)
baseSurf = DISPLAYSURF.copy()
# draw a blank space over the moving tile on the baseSurf Surface.
moveLeft, moveTop = getLeftTopOfTile(movex, movey)
pygame.draw.rect(baseSurf, BGCOLOR, (moveLeft, moveTop, TILESIZE, TILESIZE))
for i in range(0, TILESIZE, animationSpeed):
# animate the tile sliding over
checkForQuit()
DISPLAYSURF.blit(baseSurf, (0, 0))
if direction == UP:
drawTile(movex, movey, board[movex][movey], 0, -i)
if direction == DOWN:
drawTile(movex, movey, board[movex][movey], 0, i)
if direction == LEFT:
drawTile(movex, movey, board[movex][movey], -i, 0)
if direction == RIGHT:
drawTile(movex, movey, board[movex][movey], i, 0)
pygame.display.update()
FPSCLOCK.tick(FPS)
def generateNewPuzzle(numSlides):
# From a starting configuration, make numSlides number of moves (and
# animate these moves).
sequence = []
board = getStartingBoard()
drawBoard(board, '')
pygame.display.update()
pygame.time.wait(500) # pause 500 milliseconds for effect
lastMove = None
for i in range(numSlides):
move = getRandomMove(board, lastMove)
slideAnimation(board, move, 'Generating new puzzle...', animationSpeed=int(TILESIZE / 3))
makeMove(board, move)
sequence.append(move)
lastMove = move
return (board, sequence)
def resetAnimation(board, allMoves):
# make all of the moves in allMoves in reverse.
revAllMoves = allMoves[:] # gets a copy of the list
revAllMoves.reverse()
for move in revAllMoves:
if move == UP:
oppositeMove = DOWN
elif move == DOWN:
oppositeMove = UP
elif move == RIGHT:
oppositeMove = LEFT
elif move == LEFT:
oppositeMove = RIGHT
slideAnimation(board, oppositeMove, '', animationSpeed=int(TILESIZE / 2))
makeMove(board, oppositeMove)
if __name__ == '__main__':
main()
Here is the bubble sort code:
def shortBubbleSort(alist):
exchanges = True
passnum = len(alist)-1
while passnum > 0 and exchanges:
exchanges = False
for i in range(passnum):
if alist[i]>alist[i+1]:
exchanges = True
temp = alist[i]
alist[i] = alist[i+1]
alist[i+1] = temp
passnum = passnum-1
alist=[20,30,40,90,50,60,70,80,100,110]
shortBubbleSort(alist)
print(alist)
I am not sure how to select the numbers I have and arrange them using bubble sort method.
Oh - I found your code very complex for python and bubble-sort.
Here is the my view:
def bubbleSort(alist):
for passnum in range(len(alist)-1, 0, -1):
for i in range(passnum):
if alist[i] > alist[i+1]:
alist[i] , alist[i+1] = alist[i+1] , alist[i]
alist = [54,26,93,17,77,31,44,55,20]
bubbleSort(alist)
print(alist)
You do not need any flags or temp variables - this is the python, the language of magic! :) :) :) abracadabra!!!

Conway's game of life list index error

So I'm trying to make Conway's game of life in Python/pygame, and the first iteration of making the new grid works, but the second wont because of a list index out of range error. I have been trying to figure out what's wrong, but the list index shouldn't be out of range. This is my code, the mistake is supposedly in changevalue() but i suspect it isn't, since the first iteration works:
import pygame
import random
width = 400
height = 400
blocksize = 10
white = (255, 255, 255)
black = (0, 0, 0)
visual = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
IsOn = True
grid = []
templist = []
tempgrid = []
class square(object):
def __init__(self, x, y, alive):
self.x = x
self.y = y
self.alive = alive
for y in range(height/blocksize):
templist = []
for x in range(width/blocksize):
templist.append(square(x, y, random.choice([True, False, False, False])))
grid.append(templist)
def changevalue(cx, cy, cgrid):
neighbours = []
for dy in range(3):
ddy = dy - 1
for dx in range(3):
ddx = dx - 1
if not (dx - 1 == 0 and dy - 1 == 0):
#print cgrid[(cy + ddy)%len(cgrid)][(cx + ddx)%len(cgrid[y])].alive
#NO ERRORS
#print len(cgrid) > (cy + ddy)%len(cgrid), len(cgrid[y]) > (cx + ddx)%len(cgrid[cy])
#NO ERRORS
neighbours.append(cgrid[(cy + ddy)%len(cgrid)][(cx + ddx)%len(cgrid[cy])].alive)
return len(filter(lambda p: p == True, neighbours))
while IsOn:
for event in pygame.event.get():
if event.type == pygame.QUIT:
IsOn = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_c:
proceed = True
tempgrid = []
for times in range(len(grid)):
tempgrid.append([])
for ty in range(len(grid)):
for tx in range(len(grid[ty])):
if changevalue(tx, ty, grid) < 2 and grid[ty][tx].alive == True:
tempgrid[ty].append(square(tx, ty, False))
elif changevalue(tx, ty, grid) > 3 and grid[ty][tx].alive == True:
tempgrid[ty].append(square(tx, ty, False))
elif changevalue(tx, ty, grid) == 3 and grid[ty][tx].alive == False:
tempgrid[ty].append(square(tx, ty, True))
grid = list(tempgrid)
visual.fill(white)
for y in range(len(grid)):
for x in range(len(grid[y])):
if grid[y][x].alive == True:
pygame.draw.rect(visual, black, (grid[y][x].x*blocksize, grid[y][x].y*blocksize, blocksize, blocksize))
pygame.display.update()
clock.tick(2)
pygame.quit()
quit()
Thanks for your help!
You don't copy square which doesn't change value - so new rows have different length - and later you have problem with index
You need something like this
if changevalue ...:
...
elif changevalue ...:
...
elif changevalue ...:
...
else:
# copy other elements
tempgrid[ty].append(grid[ty][tx])

Categories