Matplotlib.pyplot not updating dynamic line graph using set_data - python

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)

Related

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.

Drawing a tan-wave in paint using pyautogui and math

I am having a difficulty in implementing drawing a tan function in python since it doesn't look smooth, and when having reached 90/180, it doesn't look nice, so im looking for some improvements on this part.
def tan_wave():
pyautogui.click()
theta = 0
step = 10
length = 10
curr_x, curr_y = pyautogui.position()
prev = (curr_x, curr_y)
decrease_flag = False
while theta <= 180:
if (theta % 90) == 0:
theta -= 0.99999
decrease_flag = True
x = curr_x + theta
y = curr_y - math.tan(math.radians(theta))*length
print(x, y, theta)
pyautogui.click(*prev)
pyautogui.dragTo(x, y)
pyautogui.click(x, y)
prev = (x, y)
if decrease_flag:
theta += 0.99999
decrease_flag = False
theta += step
print('Done')
tan_wave()

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.

Detect which cube a user is looking at?

I am making game sort of like Minecraft using python. I have a world that the user can walk around and look around in but I don't know how to make it so they can break and place blocks.
I need to know how to calculate the block that they are looking at from a 3d array of the blocks in the world (blocks, format:[[[a,b,c],[d,e,f],[g,h,i]],[[j,k,l],[m,n,o],[p,q,r]],[[s,t,u],[v,w,x],[y,z,0]]]), their position (x,y,z) and head rotation (xrot,yrot).
I also only need it in a certain distance away from where they are, maybe 5 blocks. I tried to find a function for a line and kind of follow it but that didn't work out and I looked around on the internet and I couldn't find what I needed.
I need to be able to figure out which block they would break or where a new block would go based of the side they are looking at.
I need to find which face of which cube I am looking at. This is the code I made but some of the math must be off because it isn't working.
def get_looking_at(xrot, yrot, xpos, ypos, zpos, blocks, reach):
xrot, yrot = math.radians(xrot), math.radians(yrot)
xform = sin(xrot)*cos(yrot)+xpos
yform = sin(yrot)+ypos
zform = -(cos(xrot)*cos(yrot))+zpos
xforward = xform-xpos >= 0
yforward = yform-ypos >= 0
zforward = zform-zpos >= 0
if xforward:
xset = [floor(x+xpos+.5)+.5 for x in range(reach)]
else:
xset = [floor((-x)+xpos+.5)-.5 for x in range(reach)]
if yforward:
yset = [ceil(y+ypos) for y in range(reach)]
else:
yset = [floor((-y)+ypos) for y in range(reach)]
if zforward:
zset = [floor(z+zpos+.5)+.5 for z in range(reach)]
else:
zset = [floor((-x)+xpos+.5)-.5 for x in range(reach)]
xint = []
yint = []
zint = []
for x in xset:
y = ((yform-ypos)*x)/(xform-xpos)
z = ((zform-zpos)*x)/(xform-xpos)
xint.append((x, y+ypos, z+zpos))
for y in yset:
x = ((xform-xpos)*y)/(yform-ypos)
z = ((zform-zpos)*y)/(yform-ypos)
yint.append((x+xpos, y, z+zpos))
for z in zset:
x = ((xform-xpos)*z)/(zform-zpos)
y = ((yform-ypos)*z)/(zform-zpos)
zint.append((x+xpos,y+ypos,z))
intercepts = dict()
for pos in xint:
intercepts[(pos[0]-xpos)**2+(pos[1]-ypos)**2+(pos[2]-zpos)**2] = (pos[0], pos[1], pos[2], "x")
for pos in yint:
intercepts[(pos[0]-xpos)**2+(pos[1]-ypos)**2+(pos[2]-zpos)**2] = (pos[0], pos[1], pos[2], "y")
for pos in zint:
intercepts[(pos[0]-xpos)**2+(pos[1]-ypos)**2+(pos[2]-zpos)**2] = (pos[0], pos[1], pos[2], "z")
indices = [x for x in intercepts]
indices.sort()
for index in indices:
connection = intercepts[index]
if xforward:
x = floor(connection[0]+.5)
xdir = "e"
else:
x = ceil(connection[0]-.5)
xdir = "w"
if yforward:
y = floor(connection[1])
ydir = "d"
else:
y = floor(connection[1])+1
ydir = "u"
if zforward:
z = ceil(connection[2]-.5)
zdir = "n"
else:
z = floor(connection[2]+.5)
zdir = "s"
print(x,y,z)
try:
if blocks.get_data(x, y, z) != None:
if math.sqrt(index) <= reach:
if connection[3] == "x":
return x, y, z, xdir
if connection[3] == "y":
return x, y, z, ydir
if connection[3] == "z":
return x, y, z, zdir
else:
return
else:
continue
except IndexError:
continue
return
You could make a sphere of contact around the player and use a radius "protruding" from the player's face.
Radius r would be the maximum distance the user could look at a block and still be able to affect it.
Using triangles you could detect if the end of the radius is inside a block or not, etc.

