Why does open set never go to 0? - python

I am trying to create the A Star algorithm and apply to the game Snake. The problem I'm having is that I never get anything drawn on the window because it never reaches that code. It gets stuck when trying to calculate the path in the method run(). Specifically, open set never goes to zero and it adds already existing nodes.
I've tried printing the various lists to the console, but all i've discovered is that "current" repeats tiles at random points, and "openSet and "closedSet" continue to get larger with these repeated tiles. I've also tested my Tile object, my contains method, and my giveNeighbors method and all of these seem to be working and give the expected results. Also as a reference, I am going of the pseudo code found here: https://en.wikipedia.org/wiki/A*_search_algorithm
import Tile
import pygame
class A_star:
def __init__(self):
self.closedSet = []
self.openSet = []
def run(self, start, goal):
self.closedSet = []
self.openSet = [start]
path = []
#THE LENGTH OF OPEN SET NEVER GOES TO ZERO!
while len(self.openSet) > 0:
#Set current to node in openSet with lowest fscore
current = self.openSet[0]
currindex = 0
for i, node in enumerate(self.openSet):
if node.fScore < current.fScore:
current = node
currindex = i
#romove from Open set and add to closed
self.openSet.pop(currindex)
self.closedSet.append(current)
#Reached the end
if current.tileEquals(goal):
print("Done")
while current != None:
path.append(current)
current = current.cameFrom
return path[::-1]
neighbors = self.giveNeighbors(current)
print("Current: " + str(current.posx) + ", " + str(current.posy))
print("Neighbors")
for i in neighbors:
print(str(i.posx) + ", " + str(i.posy))
for i in neighbors:
#if neighbor is already checked, then ignore it.
if not self.contains(self.closedSet, i):
#Distance between start adn neighbor. tenative gscore
tempGScore = current.gScore + 1
#if neighbor is not in openset. Discovered a new node!
if not self.contains(self.openSet, i):
self.openSet.append(i)
elif tempGScore < i.gScore:
i.gScore = tempGScore
i.cameFrom = current
i.fScore = i.gScore + self.heuristicCost(i, goal) #f = g + h
print("Open:")
for i in self.openSet:
print(str(i.posx) + ", " + str(i.posy))
print("Closed")
for i in self.closedSet:
print(str(i.posx) + ", " + str(i.posy))
#Calculates the estimated distance from a given tile to end
def heuristicCost(self, neighbor, goal):
#The snake never goes diagonal, therefore calculate manhatten distance
distance = abs(neighbor.posx - goal.posx) + abs(neighbor.posy - goal.posy)
return distance
def giveNeighbors(self, current):
neighbors = []
if current.posx > 0:
neighbors.append(Tile(current.posx - 10, current.posy))
if current.posx < 200:
neighbors.append(Tile(current.posx + 10, current.posy))
if current.posy > 0:
neighbors.append(Tile(current.posx, current.posy - 10))
if current.posy < 200:
neighbors.append(Tile(current.posx, current.posy + 10))
return neighbors
def contains(self, s, tile):
for i in s:
if i.tileEquals(tile):
return True
else:
return False
def testAStar():
pygame.init()
window = pygame.display.set_mode((200, 200))
pygame.display.set_caption("AStar Test")
window.fill((255,255,255))
clock = pygame.time.Clock()
start = Tile(0, 0)
end = Tile(190, 190)
astar = A_star()
path = astar.run(start, end)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.draw.rect(window, (0, 255, 0), (start.posx, start.posy, 10, 10))
pygame.draw.rect(window, (255, 0, 0), (end.posx, end.posy, 10, 10))
for i in path:
pygame.draw.rect(window, (0, 0, 255), (i.posx, i.posy, 10, 10))
print("drew stuff")
pygame.display.update()
window.fill((255,255,255))
clock.tick(10)
pygame.quit()
if __name__== "__main__":
testAStar()
class Tile:
def __init__(self, x, y):
self.posx = x
self.posy = y
self.fScore = 0
self.gScore = 0
self.cameFrom = None
def nextTileRight(self):
self.posx = self.posx + 10
def nextTileDown(self):
self.posy = self.posy + 10
def nextTileUp(self):
self.posy = self.posy - 10
def nextTileLeft(self):
self.posx = self.posx - 10
def tileEquals(self, t):
if self.posx == t.posx and self.posy == t.posy:
return True
else:
return False
The expected result should be a non-diagonal path drawn on the window from the start node to the end node. (Also, sorry if my indentation is off. I am still very new to this website)

I'm not sure why you are checking openSets == 0 , you are looking for currentNode = destination or all the neighbours are closed meaning everything has been checked and no path was found.

Related

Why doesnt it show the path?

I am trying to show the path taken for this Dijkstra's algo, however, once the program ends, it should display the path taken and how many steps it has taken. But it only shows the end_pos and 1 step I have looked over the code multiple times but maybe im too tired to recognise the mistake or im just stupid.... Anyone have any idea?
# imports go here
class DijkstraPathfinder:
#staticmethod
def find_path():
def _get_start_and_end_pos():
start_pos, end_pos = None, None
for row in grid:
for square in row:
if square.state == SquareState.START:
start_pos = square.row, square.col
elif square.state == SquareState.END:
end_pos = square.row, square.col
return start_pos, end_pos
def _get_valid_neighbours(r: int, c: int):
"""
Find valid neighbours for a square:
valid means it's not a wall, has not been visited
:param r: row
:param c: column
:return: valid neighbours
:rtype: list()
"""
up_neighbour = (r, c + 1)
down_neighbour = (r, c - 1)
left_neighbour = (r - 1, c)
right_neighbour = (r + 1, c)
neighbours = [up_neighbour, down_neighbour, left_neighbour, right_neighbour]
v_neighbours = list()
for neighbour in neighbours:
r, c = neighbour
if r in range(settings.ROWS) and c in range(settings.ROWS):
if grid[r][c].state not in [SquareState.VISITED, SquareState.WALL, SquareState.START]:
v_neighbours.append(neighbour)
return v_neighbours
def _reconstruct_path():
path = [end_pos]
current = end_pos
while prev[current]:
path.append(prev[current])
current = prev[current]
# print(u)
path.reverse()
return path
WINDOW = pygame.display.set_mode((800, 800))
grid = Creator.get_grid()
start_pos, end_pos = _get_start_and_end_pos()
print()
print(f'Starting position: {start_pos}')
print(f'Ending position: {end_pos}')
print()
dist = {}
prev = {}
q = list()
finished = False
while not finished:
for row in grid:
for square in row:
pos = square.get_pos()
dist[square.get_pos()] = float("inf")
prev[square.get_pos()] = None
if square.state in [SquareState.EMPTY, SquareState.START, SquareState.END]:
q.append(pos)
dist[start_pos] = 0
while q:
u = min(q, key=dist.__getitem__)
q.remove(u)
for v in _get_valid_neighbours(*u):
alt = dist[u] + 1
if alt < dist[v]:
dist[v] = alt
prev[v] = u
vr, vc = v
grid[vr][vc].change_state(SquareState.VISITED)
if v == end_pos:
q = list()
break
picasso.draw(WINDOW, grid)
for event in pygame.event.get():
if event.type == pygame.QUIT:
finished = True
q = list()
break
path = _reconstruct_path()
for square in path:
row, col = square
grid[row][col].change_state(SquareState.PATH)
picasso.draw(WINDOW, grid)
for event in pygame.event.get():
if event.type == pygame.QUIT:
finished = True
break
print(f'Path found, took {len(path)} moves')
print(f'Moves: {path}')
print()
pygame.quit()
if __name__ == '__main__':
DijkstraPathfinder.find_path()
I Have tried to change this up, especially the last prints but it seems that it still does the same thing, where it only shows that "Path found, took 1 moves" and then below it shows "Moves: end_pos" where end_pos is wherever you had placed your final point.
In your implementation where you iterate over the neighbours:
for v in _get_valid_neighbours(*u)
You don't append anything to q, so the iteration stops there. As a result, in _reconstruct_path(), there is no prev node for the end point, and you get the path length as one.

