Related
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.
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)
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
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.
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)