Using recursion in pygame - python

I'm new to python and programming in general and am working on a final project for a python centric class. One of the requirements that I cant seem to figure out how to make work is to integrate recursion into our code in order to show a working knowledge. I've worked up a simple "bullet hell" style game using pygame.
My goal is that when contact is made between a bullet and an enemy, that a series of bullet sets will be launched from the player position as a sort of short-term modifier.
This code runs in the main loop whenever a bullet hits an enemy:
for i in reversed(range(len(bullets))):
for j in reversed(range(len(enemies))):
if bullets[i].collided(enemies[j].rect):
del enemies[j]
del bullets[I]
s.global_score += 100
more_bullets(10)
#print("Hit!")
#print(s.global_score)
break
The "more_bullets" function is the focus of my recursion, and calls this:
def more_bullets(n):
if(n > 0):
spawnx = sq.rect.x+10 + sq.rect.width/2 - 10
b = Square(s.red, spawnx,sq.rect.y, 10,30)
b.direction = 'N'
b.player_speed = 10
bullets.append(b)
spawnx = sq.rect.x-10 + sq.rect.width/2 - 10
b = Square(s.red, spawnx,sq.rect.y, 10,30)
b.direction = 'N'
b.player_speed = 10
bullets.append(b)
pygame.display.update()
more_bullets(n-1)
print(f"Fired x {n}")
The outcome currently is that my debug does print 10 times making me think that the recursion is functioning correctly, however only one set of bullets is firing when the collision occurs. I'm thinking that all 10 bullets are firing faster than I can register it and just stacking on the screen.
Is there an easy-to-use function that might slow down the firing of the bullets? or have messed up something more fundamentally here?

I'm thinking that all 10 bullets are firing faster than I can register it and just stacking on the screen.
You're correct.
I don't believe there's an "easy way" to do what you're asking the way you think. The recursion is immediate, meaning that the function runs 10 times right away when it's called. For it to send out a burst of staggered bullets, you'd need some kind of timer or queue or something along those lines that runs alongside your main loop, and recursion isn't really a natural fit for that. The same will go for any kind of game logic function that plays out over a period of time.
This isn't what's being asked, but here's an idea of what you could do, even though it's kind of a redundant use of recursion: add a parameter to that function that dictates the angle the bullet is being shot. Then, add a little bit to that parameter on every recursive call. That way an arc of bullets will be shot at the same time.

Related

How to speed up or optimize for loop used for collision detection using pygame/pytmx?

I am using a for loop combined with .colliderect() for collision detection while attempting to make a game using pygame, the loop gets too slow with ~340 wall Rectangles, I was wondering if it could be faster somehow because it severely effects the game play loop?
I have tried using coordinate points on different places on every wall but only works if you're moving certain amounts of pixels at a time and every time you half the movement speed it quadruples the amount of coordinate points you save.
#disregard indent, this is all in an update function that is called every time that a player decides to move.
self._old_position = self.position
PlayerRectangle = pygame.Rect(self.position[0]+ x,self.position[1]+y,16,16)
cwd = os.getcwd()
tmxData = load_pygame(cwd+"\\Maps\\TestfileMap.tmx")
movement = True
for obj in self.walls:
if(pygame.Rect(obj[0],obj[1],16,16).colliderect(PlayerRectangle)):
movement = False
self.move_back()
else:
continue
if movement:
self.position[0] += x
self.position[1] += y
self.stats["position"]=self.position
self.rect.topleft = self.position
self.feet.midbottom = self.rect.midbottom
The provided code works, however it is too slow, I was wondering if there is a different method in collision detection or if there is a way to make what is shown faster, it bogs down things greatly. Thank you
EDIT:
So the solution was basically that I had load_pygame that ran literally every time it looped simply take out the line that does that and it clears things up a lot more, for further optimization change the line that makes a Rect for each object and just use a list of Rects that are already constructed, this limits function calls.
Without a minimal working example it is tough to give assertive advice.
As I stated in the comments, there is a spurious call to a "load_pygame" function that seems to read file data inside this code - that alone could be the cause of your slowdown. Moreover, the data read is not used in the collision detection.
The other advice I't have is to let your walls rectangles pre-calculated in a sprite group, instead of creating new rectangles for all walls in every check with:
pygame.Rect(obj[0],obj[1],16,16).colliderect(PlayerRectangle)). then you can use the sprite method "spritecollideany" - https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.spritecollideany - that should optimize the rectangle-collision verification.
(This will also require your Player object to be a Sprite if it is not already).

python curses while loop and timeout

