When the game starts, a pipe should generate every few seconds and then delete itself while it is off the screen. Except... the pipes does not appear on the screen at all. I tried checking how many pipes there were present and the number fluctuated between 2 and 3 meaning that pipes were being generated and destroyed???? I would also appreciate any advice on how to make my code more efficient/readable.
This is my current code which doesn't work.
from tkinter import \*
from PIL import Image, ImageTk
import tkinter as tk
import random
class Bird:
BIRD_WIDTH = 75
BIRD_HEIGHT = 50
BIRD_GRAVITY = 0.2
    def __init__(self, canvas, x, y):
        self.canvas = canvas
        self.image = Image.open("bird.png")
        self.image = self.image.resize((self.BIRD_WIDTH, self.BIRD_HEIGHT),
                                       Image.ANTIALIAS)
        self.photo = ImageTk.PhotoImage(self.image)
        self.bird_id = self.canvas.create_image(x,
                                                y,
                                                image=self.photo,
                                                anchor="nw")
        self.velocity = 0
    
    def move(self):
        bird_x, bird_y = self.canvas.coords(self.bird_id)
        self.velocity += self.BIRD_GRAVITY # add gravity to velocity
        bird_y += self.velocity # add velocity to y-coordinate
        self.canvas.coords(self.bird_id, bird_x, bird_y)
class Pipe:
PIPE_WIDTH = 600
PIPE_HEIGHT = 1000
PIPE_SPEED = 5
def __init__(self, canvas, x, y, photo):
self.canvas = canvas
self.pipe_id = self.canvas.create_image(x,
y,
image=photo,
anchor="nw")
    def move(self):
        self.canvas.move(self.pipe_id, -self.PIPE_SPEED, 0)
    
    @classmethod
    def create_pipe(cls, canvas):
        image = Image.open("pipes.png")
        image = image.resize((cls.PIPE_WIDTH, cls.PIPE_HEIGHT), Image.ANTIALIAS)
        photo = ImageTk.PhotoImage(image)
        return cls(canvas, 1200, random.randint(-300, -100), photo)
class Gui:
    CANVAS_WIDTH = 1200
    CANVAS_HEIGHT = 5000
    WINDOW_WIDTH = 1440
    WINDOW_HEIGHT = 500
    JUMP_HEIGHT = 5.5
    PIPE_INTERVAL = 100
    PIPE_COUNTER = 0
    SCORE = 0
    
    def __init__(self, start):
        self.start = start
    
        # Set up the main window
        self.root = tk.Tk()
        self.root.title("Flappy Bird")
        self.root.geometry(f"{self.WINDOW_WIDTH}x{self.WINDOW_HEIGHT}")
    
    # Set up the game canvas
        self.canvas = tk.Canvas(self.root,
                                width=self.CANVAS_WIDTH,
                                height=self.CANVAS_HEIGHT)
        self.canvas.pack()
    
    # Create the bird and pipes
        self.bird = Bird(self.canvas, 100, 200)
    
        self.pipes = []
    
        self.pipes.append(Pipe.create_pipe(self.canvas))    
    
        self.bind_keys()
    
        # Schedule the game loop
        self.game_loop()
    
        self.start_game()
    
    def bind_keys(self):
        self.root.bind("<space>", self.flap_up)
    
    def flap_up(self, event):
        self.bird.velocity = -self.JUMP_HEIGHT    
     
    
    def check_collision(self):
        pass
    
    
    def game_over(self):
        pass
    
    def start_game(self):
        self.root.mainloop()
    
    def game_loop(self):
       # Update the position of the bird
        self.bird.move()
    
    # Move the pipe to the left
        for pipe in self.pipes:
           pipe.move()
    
    # Delete pipes that are off the screen
    for pipe in self.pipes:
        pipe_x = self.canvas.coords(pipe.pipe_id)[0]
        if pipe_x < -pipe.PIPE_WIDTH:
            self.canvas.delete(pipe.pipe_id)
            self.pipes.remove(pipe)
            self.SCORE += 1
           
    self.PIPE_COUNTER += 1
    if self.PIPE_COUNTER >= self.PIPE_INTERVAL:
        self.PIPE_COUNTER = 0
        new_pipe = Pipe.create_pipe(self.canvas)
        self.pipes.append(new_pipe)
    
    print(len(self.pipes))
    
    # Check for collision
    if self.check_collision():
        self.game_over()
    else:
      # Schedule the next iteration of the game loop
        self.canvas.after(10, self.game_loop)
