In the Swap function, I am checking under some index a button class instance, which I checked using print statements, but for some reason it still gives me an error saying that it has no such attribute check click. Any tips on formatting are also welcome, I am just a beginner. I am using three different arrays to hold various instances, values and coordinates for each corresponding array position. I am trying to make a sort of match 3 game. Thanks for any help
from os import access
import pygame,sys
from random import randrange
import numpy
#Constants
Columns =5
Rows = 5
X,Y = 320,0
class Button:
def __init__(self,x,y,image,scale):
self.x = x
self.y=y
self.image = pygame.transform.scale(image,(scale,scale))
self.scale = scale
self.rect = self.image.get_rect(topleft=(x,y))
self.clicked = False
self.Action = False
def Draw(self):
Win.blit(self.image,(self.x,self.y))
def CheckClick(self):
isClicked = False
mousepos= pygame.mouse.get_pos()
if self.rect.collidepoint(mousepos):
if pygame.mouse.get_pressed()[0] ==1 and self.clicked == False:
self.clicked = True
self.Action = True
if pygame.mouse.get_pressed()[0] ==0:
self.clicked = False
return self.Action
#Win
WinWidth, WinHeight = 1280,800
Win = pygame.display.set_mode((WinWidth,WinHeight))
#IMAGES
test_img = pygame.image.load("test.png")
red_img = pygame.image.load("Red.png")
green_img = pygame.image.load("green.png")
blue_img = pygame.image.load("blue.png")
#Board
Board = [[ randrange(0,3) for column in range(Columns)] for row in range(Rows) ]
BoardObjs = []
BoardXYs = [[None for column in range(Columns)]for row in range(Rows)]
#Fill BoardXYS
for k in range(len(Board)):
for j in range(len(Board[1])):
BoardXYs[k][j] = [X,Y]
X += 160
X = 320
Y += 160
for r in range(len(Board)):
for t in range(len(Board[1])):
if Board[r][t] == 0:
BoardObjs.append(Button(BoardXYs[r][t][0],BoardXYs[r][t][1],red_img,100))
if Board[r][t] == 1:
BoardObjs.append(Button(BoardXYs[r][t][0],BoardXYs[r][t][1],green_img,100))
if Board[r][t] == 2:
BoardObjs.append(Button(BoardXYs[r][t][0],BoardXYs[r][t][1],blue_img,100))
BoardObjs = [[BoardObjs[0],BoardObjs[1],BoardObjs[2],BoardObjs[3],BoardObjs[4]],
[BoardObjs[5],BoardObjs[6],BoardObjs[7],BoardObjs[8],BoardObjs[9]],
[BoardObjs[10],BoardObjs[11],BoardObjs[12],BoardObjs[13],BoardObjs[14]],
[BoardObjs[15],BoardObjs[16],BoardObjs[17],BoardObjs[18],BoardObjs[19]],
[BoardObjs[20],BoardObjs[21],BoardObjs[22],BoardObjs[23],BoardObjs[24]]]
print(len(Board))
print(len(BoardObjs))
#Images
img_0 = pygame.Rect(0,0,64,64)
TouchingReds = 0
def CheckMatches(Board):
VerticalMatch = False
HorizontalMatch = False
for m in range(len(Board)):
for n in range(len(Board[1])-2):
if Board[m][n]==Board[m][n+1]==Board[m][n+2]:
HorizontalMatch = True
Board[m][n] = None
Board[m][n+1] = None
Board[m][n+2] = None
BoardObjs[m][n] = None
BoardObjs[m][n+1] = None
BoardObjs[m][n+2] = None
for o in range(len(Board[1])-2):
for u in range(len(Board)):
if Board[o][u]==Board[o+1][u]==Board[o+2][u]:
VerticalMatch = True
Board[o][u] = None
Board[o+1][u] = None
Board[o+2][u] = None
BoardObjs[o][u] = None
BoardObjs[o+1][u] = None
BoardObjs[o+2][u] = None
def Draw(Board,BoardXYs):
DoAppend = True
Win.fill((255,255,255))
for y in range(len(BoardObjs)):
for u in range(len(BoardObjs[1])):
if BoardObjs[y][u] != None:
BoardObjs[y][u].Draw()
pygame.display.update()
def Swap():
FirstClick = False
for i in range(len(BoardObjs)):
for t in range(len(BoardObjs[1])):
if BoardObjs[i][t] != None:
if BoardObjs[i][t].CheckClick and FirstClick == True:
BoardObjs[num][num2]=BoardObjs[i][t]
BoardObjs[i][t]=Buffer
FirstClick = False
print(BoardObjs[i][t])
if BoardObjs[i][t] !=None:
if BoardObjs[i][t].CheckClick:
Buffer = BoardObjs[i]
num =i
num2 = t
FirstClick = True
#ZEROS - RED
#ONES - GREEN
#TWOS - BLUE
clock = pygame.time.Clock()
def GameLoop():
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
CheckMatches(Board)
Draw(Board,BoardXYs)
Swap()
pygame.quit()
sys.exit()
if __name__ == "__main__":
GameLoop()
The mistake is in the Swap() function. Buffer is assigned to an item in the grid (BoardObjs[i][t]=Buffer). So Buffer needs to be a Button object instead of a row (list of objects):
Buffer = BoardObjs[i]
Buffer = BoardObjs[i][t]
The initialization of the board can be simplified:
#Board
Board = [[randrange(0,3) for column in range(Columns)] for row in range(Rows) ]
BoardXYs = [[(j*160+320, k*160) for j in range(Columns)]for k in range(Rows)]
BoardObjs = []
for r in range(len(Board)):
BoardObjs.append([])
for t in range(len(Board[1])):
image = [red_img, green_img, blue_img][board[r][t]]
BoardObjs[-1].append(Button(*BoardXYs[r][t], image, 100))
The CheckClick function you added is a function, not an attribute. Make sure to put parentheses after the name, even if it takes no argument.
Try, BoardObjs[i][t].CheckClick()
Related
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.
So, recently I started doing some python programming, and came across a video on Youtube in which guy showcases some of his simulations made in pygame (https://www.youtube.com/watch?v=M39R2A8kuh8).
I decided to do the easiest one, the Falling Sand Simulation. I implemented eveything correctly, but when it came to updating the grid I just couldn't do it right. In the end cells are positioned correctly at the bottom of screen, but they don't fall slowly, instead they just instantly teleport there. That's happening because when for loop comes across the cell it is being updated and falling down one row down, then loop comes across that same cell once more and same thing happens
I tried fixing it with second array which holds old grid and for some reason it didn't work.
Here's the code (please ignore my bad code, just a beginner xd):
import pygame
import random
from time import sleep
pygame.init()
WIDTH, HEIGHT = 800, 800
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Falling Sand Simulation")
BLACK = (0, 0, 0)
ORANGE = (158, 103, 32)
class Grid:
def __init__(self, width, height):
self.rows = int(width / 2)
self.columns = int(width / 2)
self.PreviousGrid = [[0 for i in range(self.columns)] for j in range(self.rows)]
self.CurrentGrid = [[0 for i in range(self.columns)] for j in range(self.rows)]
def add_cell(self, xpos, ypos):
xcell = int(xpos / 2)
ycell = int(ypos / 2)
self.CurrentGrid[xcell][ycell] = 1
def update_grid(self):
self.PreviousGrid = self.CurrentGrid
for i in range(self.rows):
if (i+1) != self.rows:
for j in range(self.columns):
if (j+1) != self.columns:
if self.PreviousGrid[i][j] == 0:
pass
else:
if self.PreviousGrid[i][j+1] == 0:
self.CurrentGrid[i][j] = 0
self.CurrentGrid[i][j+1] = 1
elif self.PreviousGrid[i-1][j+1] == 0 and self.PreviousGrid[i+1][j+1] == 0:
self.CurrentGrid[i][j] = 0
choice = random.randint(0, 1)
if choice == 0:
self.CurrentGrid[i-1][j+1] = 1
else:
self.CurrentGrid[i+1][j+1] = 1
elif self.PreviousGrid[i-1][j+1] == 0:
self.CurrentGrid[i][j] = 0
self.CurrentGrid[i-1][j+1] = 1
elif self.PreviousGrid[i+1][j+1] == 0:
self.CurrentGrid[i][j] = 0
self.CurrentGrid[i+1][j+1] = 1
def draw_grid(self, win):
for i in range(self.rows):
for j in range(self.columns):
if self.CurrentGrid[i][j] == 0:
pass
elif self.CurrentGrid[i][j] == 1:
pygame.draw.rect(win, ORANGE, pygame.Rect(int(i*2), int(j*2), 4, 4))
def main():
run = True
clock = pygame.time.Clock()
grid = Grid(WIDTH, HEIGHT)
update_rate = 0.05
countdownMS = update_rate
paused = False
while run:
clock.tick(30)
WIN.fill(BLACK)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sec = clock.get_rawtime()/100;
countdownMS -= sec;
if countdownMS < 0.0:
grid.update_grid()
countdownMS = update_rate
grid.draw_grid(WIN)
if pygame.mouse.get_pressed()[0]:
xpos, ypos = event.pos
grid.add_cell(xpos, ypos)
pygame.display.update()
pygame.quit()
if __name__ == '__main__':
main()
You have to create a new empty grid in update_grid. Copy the bottom line of the old grid and fill the rest of the new grid depending on the previous grid:
class Grid:
# [...]
def update_grid(self):
self.PreviousGrid = self.CurrentGrid
# create a new and empty grid
self.CurrentGrid = [[0 for i in range(self.columns)] for j in range(self.rows)]
for i in range(self.rows):
self.CurrentGrid[i][self.columns-1] = self.PreviousGrid[i][self.columns-1]
# fill the new grid depending on the previous grid
for i in range(self.rows):
if i+1 < self.rows:
for j in range(self.columns):
if j+1 < self.columns:
if self.PreviousGrid[i][j] == 1:
if self.PreviousGrid[i][j+1] == 0:
self.CurrentGrid[i][j+1] = 1
elif self.PreviousGrid[i-1][j+1] == 0 and self.PreviousGrid[i+1][j+1] == 0:
self.CurrentGrid[i+random.choice([-1, 1])][j+1] = 1
elif self.PreviousGrid[i-1][j+1] == 0:
self.CurrentGrid[i-1][j+1] = 1
elif self.PreviousGrid[i+1][j+1] == 0:
self.CurrentGrid[i+1][j+1] = 1
else:
self.CurrentGrid[i][j] = 1
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've been working all day on a bit of code for a maze generation algorithm. In this snippet:
if nextCoords[2] == '<':
cells[x][y].l = False
x, y = nextCoords[0], nextCoords[1]
cells[x][y].r = False
The first line of the if statement, cells[x][y].l = False executes as expected, but for some reason cells[x][y].r = False does not keep the change made.
Here's the full code (with some prints from hopeless debugging):
import random
class Cell():
def __init__(self, x, y):
self.x = x
self.y = y
self.visited = False
self.isStart = False
self.isEnd = False
self.isNormal = True
self.u = True
self.d = True
self.l = True
self.r = True
def __str__(self):
# For console applications
return "{0}{1}{2}{3}{4}{5}{6}".format(
'^' if not self.u else ' ',
'v' if not self.d else ' ',
'<' if not self.l else ' ',
'>' if not self.r else ' ',
'+' if self.isStart else '',
'#' if self.isEnd else '',
' ' if self.isNormal else ''
)
def __repr__(self):
return self.__str__()
def mazegen(w=3, h=3, printOut=False):
x = 0
y = 0
cells = [[Cell(x, y) for x in range(w)] for y in range(h)]
stack = []
cells[x][y].isStart = True
cells[x][y].isNormal = False
while True:
allVisited = True
for i in cells:
for j in i:
if not j.visited:
allVisited = False
if allVisited:
finalCell = cells[ stack[-1][0] ][ stack[-1][1] ]
finalCell.isNormal = False
finalCell.isEnd = True
break
cells[x][y].visited = True
choices = []
if x > 0:
choices.append((x-1, y, '<'))
if x < w-1:
choices.append((x+1, y, '>'))
if y > 0:
choices.append((x, y-1, '^'))
if y < h-1:
choices.append((x, y+1, 'v'))
betterChoices = []
for c in choices:
if not cells[ c[0] ][ c[1] ].visited:
betterChoices.append(c)
if betterChoices != []:
nextCoords = random.choice(betterChoices)
else:
popped = stack.pop()
x, y = popped[0], popped[1]
continue
stack.append((x, y))
print(nextCoords[2])
if nextCoords[2] == '^':
cells[x][y].u = False
print("This cell is now {}.".format(cells[x][y]))
x, y = nextCoords[0], nextCoords[1]
cells[x][y].d = False
print("The next cell is {}.".format(cells[x][y]))
if nextCoords[2] == 'v':
cells[x][y].d = False
print("Old: ({0}, {1})".format(x, y))
print(cells[x][y])
x, y = nextCoords[0], nextCoords[1]
print("New: ({0}, {1})".format(x, y))
print(cells[x][y])
cells[x][y].u = False
print(cells[x][y])
if nextCoords[2] == '<':
cells[x][y].l = False
x, y = nextCoords[0], nextCoords[1]
cells[x][y].r = False
if nextCoords[2] == '>':
cells[x][y].r = False
x, y = nextCoords[0], nextCoords[1]
cells[x][y].l = False
print("Moved {}\n".format(nextCoords[2]))
if printOut:
for i in cells:
for j in i:
print("| ", end='')
print(j, end=' | ')
print()
print("----------"*w)
return cells
after i cleaned up your code it's now working and looking fine :]
import pygame, sys, random
class Cell:
visited=isStart=isEnd=False
u=d=l=r=isNormal=True
def __init__(self,x,y): self.x,self.y = x,y
def mazegen(w,h):
x,y = 0,0
cells = [[Cell(x,y) for y in range(h)] for x in range(w)]
stack = []
cells[x][y].isStart = True
cells[x][y].isNormal = False
while True:
allVisited = True
for i in cells:
for j in i:
if not j.visited: allVisited = False
if allVisited:
finalCell = cells[ stack[-1][0] ][ stack[-1][1] ]
finalCell.isNormal = False
finalCell.isEnd = True
break
cells[x][y].visited = True
choices = []
if x>0: choices.append((x-1, y, '<'))
if x<w-1: choices.append((x+1, y, '>'))
if y>0: choices.append((x, y-1, '^'))
if y<h-1: choices.append((x, y+1, 'v'))
betterChoices = []
for c in choices:
if not cells[c[0]][c[1]].visited: betterChoices.append(c)
if betterChoices != []:
nextCoords = random.choice(betterChoices)
else:
popped = stack.pop()
x,y = popped[0],popped[1]
continue
stack.append((x,y))
if nextCoords[2] == '^':
cells[x][y].u = False
x,y = nextCoords[0], nextCoords[1]
cells[x][y].d = False
if nextCoords[2] == 'v':
cells[x][y].d = False
x,y = nextCoords[0], nextCoords[1]
cells[x][y].u = False
if nextCoords[2] == '<':
cells[x][y].l = False
x,y = nextCoords[0], nextCoords[1]
cells[x][y].r = False
if nextCoords[2] == '>':
cells[x][y].r = False
x,y = nextCoords[0], nextCoords[1]
cells[x][y].l = False
return cells
BLACK = (0,0,0)
WHITE = (255,255,255)
RED = (255,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
screen = pygame.display.set_mode((400,400))
pygame.display.set_caption("Maze Generator!")
cells = mazegen(10,10)
while True:
key = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type==pygame.QUIT: pygame.quit(); sys.exit()
if event.type==pygame.KEYDOWN:
if key[pygame.K_LALT] and event.key==pygame.K_F4: pygame.quit(); sys.exit() # alt + f4
screen.fill(BLUE)
for i in cells:
for cell in i:
img = pygame.Surface((20,20))
img.fill(WHITE)
if cell.u:pygame.draw.line(img,BLACK,(0,0),(20,0))
if cell.d: pygame.draw.line(img,BLACK,(0,20),(20,20))
if cell.l: pygame.draw.line(img,BLACK,(0,0),(0,20))
if cell.r: pygame.draw.line(img,BLACK,(20,0),(20,20))
screen.blit(img,(cell.x*20, cell.y*20))
pygame.display.flip()
also here's my really compact example. (i use pygame just to save it as an image. lol)
import pygame, random
def maze(*gs):
gs = (gs[0]+3-gs[0]%4,gs[1]+3-gs[1]%4) # rounding grid size for cleaner presentation
adjacent = lambda x,y: [(a,b) for a,b in ((x-2,y),(x+2,y),(x,y-2),(x,y+2)) if (a,b) not in tiles and a+1 and b+1 and a<gs[0] and b<gs[1]]
pos = (gs[0]//2,gs[1]//2); tiles, points, direction = [pos], [], (0,0)
for l in range(((gs[0]-1)//2)*((gs[1]-1)//2)-1): # total loops in algorithm
adj = adjacent(*pos) # get available adjacents
for i in points[::-1]:
if adj: break # we have adjacents, we don't need to backtrack
adj = adjacent(*i) # check if corner has spare adjacents
if adj: points, pos = points[:points.index(i)], i # if so, remove corner, backtrack pos
rand = random.choice(adj); new_dir = rand[0]-pos[0], rand[1]-pos[1] # get random dir
if new_dir != direction and len(adj)>1: points += [pos] # corner and more adj remain
tiles, pos, direction = tiles+[(rand[0]-new_dir[0]//2,rand[1]-new_dir[1]//2),rand], rand, new_dir # add path, move in direction
pygame.init() # lets use pygame
surface = pygame.Surface(gs); surface.fill((0,0,0)) # create black image
for i in tiles: surface.set_at(i,(255,255,255)) # add white path
pygame.image.save(surface,"maze.png") # save as png
maze(100,100) # create 100x100 pixel maze
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])