Pygame game of life running slow - python
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)
Related
pygame developing a tic-tac-toe game application using minimax algorythm error
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()
Missing item from Python List when counting execution time for A* Pathfinding algorithm
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.
python need help seeing where my set is being changed during iteration
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.
AttributeError: 'OAndX' object has no attribute 'run'
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)
Scrolling in 2D game?
I'm trying to add a scrolling "camera" that follows the player when it moves but can't figure out how to do this. I know that you can just move the level in the opposite direction when you press one of the movement keys but I'd rather not do that as I plan on adding enemies later on and don't want have to keep update their coordinates as the player moves. I've added my code with a sample level below. Code: import pygame, sys, time, random, math from pygame.locals import * BACKGROUNDCOLOR = (255, 255, 255) WINDOWW = 800 WINDOWH = 600 PLAYERW = 66 PLAYERH = 22 FPS = 60 MOVESPEED = 3 YACCEL = 0.13 GRAVITY = 2 BLOCKSIZE = 30 pygame.init() screen = pygame.display.set_mode((WINDOWW, WINDOWH), 0, 32) mainClock = pygame.time.Clock() testLevel = [ (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,), (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,)] def createblock(length, height, color): tmpblock = pygame.Surface((length, height)) tmpblock.fill(color) tmpblock.convert() return tmpblock def terminate(): # Used to shut down the software pygame.quit() sys.exit() def add_level(lvl, bSize): # Creates the level based on a map (lvl) and the size of blocks bList = [] # List of every block bListDisp = [] # List of every block to display bTypeList = [] # List with corresponding type of block(wall, air, etc.) for y in range(len(lvl)): for x in range(len(lvl[0])): if lvl[y][x] == 0: # If the block type on lvl[y][x] is '0', write "air" down in the type list bTypeList.append("air") elif lvl[y][x] == 1: # If the block type on lvl[y][x] is '1', write "wall" down in the type list bTypeList.append("solid") bList.append(pygame.Rect((bSize * x), (bSize * y), bSize, bSize)) #Append every block that is registered bListDisp.append(pygame.Rect((bSize * x), (bSize * y), bSize, bSize)) #Append every block to display that is registered return bList, bListDisp, bTypeList player = pygame.Rect((WINDOWW/2), (WINDOWH - BLOCKSIZE*3), PLAYERW, PLAYERH) wallblock = createblock(BLOCKSIZE, BLOCKSIZE,(20,0,50)) lastTime = pygame.time.get_ticks() isGrounded = False vx = 0 vy = 0 allLevels = [testLevel] # A list containing all lvls(only one for now) maxLevel = len(allLevels) # Checks which level is the last currLevel = allLevels[0] # Current level(start with the first lvl) blockList, blockListDisp, blockTypeList = add_level(currLevel, BLOCKSIZE) # A list with every block and another list with the blocks types thrusters = True jumping = False falling = True while True: """COLLISION""" collision = False for i in range(len(blockTypeList)): if blockTypeList[i] == "solid": if player.colliderect(blockList[i]): collision = True if vx > 0 and not falling: player.right = blockListDisp[i].left vx = 0 print('Collide Right') if vx < 0 and not falling: player.left = blockListDisp[i].right vx = 0 print('Collide Left') if vy > 0: player.bottom = blockListDisp[i].top isGrounded = True falling = False vy = 0 print('Collide Bottom') if vy < 0: player.top = blockListDisp[i].bottom vy = 0 print('Collide Top') else: player.bottom += 1 if player.colliderect(blockList[i]): collision = True #isGrounded = True #falling = False player.bottom -= 1 if not collision: falling = True isGrounded = False # Input pressedKeys = pygame.key.get_pressed() # Checks which keys are being pressed timeDiff = pygame.time.get_ticks() - lastTime # Calculates time difference lastTime += timeDiff # Last time checked reset to current time # Shut-down if the ESC-key is pressed or the window is "crossed down" for event in pygame.event.get(): if event.type == QUIT or event.type == KEYDOWN and event.key == K_ESCAPE: terminate() """X-axis control""" if pressedKeys[ord('a')]: vx = -MOVESPEED if pressedKeys[ord('d')]: vx = MOVESPEED if not pressedKeys[ord('d')] and not pressedKeys[ord('a')]: vx = 0 """Y-axis control""" # Controls for jumping if pressedKeys[ord('w')] and thrusters == True: vy -= YACCEL * timeDiff; # Accelerate along the y-xis when "jumping", but not above/below max speed if vy <= -4: vy = -4 isGrounded = False # You are airborne jumping = True # You are jumping if event.type == KEYUP: # If you let go of the "jump"-button, stop jumping if event.key == ord('w') and vy < 0 and not isGrounded: jumping = False falling = True player.x += vx player.y += vy # Gravity if not isGrounded or falling: vy += 0.3 if vy > 80: vy = 80 screen.fill(BACKGROUNDCOLOR) for i in range(len(blockTypeList)): if blockTypeList[i] == "solid": screen.blit(wallblock, (blockListDisp[i].x, blockListDisp[i].y)) #blit the wall-block graphics pygame.draw.rect(screen, (0, 0, 0), player) pygame.display.update() mainClock.tick(FPS)
The trick is to keep track of camera coordinates and use these as an offset in your rendering code. It looks like you're doing you're rendering right at the end of the code you've posted, drawing each block with coord x,y to pixel x,y on the screen. As you say, shifting the level around isn't great. Instead, have your key inputs (or other camera moving device) change cameraX and cameraY variables, and then add (or subtract, depending which direction you want to go) these values from the block x and y values to change which pixels map to which blocks. I.e. change your rendering to: screen.blit(wallblock, (blockListDisp[i].x + cameraX, blockListDisp[i].y + cameraY)) This means if your camera moves to (10, 20) then you map your block at (5, 5) to (15, 25) on the screen, shifting your whole level across while your underlying model of the level stays the same. Make sense? You can also take this slightly further; if your camera is only being moved to follow your character you can make swap cameraX and cameraY in the above for some function of the character position, and have the whole thing just managed directly there.