I am having a hard time understanding the window.timeout() function. To be more specific, I am toying with a "snake" game in python:
s = curses.initscr()
curses.curs_set(0)
w = curses.newwin()
w.timeout(100)
while True:
move snake until it hits the wall
I understand that in this case, timeout(100) determines how fast the snake "moves", i.e. printing out new characters on the screen. However, I got stuck when I want to amend the code so that it waits until someone press "start". I wrote something like:
w.timeout(100)
while True:
if w.getch() is not start:
stay at the initial screen
else:
while True:
move the snake until it hits the wall
However, in this case, the timeout(100) seems to govern how long each time the program waits for w.getch(), not how long to wait between each time the snake moves. Also, I notice that in the first example, the timeout is declared at the top, outside the while loop. This looks weird to me because normally if I want to pause a while loop, I would put sleep() at the bottom inside the while loop.
If you want to pause between snake moves, you could use napms to wait a given number of milliseconds (and unlike sleep, does not interfere with screen updates). Setting w.timeout to 100 (milliseconds) is probably too long. If you're not concerned with reading function-keys, you could use nodelay to set the w.getch to non-blocking, relying on the napms to slow down the loop.
Regarding the followup comment: in ncurses, the wtimeout function sets a property of the window named _delay, which acts within the getch function, ultimately passed to a timed-wait function that would return early if there's data to be read.

Python thread executing function twice

Atm I'm working on a game using tkinter. I'm working on algorithm that moves enemies through the game map.
I will post only relevant lines, since the code is quiet hard-to-read.
I'm using class Timer imported from threading.
2 relevant functions are:
def add_enemies(self):
enemy = self.enemies.pop(0)
if enemy in '.':
Timer(1, self.add_enemies).start()
else:
self.move_enemy(enemy,set())
if not self.game_ended and self.enemies:
Timer(1, self.add_enemies).start()
def move_enemy(self, enemy, visited):
if type(enemy) == str:
if enemy in 'm':
enemy = Mongol(self.canvas, *self.start)
visited.add((enemy.x,enemy.y))
for move in (-1,0),(0,1),(1,0),(0,-1):
pos = (enemy.x + move[0], enemy.y+move[1])
if pos in self.way and pos not in visited:
print(pos)
enemy.move(*move)
enemy.take_shot(6)
visited.add(pos)
Timer(0.5, lambda: self.move_enemy(enemy, visited)).start()
This function should move the enemy, wait 0.5 second, then create a thread in which it recursively calls itself and moves the enemy again.
It might not be the best solution the create another thread inside the thread, but all other solutions made my GUI to freeze while executing the function.
Problem is, that Timer executes function move_enemy twice. Nothing else in the code can cause this problem.
Simple print test of enemy position shows this:
It simply moves the enemy twice, and increases its position twice as shown on the picture.
This is a wild guess, but substituting the enemy object with a new enemy object with a modified location in
Timer(0.5, lambda: self.move_enemy(enemy, visited)).start() might get the inbetween coordinates ((3,1),(5,1) and others). (If you do the same thing twice with the same input, you get two identical values.)
From the output I deduce: visited.add(pos) doesn't add positions to visited hence the if pos in self.way and pos not in visited: clause doesn't filter out already tested values (hence the (2,1)(2,1) printout). Try, if .append() works better.
I tried my best :D gl hf
Judging from the output, it looks like what's actually happening is that you were expecting things to execute in this order
print
move
print
move
print
move
print
move
....
but you failed to include enough synchronization and they are instead happening in this order:
print
print
move
move
print
print
move
move
....
It's not doing anything extra: it's just doing things not in the order you were hoping for.
Problem was, that tkinter is not thread safe hence it doesn't act deterministic.
I solved the problem by using Queue. My main thread, in which tkinter runs, periodically checks wheter or not it has something to draw.

Adding a countdown timer in python

I'll admit, i am a newbie with python, but here is my issue.
the version is 2.6.5 (i know i'ts an old version but there's reasons to this) and livewires is used
Bascially this game has a bunch of colored balloons in which you need to click them to make them disappear. Adjacent balloons of the same color disappear along with the clicked balloon. Once the balloons are cleared it moves on to the next level.
I need to create a timer on the top right of my screen. This timer needs to countdown in seconds (from 30 might be a good start.) However no matter what i try, either the timer does not display or the numbers are overlap eachother. I would like to know how to do this, as it has been driving me up the wall as of late.
...Of course it also needs to end the game if it reaches zero and add more time if the level is complete...
But for now i just want to focus on displaying the timer and having it count down to zero on screen.
class Timer(games.Sprite):
""" countdown timer """
def __init__(self):
timer_message = games.Text(
value = 30,
size = 50,
color = red,
x = 600,
y = 30
)
def start(self):
while self.timer_message.value != 0:
time.sleep(1)
self.timer_message.value -= 1
game.screen.add(timer_message)
Alright. I fixed the "compressing balloons table" (accidentally deleted the self_update lol) problem, but now it is saying that "global name timer_message is not defined"... despite the fact that it says timer_message = games.Text
I would paste the whole code, but i can't get the indentation right (this is my first time using this website.)
So, I understand it's been some time and if you don't need an answer anymore that's alright.
For now it's hard to answer your question in general because I don't understand the structure of the rest of your code or how you're displaying graphics. However, I can tell you while you're getting the
global name timer_message is not defined
error. It's because when you define timer_message within the __init__ function you are defining it within the local scope of the function but not for the class. In order to make it accessible to the class you need to assign to self.timer_message.
This is a consequence of how python imitates object oriented programming, but making this change should address your immediate error.

