I have built a minesweeper algorithm for a cs50 assignment. I have identidied issue with my code. (Inside the minesweeper class)
The program needs both minesweeper.py and runner.py and is executed with "python runner.py"
Upon running it with python runner.py it gives me a RuntimeError:
Traceback (most recent call last):
File "C:\Users\seng\Downloads\minesweeper\minesweeper\runner.py", line 220, in <module>
ai.add_knowledge(move, nearby)
File "C:\Users\seng\Downloads\minesweeper\minesweeper\minesweeper.py", line 230, in add_knowledge
self.updateknowledge()
File "C:\Users\seng\Downloads\minesweeper\minesweeper\minesweeper.py", line 274, in updateknowledge
for safe in safes:
RuntimeError: Set changed size during iteration
Here is the code.
minesweeper.py
import itertools
import random
import copy
class Minesweeper():
"""
Minesweeper game representation
"""
def __init__(self, height=8, width=8, mines=8):
# Set initial width, height, and number of mines
self.height = height
self.width = width
self.mines = set()
# Initialize an empty field with no mines
self.board = []
for i in range(self.height):
row = []
for j in range(self.width):
row.append(False)
self.board.append(row)
# Add mines randomly
while len(self.mines) != mines:
i = random.randrange(height)
j = random.randrange(width)
if not self.board[i][j]:
self.mines.add((i, j))
self.board[i][j] = True
# At first, player has found no mines
self.mines_found = set()
def print(self):
"""
Prints a text-based representation
of where mines are located.
"""
for i in range(self.height):
print("--" * self.width + "-")
for j in range(self.width):
if self.board[i][j]:
print("|X", end="")
else:
print("| ", end="")
print("|")
print("--" * self.width + "-")
def is_mine(self, cell):
i, j = cell
return self.board[i][j]
def nearby_mines(self, cell):
"""
Returns the number of mines that are
within one row and column of a given cell,
not including the cell itself.
"""
# Keep count of nearby mines
count = 0
# Loop over all cells within one row and column
for i in range(cell[0] - 1, cell[0] + 2):
for j in range(cell[1] - 1, cell[1] + 2):
# Ignore the cell itself
if (i, j) == cell:
continue
# Update count if cell in bounds and is mine
if 0 <= i < self.height and 0 <= j < self.width:
if self.board[i][j]:
count += 1
return count
def won(self):
"""
Checks if all mines have been flagged.
"""
return self.mines_found == self.mines
class Sentence():
"""
Logical statement about a Minesweeper game
A sentence consists of a set of board cells,
and a count of the number of those cells which are mines.
"""
def __init__(self, cells, count):
self.cells = set(cells)
self.count = count
def __eq__(self, other):
return self.cells == other.cells and self.count == other.count
def __str__(self):
return f"{self.cells} = {self.count}"
def known_mines(self):
"""
Returns the set of all cells in self.cells known to be mines.
"""
if len(self.cells) == self.count:
return self.cells
def known_safes(self):
"""
Returns the set of all cells in self.cells known to be safe.
"""
if 0 == self.count:
return self.cells
def mark_mine(self, cell):
"""
Updates internal knowledge representation given the fact that
a cell is known to be a mine.
"""
if cell in self.cells:
self.cells.remove(cell)
self.count -= 1
def mark_safe(self, cell):
"""
Updates internal knowledge representation given the fact that
a cell is known to be safe.
"""
if cell in self.cells:
self.cells.remove(cell)
class MinesweeperAI():
"""
Minesweeper game player
"""
def __init__(self, height=8, width=8):
# Set initial height and width
self.height = height
self.width = width
# Keep track of which cells have been clicked on
self.moves_made = set()
# Keep track of cells known to be safe or mines
self.mines = set()
self.safes = set()
# List of sentences about the game known to be true
self.knowledge = []
def mark_mine(self, cell):
"""
Marks a cell as a mine, and updates all knowledge
to mark that cell as a mine as well.
"""
self.mines.add(cell)
for sentence in self.knowledge:
sentence.mark_mine(cell)
def mark_safe(self, cell):
"""
Marks a cell as safe, and updates all knowledge
to mark that cell as safe as well.
"""
self.safes.add(cell)
for sentence in self.knowledge:
sentence.mark_safe(cell)
def add_knowledge(self, cell, count):
"""
Called when the Minesweeper board tells us, for a given
safe cell, how many neighboring cells have mines in them.
This function should:
1) mark the cell as a move that has been made
2) mark the cell as safe
3) add a new sentence to the AI's knowledge base
based on the value of `cell` and `count`
4) mark any additional cells as safe or as mines
if it can be concluded based on the AI's knowledge base
5) add any new sentences to the AI's knowledge base
if they can be inferred from existing knowledge
"""
#1
self.moves_made.add(cell)
#2
self.mark_safe(cell)
#3
i, j = cell
removecell = []
addcell = [(i-1, j-1), (i-1, j), (i-1, j+1),
(i, j-1), (i, j+1),
(i+1, j-1), (i+1, j), (i+1, j + 1),]
for c in addcell:
if c[0] < 0 or c[0] > 7 or c[1] < 0 or c[1] > 7:
removecell.append(c)
for c in removecell:
addcell.remove(c)
removecell = []
for c in addcell:
if c in self.mines:
removecell.append(c)
count -= len(removecell)
for c in removecell:
addcell.remove(c)
removecell = []
for c in addcell:
if c in self.safes:
removecell.append(c)
for c in removecell:
addcell.remove(c)
#need filter for empty
newsentence = Sentence(addcell, count)
if len(newsentence.cells) > 0:
self.knowledge.append(newsentence)
print("dfs")
self.updateknowledge()
print("2")
self.inference()
print("3")
def inference(self):
for sentence1 in self.knowledge:
for sentence2 in self.knowledge:
if sentence1.cells.issubset(sentence2.cells):
new_cells = sentence2.cells - sentence1.cells
new_count = sentence2.count - sentence1.count
new_sentence = Sentence(new_cells, new_count)
if new_sentence not in self.knowledge:
self.knowledge.append(new_sentence)
self.updateknowledge()
def updateknowledge(self):
keepgoing = True
while keepgoing:
keepgoing = False
for sentence in self.knowledge:
mines = sentence.known_mines()
if mines:
keepgoing = True
for mine in mines:
self.mark_mine(mine)
safes = sentence.known_safes()
if safes:
keepgoing = True
for safe in safes:
self.mark_safe(safe)
def make_safe_move(self):
"""
Returns a safe cell to choose on the Minesweeper board.
The move must be known to be safe, and not already a move
that has been made.
This function may use the knowledge in self.mines, self.safes
and self.moves_made, but should not modify any of those values.
"""
for safe in self.safes:
if safe not in self.moves_made:
return safe
return None
def make_random_move(self):
"""
Returns a move to make on the Minesweeper board.
Should choose randomly among cells that:
1) have not already been chosen, and
2) are not known to be mines
"""
moves = len(self.moves_made) + len(self.mines)
if moves == 64:
return None
while True:
i = random.randrange(self.height)
j = random.randrange(self.height)
if (i, j) not in self.moves_made and (i, j) not in self.mines:
return (i, j)
runner.py
import pygame
import sys
import time
from minesweeper import Minesweeper, MinesweeperAI
HEIGHT = 8
WIDTH = 8
MINES = 8
# Colors
BLACK = (0, 0, 0)
GRAY = (180, 180, 180)
WHITE = (255, 255, 255)
# Create game
pygame.init()
size = width, height = 600, 400
screen = pygame.display.set_mode(size)
# Fonts
OPEN_SANS = "assets/fonts/OpenSans-Regular.ttf"
smallFont = pygame.font.Font(OPEN_SANS, 20)
mediumFont = pygame.font.Font(OPEN_SANS, 28)
largeFont = pygame.font.Font(OPEN_SANS, 40)
# Compute board size
BOARD_PADDING = 20
board_width = ((2 / 3) * width) - (BOARD_PADDING * 2)
board_height = height - (BOARD_PADDING * 2)
cell_size = int(min(board_width / WIDTH, board_height / HEIGHT))
board_origin = (BOARD_PADDING, BOARD_PADDING)
# Add images
flag = pygame.image.load("assets/images/flag.png")
flag = pygame.transform.scale(flag, (cell_size, cell_size))
mine = pygame.image.load("assets/images/mine.png")
mine = pygame.transform.scale(mine, (cell_size, cell_size))
# Create game and AI agent
game = Minesweeper(height=HEIGHT, width=WIDTH, mines=MINES)
ai = MinesweeperAI(height=HEIGHT, width=WIDTH)
# Keep track of revealed cells, flagged cells, and if a mine was hit
revealed = set()
flags = set()
lost = False
# Show instructions initially
instructions = True
while True:
# Check if game quit
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(BLACK)
# Show game instructions
if instructions:
# Title
title = largeFont.render("Play Minesweeper", True, WHITE)
titleRect = title.get_rect()
titleRect.center = ((width / 2), 50)
screen.blit(title, titleRect)
# Rules
rules = [
"Click a cell to reveal it.",
"Right-click a cell to mark it as a mine.",
"Mark all mines successfully to win!"
]
for i, rule in enumerate(rules):
line = smallFont.render(rule, True, WHITE)
lineRect = line.get_rect()
lineRect.center = ((width / 2), 150 + 30 * i)
screen.blit(line, lineRect)
# Play game button
buttonRect = pygame.Rect((width / 4), (3 / 4) * height, width / 2, 50)
buttonText = mediumFont.render("Play Game", True, BLACK)
buttonTextRect = buttonText.get_rect()
buttonTextRect.center = buttonRect.center
pygame.draw.rect(screen, WHITE, buttonRect)
screen.blit(buttonText, buttonTextRect)
# Check if play button clicked
click, _, _ = pygame.mouse.get_pressed()
if click == 1:
mouse = pygame.mouse.get_pos()
if buttonRect.collidepoint(mouse):
instructions = False
time.sleep(0.3)
pygame.display.flip()
continue
# Draw board
cells = []
for i in range(HEIGHT):
row = []
for j in range(WIDTH):
# Draw rectangle for cell
rect = pygame.Rect(
board_origin[0] + j * cell_size,
board_origin[1] + i * cell_size,
cell_size, cell_size
)
pygame.draw.rect(screen, GRAY, rect)
pygame.draw.rect(screen, WHITE, rect, 3)
# Add a mine, flag, or number if needed
if game.is_mine((i, j)) and lost:
screen.blit(mine, rect)
elif (i, j) in flags:
screen.blit(flag, rect)
elif (i, j) in revealed:
neighbors = smallFont.render(
str(game.nearby_mines((i, j))),
True, BLACK
)
neighborsTextRect = neighbors.get_rect()
neighborsTextRect.center = rect.center
screen.blit(neighbors, neighborsTextRect)
row.append(rect)
cells.append(row)
# AI Move button
aiButton = pygame.Rect(
(2 / 3) * width + BOARD_PADDING, (1 / 3) * height - 50,
(width / 3) - BOARD_PADDING * 2, 50
)
buttonText = mediumFont.render("AI Move", True, BLACK)
buttonRect = buttonText.get_rect()
buttonRect.center = aiButton.center
pygame.draw.rect(screen, WHITE, aiButton)
screen.blit(buttonText, buttonRect)
# Reset button
resetButton = pygame.Rect(
(2 / 3) * width + BOARD_PADDING, (1 / 3) * height + 20,
(width / 3) - BOARD_PADDING * 2, 50
)
buttonText = mediumFont.render("Reset", True, BLACK)
buttonRect = buttonText.get_rect()
buttonRect.center = resetButton.center
pygame.draw.rect(screen, WHITE, resetButton)
screen.blit(buttonText, buttonRect)
# Display text
text = "Lost" if lost else "Won" if game.mines == flags else ""
text = mediumFont.render(text, True, WHITE)
textRect = text.get_rect()
textRect.center = ((5 / 6) * width, (2 / 3) * height)
screen.blit(text, textRect)
move = None
left, _, right = pygame.mouse.get_pressed()
# Check for a right-click to toggle flagging
if right == 1 and not lost:
mouse = pygame.mouse.get_pos()
for i in range(HEIGHT):
for j in range(WIDTH):
if cells[i][j].collidepoint(mouse) and (i, j) not in revealed:
if (i, j) in flags:
flags.remove((i, j))
else:
flags.add((i, j))
time.sleep(0.2)
elif left == 1:
mouse = pygame.mouse.get_pos()
# If AI button clicked, make an AI move
if aiButton.collidepoint(mouse) and not lost:
move = ai.make_safe_move()
if move is None:
move = ai.make_random_move()
if move is None:
flags = ai.mines.copy()
print("No moves left to make.")
else:
print("No known safe moves, AI making random move.")
else:
print("AI making safe move.")
time.sleep(0.2)
# Reset game state
elif resetButton.collidepoint(mouse):
game = Minesweeper(height=HEIGHT, width=WIDTH, mines=MINES)
ai = MinesweeperAI(height=HEIGHT, width=WIDTH)
revealed = set()
flags = set()
lost = False
continue
# User-made move
elif not lost:
for i in range(HEIGHT):
for j in range(WIDTH):
if (cells[i][j].collidepoint(mouse)
and (i, j) not in flags
and (i, j) not in revealed):
move = (i, j)
# Make move and update AI knowledge
if move:
if game.is_mine(move):
lost = True
else:
nearby = game.nearby_mines(move)
revealed.add(move)
ai.add_knowledge(move, nearby)
pygame.display.flip()
I understand that this is where the error occurs, and replacing my iterated object with a copy fixes the issue. Problem is i fail to see where i am editing the set in the first place.
class MinesweeperAI:
def updateknowledge(self):
...
safes = sentence.known_safes()
if safes:
keepgoing = True
for safe in safes.copy():
self.mark_safe(safe)
self.mark_safe(safe) leads to (in minesweeper class)
self.safes.add(cell)
for sentence in self.knowledge:
sentence.mark_safe(cell)
and sentence.mark_safe(cell) leads to
if cell in self.cells:
self.cells.remove(cell)
So how is this affecting the set of safes which I am iterating over? Would appreciate advice
The problem is that the code is "marking safes" while looping over "safes".
Try changing:
for safe in self.safes:
to:
for safe in list(self.safes):
That will make a copy of the "safes" to loop over, leaving the underlying set available for updates.
Related
I'm trying to make a tic tac toe game for 2 players - user and computer using minimax algorythm. My problem is that the code has an error and it doesn't work properly, but I'm not sure why. More explanation about code you can see in comments for code.
I have a TypeError: cannot unpack non-iterable Non-Type object for line 258 in main row, col = comp.evaluation(board)
Here is the code and comments:
file ttt1 with variables assignment:
import random
WIDTH = 600
HEIGHT = 600
ROWS = 3 #rows of the grid
COLS = 3 #cols of the grid
SQUARE_SIZE = WIDTH // COLS #size of one square of the grid - can be height devided by rows
LINE_WIDTH = 15 #width of the lines of the grid
#circle
CIRCLE_WIDTH = 20
RADIUS = SQUARE_SIZE // 3
#cross
CROSS_WIDTH = 20
OFFSET = 50
#define colors
BACKCOLOR = ((random.randint(0,255)), (random.randint(0, 255)), (random.randint(0,255)))
LINE_COLOR = ((random.randint(0,255)), (random.randint(0, 255)), (random.randint(0,255)))
CIRCLE = ((random.randint(0,255)), (random.randint(0, 255)), (random.randint(0,255)))
CROSS = ((random.randint(0,255)), (random.randint(0, 255)), (random.randint(0,255)))
file with classes and etc:
import sys #for quitting the application
import copy
import random
import pygame
import numpy as np
from ttt1 import * #importing things from ttt1 file
#pygamecode and set up
pygame.init() #initializig pygame module
screen = pygame.display.set_mode((WIDTH, HEIGHT)) #setting up the screen - width and height
pygame.display.set_caption("Tic Tac Toe game with minimax")
screen.fill(BACKCOLOR) #filling in the screen with a background colors
class Board:
def __init__(self): #2 dimensional array of 0-s
self.squares = np.zeros((ROWS, COLS)) #parameters as tuple with 0-s, making empty squares
#self.mark_squares(1, 2, 3) #for testing
#print(self.squares) #return 0-s in terminal
self.empty_square = self.squares #list of squares
self.marked_square = 0 #state of a square
def final_condition(self, show=False):
#returning 0 if there is no win till now(not a draw condition), if player 1 wins - then returns 1; if player 2 wins returns 2
#vertical winning situation
for col in range(COLS):
if self.squares[0][col] == self.squares[1][col] == self.squares[2][col] != 0:
if show:
color = CIRCLE if self.squares[0][col] == 2 else CROSS
start_pos = (col * SQUARE_SIZE + SQUARE_SIZE // 2, 20)
final_pos = (col * SQUARE_SIZE + SQUARE_SIZE // 2, HEIGHT - 20)
pygame.draw.line(screen, color, start_pos, final_pos, LINE_WIDTH)
return self.squares[0][col] #return any of column from previous if stat
#horizontal winning situation
for row in range(ROWS):
if self.squares[row][0] == self.squares[row][1] == self.squares[row][2] != 0:
if show:
color = CIRCLE if self.squares[row][0] == 2 else CROSS
start_pos = (20, row * SQUARE_SIZE + SQUARE_SIZE // 2)
final_pos = (WIDTH - 20, row * SQUARE_SIZE + SQUARE_SIZE // 2)
pygame.draw.line(screen, color, start_pos, final_pos, LINE_WIDTH)
return self.squares[row][0]
#going down diagonal winning situation
if self.squares[0][0] == self.squares[1][1] == self.squares[2][2] != 0:
if show:
color = CIRCLE if self.squares[1][1] == 2 else CROSS
start_pos = (20, 20)
final_pos = (WIDTH - 20, HEIGHT - 20)
pygame.draw.line(screen, color, start_pos, final_pos, CROSS_WIDTH)
return self.squares[1][1] #11 is a common square between 2 diagonals so it's returning this one
#going up diagonal winning situation
if self.squares[2][0] == self.squares[1][1] == self.squares[0][2] != 0:
if show:
color = CIRCLE if self.squares[1][1] == 2 else CROSS
start_pos = (20, HEIGHT - 20)
final_pos = (WIDTH - 20, 20)
pygame.draw.line(screen, color, start_pos, final_pos, CROSS_WIDTH)
return self.squares[1][1]
return 0 #no winning yet
def mark_squares(self, row, col, player): #for 2 players, one will be marked and zeros in tuple will be replaced in a square that was marked by a player with a player
self.squares[row][col] = player
#ach time square is marked it's gonna be increased by one
self.marked_square += 1
def empty_square(self, row, col): #checking if the square is empy or not
return self.squares[row][col] == 0 #if return True than it's empty, if False than bot empty
def return_empty(self):
#square that is needed to be deleted from the empty squares
empty = []
for row in range(ROWS):
for col in range(COLS): #2 dimentional array(matrix)
if self.empty_square[row, col]:
empty.append((row, col))
return empty
def isfull(self):
return self.marked_square == 9 #max amount of squares when the board is full
def isempty(self):
return self.marked_square == 0 #return if the square is marked
class MinMax:
def __init__(self, level=1, player=2):
self.level = level #level of game
self.player = player #there are 2 players
def random_choice(self, board): #random choice function
empty = board.return_empty()
index = random.randrange(0, len(empty))
return empty[index] #row and col
def minmax(self, board, max):
#checking terminal case
case = board.final_condition() #case
#player 1 wins
if case == 1:
return 1, None #evaluation, move
#player 2 wins
if case == 2:
return -1, None
#draw
elif board.isfull():
return 0, None
#coding the algorythm
if max: #if player is maximizing
max_eval = -100 #minimal eva;uation that player gets from a specific board
best_move = None
empty_square = board.return_empty()
#loop each square inside of the list of empty squares
for(row, col) in empty_square:
temp_board = copy.deepcopy(board) #copying the board for testing on other boards
temp_board.mark_squares(row, col, 1)#marking the square to a copy
eval = self.minmax(temp_board, False)[0] #it's true because of changing the player, first position is an evaluation
if eval > max_eval:
max_eval = eval #save eval into max eval
best_move = (row, col)
return max_eval, best_move
elif not max: #if player is minimizing
min_eval = 100 #minimal eva;uation that player gets from a specific board
best_move = None
empty_square = board.return_empty()
#loop each square inside of the list of empty squares
for(row, col) in empty_square:
temp_board = copy.deepcopy(board) #copying the board for testing on other boards - a copy of the object is copied into another object
temp_board.mark_squares(row, col, self.player)#marking the square to a copy
eval = self.minmax(temp_board, True)[0] #it's true because of changing the player, first position is an evaluation
if eval < min_eval:
min_eval = eval
best_move = (row, col)
return min_eval, best_move #returning the min eval with respect to best move
def evaluation(self, main_board):
if self.level == 0:
#random choice
eval = 'random'
move = self.random_choice(main_board)
else:
#minmax alg choice
eval, move = self.minmax(main_board, False)
print(f'Minimax is chosen for marking the square in position {move} with an evaluation {eval}')
return move #move is the row and the col
#drowing lines for game - lines of the grid
class TicTac:
def __init__(self): #init method for the new game objects
self.board = Board() #creating a new board class
self.comp = MinMax()
self.player = 2 #who is the next player to mark in the squares #player 1 is crosses and player 2 is circles
self.gamemode = 'computer' #by human, or by computer (ai)
self.run = True
self.lines() #colling the method for showing the lines
def make_move(self, row, col):
self.board.mark_squares(row, col, self.player) #the last parameter changes from 1 to tictac.player
#print(board.squares) #testing, return 0 and 1 in console whether it's empty or not
self.draw_figure(row, col) #graphic displaying of the information
self.another_player()
#print(board.squares) #checking the another player works or not, result in console
def reset(self):
self.__init__() #restarting all attributes to default values
def lines(self): #defining the creating the grid function
#background color after reseting
screen.fill(BACKCOLOR)
#vertical lines
pygame.draw.line(screen, LINE_COLOR,(SQUARE_SIZE, 0), (SQUARE_SIZE, HEIGHT), LINE_WIDTH) #first line
pygame.draw.line(screen, LINE_COLOR,(WIDTH - SQUARE_SIZE, 0), (WIDTH - SQUARE_SIZE, HEIGHT), LINE_WIDTH) #y axis is 0, x axis is first; second line
#horizontal lines
pygame.draw.line(screen, LINE_COLOR,(0,SQUARE_SIZE), (WIDTH, SQUARE_SIZE), LINE_WIDTH) #first line
pygame.draw.line(screen, LINE_COLOR,(0, HEIGHT - SQUARE_SIZE), (WIDTH, HEIGHT - SQUARE_SIZE), LINE_WIDTH) #second line
def draw_figure(self, row, col):
if self.player == 1:
#drawing the cross
start_down_line = (col * SQUARE_SIZE + OFFSET, row * SQUARE_SIZE + OFFSET) #adjusting the down going line of the cross
end_down_line = (col * SQUARE_SIZE + SQUARE_SIZE - OFFSET, row * SQUARE_SIZE + SQUARE_SIZE - OFFSET)
pygame.draw.line(screen, CROSS, start_down_line, end_down_line, CROSS_WIDTH) #drawing the cross
#going up line
start_up_line = (col * SQUARE_SIZE + OFFSET, row * SQUARE_SIZE + SQUARE_SIZE - OFFSET)
end_up_line = (col * SQUARE_SIZE + SQUARE_SIZE - OFFSET, row * SQUARE_SIZE + OFFSET)
pygame.draw.line(screen, CROSS, start_up_line, end_up_line, CROSS_WIDTH) #drawing the line
elif self.player == 2:
#drawing the circle
center = (col * SQUARE_SIZE + SQUARE_SIZE // 2, row * SQUARE_SIZE + SQUARE_SIZE // 2) #center position
pygame.draw.circle(screen, CIRCLE, center, RADIUS, CIRCLE_WIDTH)
def another_player(self): #chenging the player that is marking the square to the next one
self.player = self.player % 2 + 1 #first operation will return the remainder ((model)%2) if the player is 1, than it adds 1 and there is o a remainder so the player is changing
def change_gamemode(self):
#self.gamemode = 'computer' if self.gamemode == 'user' else 'user'
if self.gamemode == 'user':
self.gamemode = 'computer'
else:
self.gamemode = 'user'
def isover(self):
return self.board.final_condition(show=True) != 0 or self.board.isfull()
def main(): #main function where the code will be executing
#game object
tictac = TicTac() #object and the class
board = tictac.board
comp = tictac.comp
#mainloop
while True:
#pygame events
for event in pygame.event.get():
if event.type == pygame.QUIT: #event is any actin happening in the game(pressing the key etc)
pygame.quit()
sys.exit() #for exiting the game
if event.type == pygame.KEYDOWN: #keydown event
#g-gamemode
if event.key == pygame.K_g:
tictac.change_gamemode()
# r = restart
if event.key == pygame.K_r:
tictac.reset()
board = tictac.board #board and ai will be reseted again and start from the initializing condition
comp = tictac.comp
# 0 -random computer alg
if event.key == pygame.K_0:
comp.level = 0
# 1 - random
if event.key == pygame.K_1:
comp.level = 1
if event.type == pygame.MOUSEBUTTONDOWN: #position of coursor in the pixels
pos = event.pos #position of pixels
row = pos[1]//SQUARE_SIZE #represents y axis of the board
col = pos[0]//SQUARE_SIZE #position 0 of x axis
#print(row, col) #testing
if board.empty_square[row,col] and tictac.run:
tictac.make_move(row, col)
if tictac.isover():
tictac.run = False
#board.mark_squares(row, col, 1) #in a position row col player number 1
#print(tictac.board.squares) #testing the board, returns information to console
if tictac.gamemode == 'computer' and tictac.player == comp.player and tictac.run:
#update the screen
pygame.display.update()
#computer methods
row, col = comp.evaluation(board)
#board.mark_squares(row, col, comp.player)
tictac.make_move(row, col)
if tictac.isover():
tictac.run = False
pygame.display.update() #updating the screen
#minimax alg =
""" terminal case and the base case - terminal case is when the game is over
3 situations: 1) player 1 wins, 2) player 2 wins, 3) draw //terminal cases
if __name__ == "__main__":
main()
I have built an A* Pathfinding algorithm that finds the best route from Point A to Point B, there is a timer that starts and ends post execute of the algorithm and the path is draw, this is parsed to a global variable. so it is accessable when i run the alogrithm more than once (to gain an average time).
the global variable gets added to a list, except when i run the algorithm 5 times, only 4 values get added (I can see 5 times being recorded as the algorithm prints the time after completion). when displaying the list it always misses the first time, and only has times 2,3,4,5 if i run the algorithm 5 times. here is main.py
import astar
import pygame
def main():
timing_list = []
WIDTH = 800
WIN = pygame.display.set_mode((WIDTH, WIDTH))
for x in range(0, 4):
astar.main(WIN, WIDTH)
timing_list.insert(x, astar.full_time)
print(timing_list)
if __name__ == "__main__":
main()
and
astar.py
from queue import PriorityQueue
from random import randint
import pygame
from timing import Timing
WIDTH = 800
WIN = pygame.display.set_mode((WIDTH, WIDTH))
pygame.display.set_caption("A* Path Finding Algorithm")
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 255, 0)
YELLOW = (255, 255, 0)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
PURPLE = (128, 0, 128)
ORANGE = (255, 165, 0)
GREY = (128, 128, 128)
TURQUOISE = (64, 224, 208)
global full_time
class Spot:
def __init__(self, row, col, width, total_rows):
self.row = row
self.col = col
self.x = row * width
self.y = col * width
self.color = WHITE
self.neighbors = []
self.width = width
self.total_rows = total_rows
def get_pos(self):
return self.row, self.col
def is_closed(self):
return self.color == RED
def is_open(self):
return self.color == GREEN
def is_barrier(self):
return self.color == BLACK
def is_start(self):
return self.color == ORANGE
def is_end(self):
return self.color == TURQUOISE
def reset(self):
self.color = WHITE
def make_start(self):
self.color = ORANGE
def make_closed(self):
self.color = RED
def make_open(self):
self.color = GREEN
def make_barrier(self):
self.color = BLACK
def make_end(self):
self.color = TURQUOISE
def make_path(self):
self.color = PURPLE
def draw(self, win):
pygame.draw.rect(win, self.color, (self.x, self.y, self.width, self.width))
def update_neighbors(self, grid):
self.neighbors = []
if self.row < self.total_rows - 1 and not grid[self.row + 1][self.col].is_barrier(): # DOWN
self.neighbors.append(grid[self.row + 1][self.col])
if self.row > 0 and not grid[self.row - 1][self.col].is_barrier(): # UP
self.neighbors.append(grid[self.row - 1][self.col])
if self.col < self.total_rows - 1 and not grid[self.row][self.col + 1].is_barrier(): # RIGHT
self.neighbors.append(grid[self.row][self.col + 1])
if self.col > 0 and not grid[self.row][self.col - 1].is_barrier(): # LEFT
self.neighbors.append(grid[self.row][self.col - 1])
def __lt__(self, other):
return False
def generate_num(x, y):
return randint(x, y)
def h(p1, p2):
x1, y1 = p1
x2, y2 = p2
return abs(x1 - x2) + abs(y1 - y2)
def reconstruct_path(came_from, current, draw):
while current in came_from:
current = came_from[current]
current.make_path()
draw()
def algorithm(draw, grid, start, end):
count = 0
open_set = PriorityQueue()
open_set.put((0, count, start))
came_from = {}
g_score = {spot: float("inf") for row in grid for spot in row}
g_score[start] = 0
f_score = {spot: float("inf") for row in grid for spot in row}
f_score[start] = h(start.get_pos(), end.get_pos())
open_set_hash = {start}
while not open_set.empty():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
current = open_set.get()[2]
open_set_hash.remove(current)
if current == end:
reconstruct_path(came_from, end, draw)
end.make_end()
return True
for neighbor in current.neighbors:
temp_g_score = g_score[current] + 1
if temp_g_score < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = temp_g_score
f_score[neighbor] = temp_g_score + h(neighbor.get_pos(), end.get_pos())
if neighbor not in open_set_hash:
count += 1
open_set.put((f_score[neighbor], count, neighbor))
open_set_hash.add(neighbor)
neighbor.make_open()
draw()
if current != start:
current.make_closed()
return False
def make_grid(rows, width):
grid = []
gap = width // rows
for i in range(rows):
grid.append([])
for j in range(rows):
spot = Spot(i, j, gap, rows)
grid[i].append(spot)
return grid
def draw_grid(win, rows, width):
gap = width // rows
for i in range(rows):
pygame.draw.line(win, GREY, (0, i * gap), (width, i * gap))
for j in range(rows):
pygame.draw.line(win, GREY, (j * gap, 0), (j * gap, width))
def draw(win, grid, rows, width):
win.fill(WHITE)
for row in grid:
for spot in row:
spot.draw(win)
draw_grid(win, rows, width)
pygame.display.update()
def get_clicked_pos(pos, rows, width):
gap = width // rows
y, x = pos
row = y // gap
col = x // gap
return row, col
def main(win, width):
rows = 50
grid = make_grid(rows, width)
start = None
end = None
t = Timing()
run = True
setup_config = True
while run:
draw(win, grid, rows, width)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
setup_config = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
run = False
setup_config = False
while setup_config:
for x in range(0, 800):
row_pos = generate_num(0, rows - 1)
col_pos = generate_num(0, rows - 1)
spot = grid[row_pos][col_pos]
if not start and spot != end:
start = spot
start.make_start()
elif not end and spot != start:
end = spot
end.make_end()
elif spot != end and spot != start:
spot.make_barrier()
t.start()
for row in grid:
for spot in row:
spot.update_neighbors(grid)
algorithm(lambda: draw(win, grid, rows, width), grid, start, end)
global full_time
full_time = t.stop()
setup_config = False
run = False
pygame.QUIT
main(WIN, WIDTH)
and timing.py my timer class
import time
class TimerError(Exception):
"""A custom exception used to report errors in use of Timer class"""
class Timing:
def __init__(self):
self._start_time = None
def start(self):
"""Start a new timer"""
if self._start_time is not None:
raise TimerError(f"Timer is running. Use .stop() to stop it")
self._start_time = time.perf_counter()
def stop(self):
"""Stop the timer, and report the elapsed time"""
if self._start_time is None:
raise TimerError(f"Timer is not running. Use .start() to start it")
elapsed_time = time.perf_counter() - self._start_time
self._start_time = None
print(f"Elapsed time: {elapsed_time:0.4f} seconds")
return elapsed_time
EDIT:
the misterious 5th print is coming from this line, of course
full_time = t.stop()
With 4 iteractions of your main loop, you reach at this line 5 times. That's the reason you have 5 prints.
You are very right on your comment, when you import astar with the command
import astar
on your main.py file, you are executing the main(WIN, WIDTH) that you have at the end of astar.py. Thus resulting in 5 prints.
>>> for i in range(0, 4):
... print(i)
...
0
1
2
3
>>>
Your main for loop iterates 4 times, each time inserting one element to your empty list. By the way, I would to it like this rather than the cumbersome insert.
list.append(astar.full_time) # append inserts at the end of the list
I am surprised that you have 5 of the output of the format
print(f"Elapsed time: {elapsed_time:0.4f} seconds")
By the way, I am also wondering why do you have these statements:
run = True
while run:
run = False
and a similar statement with a boolean named setup_config and a while. You could get yourself a cleaner code by suppressing this while, since it only executes once. Please, correct me if I am wrong or I am missing something from your context.
Notice that you can also remove the ifs related to the pass commands.
def main(win, width):
rows = 50
grid = make_grid(rows, width)
start = None
end = None
t = Timing()
draw(win, grid, rows, width)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pass
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pass
for x in range(0, 800):
row_pos = generate_num(0, rows - 1)
col_pos = generate_num(0, rows - 1)
spot = grid[row_pos][col_pos]
if not start and spot != end:
start = spot
start.make_start()
elif not end and spot != start:
end = spot
end.make_end()
elif spot != end and spot != start:
spot.make_barrier()
t.start()
for row in grid:
for spot in row:
spot.update_neighbors(grid)
algorithm(lambda: draw(win, grid, rows, width), grid, start, end)
global full_time
full_time = t.stop()
pygame.QUIT
The extra print is mostly like coming from a surprising extra iteraction of this for loop
for event in pygame.event.get():
Check the value of event, by printing it and check if there's something unusual there.
My issue is with my def countNeighbours(self, grid) function. In the grid, every cell is surround by four walls making each of them rectangles. I decided to use the wrap around method where the an edge cell'
s neighbor is on the other side. This way none of the indices will be out of bounds. However now I've run into the problem where the maze pathing goes straight down and breaks. I can't comprehend why exactly.
Screenshot of the bug.
The goal was for the maze pathing to randomly visited a couple cells and destroy the walls before stopping. Then I'd move on the the backtracking step.
Here is the entire code:
import pygame
import random
pygame.init()
screenWidth = 604
screenHeight = 604
FPS = 60
# Colors
Red = (255, 0, 0)
Blue = (0, 100, 200, 50)
Green = (0, 200, 100, 50)
White = (255, 255, 255)
Black = (0, 0, 0)
# TODO: FIX THE COUNTING FUNCTION- MESSES UP THE PATHING
class Cell:
def __init__(self, i, j):
self.i = i # Row
self.j = j # Column
self.visited = False
# Checks to see if wall(s) of the cell exists
# Order: Top, Right, Bottom, Left
self.wall = [True, True, True, True]
def draw(self, surface):
x = self.i * length
y = self.j * length
# Our Path in Green
if self.visited:
pygame.draw.rect(displayWindow, Green, (x, y, length, length))
# Top Line Start Pos, End Pos
if self.wall[0]:
pygame.draw.line(displayWindow, White, (x, y), (x + length, y), 1)
# Right Line
if self.wall[1]:
pygame.draw.line(displayWindow, White, (x + length, y), (x + length, y + length), 1)
# Bottom
if self.wall[2]:
pygame.draw.line(displayWindow, White, (x + length, y + length), (x, y + length), 1)
# Left
if self.wall[3]:
pygame.draw.line(displayWindow, White, (x, y + length), (x, y), 1)
self.changed = False
# Function adds the unvisited to the cell's neighbor array and returns the next cell
def countNeighbors(self, grid):
global x, y
self.neighbors = []
top = grid[(((self.i + 1) + x) % x)][self.j]
right = grid[self.i][(((self.j + 1) + y) % y)]
bottom = grid[(((self.i - 1) + x) % x)][self.j]
left = grid[self.i][(((self.j - 1) + y) % y)]
# If the neighbor cell is unvisited add to array
if not top.visited:
self.neighbors.append(top)
if not right.visited:
self.neighbors.append(right)
if not bottom.visited:
self.neighbors.append(bottom)
if not left.visited:
self.neighbors.append(left)
# pick random unvisted cell as our next
if len(self.neighbors) > 0:
p = random.randrange(0, len(self.neighbors))
return self.neighbors[p]
# Highlights the current cell
def marker(self):
x = self.i * length
y = self.j * length
pygame.draw.rect(displayWindow, Blue, (x, y, length, length))
# Rows and Columns, length will be our width of each cell
length = 40
rows = screenWidth // length
cols = screenHeight // length
print(rows, cols)
# store cells
grid = [[]*cols]*rows
for i in range(rows):
for j in range(cols):
cell = Cell(i, j)
grid[i].append(cell)
current = grid[0][0]
x = len(grid)
y = len(grid[i])
def display(surface):
global current
for i in range(len(grid)):
for j in range(len(grid[i])):
grid[i][j].draw(surface)
# Mark first cell as visited
current.visited = True
current.marker()
# Check neighbors of current cell
nextCell = current.countNeighbors(grid)
if nextCell:
nextCell.visited = True
deleteWall(current, nextCell)
current = nextCell
def deleteWall(a, b):
# Right and Left Walls
p = a.i - b.i
if p == 1: # neighbor to the left
a.wall[3] = False
b.wall[1] = False
elif p == -1: # neighbor to the right
a.wall[1] = False
b.wall[3] = False
# Top and Bottom Walls
q = a.j - b.j
if q == 1: # neighbor above
a.wall[0] = False
b.wall[2] = False
elif q == -1: # neighbor below
a.wall[2] = False
b.wall[0] = False
displayWindow = pygame.display.set_mode((screenWidth, screenHeight))
pygame.display.set_caption("Maze Generator")
def main():
global FPS
FPSclock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
FPSclock.tick(FPS)
display(displayWindow)
pygame.display.update()
if __name__ == '__main__':
main()
Found the bug:
grid = [[]*cols]*rows
And the correct functions were:
def countNeighbors(self, grid):
global x, y
self.neighbors = []
# Edge Cases
# Top Cell
if self.j == 0:
self.neighbors.append(None)
else:
self.neighbors.append(grid[self.i][self.j - 1])
# Right
if self.i == x - 1:
self.neighbors.append(None)
else:
self.neighbors.append(grid[self.i + 1][self.j])
# Bottom
if self.j == y - 1:
self.neighbors.append(None)
else:
self.neighbors.append(grid[self.i][self.j + 1])
# Left
if self.i == 0:
self.neighbors.append(None)
else:
self.neighbors.append(grid[self.i - 1][self.j])
grid = []
for i in range(rows):
column = []
for j in range(cols):
cell = Cell(i, j)
column.append(cell)
grid.append(column)
def display(surface):
global current
for i in range(len(grid)):
for j in range(len(grid[i])):
grid[i][j].draw(surface)
grid[i][j].countNeighbors(grid)
current.visited = True
current.marker()
next = random.choice(current.neighbors)
if next and not next.visited:
next.visited = True
deleteWall(current, next)
current = next
I've decided to create a simpler version of the Game of Life since I couldn't really work with my old one. I now have a GameOfLife class, but it is running very slow. When I click on a cell, it takes some time before it activates/deactivates, etc. Does anyone know why it's slow? Is there something wrong with the FPSCLOCK, or am I running something that shouldn't be running?
import pygame
# Defining the grid dimensions.
GRID_SIZE = width, height = 500, 500
# Defining the size of the cells, and how many cells there are in the x and y direction.
CELL_SIZE = 10
X_CELLS = int(width/CELL_SIZE)
Y_CELLS = int(height/CELL_SIZE)
# Defining a color for dead cells (background) and alive cells.
COLOR_DEAD = 0 #background
COLOR_ALIVE = 1 #alive_cell
colors = []
colors.append(( 0, 0, 0)) #Black
colors.append((0, 128, 128)) #blue
# Two lists, one for the current generation, and one for the next generation, so you can have iterations.
current_generation = [[COLOR_DEAD for y in range(Y_CELLS)] for x in range(X_CELLS)]
next_generation = [[COLOR_DEAD for y in range(Y_CELLS)] for x in range(X_CELLS)]
fps_max = 10
class GameOfLife:
def __init__(self):
self.next_iteration = False
self.game_over = False
# Starting pygame
pygame.init()
pygame.display.set_caption("Game of Life - Created by") # Gives a title to the window
self.screen = pygame.display.set_mode(GRID_SIZE) # Create the window with the GRID_SIZE.
#Clock to set the FPS
self.FPSCLOCK = pygame.time.Clock()
# Initialise the generations
self.init_gen(current_generation, COLOR_DEAD)
# Initializing all the cells.
def init_gen(self, generation, c):
for y in range(Y_CELLS):
for x in range(X_CELLS):
generation[x][y] = c
# Drawing the cells, color black or blue at location x/y.
def draw_cell(self, x, y, c):
pos = (int(x * CELL_SIZE + CELL_SIZE / 2),
int(y * CELL_SIZE + CELL_SIZE / 2))
# pygame.draw.rect(screen, colors[c], pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE-1, CELL_SIZE-1))
# pygame.draw.circle(screen, colors[c], pos, CELL_SIZE, CELL_SIZE) #Weird form, can also be used instead of rectangles
pygame.draw.circle(self.screen, colors[c], pos, 5,0) # Use the last two arguments (radius, width) to change the look of the circles.
pygame.display.flip()
# Updating the cells.
def update_gen(self):
global current_generation
for y in range(Y_CELLS):
for x in range(X_CELLS):
c = next_generation[x][y]
self.draw_cell(x, y, c)
# Update current_generation
current_generation = list(next_generation)
# Activate a living cell
def activate_living_cell(self, x, y):
global next_generation
next_generation[x][y] = COLOR_ALIVE
# Deactivate a living cell
def deactivate_living_cell(self, x, y):
global next_generation
next_generation[x][y] = COLOR_DEAD
# Function to check neighbor cell
def check_cells(self, x, y):
# Ignoring cells off the edge
if (x < 0) or (y < 0): return 0
if (x >= X_CELLS) or (y >= Y_CELLS): return 0
if current_generation[x][y] == COLOR_ALIVE:
return 1
else:
return 0
def check_cell_neighbors(self, row_index, col_index):
# Get the number of alive cells surrounding the current cell
num_alive_neighbors = 0
num_alive_neighbors += self.check_cells(row_index - 1, col_index - 1)
num_alive_neighbors += self.check_cells(row_index - 1, col_index)
num_alive_neighbors += self.check_cells(row_index - 1, col_index + 1)
num_alive_neighbors += self.check_cells(row_index, col_index - 1)
num_alive_neighbors += self.check_cells(row_index, col_index + 1)
num_alive_neighbors += self.check_cells(row_index + 1, col_index - 1)
num_alive_neighbors += self.check_cells(row_index + 1, col_index)
num_alive_neighbors += self.check_cells(row_index + 1, col_index + 1)
return num_alive_neighbors
# Rules
# 1 Any live cell with fewer than two live neighbors dies, as if by underpopulation.
# 2 Any live cell with two or three live neighbors lives on to the next generation.
# 3 Any live cell with more than three live neighbors dies, as if by overpopulation.
# 4 Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
def create_next_gen(self):
for y in range(Y_CELLS):
for x in range(X_CELLS):
# If cell is live, count neighboring live cells
n = self.check_cell_neighbors(x, y) # number of neighbors
c = current_generation[x][y] # current cell (either dead or alive).
if c == COLOR_ALIVE: # If the cell is living:
if (n < 2): # Rule number 1, underpopulation
next_generation[x][y] = COLOR_DEAD
elif (n > 3): # Rule number 3, overpopulation
next_generation[x][y] = COLOR_DEAD
else: # Rule number 3, 2 or 3 neighbors, staying alive.
next_generation[x][y] = COLOR_ALIVE
else: # if the cell is dead:
if (n == 3):
# Rule number 4: A dead cell with three living neighbors becomes alive.
next_generation[x][y] = COLOR_ALIVE
# Runs the game loop
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.game_over = True # If pressing the quit button, it closes the window.
if event.type == pygame.MOUSEBUTTONDOWN: # if pressing the mouse button, it gets the position. If the cell is dead, make it alive, if the cell is alive, make it dead.
posn = pygame.mouse.get_pos()
x = int(posn[0] / CELL_SIZE)
y = int(posn[1] / CELL_SIZE)
if next_generation[x][y] == COLOR_DEAD:
self.activate_living_cell(x, y)
else:
self.deactivate_living_cell(x, y)
# Check for q, g, s or w keys
if event.type == pygame.KEYDOWN: # keydown --> quits when the button goes down. keyup --> quits when the button goes up again.
if event.unicode == 'q': # Press q to quit.
self.game_over = True
print("q")
elif event.key == pygame.K_SPACE: # Space for the next iteration manually.
self.create_next_gen()
print("keypress")
elif event.unicode == 'a': # a to automate the iterations.
self.next_iteration = True
print("a")
elif event.unicode == 's': # s to stop the automated iterations.
self.next_iteration = False
print("s")
elif event.unicode == 'r': # r to reset the grid.
self.next_iteration = False
self.init_gen(next_generation, COLOR_DEAD)
print("r")
def run(self):
while not self.game_over:
# Set the frames per second.
self.handle_events()
if self.next_iteration: # if next iteration is true, the next gen is created according to the rules.
self.create_next_gen()
# Updating
self.update_gen()
self.FPSCLOCK.tick(fps_max)
if __name__ == "__main__":
game = GameOfLife()
game.run()
The pygame.display.flip() call redraws the whole screen - and you are calling it once for each redrawn cell.
Move that call from the draw_cell method above to the end of the method gen_update() and you should be fine. Better yet, just remove it from any methods, and place it at the end of the main loop, just before calling the clock tick:
def run(self):
while not self.game_over:
# Set the frames per second.
self.handle_events()
if self.next_iteration: # if next iteration is true, the next gen is created according to the rules.
self.create_next_gen()
# Updating
self.update_gen()
pygame.display.flip()
self.FPSCLOCK.tick(fps_max)
As per topic, below is my code:
import pygame #provides Pygame framework.
import sys #provides sys.exit function
from pygame import *
from pygame.locals import * # Import constants used by PyGame
# game classes
class OAndX:
def __init__(self):
# Initialize Pygame
pygame.init()
# Create the clock to manage the game loop
self.clock = time.Clock()
display.set_caption("Noughts and Crosses")
# Create a windows with a resolution of 640 x 480
self.displaySize=(640,480)
self.screen=display.set_mode(self.displaySize)
# will either be 0 or X
self.player="0"
# The background class
class Background:
def __init__(self,displaySize):
self.image=Surface(displaySize)
# Draw a title
# Create the font we'll use
self.font=font.Font(None,(displaySize[0]/12))
self.text = self.font.render("Noughts and crosses",True,(Color("White")))
# Work out where to place the text
self.textRect = self.text.get_rect()
self.textRect.centerx=displaySize[0] / 2
# Add a little margin
self.textRect.top = displaySize[1] * 0.02
# Blit the text to the background image
self.image.blit(self.text, self.textRect)
def draw(self, display):
display.blit(self.image, (0, 0))
# A class for an individual grid square
class GridSquare(sprite.Sprite):
def __init__(self, position, gridSize):
# Initialise the sprite base class
super(GridSquare, self).__init__()
# We want to know which row and column we are in
self.position = position
# State can be “X”, “O” or “”
self.state = ""
self.permanentState = False
self.newState = ""
# Work out the position and size of the square
width = gridSize[0] / 3
height = gridSize[1] / 3
# Get the x and y co ordinate of the top left corner
x = (position[0] * width) - width
y = (position[1] * height) - height
# Create the image, the rect and then position the rect
self.image = Surface((width,height))
self.image.fill(Color("white"))
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
# The rect we have is white, which is the parent rect
# We will draw another rect in the middle so that we have
# a white border but a blue center
self.childImage = Surface(((self.rect.w * 0.9), (self.rect.h * 0.9)))
self.childImage.fill(Color("blue"))
self.childRect = self.childImage.get_rect()
self.childRect.center = ((width /2), (height / 2))
self.image.blit(self.childImage,self.childRect)
# Create the font we’ll use to display O and X
self.font = font.Font(None, (self.childRect.w))
class Grid:
def __init__(self, displaySize):
self.image=Surface(displaySize)
# Make a number of grid squares
gridSize = (displaySize[0] * 0.75,displaySize[1] * 0.75)
# Work out the co-ordinate of the top left corner of the grid so that it can be centered on the screen
self.position = ((displaySize[0] /2) - (gridSize[0] / 2),(displaySize[1] / 2) - (gridSize[1] / 2))
# An empty array to hold our grid squares in
self.squares = []
for row in range(1,4):
# Loop to make 3 rows
for column in range(1,4):
# Loop to make 3 columns
squarePosition = (column,row)
self.squares.append(GridSquare(squarePosition, gridSize))
# Get the squares in a sprite group
self.sprites = sprite.Group()
for square in self.squares:
self.sprites.add(square)
def draw(self, display):
self.sprites.update()
self.sprites.draw(self.image)
display.blit(self.image, self.position)
def update(self):
# Need to update if we need to set a new state
if (self.state != self.newState):
# Refill the childImage blue
self.childImage.fill(Color("blue"))
text = self.font.render(self.newState, True, (Color("white")))
textRect = text.get_rect()
textRect.center = ((self.childRect.w / 2),(self.childRect.h / 2))
# We need to blit twice because the new child image needs to be blitted to the parent image
self.childImage.blit(text,textRect)
self.image.blit(self.childImage, self.childRect)
# Reset the newState variable
self.state = self.newState
self.newState = ""
def setState(self, newState,permanent=False):
if not self.permanentState:
self.newState = newState
if permanent:
self.permanentState = True
def reset(self):
# Create an instance of our background and grid class
self.background =Background(self.displaySize)
self.grid = Grid(self.displaySize)
def getSquareState(self, column, row):
# Get the square with the requested position
for square in self.squares:
if square.position == (column,row):
return square.state
def full(self):
# Finds out if the grid is full
count = 0
for square in self.squares:
if square.permanentState ==True:
count += 1
if count == 9:
return True
else:
return False
def getWinner(self):
players = ["X", "O"]
for player in players:
# check horizontal spaces
for column in range (1, 4):
for row in range (1, 4):
square1 = self.grid.getSquareState(column, row)
square2 = self.grid.getSquareState((column + 1),row)
square3 = self.grid.getSquareState((column + 2), row)
# Get the player of the square (either O or X)
if (square1 == player) and (square2 == player) and (square3 == player):
return player
# check vertical spaces
for column in range (1, 4):
for row in range (1, 4):
square1 = self.grid.getSquareState(column, row)
square2 = self.grid.getSquareState(column, (row + 1))
square3 = self.grid.getSquareState(column, (row + 2))
# Get the player of the square (either O or X)
if (square1 == player) and (square2 == player) and (square3 == player):
return player
# check forwards diagonal spaces
for column in range (1, 4):
for row in range (1, 4):
square1 = self.grid.getSquareState(column, row)
square2 = self.grid.getSquareState((column + 1), (row - 1))
square3 = self.grid.getSquareState((column + 2), (row - 2))
# Get the player of the square (either O or X)
if (square1 == player) and (square2 == player) and (square3 == player):
return player
# check backwards diagonal spaces
for column in range (1, 4):
for row in range (1, 4):
square1 = self.grid.getSquareState(column, row)
square2 = self.grid.getSquareState((column + 1), (row + 1))
square3 = self.grid.getSquareState((column + 2), (row + 2))
# Get the player of the square (either O or X)
if (square1 == player) and (square2 == player) and (square3 == player):
return player
# Check if grid is full if someone hasn’t won already
if self.grid.full():
return "draw"
def winMessage(self, winner):
# Display message then reset the game to its initial state
# Blank out the screen
self.screen.fill(Color("Black"))
# Create the font we’ll use
textFont = font.Font(None, (self.displaySize[0] / 6))
textString = ""
if winner == "draw":
textString = "It was a draw!"
else:
textString = winner + " Wins!"
# Create the text surface
text = textFont.render(textString,True,(Color("white")))
textRect = text.get_rect()
textRect.centerx = self.displaySize[0] / 2
textRect.centery = self.displaySize[1] / 2
# Blit changes and update the display before we sleep
self.screen.blit(text, textRect)
display.update()
# time.wait comes from pygame libs
time.wait(2000)
# Set game to its initial state
self.reset()
def run(self):
while True:
# Our Game loop,Handle events
self.handleEvents()
# Draw our background and grid
self.background.draw(self. screen)
self.grid.draw(self.screen)
# Update our display
display.update()
# Limit the game to 10 fps
self.clock.tick(10)
def handleEvents(self):
# We need to know if the mouse has been clicked later on
mouseClicked = False
# Handle events, starting with quit
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == MOUSEBUTTONUP:
mouseClicked = True
# Get the co ordinate of the mouse mousex, mousey = mouse.get_pos() ,These are relative to the top left of the screen,and we need to make them relative to the top left of the grid
mousex -= self.grid.position[0]
mousey -= self.grid.position[1]
# Find which rect the mouse is in
for square in self.grid.squares:
if square.rect.collidepoint((mousex, mousey)):
if mouseClicked:
square.setState(self.player, True)
# Change to next player
if self.player == "O":
self.player = "X"
else:
self.player = "O"
# Check for a winner
winner = self.getWinner()
if winner:
self.winMessage(winner)
else:
square.setState(self.player)
else:
# Set it to blank, only if
permanentState == False
square.setState("")
if __name__ == "__main__":
game = OAndX()
game.run()
I'm new to python language but I need to understand why caused to error "AttributeError: 'OAndX' object has no attribute 'run'" when I using python3 interpreter to run it.Appreciate thankful to anyone could help.
You should actually place your run method under you class OAndX method as follows:
class OAndX:
def __init__(self):
# Initialize Pygame
pygame.init()
# Create the clock to manage the game loop
self.clock = time.Clock()
display.set_caption("Noughts and Crosses")
# Create a windows with a resolution of 640 x 480
self.displaySize=(640,480)
self.screen=display.set_mode(self.displaySize)
# will either be 0 or X
self.player="0"
def run(self):
while True:
# Our Game loop,Handle events
self.handleEvents()
# Draw our background and grid
self.background.draw(self. screen)
self.grid.draw(self.screen)
# Update our display
display.update()
# Limit the game to 10 fps
self.clock.tick(10)