How to move pygame rects in a list?

So I am trying to create a series of "boids" at random locations, which fly at random speeds, but I am having some trouble moving the rects which are in a list, although I can draw them. I am using a provided vector module, the entire code and the module can be found here. The png I am using for the sprites.
Update: I got a rect moving, by using the instance position vector instead of the class vector. But now only one boid is drawn. I suspect that more boids are drawn at the same exact position.
class Boid():
def __init__(self, screen):
self.bird = pygame.image.load("birdie.png")
self._pos = Vector2D(random.randint(0, screen.get_width()),
random.randint(0, screen.get_height()))
self._vel = Vector2D((random.randint(1, 10) / 5.0),
(random.randint(1, 10) / 5.0))
self.speed = random.randint(1, 5)
self.bird_rect = self.bird.get_rect(center=(self._pos.x, self._pos.y))
self._boids = []
def add_boid(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self._boids.append(Boid(screen))
def move_boids(self):
s = Screen()
#self.bird_rect.move_ip(self._vel.x, self._vel.y)
self._pos += (self._vel * self.speed)
#bounds check
if self._pos.x + self.bird_rect.width >= s.width:
self._pos.x = s.width - self.bird_rect.width
self._vel.x *= -1
elif self._pos.x <= 0:
self._pos.x = 0
self._vel.x *= -1
if self._pos.y - self.bird_rect.height <= 0:
self._pos.y = self.bird_rect.height
self._vel.y *= -1
elif self._pos.y >= s.height:
self._pos.y = s.height - self.bird_rect.height
self._vel.y *= -1
def draw_boids(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
print(len(self._boids))
for boid in self._boids:
self.boidRect = pygame.Rect(self.bird_rect)
#edit: changed boid._pos.x and y to self._pos.x and y
self.boidRect.x = self._pos.x
self.boidRect.y = self._pos.y
screen.blit(self.bird, self.boidRect)
You have to iterate over all boids in the self._boids list and update their _pos and bird_rect attributes to move them.
def move_boids(self):
s = Screen()
for boid in self._boids:
boid._pos += boid._vel * boid.speed
boid.bird_rect.center = boid._pos
# Bounds check.
if boid._pos.x + boid.bird_rect.width >= s.width:
boid._pos.x = s.width - boid.bird_rect.width
boid._vel.x *= -1
elif boid._pos.x <= 0:
boid._pos.x = 0
boid._vel.x *= -1
if boid._pos.y - boid.bird_rect.height <= 0:
boid._pos.y = boid.bird_rect.height
boid._vel.y *= -1
elif boid._pos.y >= s.height:
boid._pos.y = s.height - boid.bird_rect.height
boid._vel.y *= -1
You can also simplify the draw method a bit.
def draw_boids(self):
# Blit all boids at their rects.
for boid in self._boids:
screen.blit(boid.bird, boid.bird_rect)

Categories

Resources