Trying to create two objects at the same time but it creates one

Question:

I’m trying to develop a simple game while I’m learning python. Game is not complex, random cars (which are squares) are spawning at right of the screen and they are going to left. We are a turtle, trying to avoid them and make it to the top of the screen.

The problem is my code below doesn’t spawn two objects at the same time, it spawns just one.

from turtle import Screen
from turt import Turt
from spawnpoint import SpawnPoint
from cars import Car
import random

screen = Screen()
screen.setup(1000, 700)
screen.title("Cars and Turtle")
screen.bgcolor("gray")

turt = Turt()

is_game_on = True

screen.listen()
screen.onkey(turt.move_up, "Up")

spawn_points_ycords = [300, 200, 100, 0, -100, -200]
spawn_1 = SpawnPoint(spawn_points_ycords[0])
spawn_2 = SpawnPoint(spawn_points_ycords[1])
spawn_3 = SpawnPoint(spawn_points_ycords[2])
spawn_4 = SpawnPoint(spawn_points_ycords[3])
spawn_5 = SpawnPoint(spawn_points_ycords[4])
spawn_6 = SpawnPoint(spawn_points_ycords[5])
spawn_points = [spawn_1, spawn_2, spawn_3, spawn_4, spawn_5, spawn_6]

while is_game_on:
    for n in range(60):
        if n == 59:
            random_spawn = spawn_points[random.randint(0, len(spawn_points)-1)]
            random_spawn_2 = spawn_points[random.randint(0, len(spawn_points)-1)]
            while random_spawn_2 == random_spawn:
                random_spawn_2 = spawn_points[random.randint(0, len(spawn_points) - 1)]
            random_spawn_car = Car(random_spawn.spawn.ycor())
            random_spawn_2_car = Car(random_spawn_2.spawn.ycor())

screen.exitonclick()

My spawn points class code:

from turtle import Turtle


class SpawnPoint:
    def __init__(self, ycor):
        self.spawn = Turtle()
        self.spawn.hideturtle()
        self.spawn.speed(0)
        self.spawn.penup()
        self.spawn.goto(600, ycor)
        self.spawn.showturtle()
        self.new_car = None

and my car class code:

from turtle import Turtle
import random


class Car:
    def __init__(self, ycor):
        self.body = Turtle()
        self.body.hideturtle()
        self.body.penup()
        self.body.shape("square")
        self.colors = ["black", "red", "orange", "blue", "green", "yellow"]
        self.body.color(self.colors[random.randint(0, len(self.colors)-1)])
        self.body.shapesize(1, 5, 0)
        self.body.speed(2)
        self.body.goto(700, ycor)
        self.body.showturtle()
        self.body.goto(-700, ycor)

I can’t figure it out to solve this bug. I’m using Turtle module.

Asked By: firat

||

Answers:

The two car objects are created, but the problem is that you haven’t implemented real-time movement for multiple turtles, so the first turtle completes its 5-second journey across the screen before the second one is created or begins moving.

When you encounter problems like this, my suggestion is to strip the problem down to a minimal, reproducible example. This process makes the problem obvious by removing the noise. For example, if you move the spawn and destination points onto the visible screen, the problem becomes clear.

Here’s an even more minimal demonstration:

from turtle import Turtle


t = Turtle()
tt = Turtle()
t.speed(2)
tt.speed(2)

t.goto(100, 100)
tt.goto(200, 100)

t.Screen().exitonclick()

When you run this, you’ll see that t moves from 0, 0 to 100, 100 in about a second. Only once t arrives at the destination does tt even begin moving. Adding print statements on each line is another way to see that each goto blocks the script completely until the slow movement completes. The simultaneous movement you expect is not the default behavior of turtle.

The typical solution is to use tracer(0) to disable the internal update/rendering loop that turtle uses to smooth out gotos. Once you’ve done this, you can reposition turtles at will and call turtle.update() to render a frame. It’s up to you to reimplement smooth movement, which is not difficult to do.

Also worth noting, your exitonclick call is never reached and the for loop with if n == 59: is pointless. Cars are never garbage collected, and probably shouldn’t be doing so much work in the initializer. Kudos for using composition rather than inheritance for your classes, though.

I’d also caution against adding complexity like multiple classes and files before you’ve convinced yourself that the basics are operational. If something as simple as movement isn’t working as you expect, all of that other stuff only gets in the way of debugging. Run your code often, building up guarantees about its behavior as you work.

Here’s a quick sketch of how you might begin to redesign your project. It’s based on my recommended real-time setup for turtle.

import turtle


class Car:
    def __init__(self, x, y, speed):
        self.x = x
        self.y = y
        self.speed = speed
        self.body = turtle.Turtle()
        self.body.shape("square")
        self.body.penup()
        self.body.goto(x, y)

    def move(self):
        self.x -= self.speed
        w = turtle.screensize()[0]

        if self.x < -w:
            self.x = w

        self.body.goto(self.x, self.y)


def tick():
    for action in keys_pressed:
        actions[action]()

    for car in cars:
        car.move()

    turtle.update()
    win.ontimer(tick, frame_delay_ms)


if __name__ == "__main__":
    w, h = turtle.screensize()
    turtle.tracer(0)
    t = turtle.Turtle()
    t.penup()
    t.goto(0, -h + 50)
    t.left(90)
    cars = [
        Car(w, -200, 3),
        Car(w, -100, 5),
        Car(w, 0, 4.5),
        Car(w, 100, 4),
        Car(w, 200, 6),
    ]
    frame_delay_ms = 1000 // 30
    step_speed = 10
    actions = dict(
        u=lambda: t.forward(step_speed),
    )
    win = turtle.Screen()
    keys_pressed = set()
    win.onkeypress(lambda: keys_pressed.add("u"), "Up")
    win.onkeyrelease(lambda: keys_pressed.remove("u"), "Up")
    win.listen()
    tick()
    win.exitonclick()
Answered By: ggorlen