0

I am trying to make a space invaders game using turtle, and in order to move the bullet up the screen I am trying to update the y coordinate of the bullet every 0.5 seconds. I tried to do this with a while loop and the time.sleep() function, but everytime I try to fire a bullet, the program crashes. Is there any way I can stop it from doing this? Or why is it not working? Is there an equally effective method that will not crash my program?

import turtle
import time

userx = 0

x = 0
y = -300

enemies = [(300,50, "left"), (400,50, "left"), (350, -50, "right")]

bullets = []

gameover = False

def frame():
    pass
    ship = turtle.Turtle()
    ship.pu()
    ship.ht()
    ship.setpos(userx, -300)
    ship.pd()
    ship.circle(5)

    bullet = turtle.Turtle()
    bullet.pu()
    bullet.ht()
    bullet.setpos(x, y)
    bullet.pd()
    bullet.circle(2)

def key_left():
    global userx
    pass
    userx += -10
    print(userx)

def key_right():
    global userx
    pass
    userx += 10
    print(userx)

def key_space():
    pass # your code here
    global x
    global y
    global bullets
    x = userx
    while y <= 300:
        time.sleep(0.5)
        y += 4
    else: y = 0
    bullets += (x,y)

def physics():
    global bullets
    global enemies
    pass

def ai():
    global enemies
    global gameover
    pass

def reset():
    global enemies
    global bullets
    global userx
    global gameover
    pass

def main():
    turtle.tracer(0,0)
    turtle.hideturtle()
    turtle.onkey(key_left, "Left")
    turtle.onkey(key_right, "Right")
    turtle.onkey(key_space, "space")
    turtle.listen()
    reset()
    while not gameover:
        turtle.clear()
        physics()
        ai()
        frame()
        turtle.update()
        time.sleep(0.05)

