Python/Pygame Making An Object Follow Another Object - python

I Have Been Trying for days on how to do this. Basically You Control A Player, And zombies follow you.
Problem Is, I Cant Seem To Get The Zombies To Follow! I Tried If Statements For Example
if playerx > zombiex:
zombiex=zombiex - 2
screen.blit(zombie,(zombiex,zombiey))
aaaaaand That Didnt Work.... :/
Any Ideas?

Maybe this is what you searched for.
def length(x, y):
return (x ** 2 + y ** 2) ** .5
def norm(x, y):
_len = length(x, y)
return x / _len, y / _len
class ZombieController(object):
def __init__(self, zombie_view_range):
self._zombs = []
self.append = self._zombs.append
self._range = zombie_view_range
def NextFrame(self, player_pos):
px, py = player_pos
_range = self._range
for zombie in self._zombs:
x, y = zombie.position
dx, dy = px - x, py - y
_len = length(dx, dy)
if _len <= _range:
speed = zombie.speed
direction = norm(dx, dy)
zombie.positon = x + direction[0] * speed, y + direction[1] * speed

First, can't answer your whole questions as there's not enough information. How doesn't it work exactly?
Second, if you want the zombies to follow, you need thier coordinates to converge with the players so you need something like:
if playerx > zombiex:
zombiex = zombiex + max(zombiespeed, playerx - zombiex)
elif playerx < zombiex:
zombiex = zombiex - max(zombiespeed, zombiex - playerx)
NB:
I replace 2 with zombiespeed which you define elsewhere so you can change the speed in one place for future.
I use max() to ensure the zombie won't move PAST the player when very close.
You'd obviously do the same for the y direction too.

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.

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.

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)

Preventing a step back in python for rhino

Im trying to make a stepping function in python for rhino,
The function is supposed to make a step in a random direction, without going backwards.
How do i prevent a step back?
import rhinoscriptsyntax as rs
import random as r
r.seed(seed)
class Walker:
def __init__(self):
self.x = 0
self.y = 0
def point(self):
shape = rs.AddPoint(self.x, self.y, 0)
return shape
def step(self):
choice = r.randint(0,3)
choice = r.randint(1,3)
if (choice == 0):
self.x = self.x + r.choice(uList)
elif (choice == 1):
self.x = self.x - r.choice(uList)
elif (choice == 2):
self.y = self.y + r.choice(uList)
else:
self.y = self.y - r.choice(uList)
uList = [8,11,14]
w = Walker()
pList = []
for t in range(time):
w.step()
pList.append(w.point())
for t-1 in range(time):
a = pList
This line:
choice = r.randint(0,3)
chooses one of 4 directions randomly. But if you don't want to go backwards, then you only want forward, left and right. So change the parameters to randint() so that it only chooses from 3 possible numbers, avoiding the one that corresponds to the direction you are calling backwards, whichever that is.

Collision issue on Python snake game

Have an issue with a game of snake in Python im making where my collision check isnt working. I have written a function to check a collision between the snake and the food itself. When it collides it doesnt do anything, i have written it to undraw if it collides using the function, i also put in a print function to see if the function was working if i was using it and saw no print.
def collide(block1,block2):
if math.sqrt(((block2.getCenterX() - block1.getCenterX()) **2)+ ((block2.getCenterY() - block1.getCenterY())**2)) < BLOCK_SIZE:
print("true")
return True
else:
return False
------------------------------------------------------- not part of functiom
if collide(theSnake[0],food) == True:
food.undraw()
foodX = random.randint(BLOCK_SIZE, WIN_WIDTH-BLOCK_SIZE)
foodY = random.randint(BLOCK_SIZE, WIN_HEIGHT-BLOCK_SIZE)
food.draw()
theSnake.append(block)
else:
foodX = foodX
foodY = foodY
I'd suggest you modify your collide function to provide more information. eg.
def collide(block1,block2):
dx = block2.getCenterX() - block1.getCenterX()
dy = block2.getCenterY() - block1.getCenterY()
dist = math.sqrt(dx ** 2 + dy ** 2) # equivalent to math.hypot(dx, dy)
if dist < BLOCK_SIZE:
print("true")
return True
else:
print("false", dx, dy, dist)
return False

Categories

Resources