Multithreading in python with matplotlib

Alright, I think it's finally time to call on every python user's best friend: Stack Overflow.
Bear in mind that I am at a bit of a beginner level in python, so obvious solutions and optimisations might not have occurred to me.
My Error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSWindow drag regions should only be invalidated on the Main Thread!'
abort() called
terminating with uncaught exception of type NSException
There is a stack overflow question on this error as well but under a different context but my attempts to fix the error using backend "Agg" with matplotlib didn't work. There were no longer any threading errors but matplotlib errors which didn't make any sense (as in they shouldn't have been there) appeared. This error was described in the link above in the apple developer support page and I couldn't implement those solutions either (prob cuz im a bad programmer).
Note: I'm using macOS, and this error only seems to happen on macOS with matplotlib.
Also the error shouldn't happen in my case because I'm trying to allow only the first thread to access the display function (which might be the part which is going wrong?)
I've been making this little evolution simulator (a little similar to this one) which I'm still on the starting stage of. Here is the code:
import random
import math
from matplotlib import pyplot as plt
import threading
class Element:
default_attr = {
"colour": "#000000",
"survival": 75,
"reproduction": 50,
"energy": 150,
"sensory_range": 100,
"genetic_deviation": 5,
"socialization": 20,
"position": (0, 0,),
"objective_attained": False,
"socialization_attained": False
}
__slots__ = (
"colour",
"survival",
"reproduction",
"energy",
"sensory_range",
"genetic_deviation",
"socialization",
"position",
"objective_attained",
"socialization_attained",
)
def __init__(self, **attributes):
Element.__slots__ = tuple((i + "s" for i in self.__slots__))
self.default_attr.update(attributes)
for key, value in self.default_attr.items():
setattr(self, key, value)
for key, value in self.default_attr.items():
try:
setattr(Element, key + "s", getattr(Element, key + "s") + [value])
except AttributeError:
setattr(Element, key + "s", [value])
def move(self, objective_colour, delay, height, width, energy=None):
if energy is None:
energy = self.energy
lock = threading.RLock()
event = threading.Event()
objective_positions = tuple((p for i, p in enumerate(Element.positions) if Element.colours[i] == objective_colour))
positions = tuple((p for i, p in enumerate(Element.positions) if Element.colours[i] == self.colour and p != self.position))
objectives_in_range = []
for objective in objective_positions:
if ((objective[0] - self.position[0])**2 + (objective[1] - self.position[1])**2)**0.5 <= self.sensory_range:
objectives_in_range.append([objective[0] - self.position[0], objective[1] - self.position[1]])
objectives = tuple(sorted(objectives_in_range, key=lambda x: (x[0]**2 + x[1]**2)**0.5))
positions_in_range = []
for pos in positions:
if ((pos[0] - self.position[0])**2 + (pos[1] - self.position[1])**2)**0.5 <= self.sensory_range:
positions_in_range.append([pos[0] - self.position[0], pos[1] - self.position[1]])
positions = tuple(sorted(positions_in_range, key=lambda x: (x[0]**2 + x[1]**2)**0.5))
if positions:
cluster = [0, 0]
for pos in positions:
cluster[0] += pos[0] + self.position[0]
cluster[1] += pos[1] + self.position[0]
midpoint = (cluster[0] / len(positions) - self.position[0], cluster[1] / len(positions) - self.position[1],)
try:
distance = 100 / (midpoint[0] ** 2 + midpoint[1] ** 2) ** 0.5 * (height if height > width else width) / 100
except ArithmeticError:
distance = 100
if self.socialization <= distance:
self.socialization_attained = True
if self.objective_attained is False and not objectives and self.socialization_attained is False and not positions and energy > self.energy*0.5:
direction = math.radians(random.uniform(0.0, 360.0))
old_position = self.position
self.position = (self.position[0] + math.sin(direction), self.position[1] + math.cos(direction),)
if 90 <= direction <= 270:
self.position = (self.position[0] * -1, self.position[1] * -1,)
for i, position in enumerate(Element.positions):
if position == old_position and Element.colours[i] == self.colour:
Element.positions[i] = self.position
break
with lock:
if not event.is_set():
display(delay, height, width)
event.set()
event.clear()
self.move(objective_colour, delay, height, width, energy - 1)
elif self.objective_attained is False and energy > 0 and objectives:
try:
x, y = math.sin(math.atan(objectives[0][0] / objectives[0][1])), math.cos(math.atan(objectives[0][0] / objectives[0][1]))
if objectives[0][1] < 0:
x *= -1
y *= -1
except ArithmeticError:
x, y = 1 if objectives[0][0] > 0 else -1, 0
old_position = self.position
self.position = tuple(map(lambda x, y: x + y, self.position, (x, y,)))
for i, position in enumerate(Element.positions):
if position == old_position and Element.colours[i] == self.colour:
Element.positions[i] = self.position
break
if (self.position[0] - old_position[0] - objectives[0][0])**2 + (self.position[1] - old_position[1] - objectives[0][1])**2 <= 1:
self.objective_attained = True
with lock:
for i, position in enumerate(Element.positions):
if [int(position[0]), int(position[1])] == [objectives[0][0] + old_position[0], objectives[0][1] + old_position[1]] and Element.colours[i] == objective_colour:
Element.positions.pop(i)
Element.colours.pop(i)
break
with lock:
if not event.is_set():
display(delay, height, width)
event.set()
# a little confusion here, do threads pause over here until all threads have exited the with lock statement or not? If not I need to change the line below.
event.clear()
if self.objective_attained is True:
self.move(objective_colour, delay, height, width, (energy - 1) * 1.5)
else:
self.move(objective_colour, delay, height, width, energy - 1)
elif self.socialization_attained is False and energy > 0 and positions and self.socialization > distance:
try:
x, y = math.sin(math.atan(midpoint[0] / midpoint[1])), math.cos(math.atan(midpoint[0] / midpoint[1]))
if midpoint[1] < 0:
x *= -1
y *= -1
except ArithmeticError:
x, y = 1 if midpoint[0] > 0 else -1, 0
old_position = self.position
self.position = tuple(map(lambda x, y: x + y, self.position, (x, y,)))
for i, position in enumerate(Element.positions):
if position == old_position and Element.colours[i] == self.colour:
Element.positions[i] = self.position
break
with lock:
if not event.is_set():
display(delay, height, width)
event.set()
event.clear()
self.move(objective_colour, delay, height, width, energy - 1)
else:
for thread in globals() ["threads"]:
thread.join()
# a little confusion here too on whether this would wait till all threads reach this statement before joining them
def display(delay, height, width):
x = tuple((i[0] for i in Element.positions)) + (0, width,)
y = tuple((i[1] for i in Element.positions)) + (0, height,)
c = tuple(Element.colours) + ("#FFFFFF",) * 2
plt.scatter(x, y, c=c)
plt.show()
plt.pause(delay)
plt.close()
r = lambda x: random.randint(0, x)
elements = tuple((Element(position=(r(200), r(200),)) for i in range(10))) + tuple((Element(position=(r(200), r(200),), colour="#FF0000") for i in range(10)))
[Element(colour="#00FF00", position=(r(200), r(200),), energy=0, reproduction=0) for i in range(20)]
globals() ["threads"] = []
for organism in elements:
globals() ["threads"].append(threading.Thread(target=organism.move, args=("#00FF00", 0.02, 200, 200,)))
globals() ["threads"][-1].start()
This is a big chunk of code but this is my first time using multithreading so I don't know where the error could pop up, though I have narrowed it down to this section fs.
Sry for the eyesore, ik this is a really long question, but I would be really grateful
if u could help!
This issue goes by a few names, the most common of which is "cross-threading". This occurs when you perform GUI operations (in your case, matplotlib calls) from non-GUI threads. This is a no-no regardless of OS.
To solve the problem, ensure that you're making matplotlib calls from the main thread. A good starting point is on line 176: UserWarning: Starting a Matplotlib GUI outside of the main thread will likely fail.

foobar failing test case - a* with breakable wall

Working on the google foo-bar challenges and I am stuck on one failing test case (which is a hidden one - so I can't directly see what the problem is)
basically the test is an implementation of a maze solver with a single breakable wall.
I'm doing a modified a* search - with a Boolean flag for paths that contain a broken wall, so that when it reaches the second wall (along a path with a broken wall) it skips it for that path.
I've gone over this code and I can't seem to see the error (if there even is one - at this point I'm almost convinced the test case is somehow wrong)
the parameters:
given a grid of height x,y filled with zero or 1, representing spaces and walls respectively: find the shortest path if you can break exactly 1 wall (if needed)
an example:
[
[0,1,0,0,0,1,0,0,0,0,0]
[0,0,0,1,0,0,1,0,1,1,0]
[1,1,1,1,1,0,1,0,1,0,1]
[0,0,0,0,0,0,0,0,1,1,0]
]
22 is the shortest path breaking 1 wall.
I want a pointer in the right direction: I feel like whatever I'm missing is trivial.
below is the code.
from math import sqrt, ceil
cardinal_moves=[(0,1), (0,-1), (1,0), (-1,0)]
class Node:
def __init__(self, pos ,parent =None):
self.parent = parent
self.pos = pos
self.g = 0
self.h = 0
self.f = 0
self.wall_broken = False
def __eq__(self, other):
return ((self.pos == other.pos) and (self.wall_broken == other.wall_broken))
def update_heuristic(self,parent,end):
self.g = parent.g + 1
self.h = ceil(sqrt((end.pos[0] - self.pos[0])**2 + (end.pos[1] - self.pos[1])**2))
self.f = self.g + self.h
def not_inside_maze(pos,maze):
return pos[0] < 0 or pos[0] >= len(maze) or pos[1] < 0 or pos[1] >= len(maze[0])
def astar_with_1_breakable_wall(maze):
start_pos = (0,0)
end_pos = (len(maze)-1, len(maze[0])-1)
start = Node(start_pos)
end = Node(end_pos)
not_visted = []
visited = []
not_visted.append(start)
while not_visted:
current_node = not_visted[0]
for node in not_visted:
if current_node.f > node.f:
current_node = node
not_visted.remove(current_node)
visited.append(current_node)
if current_node.pos == end.pos:
temp = current_node
path = []
while temp:
path.append(temp.pos)
temp = temp.parent
return path[::-1]
children = []
for position in cardinal_moves:
new_pos = (current_node.pos[0] + position[0], current_node.pos[1] + position[1])
if not_inside_maze(new_pos,maze):
continue
if maze[new_pos[0]][new_pos[1]] == 1:
if current_node.wall_broken:
continue
else:
check = Node(new_pos,current_node)
check.wall_broken = True
already_visited = False
for node in visited:
if check == node:
already_visited = True
break
if not already_visited:
children.append(check)
continue
already_visited = False
check = Node(new_pos,current_node)
check.wall_broken = current_node.wall_broken
for node in visited:
if check == node:
already_visited = True
break
if already_visited:
continue
children.append(check)
for child in children:
child.update_heuristic(current_node,end)
for open_node in not_visted:
if open_node == child:
if open_node.g > child.g:
idx = not_visted.index(open_node)
not_visted[idx] = child
continue
else:
continue
not_visted.append(child)
def shortest_path(maze):
if (astar_with_1_breakable_wall(maze)):
return len(astar_with_1_breakable_wall(maze))
else:
return -1
but every check I make on my machine says this is correct:
#imports added
import sys
#then added this below the maze solver
test = [
[0,1,0,0,0,1,0,0,0,0,0],
[0,0,0,1,0,0,1,0,1,1,0],
[1,1,1,1,1,0,1,0,1,0,1],
[0,0,0,0,0,0,0,0,1,1,0],
]
test2=[
[0,1,0,0,0,0,1,0,0,0,0],
[0,1,0,1,1,0,1,0,1,0,0],
[0,0,0,1,0,0,1,0,1,0,0],
[1,1,1,1,0,1,1,0,1,0,1],
[0,0,0,0,0,0,0,0,1,0,1],
]
print("first test")
print(shortest_path(test))
print("second test")
print(shortest_path(test))
#both of these tests give the correct result
def generate_matrix(h,w,n):
sequence = "{0:b}".format(n).zfill(h*w)
maze =[]
if(sequence[0]=="1" or sequence[len(sequence)-1]=="1"):
return -1
i=0
for y in range(h):
maze.append([])
for x in range(w):
maze[y].append(int(sequence[i]))
i=i+1
return(maze)
for i in range(20):
for j in range(20):
if(i < 6 or j <6):
continue
shortest=j+i
for k in range(2**(i*j)):
if not k%2==0:
continue
if k> 2**(i*j-1):
continue
maze = generate_matrix(i,j,k)
if(not maze == -1):
path = astar_with_1_breakable_wall(maze)
res = shortest_path(maze)
if(res > shortest):
print("\n",res, "shortest was ", shortest)
for index,line in enumerate(maze):
for indx,cell in enumerate(line):
if ((index,indx) in path):
print("\033[94m"+str(cell),end="")
else:
print("\033[92m"+str(cell),end="")
print("\n")
for point in path:
print(point ," ", end="")
print("\n\n")
else:
sys.stdout.write("\r")
the above code makes every matrix possibility, and prints (with highlighting the path) the matrix if the path is longer than the base case.
every result is as I expect - returning the correct shortest path... I have not found the issue with why only the 3rd test case fails...
the answer was something specific to python 2.7.13 apparently - it took a bit of debugging to figure it out, but the not_visited.remove(current_node) was the thing that was failing in certain cases.

My python code is changing a variable in a list but i cant see how/why it is changed

Hello i am trying to learn some basics by making some mistakes.
I am making a basic python based snake game, using as little of non built in modules as i can.
at the moment the main one is tkinter.
Some of my logic is changing a member of a list, when i do not expect it do so and i can't see why.
The change is happening at line 60 (21) where index 1 is changed to equal index 0
when only index 0 should change only when "middle" occurs on line 71 (31)
the error is within this function
self.player[1] changes when i did not expect
def move(self):
print("old snake pos {}".format(self.player))
for i in range(len(self.player)-1, -1, -1):
# runs through loop backwards as the positions of the next piece needs to be known
print(i)
if i == len(self.player)-1:
print("last piece")
if self.eat():
print("eat = True")
self.player.append(self.player[-1])
print("{} changed from {} to {}".format(i, self.player[i], self.player[i-1]))
self.player[i] = self.player[i-1]
else:
if i == 0:
print("Head")
print(self.vel)
print(self.player[0])
print(self.player[1])
self.player[0][0] = self.player[0][0] + self.vel[0]
print("why has it changed????????")
print(self.player[0])
print(self.player[1])
self.player[0][1] = self.player[0][1] + self.vel[1]
print(self.player[1])
print(self.player)
continue
print("middle piece")
print("{} changed from {} to {}".format(i, self.player[i], self.player[i - 1]))
self.player[i] = self.player[i - 1]
print(self.player[i])
print(self.player[i])
print(self.player)
print("new snake pos {}".format(self.player))
i have tried different checks, but it doesnt seem to be doing any line that i dont expect at the right time.
and the full code is this:
# programme for learning more about creating graphical displays in Python
# will start with some investigation and use of tkinter resource
# then move to creating a snake style game
import tkinter as tk
import random as r
import time
class SnakeApp(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.setup()
self.gameloop()
def mat2str(self, matrix):
string = ""
for y in matrix:
for x in y:
string += x
string += "\n"
return string
# random an int co-ordinate
def newpos(self):
return [r.randint(0, self.wx), r.randint(0, self.wy)]
# check if the fruit has been eaten
def eat(self):
if any([s == self.fruit for s in self.player]):
print("fruit col detected {}".format(self.fruit))
# new position
self.fruit = self.newpos()
# increase the game speed by 2.5%
self.dt = self.dt * 0.975
# need to add a segment to the player
return True
# move the game 1 turn and compute all the logic before the next frame is drawn
def move(self):
print("old snake pos {}".format(self.player))
for i in range(len(self.player)-1, -1, -1):
# runs through loop backwards as the positions of the next piece needs to be known
print(i)
if i == len(self.player)-1:
print("last piece")
if self.eat():
print("eat = True")
self.player.append(self.player[-1])
print("{} changed from {} to {}".format(i, self.player[i], self.player[i-1]))
self.player[i] = self.player[i-1]
else:
if i == 0:
print("Head")
print(self.vel)
print(self.player[0])
print(self.player[1])
self.player[0][0] = self.player[0][0] + self.vel[0]
print("why has it changed????????")
print(self.player[0])
print(self.player[1])
self.player[0][1] = self.player[0][1] + self.vel[1]
print(self.player[1])
print(self.player)
continue
print("middle piece")
print("{} changed from {} to {}".format(i, self.player[i], self.player[i - 1]))
self.player[i] = self.player[i - 1]
print(self.player[i])
print(self.player[i])
print(self.player)
print("new snake pos {}".format(self.player))
def up(self, event):
print("up")
if self.vel != [0, 1]:
self.vel = [0, -1]
def down(self, event):
print("down")
if self.vel != [0, -1]:
self.vel = [0, 1]
def left(self, event):
print("left")
if self.vel != [1, 0]:
self.vel = [-1, 0]
def right(self, event):
print("right")
if self.vel != [-1, 0]:
self.vel = [1, 0]
def drawempty(self, wx, wy):
frame = []
for y in range(wy):
xlayer = []
for x in range(wx):
xlayer.append("-")
frame.append(xlayer)
return frame
def redraw(self, player, object):
# self.drawempty(self.wx, self.wy)
# replaced redraw each frame with a static empty frame build
print(self.gameframe)
self.gameframe = self.drawempty(self.wx, self.wy)
print(self.gameframe)
# set the string in the co-ord of the object to A
# check for collision needs to occur before this - all game logic before
# set the string in the co-ords of all players to *
print(object)
self.gameframe[object[1]][object[0]] = "A"
for b in player:
self.gameframe[b[1]][b[0]] = "*"
def setup(self):
# set game size
self.wx = 20
self.wy = 20
self.master.geometry("300x300")
self.vel = [-1, 0]
self.dt = 1
# create text matrices of spaces of size wx * wy
#self.gameframe = tk.Variable()
self.gameframe = []
#self.emptyframe = tk.Variable()
self.emptyframe = self.drawempty(self.wx, self.wy)
self.gameframe = self.emptyframe
# create a player and fruit object in the space
self.player = [[round(self.wx / 2), round(self.wy / 2)],[round(self.wx / 2) + 1, round(self.wy / 2)],[round(self.wx / 2) + 2, round(self.wy / 2)]]
self.fruit = self.newpos()
self.redraw(self.player, self.fruit)
self.game = tk.Text(self, height=self.wy, width=self.wx)
self.game.pack()
self.game.insert(tk.END, self.mat2str(self.gameframe))
self.master.bind("<Up>", self.up)
self.master.bind("<Down>", self.down)
self.master.bind("<Left>", self.left)
self.master.bind("<Right>", self.right)
def gameloop(self):
while True:
self.redraw(self.player, self.fruit)
self.game.delete('1.0', tk.END)
self.game.insert(tk.END, self.mat2str(self.gameframe))
self.move()
self.master.update()
time.sleep(self.dt)
Snake = tk.Tk()
app = SnakeApp(master=Snake)
The problem can be found at your move function as this point (Line 122)
self.player[i] = self.player[i - 1] # i == 1, self.player[1] = self.player[0]
At the point when i is 1 this code block is reached and it sets self.player[1] to self.player[0]
The problem is that self.player[0] after that is equal by reference to self.player[1], which means that when one changes, the other does as well (which you said was your problem).
What you need to do to prevent that from happening is to create a copy of self.player[i-1] and set that equal to self.player[i]. This can be achieved through multiple ways.
In your case because you have a list of lists the following should be fine
self.player[i] = [item for item in self.player[i-1]] # Create a new list
if you want use a built-in functions for this you can use deepcopy
from copy import deepcopy
# ...
self.player[i] = deepcopy(self.player[i-1])
Note that this behavior can also be found on lines 25,43, 104 and 122. So you'll have to change all of them to create a copy as shown above.
The code that should be modified is:
self.player[i] = self.player[i - 1]
When you change element 1 to be element 0, you are not making a copy but making them pointing to the same element, which is 0. So when you change element 0, element 1 change as well.
Example why this leads to what you see in code:
player = [[1,2], [3,4]]
i = 1
player[i] = player[i - 1]
player[0][0] = 100
print(player)
[[100, 2], [100, 2]]
One quick solution to create a copy:
self.player[i] = [x for x in self.player[i - 1]]

Low frame rate, possibly because un-accelerated graphics

So I began creating a game a couple months ago using pygame, it has a top view similar to Starcraft, Age of empires etc... I have written around 1320 lines of code to create the basis of my game; however, I am experiencing issues with frame rate when blitting images and believe this is because I cannot use accelerated graphics with pygame. The way I am currently blitting images is by blitting all images ahead of time on a surface which I then subsurface to create a blit image of my entire screen. Is there a more effective way that i should be utilizing?
So my assumption is that would be huge mess to look through and I do not want to waste your guy's time. Essentially any time I blit a surface the size of my screen my framerate drops by ~20 frames, is there a way I can avoid this in pygame?
##PYGAME INITATE##
import pygame, os
os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.init()
_W,_H = pygame.display.Info().current_w, pygame.display.Info().current_h
flags = pygame.DOUBLEBUF | pygame.HWSURFACE
gameDisplay = pygame.display.set_mode((_W,_H),pygame.FULLSCREEN ) ## CREATES SCREEN YOU DISPLAY ON ##
gameDisplay.fill((0,0,0)) ## FILL COLOR OF SCREEN ##
pygame.display.set_caption("Dope Game") ## SETS NAME ##
gameClock = pygame.time.Clock() ## CLOCK OF THE GAME ##
import math
import os
import random
import copy
SQRT = math.sqrt
PI = math.pi
cos = math.cos
sin = math.sin
## REPEATABLE FUNCTIONS ##
def loadScale(file,command,sizeX,sizeY):
temp = pygame.image.load(file)
tempInfo = temp.get_rect()
tempInfo1,tempInfo2,tempInfo3,tempInfo4 = temp.get_rect()
tempInfo3 = int(tempInfo3)
tempInfo4 = int(tempInfo4)
if (command == "ratio"):
tempInfo3 = tempInfo3*sizeX
tempInfo4 = tempInfo4*sizeY
temp = pygame.transform.scale(temp,(int(tempInfo3),int(tempInfo4) ) )
elif (command == "size"):
temp = pygame.transform.scale(temp, (sizeX,sizeY) )
return(temp)
## NON GAME RELATED CLASSES ##
class EnterFrame():
def __init__(self,frameReset,function,parse,reset):
self.frameReset = frameReset
self.currentFrame = frameReset
self.function = function
self.parse = parse
self.reset = reset
if (self.reset != "onComplete"):
self.reset = (reset-1)
enterFrameTable.append(self)
def step(self,enterFrameTable):
if (self.currentFrame == 0):
self.function(self.parse)
if (self.reset != "onComplete"):
if (self.reset > 0):
self.currentFrame = self.frameReset
self.reset = self.reset-1
else:
enterFrameTable.remove(self)
del self
else:
self.currentFrame = self.frameReset
else:
self.currentFrame = self.currentFrame-1
class PlayerCreation():
def __init__(self):
self.x = _W
self.y = _H
self.view = [1600,1600]
self.viewShift = []
self.viewChangeSpeed = 25
def moveView(self,key):
add = EnterFrame(0,self.moveViewAction,key,"onComplete")
self.viewShift.append([add,key])
def moveViewAction(self,key):
if (key == "up"):
self.view[1] = self.view[1]-self.viewChangeSpeed
Map.recenterView()
if (self.view[1] < 0):
self.view[1] = 0
elif (key == "right"):
self.view[0] = self.view[0]+self.viewChangeSpeed
Map.recenterView()
if (self.view[0] > Map.tileSize*4):
self.view[0] = Map.tileSize*4
elif (key == "down"):
self.view[1] = self.view[1]+self.viewChangeSpeed
Map.recenterView()
if (self.view[1] > Map.tileSize*4):
self.view[1] = Map.tileSize*4
elif (key == "left"):
self.view[0] = self.view[0]-self.viewChangeSpeed
Map.recenterView()
if (self.view[0] < 0):
self.view[0] = 0
def endMoveView(self,key):
for i in range(len(self.viewShift)-1,-1,-1 ):
if (self.viewShift[i][1] == key):
enterFrameTable.remove(self.viewShift[i][0])
del self.viewShift[i]
class ImageCreation():
def __init__(self,name,image,type,hitBox):
self.name = name
self.image = image
self.type = type
self.hitBox = hitBox
self.rect = self.image.get_rect()
if (self.hitBox != "none"):
self.shiftX = hitBox[0][0]
self.shiftY = hitBox[0][1]
for i in range(1,len(hitBox) ):
if (hitBox[i][0] < self.shiftX):
self.shiftX = hitBox[i][0]
if (hitBox[i][1] < self.shiftY):
self.shiftY = hitBox[i][1]
else:
self.shiftX = self.rect[2]/2
self.shiftY = self.rect[3]/2
imageTable.append(self)
def draw(self,x,y):
image = self.image
self.blit = gameDisplay.blit(image,(x,y) )
class MapCreation():
def __init__(self):
self.tileSize = 800
self.size = self.tileSize*10
self.tiles = []
self.loadedTiles = []
self.surface = pygame.Surface([self.tileSize*5,self.tileSize*5], pygame.SRCALPHA, 32)
self.centerTile = [5,5]
self.drawPoint = [(_W-self.tileSize)/2,(_H-self.tileSize)/2]
self.amount = round(self.size/self.tileSize)
backGround = loadScale("Grass.png","size",self.tileSize,self.tileSize)
for i in range(0,self.amount):
for u in range(0,self.amount):
image = copy.copy(backGround)
newTile = Tile(image,[u*self.tileSize,i*self.tileSize])
self.tiles.append(newTile)
info = imageFind("House.png")
tile = self.tiles[55]
imageObject(info,tile,[240,50])
self.loadTiles("center")
def recenterView(self):
if (Player.view[0] > 3*self.tileSize):
self.centerTile[0] = self.centerTile[0]+1
Player.view[0] = Player.view[0]-self.tileSize
self.loadTiles("right")
print("right")
elif (Player.view[0] < 1*self.tileSize):
self.centerTile[0] = self.centerTile[0]-1
Player.view[0] = Player.view[0]+self.tileSize
self.loadTiles("center")
print("center")
if (Player.view[1] > 3*self.tileSize):
self.centerTile[1] = self.centerTile[1]+1
Player.view[1] = Player.view[1]-self.tileSize
self.loadTiles("center")
print("center")
elif (Player.view[1] < 1*self.tileSize):
self.centerTile[1] = self.centerTile[1]-1
Player.view[1] = Player.view[1]+self.tileSize
self.loadTiles("center")
print("center")
def loadTiles(self,load):
tileIndex = self.centerTile[0]+self.centerTile[1]*self.amount
if (load == "center"):
self.loadedTiles = []
for i in range(-2,3):
for u in range(-2,3):
loadTile = tileIndex + i*self.amount + u
self.loadedTiles.append(self.tiles[loadTile])
self.tiles[loadTile].loaded = [u+2,i+2]
self.surface = pygame.Surface([self.tileSize*5,self.tileSize*5], pygame.SRCALPHA, 32)
for i in range(0,len(self.loadedTiles) ):
sx = self.loadedTiles[i].loaded[0]*self.tileSize
sy = self.loadedTiles[i].loaded[1]*self.tileSize
self.surface.blit(self.loadedTiles[i].buttomLayer,(sx,sy) )
for i in range(0,len(self.loadedTiles) ):
sx = self.loadedTiles[i].loaded[0]*(self.tileSize+2)
sy = self.loadedTiles[i].loaded[1]*(self.tileSize+2)
self.surface.blit(self.loadedTiles[i].middleLayer,(sx,sy) )
for i in range(0,len(self.loadedTiles) ):
sx = self.loadedTiles[i].loaded[0]*(self.tileSize+2)
sy = self.loadedTiles[i].loaded[1]*(self.tileSize+2)
self.surface.blit(self.loadedTiles[i].topLayer,(sx,sy) )
elif (load == "right"):
self.loadedTiles = []
for i in range(-2,3):
for u in range(-2,3):
loadTile = tileIndex + i*self.amount + u
self.loadedTiles.append(self.tiles[loadTile])
self.tiles[loadTile].loaded = [u+2,i+2]
## OLD METHOD THAT WASNT WORKING ##
##subSurf = self.surface.subsurface(self.tileSize,0,self.tileSize*1,self.tileSize*5)
##self.surface = pygame.Surface([self.tileSize*5,self.tileSize*5], pygame.SRCALPHA, 32)
##self.surface.blit(subSurf,(0,0) )
## NEW METHOD ##
self.surface.scroll(dx=-self.tileSize*1,dy=0)
def draw(self):
global Player
image = self.surface.subsurface(Player.view[0],Player.view[1],self.tileSize,self.tileSize)
image = pygame.transform.scale(image,(self.tileSize,self.tileSize) )
gameDisplay.blit(image,((_W-self.tileSize)/2,(_H-self.tileSize)/2) )
image = pygame.transform.scale(self.surface,(300,300) )
gameDisplay.blit(image,(0,0 ) )
class Tile():
def __init__(self,image,coords):
transparentSurface = pygame.Surface([1000,1000], pygame.SRCALPHA, 32)
self.x = coords[0]
self.y = coords[1]
self.loaded = False
self.buttomLayer = image
self.middleLayer = copy.copy(transparentSurface)
self.topLayer = transparentSurface
class imageObject():
def __init__(self,info,tile,coords):
self.info = info
self.image = info.image
self.rect = self.image.get_rect()
self.x = coords[0]
self.y = coords[1]
self.hitBox = []
if (self.info.hitBox != "none"):
for i in range(0,len(self.info.hitBox) ):
self.hitBox.append([self.info.hitBox[i][0]+self.x,self.info.hitBox[i][1]+self.y])
#self.object = createObject(self.hitBox,True,"none")
if (info.type == "background"):
tile.buttomLayer.blit(self.image,(self.x,self.y) )
if (info.type == "object"):
tile.middleLayer.blit(self.image,(self.x,self.y) )
if (info.type == "object alphas"):
tile.topLayer.blit(self.image,(self.x,self.y) )
def imageFind(name):
for i in range(0,len(imageTable) ):
if (name == imageTable[i].name):
return(imageTable[i])
return("none")
def imageLoad(types):
if (types == "basic"):
image = loadScale("House.png","ratio",1,1)
basicHouse = ImageCreation("House.png",image,"object",[[190, 375], [350, 375], [350, 235], [190, 235]])
image = loadScale("Grass.png","ratio",1,1)
grass = ImageCreation("Grass.png",image,"background","none")
def enterFrameHandle(enterFrameTable):
for i in range(len(enterFrameTable)-1,-1,-1 ):
enterFrameTable[i].step(enterFrameTable)
def EventHandle(event):
global Player
if (event.type == pygame.QUIT):
endGame()
elif(event.type == pygame.KEYDOWN):
key = (pygame.key.name(event.key) )
if (key == "escape"):
endGame()
elif (key == "up" or key == "right" or key == "down" or key == "left"):
Player.moveView(key)
elif(event.type == pygame.KEYUP):
key = (pygame.key.name(event.key) )
if (key == "up" or key == "right" or key == "down" or key == "left"):
Player.endMoveView(key)
def endGame():
global QuitGame
QuitGame = True
def mainLoop():
## GLOBALS ##
global QuitGame
QuitGame = False
global Player
Player = PlayerCreation()
## MAIN TABLES ##
global enterFrameTable
enterFrameTable = []
global basicObjectTable
basicObjectTable = []
## TEMP TABLES ##
global imageTable
imageTable = []
## START UP LOOPS ##
imageLoad("basic")
global Map
Map = MapCreation()
## Temporary ##
while (QuitGame == False):
enterFrameHandle(enterFrameTable)
for event in pygame.event.get():
EventHandle(event)
Map.draw()
pygame.display.update() ## updates the screen ##
gameDisplay.fill([0,0,0]) ## Clears screen for next frame ##
gameClock.tick(64) ## The FPS ##
fps = gameClock.get_fps()
if (fps < 64 and fps != 0):
fps = gameClock.get_fps()
print("FPS HAS DROPPED TOO LOW DOWN TO",fps)
runGameNow = True
mainLoop()
pygame.quit()
quit()
Issue with path finder is due to picking up on itteration lines when finding nearest point.
The problem is quite straight forward really.
Your view the amount of lines as disease and the cause must be Pygame. Usually, or at least in almost any case - if you're not a god at using the language and library, the problem is probably not the language or the library. Because odds are you are no wear near to pushing the limits of what the two can deliver for you.
One dead give-away of this fact is, if you look at what your eyes see on screen, you'll quickly notice that whenever your background is about to loop around - that's when the glitch/frame drop occur.
Obviously, this COULD be a cause of the library doing something suspicious.. if it was the library that did the actual loop-around.
But in your code, this is a implementation you've done yourself.
So the best bet is to start looking there.
Just to be extremely confident in where the delay occurs - we can have a look at the profiler that comes with Python (cProfiler).
python -m cProfile -o sample_data.pyprof awesome_game.py
pyprof2calltree -i sample_data.pyprof -k
The result would show something along the lines of:
From this we can see that a lot of processing time goes to pygame.Surface and enterFrameHandle. And I bet that pygame.Surface is called within enterFrameHandle somewhere down the line.
So the best bet is to start at the end of the eventFrameHandle chain.
And in this breakdown, that's loadTiles.
And straight off the bat, a lot of warning signs in here.
There's at least four loops in here.. and loops are bad, because those take processing time.
I added debug information by simply doing:
def loadTiles
if load == "center":
print('Loading center')
elif load == "right":
print('Loading right')
Apparently center gets triggered whenever the glitch occurs.
So that narrows it down a little further. So i added timers around each for loop.
Loop one: takes 0.0 seconds
Loop two: takes 0.29 seconds
Loop three: takes 0.5 seconds
Loop four: takes 0.6 seconds
All in all, these are EXTREMELY bad for you, since they directly impact the render sequence. So why is this? Lets break it down even further.
We'll start off with the first loop that takes up 0.3 seconds:
for i in range(0,len(self.loadedTiles) ):
sx = self.loadedTiles[i].loaded[0]*self.tileSize
sy = self.loadedTiles[i].loaded[1]*self.tileSize
self.surface.blit(self.loadedTiles[i].buttomLayer,(sx,sy) )
So len(self.loadedTiles) will be 25.
That means this loop has to iterate 25 times per render cycle where loadTiles("center") is called.
Each cycle takes almost exactly 0.01 seconds which amounts up to 0.25 seconds per full loop. It's not terrible, well it is.. If you want 60 FPS out of your game, no loop or function call can take more than 0.0166 seconds in total.
So we've already overrun our desired FPS target. So we gotta knock a few milliseconds off this if we wanna get anywhere.
The loop in itself is not that bad, i mean 25 iterations, a modern PC can do that in less time than time() can measure. So, it all points to self.surface.blit().. Which from experience, it sure is.
This also correlate with the fact that our graph above spends 30% of the total CPU time in pygame.surface.blit.. So here's our first thief.
Looking at the rest of the loops, they are essentially the same, except with bigger numbers in the math which takes slight longer to calculate, hence probably the time difference in the loops.
So, what can we do to shrink down .blit times?
Well we can stop calling blit in every loop iteration, and just move the sprite objects positions and then blit them one by one.
But sensei, that's almost the same thing?
Well yes my soon to be ninja turtle, it is.. That's why, we'll move the sprites in to batches/groups, and render the group. Thus, we can change the positions (fast operation), and because we moved the rendering outside of the loops, we can also convert them into a group object and render the group.
First, we'll convert your objects into pygame.sprite.Sprite, these are clever little objects that contain both collision detection, moving arounds etc.
self.grass = pygame.sprite.Sprite()
self.grass.image = pygame.image.load("Grass.png")
self.grass.rect = pygame.Rect(0, 0, 60, 60)
And you add this into a rendering group (a batch):
self.main_group = pygame.sprite.Group(self.grass)
Bam, and instead of updating 25 sprites, now you can do:
self.main_group.draw(gameDisplay)
And bam, SPEED!
Now, your code is NOT designed for this.
So this will take a while to fix and correct this design flaw.
Because this would take hours for me in order to keep your original code as close as possible, i ignored this and reworked your entire MapCreation and modified your PlayerCreation and how you move the map around (I changed from trying to recenter each time, it's a nice idea but far to complex to implement 1PM rewriting someone else's code).. So.. Here's the new approach to your problem:
import pygame, os
from time import time
from collections import OrderedDict
os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.init()
_W,_H = pygame.display.Info().current_w, pygame.display.Info().current_h
flags = pygame.DOUBLEBUF | pygame.HWSURFACE | pygame.RESIZABLE
gameDisplay = pygame.display.set_mode((500, 500))
gameDisplay.fill((0,0,0)) ## FILL COLOR OF SCREEN ##
pygame.display.set_caption("Dope Game") ## SETS NAME ##
gameClock = pygame.time.Clock() ## CLOCK OF THE GAME ##
import math
import os
import random
import copy
SQRT = math.sqrt
PI = math.pi
cos = math.cos
sin = math.sin
## REPEATABLE FUNCTIONS ##
def loadScale(file,command,sizeX,sizeY):
temp = pygame.image.load(file)
tempInfo = temp.get_rect()
tempInfo1,tempInfo2,tempInfo3,tempInfo4 = temp.get_rect()
tempInfo3 = int(tempInfo3)
tempInfo4 = int(tempInfo4)
if (command == "ratio"):
tempInfo3 = tempInfo3*sizeX
tempInfo4 = tempInfo4*sizeY
temp = pygame.transform.scale(temp,(int(tempInfo3),int(tempInfo4) ) )
elif (command == "size"):
temp = pygame.transform.scale(temp, (sizeX,sizeY) )
return(temp)
## NON GAME RELATED CLASSES ##
class EnterFrame():
def __init__(self,frameReset,function,parse,reset):
self.frameReset = frameReset
self.currentFrame = frameReset
self.function = function
self.parse = parse
self.reset = reset
if (self.reset != "onComplete"):
self.reset = (reset-1)
enterFrameTable.append(self)
def step(self,enterFrameTable):
if (self.currentFrame == 0):
self.function(self.parse)
if (self.reset != "onComplete"):
if (self.reset > 0):
self.currentFrame = self.frameReset
self.reset = self.reset-1
else:
enterFrameTable.remove(self)
del self
else:
self.currentFrame = self.frameReset
else:
self.currentFrame = self.currentFrame-1
class PlayerCreation():
def __init__(self):
self.x = _W
self.y = _H
self.view = [1600,1600]
self.viewShift = []
self.viewChangeSpeed = 25
def moveView(self,key):
add = EnterFrame(0,self.moveViewAction,key,"onComplete")
self.viewShift.append([add,key])
def moveViewAction(self,key):
if (key == "up"):
self.view[1] = self.view[1]-self.viewChangeSpeed
Map.move_tile(0, 1) # Player moves up, so the tiles should move down -> (0, 1) == (x, y)
if (self.view[1] < 0):
self.view[1] = 0
elif (key == "right"):
self.view[0] = self.view[0]+self.viewChangeSpeed
Map.move_tile(-1, 0)
if (self.view[0] > Map.tileSize*4):
self.view[0] = Map.tileSize*4
elif (key == "down"):
self.view[1] = self.view[1]+self.viewChangeSpeed
Map.move_tile(0, -1)
if (self.view[1] > Map.tileSize*4):
self.view[1] = Map.tileSize*4
elif (key == "left"):
self.view[0] = self.view[0]-self.viewChangeSpeed
Map.move_tile(1, 0)
if (self.view[0] < 0):
self.view[0] = 0
def endMoveView(self,key):
for i in range(len(self.viewShift)-1,-1,-1 ):
if (self.viewShift[i][1] == key):
enterFrameTable.remove(self.viewShift[i][0])
del self.viewShift[i]
class ImageCreation():
def __init__(self,name,image,type,hitBox):
self.name = name
self.image = image
self.type = type
self.hitBox = hitBox
self.rect = self.image.get_rect()
if (self.hitBox != "none"):
self.shiftX = hitBox[0][0]
self.shiftY = hitBox[0][1]
for i in range(1,len(hitBox) ):
if (hitBox[i][0] < self.shiftX):
self.shiftX = hitBox[i][0]
if (hitBox[i][1] < self.shiftY):
self.shiftY = hitBox[i][1]
else:
self.shiftX = self.rect[2]/2
self.shiftY = self.rect[3]/2
imageTable.append(self)
def draw(self,x,y):
image = self.image
self.blit = gameDisplay.blit(image,(x,y) )
class MapCreation():
def __init__(self):
self.tileSize = 800
self.size = self.tileSize*10
self.tiles = []
self.centerTile = [5,5]
self.amount = round(self.size/self.tileSize)
self.sprites = OrderedDict()
self.grass_image = pygame.image.load("Grass.png")
self.grass_group = pygame.sprite.Group()
for x in range(0, self.amount*60, 60): ## 10*60, but we step 60 pixels, so in the end, this will be 10 steps.
for y in range(0, self.amount*60, 60): ## Which is the same as `for x in range(self.amount)` but we scale it up
## to give us pixels instead of just the ammount.
index = len(self.sprites) # -- Generate a index for this sprite. Used for access later (to update pos for instace)
## == Create the sprite, add a image to it and define a position and size.
self.sprites[index] = pygame.sprite.Sprite()
self.sprites[index].image = self.grass_image
self.sprites[index].rect = pygame.Rect(x, y, 60, 60)
## == Then add the sprite to the grass group.
self.grass_group.add(self.sprites[index])
def move_tile(self, dx, dy):
for index in self.sprites:
x, y, width, height = self.sprites[index].rect
## == this is how you move the sprites:d
self.sprites[index].rect = pygame.Rect(x+dx, y+dy, 60, 60)
def draw(self):
self.grass_group.update()
self.grass_group.draw(gameDisplay)
class Tile():
def __init__(self,image,coords):
transparentSurface = pygame.Surface([1000,1000], pygame.SRCALPHA, 32)
self.x = coords[0]
self.y = coords[1]
self.loaded = False
self.buttomLayer = image
self.middleLayer = copy.copy(transparentSurface)
self.topLayer = transparentSurface
class imageObject():
def __init__(self,info,tile,coords):
self.info = info
self.image = info.image
self.rect = self.image.get_rect()
self.x = coords[0]
self.y = coords[1]
self.hitBox = []
if (self.info.hitBox != "none"):
for i in range(0,len(self.info.hitBox) ):
self.hitBox.append([self.info.hitBox[i][0]+self.x,self.info.hitBox[i][1]+self.y])
#self.object = createObject(self.hitBox,True,"none")
if (info.type == "background"):
tile.buttomLayer.blit(self.image,(self.x,self.y) )
if (info.type == "object"):
tile.middleLayer.blit(self.image,(self.x,self.y) )
if (info.type == "object alphas"):
tile.topLayer.blit(self.image,(self.x,self.y) )
def imageFind(name):
for i in range(0,len(imageTable) ):
if (name == imageTable[i].name):
return(imageTable[i])
return("none")
def imageLoad(types):
if (types == "basic"):
image = loadScale("House.png","ratio",1,1)
basicHouse = ImageCreation("House.png",image,"object",[[190, 375], [350, 375], [350, 235], [190, 235]])
image = loadScale("Grass.png","ratio",1,1)
grass = ImageCreation("Grass.png",image,"background","none")
def enterFrameHandle(enterFrameTable):
for i in range(len(enterFrameTable)-1,-1,-1 ):
enterFrameTable[i].step(enterFrameTable)
def EventHandle(event):
global Player
if (event.type == pygame.QUIT):
endGame()
elif(event.type == pygame.KEYDOWN):
key = (pygame.key.name(event.key) )
if (key == "escape"):
endGame()
elif (key == "up" or key == "right" or key == "down" or key == "left"):
Player.moveView(key)
elif(event.type == pygame.KEYUP):
key = (pygame.key.name(event.key) )
if (key == "up" or key == "right" or key == "down" or key == "left"):
Player.endMoveView(key)
def endGame():
global QuitGame
QuitGame = True
def mainLoop():
## GLOBALS ##
global QuitGame
QuitGame = False
global Player
Player = PlayerCreation()
## MAIN TABLES ##
global enterFrameTable
enterFrameTable = []
global basicObjectTable
basicObjectTable = []
## TEMP TABLES ##
global imageTable
imageTable = []
## START UP LOOPS ##
imageLoad("basic")
global Map
Map = MapCreation()
## Temporary ##
while (QuitGame == False):
enterFrameHandle(enterFrameTable)
for event in pygame.event.get():
EventHandle(event)
pygame.display.update() ## updates the screen ##
gameDisplay.fill([0,0,0]) ## Clears screen for next frame ##
#Map.draw()
Map.grass_group.update()
Map.grass_group.draw(gameDisplay)
gameClock.tick(64) ## The FPS ##
fps = gameClock.get_fps()
if (fps < 64 and fps != 0):
fps = gameClock.get_fps()
print("FPS HAS DROPPED TOO LOW DOWN TO",fps)
pygame.display.flip()
runGameNow = True
mainLoop()
pygame.quit()
quit()
This code rarely drop down in FPS.
And when it does, it drops down to ~63 FPS because I moved the window around (that's a taxing thing to do because all the OpenGL references need to compensate for the new window position and events that triggers within Pygame (resize events, move events etc are taxing, but occurs once and not every render loop.. so it's acceptable).
Conclusion
It's rarely the library's fault for slow performance.
Stop thinking as a developer for a second, and watch the screen and see if you can find clues to as where the fault may lie. In this case a obvious glitch occurred every time the tiles rotated around the screen edge.. That's a clue! Not a single programming skill needed to visually see this bug.
Use a profiler and debug where your code spends most of the time.
Try to see how others have solved game designs.. You're on the right track by putting everything in classes, but that mostly helps your readability of the code - try to see what's in other peoples classes and how they use Pygame calls.
All in all, it's quite a fun and nice code you got going.
It's just not made for performance and it's hard to code in afterwards.. so maybe it's not such a bad idea to start all over? But keep your old code base for reference and avoid the obvious traps:
loops in the render sequence
rendering objects one by one, use batches instead
Only do loops to update positions/stats, not graphics!
Last note on the topic: Graphic cards are huge power houses. They can calculate a few million operations a second if not more. And each time you blit something to the screen, the graphics card needs to interrupt it's calculations and memory allocation to flip around buffers and update what you see on the screen.. And that's slow work because it's not calculations, it's operational tasks (which usually includes waiting for queues and signals).. So instead of throwing hundreds of blit's a second to the graphics card, throw math operations at it by sending it textures (math data describing how things look).. and then call blit (draw, in this case) once and let the graphics card take all the math you threw at it - do one big logical operation - and presto.. x100 the speed already.

Categories

Resources