Why is turtle lightening pixels?

My program for creating a Mandelbrot set has a bug: whenever the pen changes colors, and every 42nd pixel after that, is lighter. This is, rather coincidentally, a mandelbug (yes, I just learned that term), as it is inconsistent for many pixels near an "edge" (it might actually be blurred between the color it's supposed to be and the color the last, or next, pixel is supposed to be), but it's always the 42nd pixel after that one until the next color change. I am using OSX 10.6.8, PYTHON 2.7. When I wrote this program at school, it worked perfectly (Windows), and then I sent it to myself, and worked on it a little more (mostly just making the sample size and therefore image larger), and ran it, I got this bug. EDIT: My bad, I forgot to mention that this only happens with my Mandelbrot program, the few other turtle programs I have at home are fine.
Parts of screenshots (so that you don't have to wait forever while the program runs to see what I'm talking about):
From my first version from home:
From the current version (sideways):
Heres the code:
import turtle
import math
turtle.speed(0)
def benoit(onelen):
turtle.left(90)
for x in range(-2*onelen, onelen):
turtle.up()
turtle.goto(x, int(-1.5*onelen)-1)
turtle.down()
for y in range(int(-1.5*onelen)-1, int(1.5*onelen)-1):
z = complex(0,0)
c = complex(x*1.0/onelen,y*1.0/onelen)
for k in range(20):
z = z*z+c
if abs(z) > 2:
g = .2 + .8*(20-k)/20
break
if k == 19:
g = 0
turtle.pencolor(0,g,0)
turtle.forward(1)
benoit(250)
x = raw_input("Press Enter to Exityadayadayada")
EDIT: A fix has been suggested by DSM, who likes this bug. However, I have no experience editing Python source code, and all the underscores are making me nervous. Can someone tell me specifically what to edit and/or how?
Wow. I think this is one of my favourite bugs ever, and believe it or not, the fact that the number happens to be 42 is actually relevant! Well, peripherally, anyhow..
In turtle.py:
def _goto(self, end):
"""Move the pen to the point end, thereby drawing a line
if pen is down. All other methodes for turtle movement depend
on this one.
[...]
###### vererbung!!!!!!!!!!!!!!!!!!!!!!
self._position = end
if self._creatingPoly:
self._poly.append(end)
if len(self.currentLine) > 42: # 42! answer to the ultimate question
# of life, the universe and everything
self._newLine()
self._update() #count=True)
So the problem comes about when it decides to break a line, apparently for performance reasons:
def _newLine(self, usePos=True):
"""Closes current line item and starts a new one.
Remark: if current line became too long, animation
performance (via _drawline) slowed down considerably.
"""
I was able to "fix" the bug by bumping up the linenumber limit and/or scattering self._pencolor references in places that didn't have any. But you're not crazy, anyway, and it's not really anything that you're doing. :-)
Can i offer a suggestion?
i tried your code and it was taking forever to run which you are aware of but what you may not be aware of is the tracer function... i simply put at the beginning of your code:
wn=turtle.Screen()
wn.tracer(10000)
that also eliminates the need for the speed(0) function :)
Try that and run it again, i did and it rendered the whole image in 62 seconds, i timed it by importing the time module by putting this code at the beginning:
import time
st=time.time()
and this code at the end:
print time.time()-st
Well done by the way, Ive just made my own thats a lot slower and lower quality then yours but was using an array of the square shape and stamping to each location i wanted in the array lol, but will be trying to improve it in the future as i only found out turtle existed less then a week ago.
One last thing, if you type:
from turtle import *
instead of "import turtle" you dont need to put turtle at the beginning of every function call :) same thing goes for every other module.
Ive included the pic of your fractal that took 62 seconds to render on my machine thats not even that powerfulYour code run on my weak machine.
I hope all this helps you greatly. also youll notice i dont have that light line problem, not sure if you fixed that issue in the original code up top?

Categories

Resources