main()
Billiam
  • 165
  • 1
  • 13
  • Are you sure it's not crashing because of that `pass` ? – F. Leone Dec 09 '17 at 18:05
  • We can't help you with just this snippet of code, we're going to need more context. Also, don't use `time.sleep`, but use the default tkinter display loop (like [`after`](http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.after-method)). – Daniel Dec 09 '17 at 18:05
  • @F.Leone, it's not the pass I got rid of it just now to check and the problem persists – Billiam Dec 09 '17 at 18:07
  • There is no draw command inside the while - it will sleep 1 second and increase y until it reaches 301 or more. SInce when can you combine a `else:` with `while` - I have never seen that before.... Is there some kind of async draw using y running beside that? wow: [else on while](https://stackoverflow.com/questions/3295938/else-clause-on-python-while-statement) - learned smth. – Patrick Artner Dec 09 '17 at 18:07
  • 1
    `the program crashes` - that's not very descriptive, can you explain that better? – wwii Dec 09 '17 at 18:09
  • @Coal_ i updated the question to add the entire code as it is written thus far – Billiam Dec 09 '17 at 18:11
  • use [ontimer()](https://docs.python.org/3.6/library/turtle.html#turtle.ontimer) to execute function in intervals - and you will no need `sleep` (ontimer use time in milliseconds 0.5s = 500ms) – furas Dec 09 '17 at 18:11
  • @wwii when I run the game and hit space bar to trigger the bullet function, a single bullet will appear stationary above the ship and then the launcher will freeze and remain frozen until i restart the program – Billiam Dec 09 '17 at 18:12
  • turtle (similar to GUI frameworks) runs mainloop (event loop) which gets events from system, sends event to object, redraws items, etc. If you run long-running function (ie. you use `sleep` or `while` loop) then it doesn't returns to mainloop and mainloop can't get events, updates items, etc., and it looks like it freezes – furas Dec 09 '17 at 18:18
  • @furas where would i use the ontimer function? i'm trying not to call any turtle functions outisde of the frame() function (if possible) and the bullets themselves are not actually turtles. – Billiam Dec 09 '17 at 18:18
  • inside `key_space` you could use ie. ontimer(500, move_bullet) to run new function `move_bullet`) and `turtle/mainloop` will run it every 500ms but it will not block mainloop - and you don't need `while` inside. – furas Dec 09 '17 at 18:22
  • I see you have `while not gameover:` so you will no need `ontimer()` - `while not gameover:` can execute function which will move bullets in evry loop. – furas Dec 09 '17 at 18:43

2 Answers2

1

I would:

def key_space():
    # simply spawn a new bullet and put it into your bullets list 

def advance_bullet(): # call this after paintnig all bullets in frame 
    # iterate over all bullets inside bullets, advance the Y position by 3

def frame(): 
    # add code to paint all bullets inside bullets - and call advance_bullets()

In the code that checks for collision with enemies:

# remove a bullet from bullets when its outside your screen (or passed all enemies max y coord) - no need to track that bullet anymore

if you ned to advance the bullets slower then your main loops intervall, make a "framespassed" counter and see if framespassed % something == 0 and only then advance your bullets.

Adapted to what you already have

You need to change these parts:

def frame():
    global bullets
    pass
    ship = turtle.Turtle()
    ship.pu()
    ship.ht()
    ship.setpos(userx, -300)
    ship.pd()
    ship.circle(5)

    # debugging all bullets: 
    # print(bullets) # remove this

    for idx in range(0,len(bullets)): # paint ALL bullets in the bullets list
        bulletTurtle = turtle.Turtle()
        b = bullets[idx] # current bullet we are painting
        bulletTurtle.pu()
        bulletTurtle.ht()
        bulletTurtle.setpos(b[0], b[1])
        bulletTurtle.pd()
        bulletTurtle.circle(2)
        b[1] += 13 # quick and dirty approach, move bulltet after painting
                   # to save another complete bullets-foreach-loop 

    # quick n dirty bullet removal for out of screen bullets
    # normally I would do this probably in your "check if enemy hit" 
    # method as you are going over bullets there as well and can remove
    # them as soon as they passed all enemies 
    bullets = [x for x in bullets if x[1] < 500] 


def key_space():
    global x
    global y
    global bullets
    bullets.append([userx,-300]) # start a new bullet at current player position

Edit:

You might want to take a look at turtle.shape, turtle.size and turtle.stamp - by using a "circle" shape and a size that fits, you might be able to "stamp" this shape down. Advantage: you can simply delete the stamped shape - by its integer-id. I haven*t worked with turtle ever - consider yourself if that would be a way to easily redraw your players position and/or bullets

Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
1

It is not ideal but it starts working.

In key_space you only add new bullet to list.
In while not gameover you run function which will move all bullets.
In frame you draw all bullets.

import turtle
import time

userx = 0

x = 0
y = -300

enemies = [(300,50, "left"), (400,50, "left"), (350, -50, "right")]

bullets = []

gameover = False

def frame():
    pass
    ship = turtle.Turtle()
    ship.pu()
    ship.ht()
    ship.setpos(userx, -300)
    ship.pd()
    ship.circle(5)

    # redraw bullets
    for x, y in bullets:
        bullet = turtle.Turtle()
        bullet.pu()
        bullet.ht()
        bullet.setpos(x, y)
        bullet.pd()
        bullet.circle(2)

def key_left():
    global userx
    pass
    userx += -10
    print(userx)

def key_right():
    global userx
    pass
    userx += 10
    print(userx)

def key_space():
    pass # your code here
    global x
    global y
    global bullets
    x = userx

    # add bullet to list
    bullets.append([x, -300])

def move_bullets():
    global bullets

    live_bullets = []

    # move every bullet and check if should still live
    for x, y in bullets:
        y += 4

        # keep only some bullets and other forget (kill/remove)
        if y <= 300:
            live_bullets.append([x, y])

    bullets = live_bullets

def physics():
    global bullets
    global enemies
    pass

def ai():
    global enemies
    global gameover
    pass

def reset():
    global enemies
    global bullets
    global userx
    global gameover
    pass

def main():
    turtle.tracer(0,0)
    turtle.hideturtle()
    turtle.onkey(key_left, "Left")
    turtle.onkey(key_right, "Right")
    turtle.onkey(key_space, "space")
    turtle.listen()
    reset()
    while not gameover:
        turtle.clear()
        physics()
        ai()
        move_bullets() # <--  move bullets (or maybe before physics)
        frame()
        turtle.update()
        time.sleep(0.05)

main()
furas
  • 134,197
  • 12
  • 106
  • 148