gui = Gui(None)
gui.start_game()
Here is a previous iteration of my code that succeeds in generating the pipes. (but of course I need the pipes to generate every few seconds) I haven't found any notable difference that makes this code work but the latest version to not work. I also asked chatgpt but it also couldn't detect any notable difference.
from tkinter import \*
from PIL import Image, ImageTk
import tkinter as tk
import random
class Bird:
BIRD_WIDTH = 75
BIRD_HEIGHT = 50
BIRD_GRAVITY = 0.1
    def __init__(self, canvas, x, y):
        self.canvas = canvas
        self.image = Image.open("bird.png")
        self.image = self.image.resize((self.BIRD_WIDTH, self.BIRD_HEIGHT),
                                       Image.ANTIALIAS)
        self.photo = ImageTk.PhotoImage(self.image)
        self.bird_id = self.canvas.create_image(x,
                                            y,
                                            image=self.photo,
                                            anchor="nw")
        self.velocity = 0
    
    def move(self):
        bird_x, bird_y = self.canvas.coords(self.bird_id)
        self.velocity += self.BIRD_GRAVITY # add gravity to velocity
        bird_y += self.velocity # add velocity to y-coordinate
        self.canvas.coords(self.bird_id, bird_x, bird_y)
class Pipe:
PIPE_WIDTH = 600
PIPE_HEIGHT = 1000
    def __init__(self, canvas, x, y):
        self.canvas = canvas
        self.image = Image.open("pipes.png")
        self.image = self.image.resize((self.PIPE_WIDTH, self.PIPE_HEIGHT),
                                       Image.ANTIALIAS)
        self.photo = ImageTk.PhotoImage(self.image)
        self.pipe_id = self.canvas.create_image(x,
                                                y,
                                                image=self.photo,
                                                anchor="nw")
    
    def move(self, dx, dy):
        self.canvas.move(self.pipe_id, dx, dy)
class Gui:
    CANVAS_WIDTH = 1200
    CANVAS_HEIGHT = 5000
    WINDOW_WIDTH = 1440
    WINDOW_HEIGHT = 500
    PIPE_SPEED = 4
    JUMP_HEIGHT = 4
    PIPE_INTERVAL = 300000
    
    def __init__(self, start):
        self.start = start
    
    # Set up the main window
        self.root = tk.Tk()
        self.root.title("Flappy Bird")
        self.root.geometry(f"{self.WINDOW_WIDTH}x{self.WINDOW_HEIGHT}")
    
    # Set up the game canvas
        self.canvas = tk.Canvas(self.root,
                                width=self.CANVAS_WIDTH,
                                height=self.CANVAS_HEIGHT)
        self.canvas.pack()
    
    # Create the bird and pipes
        self.bird = Bird(self.canvas, 100, 200)
        self.pipe = Pipe(self.canvas, 1200, random.randint(-300, -100))
    
        self.bind_keys()
    
    # Schedule the game loop
        self.canvas.after(10, self.game_loop)
    
    def bind_keys(self):
        self.root.bind("<space>", self.flap_up)
    
    def flap_up(self, event):
        self.bird.velocity = -self.JUMP_HEIGHT
    
    def generate_pipe(self):
        self.pipe = Pipe(self.canvas, 1200, random.randint(-300, -100))
    
     
    def game_loop(self):
    # Update the position of the bird
        self.bird.move()
    
    # Move the pipe to the left
        self.pipe.move(-self.PIPE_SPEED, 0)
    
    # Delete pipe that is off the screen
        pipe_x = self.canvas.coords(self.pipe.pipe_id)[0]
        if pipe_x < -self.pipe.PIPE_WIDTH:
            self.canvas.delete(self.pipe.pipe_id)
    
    # Check for collision
    if self.check_collision():
        self.game_over()
    else:
      # Schedule the next iteration of the game loop
        self.canvas.after(5, self.game_loop)
    
    def check_collision(self):
        pass
    
    def start_game(self):
        self.root.mainloop()
gui = Gui(None)
gui.start_game()
 
     
    