Multithreading in python with matplotlib - python

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.

Related

Matplotlib.pyplot not updating dynamic line graph using set_data

I am trying to create a plot that dynamically changes as the length of the x and y data sets change.
I got it to work by plotting the plot each loop, but that is not an efficient way to accomplish this.
I use the variable self.line, in line 165 to initialize this plot. At line 228 I change the plot self.ax2.plot(self.stepData, self.nAlive, 'b-')
I am trying to use this instead to just change the data: ```self.line.set_data = (self.stepData, self.nAlive)
Here is my code:
import random
import numpy as np
import matplotlib.pyplot as plt
# ====================================================================================
class Person:
def __init__(self, position):
self.xpos = position[0]
self.ypos = position[1]
self.alive = True
self.trapped = False # if all people are trapped, need to end program
self.distance = None # distance to closest neighbor
def move(self, xmax, ymax, bc, taken):
""" moves person, if possible, to an adjacent corner """
# cannot step where someone else is currently standing
taken[self.xpos, self.ypos] = False # moving from current spot
disallowed = set() # empty set object; will add disallowed directions
if bc == 'wall':
if self.ypos == ymax or taken[self.xpos, self.ypos + 1]:
disallowed.add('north')
if self.xpos == xmax or taken[self.xpos + 1, self.ypos]:
disallowed.add('east')
if self.ypos == 0 or taken[self.xpos, self.ypos - 1]:
disallowed.add('south')
if self.xpos == 0 or taken[self.xpos - 1, self.ypos]:
disallowed.add('west')
elif bc == 'periodic':
if (self.ypos == ymax and taken[self.xpos, (self.ypos + 1) % ymax]) \
or (self.ypos < ymax and taken[self.xpos, self.ypos + 1]):
disallowed.add('north')
if (self.xpos == xmax and taken[(self.xpos + 1) % xmax, self.ypos]) \
or (self.xpos < xmax and taken[self.xpos + 1, self.ypos]):
disallowed.add('east')
if (self.ypos == 0 and taken[self.xpos, (self.ypos - 1) % ymax]) \
or (self.ypos > 0 and taken[self.xpos, self.ypos - 1]):
disallowed.add('south')
if (self.xpos == 0 and taken[(self.xpos - 1) % xmax, self.ypos]) \
or (self.xpos > 0 and taken[self.xpos - 1, self.ypos]):
disallowed.add('west')
# Use the set method 'difference' to get set of allowed directions
allowed = {'north', 'east', 'south', 'west'}.difference(disallowed)
if len(allowed) == 0:
self.trapped = True # cannot move anymore
else:
"""
Randomly pick from the allowed directions; need to convert set
object to a list because random.choice doesn't work on sets
"""
self.direction = random.choice(list(allowed))
if self.direction == 'north':
if (bc == 'wall' and self.ypos < ymax) or bc == 'periodic':
self.ypos += 1
elif self.direction == 'east':
if (bc == 'wall' and self.xpos < xmax) or bc == 'periodic':
self.xpos += 1
elif self.direction == 'south':
if (bc == 'wall' and self.ypos > 0) or bc == 'periodic':
self.ypos -= 1
elif self.direction == 'west':
if (bc == 'wall' and self.xpos > 0) or bc == 'periodic':
self.xpos -= 1
"""
With periodic boundary conditions, it's possible that (xpos, ypos) could
be off the grid (e.g., xpos < 0 or xpos > xmax). The Python modulo
operator can be used to give exactly what we need for periodic bc. For
example, suppose xmax = 20; then if xpos = 21, 21 % 20 = 1; if xpos = -1,
-1 % 20 = 19. (Modulo result on a negative first argument may seem
strange, but it's intended for exactly this type of application. Cool!)
If 0 <= xpos < xmax, then modulo simply returns xpos. For example,
0 % 20 = 0, 14 % 20 = 14, etc. Only special case is when xpos = xmax, in
which case we want to keep xpos = xmax and not xpos % xmax = 0
"""
if self.xpos != xmax:
self.xpos = self.xpos % xmax
if self.ypos != ymax:
self.ypos = self.ypos % ymax
def proximity(self, xmax, ymax, bc, people):
"""
Finds distance from closest neighbor, will calculate actual distance over diagonals, assuming 1 square is
1 ft^2. If no one else is in range (less than 6 feet away), return None.
"""
distance = None
for person in people:
if person is not self and person.alive: # can only get infected by other alive people
x = abs(self.xpos - person.xpos)
y = abs(self.ypos - person.ypos)
if bc == 'periodic': # check other direction (across boundaries) for periodic bc
tempX1 = None
tempY1 = None
for i in range(2):
if tempX1 is None:
tempX1 = (self.xpos - 0) + (xmax - person.xpos) # check distance in one direction
else:
tempX2 = (person.xpos - 0) + (xmax - self.xpos) # check distance in other direction
if tempX2 < tempX1:
tempX1 = tempX2
if tempY1 is None:
tempY1 = (self.ypos - 0) + (ymax - person.ypos) # check distance in one direction
else:
tempY2 = (person.ypos - 0) + (ymax - self.ypos) # check distance in other direction
if tempY2 < tempY1:
tempY1 = tempY2
if tempX1 < x:
x = tempX1
if tempY1 < y:
y = tempY1
if x >= 6 or y >= 6:
pass # not close enough to infect
else: # need to find distance
temp = np.sqrt(x ^ 2 + y ^ 2)
if distance is None or temp < distance:
distance = temp
if distance is not None and distance >= 6:
distance = None
return distance
def gambleYourLife(self, distance):
if distance is not None: # chance of dying!!
p = 0.5 * np.e ** (-0.3 * distance)
if np.random.binomial(1, p) == 1:
self.alive = False
# ====================================================================================
class Earth:
def __init__(self, people, gridSize, bc, nSteps):
# if nSteps = None, goes on until 1 person left
"""
Grid class takes people inputs, along with size, boundary conditions, and number of maximum steps, and runs
on a random walk until one person is left or until nSteps is reached, depending on user input.
"""
self.people = people
self.xmax = gridSize[0]
self.ymax = gridSize[1]
self.bc = bc
self.point = []
self.nSteps = nSteps
self.nAlive = [len(self.people)] # keep track of number of people alive after each step
self.stepData = [0] # list of each step
# array to keep track of points that have are taken
self.taken = np.zeros([self.xmax + 1, self.ymax + 1], dtype=bool)
fig, (self.ax1, self.ax2) = plt.subplots(1, 2) # create new figure window and ax (plot) attributes to Earth obj
self.ax1.set_xlim(0, self.xmax)
self.ax1.set_ylim(0, self.ymax)
self.ax2.set_xlim(0, nSteps)
self.ax2.set_ylim(0, len(people) + 10)
self.line, = self.ax2.plot(self.stepData, self.nAlive, 'b-')
self.ax2.plot(self.stepData, self.nAlive, '--')
self.ax2.set_xlabel('Number of steps')
self.ax2.set_ylabel('Number of people alive')
self.ax2.set_autoscalex_on(True)
# self.ax2.autoscale_view(True, True, False)
for p in self.people:
pnt, = self.ax1.plot([p.xpos], [p.ypos], 'bo')
self.taken[p.xpos, p.ypos] = True
self.point.append(pnt)
def go(self):
step = 1
while not all([p.trapped for p in self.people]) and (self.nSteps is None or step < self.nSteps):
currAlive = 0 # number of alive each round
for i, p in enumerate(self.people):
if not p.trapped and p.alive:
p.move(self.xmax, self.ymax, self.bc, self.taken)
# update grid
self.point[i].set_data(p.xpos, p.ypos)
self.point[i].set_marker('o')
self.taken[p.xpos, p.ypos] = True
"""
When using periodic boundary conditions, a position on a
wall is identical to the corresponding position on the
opposite wall. So if a walker visits (x, ymax) then
(x, 0) must also be marked as visited; if a walker vists
(0, y) then (xmax, y) must also be marked as visited; etc.
"""
if self.bc == 'periodic':
if p.xpos == self.xmax:
self.taken[0, p.ypos] = True
elif p.xpos == 0:
self.taken[self.xmax, p.ypos] = True
if p.ypos == self.ymax:
self.taken[p.xpos, 0] = True
elif p.ypos == 0:
self.taken[p.xpos, self.ymax] = True
# plot path lines
if p.direction == 'north':
self.ax1.vlines(p.xpos, p.ypos - 1, p.ypos)
elif p.direction == 'east':
self.ax1.hlines(p.ypos, p.xpos - 1, p.xpos)
elif p.direction == 'south':
self.ax1.vlines(p.xpos, p.ypos + 1, p.ypos)
elif p.direction == 'west':
self.ax1.hlines(p.ypos, p.xpos + 1, p.xpos)
# determine proximity to others and potentially die
neighbor = p.proximity(self.xmax, self.ymax, self.bc, self.people)
p.gambleYourLife(neighbor)
if not p.alive:
self.point[i].set_color('r')
else: # still alive
currAlive += 1
# update plot
self.nAlive.append(currAlive)
self.stepData.append(step)
# self.line.set_data = (self.stepData, self.nAlive)
self.ax2.plot(self.stepData, self.nAlive, 'b-')
step += 1
plt.pause(0.2)
# ====================================================================================
def program(nPeople=10, gridSize=(20, 20), bc='wall', nSteps=100):
initPositions = set() # set of tuple of initial position of each person
flock = [] # initialize - list of all people
for p in range(nPeople):
while True:
x = random.randint(0, gridSize[0]) # randomly place person on graph, only one person per point
y = random.randint(0, gridSize[1])
if (x, y) not in initPositions:
break
initPositions.add((x, y))
tempPerson = Person(position=(x, y))
flock.append(tempPerson) # add person to list of people
flock = tuple(flock) # turn list into tuple (not sure why... not sure if necessary)
pandemic = Earth(flock, gridSize, bc, nSteps)
pandemic.go()
# main program =======================================================================
program()
You have to turn on the interactive mode with the command plt.ion(), or to show the figure "not blocked" with the argument block=False using fig.show(block=False).
Here is an example of using plt.ion():
# %%
# first cell ------------------------------
import matplotlib.pyplot as plt
x = [1,2,3]
y = [1,7,5]
plt.ion() # interactive mode on
fig,ax = plt.subplots()
line, = ax.plot(x,y)
fig.show()
# %%
# second cell ------------------------------
x.append(4)
y.append(10)
line.set_data(x,y) # set new data
ax.relim() # recompute the data limits
ax.autoscale_view() # automatic axis scaling
# %%
# third cell: just a radom example to plot sequentially ---------------------
from random import randint
for i in range(5):
x.append(x[-1]+1)
y.append(randint(0,10))
line.set_data(x,y) # set new data
ax.relim() # recompute the data limits
ax.autoscale_view() # automatic axis scaling
plt.pause(0.5)

Python Tkinter problem with looping to check if mouse clicked on line(canvas)

I want to check if the mouse clicked on a line on Tkinter canvas or not, if on a line then which line.
I had made this to detect mouse clicks.
class Link:
def __init__(self,Node1,Node2,canvas,width=5):
if self not in canvas.LinkList:
self.start_coor = Node1.Centre
self.final_coor = Node2.Centre
self.Canvas = canvas
self.Width = width
self.Shape = canvas.create_line(self.start_coor,self.final_coor,width=width)
Node1.connected(Node2)
self.Canvas.LinkList.append(self)
self.Nodes = [Node1,Node2]
self.Clicked = False
dy = self.final_coor[1] - self.start_coor[1]
dx = self.final_coor[0] - self.start_coor[0]
self.m = dy/dx
self.c = self.start_coor[1] - self.m*self.start_coor[0]
def onLineCheck(self,x,y,field=False):
#y = mx + c
#y - mx - c = 0
if not field:
field = self.Width
if (x < self.start_coor[0] and x < self.final_coor[1]) or (x > self.start_coor[0] and x > self.final_coor[1]) or (y < self.start_coor[1] and y < self.final_coor[1]) or (y > self.start_coor[1] and y > self.final_coor[1]):
return False
temp = y - (self.m*x) - self.c
if abs(temp) <= field:
return True
return False
class InputCanvas(Canvas):
def __init__(self,master=None, **kw):
super().__init__(master,**kw)
self.NodeList = []
self.LinkList = []
self.Mode = "Nodes"
def anyLinkClicked(self,e):
x,y = getMousePosition(e)
for l in self.LinkList:
if l.onLineCheck(x,y):
return l
return False
Every time when a link is created, it will automatically append itself to the canvas.LinkList. I am sure this part of the code is working properly.
So far the program works well with 1 line on Canvas(even if I remove it and draw a new one it is still working) but cannot handle more than 1 line, it can only respond to the first line created. Even if I remove the first line, the second created line won't work.
I've tried printing out the result of each onLineCheck(), it seems like the loop is looping properly through each line but it is not catching mouse clicks.
Any idea to help?
The issue is on the following line:
if (x < self.start_coor[0] and x < self.final_coor[1]) or (x > self.start_coor[0] and x > self.final_coor[1]) or (y < self.start_coor[1] and y < self.final_coor[1]) or (y > self.start_coor[1] and y > self.final_coor[1]):
return False
x < self.final_coor[1] should be x < self.final_coor[0]
x > self.final_coor[1] should be x > self.final_coor[0]
Also you should also cater vertical line, i.e. dx is zero as your code will raise ZeroDivisionError: division by zero.
You can use Canvas.find_overlapping() function to find which Link is clicked:
def anyLinkClicked(self, e):
x, y = getMousePosition(e)
found = self.find_overlapping(x, y, x, y)
if found:
for l in self.LinkList:
if found[0] == l.Shape:
return l
return False

Efficient enemy avoidance for pathfinding with fewer enemies

I am currently working on a 2D top down rogue-like game using Python. The map is a dungeon containing many open rectangular rooms (image), each with around 2-4 enemies inside. I am currently looking to implement a path-finding system where the enemies will move around each other and attempt to swarm the player.
So far, I have implemented an A* algorithm that does allow the enemies to navigate and swarm the player in this way. However, my approach is causing very low frame rates: generally around 15 FPS but it will go as low as under 1 FPS when an enemy has no path to the player. I feel it is very inefficient, since path-finding is being done for every enemy on each frame. Currently, other enemies are seen as obstacles for the A* algorithm, and the only optimization is that an enemy will move directly towards the player if there are no obstacles in its way. Here's the code:
import heapq
#...
FLOOR = 1
#...
class Game:
def __init__(self):
#...
self.pathfindingGranularity = 5
# Slope and line intersection functions are based on: https://www.codeproject.com/Tips/864704/Python-Line-Intersection-for-Pygame
def lineInRect(self, start, end, r):
if start in r and end in r: return True
if self.segmentIntersect(start, end, r.origin, Point(r.x + r.width, r.y)) is not None: return True
if self.segmentIntersect(start, end, Point(r.x, r.y + r.height), Point(r.x + r.width, r.y + r.height)) is not None: return True
if self.segmentIntersect(start, end, r.origin, Point(r.x, r.y + r.height)) is not None: return True
if self.segmentIntersect(start, end, Point(r.x + r.width, r.y), Point(r.x + r.width, r.y + r.height)) is not None: return True
return False
def slope(self, p1, p2):
if p2.x - p1.x == 0: return 1e10
return (p2.y - p1.y) / (p2.x - p1.x)
def yIntercept(self, slope, p1):
return p1.y - slope * p1.x
def lineIntersect(self, start1, end1, start2, end2):
min_allowed = 1e-5
big_value = 1e10
m1 = self.slope(start1, end1)
b1 = self.yIntercept(m1, start1)
m2 = self.slope(start2, end2)
b2 = self.yIntercept(m2, start2)
if abs(m1 - m2) < min_allowed: x = big_value if (b2 - b1 >= 0) else -big_value
else: x = (b2 - b1) / (m1 - m2)
y = m1 * x + b1
return Point(x, y)
def segmentIntersect(self, start1, end1, start2, end2):
intersection = self.lineIntersect(start1, end1, start2, end2)
def approx(f):
return round(f * 10000) / 10000
if not approx(start1.x) <= approx(intersection.x) <= approx(end1.x):
if not approx(end1.x) <= approx(intersection.x) <= approx(start1.x):
return None
if not approx(start2.x) <= approx(intersection.x) <= approx(end2.x):
if not approx(end2.x) <= approx(intersection.x) <= approx(start2.x):
return None
if not approx(start1.y) <= approx(intersection.y) <= approx(end1.y):
if not approx(end1.y) <= approx(intersection.y) <= approx(start1.y):
return None
if not approx(start2.y) <= approx(intersection.y) <= approx(end2.y):
if not approx(end2.y) <= approx(intersection.y) <= approx(start2.y):
return None
return intersection
class Enemy (Entity):
def update(self, game):
#...
if not self.getRect().intersects(game.player.getRect()) and self.canMove():
self.generatePath(game)
if self.path:
# Move towards player
elif self.canMove():
# Hurt the player
#...
def generatePath(self, game):
if not self.lineOccupied(Point(self.x, self.y), game.player.getCenterpoint(), game):
self.path = [game.player.getCenterpoint()]
return
frontier = PriorityQueue()
start = Point(self.x, self.y)
frontier.put(start, 0)
came_from = {}
came_from[start] = None
done = False
while not frontier.empty():
current = frontier.get()
if Rect(current.x + self.hitbox.x, current.y + self.hitbox.y, self.hitbox.w, self.hitbox.h).intersects(game.player.getRect()):
done = True
break
for next in self.findAdjacents(current, game):
if self.lineOccupied(current, next, game): continue
if next not in came_from:
priority = self.heuristic(next, game)
frontier.put(next, priority)
came_from[next] = current
if not done:
self.path.clear()
else:
p = [current]
while came_from[p[-1]] is not None:
p.append(came_from[p[-1]])
self.path = p[::-1][1:]
i = 0
def findAdjacents(self, currentPoint, game):
d = 1 / game.pathfindingGranularity
for x in (currentPoint.x - d, currentPoint.x, currentPoint.x + d):
for y in (currentPoint.y - d, currentPoint.y, currentPoint.y + d):
if x == currentPoint.x and y == currentPoint.y: continue
elif self.canWalkAtCoords(x, y, game):
yield Point(x, y)
def canWalkAtCoords(self, x, y, game):
for nx in (x, x + self.hitbox.w):
for ny in (y, y + self.hitbox.h):
if game.blockAt(nx, ny) != FLOOR:
return False
return True
def lineOccupied(self, start, end, game):
for e in self.room.enemies:
if e is self:
continue
for xo in (self.hitbox.x, self.hitbox.x + self.hitbox.w):
for yo in (self.hitbox.y, self.hitbox.y + self.hitbox.h):
if game.lineInRect(start + Point(xo, yo), end + Point(xo, yo), e.getRect()):
return True
return False
I feel like there should be a much more efficient solution to this, especially seeing as the room is rectangular and there are no extra walls or obstacles that the enemies need to move around, but so far my searches for a solution have come up empty-handed. Are there some optimizations I could make to increase the performance of the program? Or if not, is there a better pathfinding method I should look into? Any help would be greatly appreciated!
You should try to have you path finding start from your character and fan out using a breadth-first-search (with some adjustment for slopes). Every time you come across an enemy, you can compute its optimal path toward the player.
That way you only do one pass across the whole board rather than one for each enemy.
Let me know if you want more details.

Why does open set never go to 0?

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.

JES image duplicating

I am working on a project where I need to print any image selected by the user a number of times selected by the user too.
My code is mostly working but I need to make a nested loop that will basically change the values for the Xpos and Ypos of the original image and change it to make the new picture.
def repeat(pic):
val = requestIntegerInRange("Enter 1-10", 1, 10)
print "The user entered :" + str(val)
w = getWidth(pic)
h = getHeight(pic)
print "Height and Width of this image are:", h, w
result = makeEmptyPicture(w, h * val)
xpos = 0
while(xpos < w):
ypos = 0
while(ypos < h):
pixel = getPixel(pic, xpos, ypos)
color = getColor(pixel)
loop = 0
while(loop <= val):
newX = xpos
newY = ypos + h * val
pixel2 = getPixel(result, newX, newY)
setColor(pixel2, color)
loop = loop + 1
ypos = ypos + 1
xpos = xpos + 1
Here val is the value selected by the user to print the image a number of times.
When I run my program with the above code it shows
The error value is:
Inappropriate argument value (of correct type).Height and Width of the Picture are : 208 146
getPixel(picture,x,y): y (= 1456) is less than 0 or bigger than the height (= 1455)
An error occurred attempting to pass an argument to a function.
You are doing newY = ypos + h * val where it should be newY = ypos + h * loop.
Moreover your loop on val should stop on loop < val not loop <= val. The last copy is unwanted (it's the (val + 1)th) and that's the part that makes you program fail I think.

Categories

Resources