In my game, I have a population of fish (10). Each fish has a line of sight (an arc object in front of them). There are sharks, which are predators (they are moving around the room randomly). Each fish is assigned an intelligence number at the beginning. When a shark enters in a fish' line of sight, I want the fish to reverse his direction (or at least get away in the best direction) only if his intelligence number is greater than a random generated number.
The issue is that when I try to implement this and the shark gets in the line of sight of the fish, pygame keeps detecting a collision, so python keeps generating a random number. For example, a fish might have a very low intelligence level, but still has a very high probability to escape from the shark because python keeps detecting the collision, and it has many tries to pass the bar. For that example, I'd actually want the fish to very likely not change directions when the shark is in the line of sight.
Ideally, I want python to detect the collision once, and of course detect again if the shark goes through the line of sight again a separate time.
Here is my code, don't know how much it'd help:
class RedFish(pygame.sprite.Sprite):
def __init__(self, newDNA):
pygame.sprite.Sprite.__init__(self)
self.direction = random.uniform(0, math.pi*2)
self.speed = 2
self.intelligence = 100
def update(self):
self.rect.x -= math.sin(self.direction) * self.speed
self.rect.y -= math.cos(self.direction) * self.speed
for shark in sharks:
if pygame.sprite.collide_mask(arcsList[self.arcIndex], shark):
temp = random.randrange(400)
if (temp < self.intelligence):
self.direction = self.direction*-1
Thanks!
You can store the last shark that the fish "saw" and compare that in each iteration of the update loop.
If the fish sees the same shark again, don't recalculate.
If the fish sees another shark, recalculate and remember that shark.
If it sees no sharks, reset its "memory".
Note: I've intentionally left out a lot of your calculation code to keep this example as simple as possible.
class RedFish(pygame.sprite.Sprite):
def __init__(self, newDNA):
self.last_shark_seen = None
def update(self):
# track how many sharks we have collisions with
shark_collisions = 0
for shark in sharks:
if pygame.sprite.collide_mask(arcsList[self.arcIndex], shark):
shark_collisions += 1
if shark != self.last_shark_seen:
self.last_shark_seen = shark
# calculate new direction here
break
# no collisions means no sharks seen, reset the fish's memory
self.last_shark_seen = None
You need to track when the shark was seen, or which shark was seen. Basically update the collision detector one tenth (or 12th or 100th) as often as you update position.
Or, once a fish fails to see a shark, it's doomed, so you can add that list to a list of sharks it's seen and failed to avoid (in case it sees another shark), then when you update you can just go through the list saying that in order to change direction, a fish must beat the random number generator and also not have lost to the shark already
You could try a different random distribution, maybe something that skews left, so even if a dumb fish had multiple tries, the combined probability would still be low
Then again, if you're trying to simulate "real" fish, some might be slower to react, but they still get multiple chances.
Maybe you could shorten the arc?
Related
I'm trying to find a way to have enemies track the player in my 2d game (pygame) but not clump up
Currently, when I shoot at them, the bullet collides into and damages all of the enemies that are clumped. I would like it to be a hoard but spread out just enough to where I can't hit every single enemy at once
It looks like this
Here's a gif of them clumping
I'm not sure how I would get the individual values of the enemies' positions so I can move them when they collide or how I should move them
This is what I currently have for the enemies to track the player:
for aliveEnemies in enemy:
if playerObj.rect.x - aliveEnemies.rect.x != 0:
if playerObj.rect.x > aliveEnemies.rect.x:
aliveEnemies.rect.x += 1
if playerObj.rect.x < aliveEnemies.rect.x:
aliveEnemies.rect.x -= 1
if playerObj.rect.y - aliveEnemies.rect.y != 0:
if playerObj.rect.y > aliveEnemies.rect.y:
aliveEnemies.rect.y += 1
if playerObj.rect.y < aliveEnemies.rect.y:
aliveEnemies.rect.y -= 1"
Any help or points in the right direction would be greatly appreciated
You can do collision detection between the enemies, to determine which ones are too close. You'll also need to change their behavior, to decide what to do when they actually get too close.
If you know you'll never get too many enemies, you can try comparing every enemy with every other enemy. This will take O(N^2) work, but that is probably OK if N is limited.
If you are comparing every enemy to every other anyway, you have a wider variety of options than just "collision detection": like the Boids algorithm (which does collision avoidance instead).
Pygame rect objects have a function called "colliderect" which tests whether two rect objects are overlapping: https://www.pygame.org/docs/ref/rect.html#pygame.Rect.colliderect
You can use this to test each enemy whether they're overlapping any other enemy before moving them.
I'm having trouble designing an algorithm for a traversal problem.
I have a Ship that I control on a 2D grid and it starts on the very bottom of the grid. Each tile of the grid has a value (between 0 and 1000) equal to how much 'resource' is in that tile.
The Ship can go_left(), go_up(), go_right() or stay_still()
If the ship stay_still() it collects 25% of it's current tile's resource (rounded up to the nearest int).
If the ship uses a move command, it needs to spend 10% of it's current tile resource value rounded down. Moves that cost more than the ship has collected are illegal. (So if a ship is on a 100, it costs 10 to move off the 100, if it's on a 9 or less, moving is free).
The goal is to find a relatively short path that legally collects 1000 resource. Returning a list of the move order to corresponds to the path.
I naturally tried a recursive approach:
In sudo-code the algorithm is:
alg(position, collected, best_path):
if ship has 1000:
return best_path
alg(stay still)
if ship has enough to move:
alg(try left)
alg(try up)
alg(try right)
If you want a closer look at the actual syntax in python3 here it is:
def get_path_to_1000(self, current_position, collected_resource, path, game_map):
if collected_resource >= 1000:
return path
path_stay = path.copy().append(stay_still())
self.get_path_to_1000(current_position, collected_resource +
math.ceil(0.25 * game_map[current_position].value),
path_stay, game_map.copy().collect(current_position))
cost = math.floor(0.1 * game_map[current_position].value)
if collected_resource >= cost:
direction_list = [Direction.West, Direction.North, Direction.East]
move_list = [go_left(), go_up(), go_right()]
for i in range(3):
new_path = path.copy().append(move_list[i])
self.get_path_to_1000(
current_position.offset(direction_list[i]),
collected_resource - cost, new_path, game_map)
The problem with my approach is that the algorithm never completes because it keeps trying longer and longer lists of the ship staying still.
How can I alter my algorithm so it actually tries more than one option, returning a relatively short (or shortest) path to 1000?
The nature of this problem (ignoring the exact mechanics of the rounding down/variable cost of moving) is to find the shortest number of nodes in order to acquire 1,000 resources. Another way to look at this goal is that the ship is trying to find the most efficient move with each turn.
This issue can be solved with a slightly modified version of Dijksta's algorithm. Instead of greedily choosing the move with the least weight, we will choose the move with the most weight (greatest number of resources), and add this value to a running counter that will make sure that we reach 1000 resources total. By greedily adding the most efficient edge weights (while below 1000), we'll find the least number of moves to get a total 1000 resources.
Simply keep a list of the moves made with the algorithm and return that list when the resource counter reaches 1000.
Here's a helpful resource on how to best implement Dijkstra's algorithm:
https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/
With the few modifications, it should be your best bet!
I write a simple program in python which includes moving my mouse (I do his with PyUserInput).
However: It is only allowed to move the mouse in integer steps (say pixels).
So mouse.move(250.3,300.2) won't work.
I call the move function about 30 times in a second and move the mouse a few pixels. The speed with which I move the mouse varies from 0.5-2.5px/call. Rounding gives me 1-3 (move only want ints) which does not really represent the speed.
I search for a solution (maybe generator?) which takes my current speed (e.g. 0.7px) and gives me back a pattern (like a PWM Signal) out of 0 and 1 (e.g. 1,1,0,1,1,0...) which yields the 0.7px in average.
However this generator has to be adaptive because speed is constantly changing.
I am quite new to python and stuck with the last point: The variability of the generator function.
Here is what I have so far:
# for 0.75px/call
def getPWM(n):
nums = [1,0,1,1]
yield nums[n%4]
What you need to do is keep track of the previous position and the desired current position, and hand out the rounded coordinate. You could track the previous position in a function but it's much easier to do it in a class.
class pwm:
def __init__(self):
self.desired_position = 0.0
self.actual_position = 0
def nextPWM(self, speed):
self.desired_position += speed
movement = round(self.desired_position - self.actual_position)
self.actual_position += movement
return movement
I'm trying to create a game in which 100 rocks are scattered through a large series of coordinates randomly. The problem is that sometimes the rocks may overlap each other. The solution I came up with was to check if the rocks overlap each other, and if they do, create new randomly generate coordinates for the rock again and again until it collides with nothing. I'm not entirely sure how to do this; I've tried detecting collision between the rock and the rock list, but all that ends up happening is that it thinks that it collides with itself and always returns as True. The __init__ function generates the coordinates with random.randint(-2500,2500). When the rocks are created in the for loop, each rock is added to the list. In the update function it checks for rectangular collision between the rocks. How can I fix this? Thanks, an answer would be much appreciated. Ask me if you need more information.
Well, I guess there are a few ways you can approach this problem:
1) The first would be the one you already used, you would check for collisions each time you generate a rock and if a collision exist you would regenerate a position for the given rock
2) The second one would be to slice your "field" into rectangles of the size of the rock, just like a tilemap, therefore creating a list of possible positions, something like this:
possible_positions = [(i, j) for i in range(x_size_field//x_size_rock) for j in range(y_size_field//y_size_rock)]
for i in range(n_of_rocks):
rock_pos = random.choice(possible_positions)
possible_positions.remove(rock_pos)
But this approach would implicate in a given set of possible positions that make a "uniform" rock distribution
3) So if its really necessary to make put the rocks on absolute random positions you could create a list of possible positions like the following:
possible_positions = [[(i, j) for j in range(y_size_field-y_size_rock)] for i in range(x_size_field-x_size_rock)]
for i in range(n_of_rocks):
# X and Y represente positions on the list not on the field
x = random.randint(0, len(possible_positions))
y = random.randint(0, len(possible_positions[x]))
# The rock positions
rock_pos = possible_positions[x][y]
# Now we remove the positions on the region of the new rock
for i in range(x,x+x_size_rock):
possible_positions[i] = possible_positions[i][0:y] + possible_positions[i][y+y_size_rock:-1]
if [] in possible_positions:
possible_positions.remove([])
Of course this code may generate errors (its a rather simple code) and it needs some optimizations, but i think you may get the general ideia from this.
Sorry about my english
BACKGROUND INFO, DON'T HAVE TO READ IF YOU'D JUST LIKE TO VIEW THE PROBLEM WITH CODE BELOW:
I hope everyone is familiar with the game of sticks or "nim." If not, you set a starting amount of sticks (between 10 and 50) and draw (1-3 sticks) until there aren't any sticks left, declaring the one who pulled the last stick the loser. In my programming class we've also included the option of playing against the AI. But, the AI is no longer a dummy who randomly picks a number 1-3. Now he learns from each of his turns.
Implementation:
The AI has a bucket for each of the number of sticks left. There is a bucket for 1 stick
left, 2 sticks, 3 sticks, etc.
At the beginning of the game each bucket has 3 balls in it. Each marked with the choice
1, 2 or 3. These represent the AI’s choice of picking up 1, 2 or 3 sticks.
During the AI’s turn, it takes a random ball from the bucket representing the number of
sticks left. It reads the ball and removes that number of sticks from the pile. It then
places the ball in front of the bucket.
If the AI wins the game, then it goes through all of its choices and puts two balls back for
the chosen number for each choice it made. Increasing its chances of choosing that ball
the next time it’s faced with a choice with the given number of sticks.
If the AI loses, then it throws the ball away next to the buckets. However, if the chosen
ball is the last choice then it puts it back into the bucket. The bucket must contain at
least one of each number. So if the user chose a ball that had a number of sticks to
pick from a bucket, and it was the last ball of that choice, then if the AI loses, it must put
that ball back. It can never remove any of the choices completely from the buckets.
As more games are played the AI will reinforce good choices with extra balls for winning
sticks picked up."
Here's the code I'm working with right now.
choice=random.randint(1,maxchoice) #computer picks a random number
bucketnum=sticks #bucket to take ball from
pullnum=choice #ball to take
for i in bucket:
for bucket[bucketnum] in i:
bucketnum.pop(pullnum)
print(bucket[bucketnum])
The bucket that I'd be taking the ball out of would essentially be the number of sticks left, I'm just having trouble finding a specific bucket in the bucket list and taking out the ball. Right now I get an error message saying that bucketnum.pop(pullnum) - 'int' object has no attribute to 'pop'? This is the bucket code (lists within a list):
bucket=[]
for j in range(51):
bucket.append([1,2,3])
I may be totally confusing but if anybody has any advice or even questions for clarification, please do reply. Thanks all.
EDIT:
Here's some more code, sorry, stupid of me to refrain from adding the definitions of variables, etc.
if option==2:
sticks=""
while True:
try:
sticks=int(input("Enter the number of sticks to begin with: "))
if sticks>=10 and sticks<=50:
print("Alright, there are",sticks,"sticks in the pile.")
break
else:
print("You mut enter an integer. (10-50)")
except ValueError:
print("You must enter an integer.")
player1=True
while sticks>0:
maxchoice=min(3,sticks)
choice=-1
countbucket=0
if player1:
while choice<1 or choice>maxchoice:
try:
choice=int(input("Player 1, how many sticks would you like to take? (1-3): "))
if choice>=1 and choice<=3:
sticks-=choice
print("There are",sticks,"sticks remaining.")
else:
print("You must enter an integer from 1-3.")
except ValueError:
print("You must enter an integer.")
player1=not player1
else:
choice=random.randint(1,maxchoice)
bucketnum=sticks
pullnum=choice
for i in bucket:
for bucket[bucketnum] in i:
bucketnum.pop(pullnum)
print(bucket[bucketnum])
sticks-=1
print("Computer drew",choice,"stick(s). There are",sticks,"sticks remaining.")
player1=not player1
if player1==False:
print("Player 1 took the last stick.\nComputer wins!")
else:
print("Player 1 wins!")
This is option 2 in my program, as option 1 is Player 1 vs. Player 2. Obviously I haven't gotten very far with the implementation of the AI intelligence, it's a bit tricky.
-----> Fred S., I'm just getting started and having issues getting the mental wheel spinning. What's excerpted isn't all of the code. I'm not asking how to complete the assignment at this point, though tips on executing this new intelligent AI code would be helpful, but in this case it's more a focus on figuring out list indexing.
It looks like you're assigning the variable in the inner for loop to 'bucket[bucketnum]'. Which surprises me that that's not a syntax error, but I don't think that's what you're trying to actually do.
If you're dealing with a nested list, and the position in the list corresponds to the number of sticks left, then you want to index that list by the position in order to get that bucket, instead of iterating over that list to find it.
If you think of it like this:
buckets = [[1,2,3], ..., ..., ...]
Then the bucketnum is the position of the bucket in the list of buckets. So, in your case, if you want to grab the bucket for '26' sticks, you would access it by indexing buckets by that number.
buckets[25] # 25 since you're counting from 0+
At this point, you have the bucket in question, and can pop the choice from it.
bucket = buckets[25]
bucket.pop(pullnum)
You didn't define option
You didn't import the random library.