pythonpython-3.xturtle-graphics

Making Turtle Graphics Python game start only after main menu button has been clicked


I'm currently making a Python turtle graphics game, it's a pong game where there is a 2 paddles and they pass the ball to each other, once ball touches each side the player gets a point, the game is fully functional in 1920x1080 res.

The thing is I made a main menu type of thing where the game starts after the Start button has been clicked, the menu works exactly as intended, but even before I press start, the main game loop starts once canvas is opened, not when start button is clicked, so it looks like this: https://i.sstatic.net/u6xQR.jpg, this is what it looks like after menu button has been pressed: https://i.sstatic.net/1RC9Q.jpg

Here is the whole code, sorry if it's quite long I have been trying to solve it for 2 days

import turtle
import time

pong = turtle.Screen()
pong.title("My First Project")
pong.bgcolor("black")
pong.setup(width=1920,height=1080)
pong.tracer(0)


#Step 1: Create Both Padles and Ball(Done)
#Step 2: Movement Functions and Linking them to Keyboard(Done)
#Step 3: Scoring System and Updating it(Done)
#Step 4: Winning Screen(Done)
#Step 5: Collision with Paddles and Balls(Done)
#Step 5: Main Menu(In Progress)

#Creating Menu
mainCanvas = turtle.Turtle()
mainCanvas.color("cyan")
mainCanvas.hideturtle
mainCanvas.speed(0)
mainCanvas.begin_fill()
#list to make the main menu bg
fd_list1=[(90, 540), (0, 960), (270, 1080),(180,1920),(90,1080),(0,960)]
for hd1, fwd1 in fd_list1:
    mainCanvas.setheading(hd1)
    mainCanvas.fd(fwd1)
mainCanvas.end_fill()

#Creating Title
mainTitle = turtle.Turtle()
mainTitle.penup()
mainTitle.speed(0)
mainTitle.hideturtle()
mainTitle.goto(0,270)
mainTitle.write("Main Menu",align="center",font=("a Absolute Empire",60,"normal"))

#Create Start Option
startGame = turtle.Turtle()
startGame.speed(0)
startGame.penup()
startGame.goto(0,100)
pong.register_shape("start_400x150.gif") #PNG for start button
startGame.shape("start_400x150.gif") #Applying the PNG
startGame.shapesize(stretch_len=20,stretch_wid=20)

#Start Click Function
def clickStart(x,y):
    if x > startGame.xcor() - 200 and x < startGame.xcor() + 200 and y > startGame.ycor() - 75 and y < startGame.ycor() + 75:
        time.sleep(0.5)
        mainTitle.clear()
        pong.bgcolor("black")  
        startGame.hideturtle()

        #Get rid of Main Menu
        mainCanvas.color("black")
        mainCanvas.goto(0,0)
        mainCanvas.begin_fill()
        #List to change canvas to desired color once clicked
        fd_list=[(90, 540), (0, 960), (270, 1080),(180,1920),(90,1080),(0,960)]
        for hd, fwd in fd_list:
             mainCanvas.setheading(hd)
             mainCanvas.fd(fwd)
        mainCanvas.end_fill()
        
        #Hide Button
        startGame.goto(0,5000)

#Key Binding Main Menu
pong.listen()
pong.onscreenclick(clickStart,1)

#creating Paddle A, Player 1(left Side)
paddleA = turtle.Turtle()
paddleA.speed(0)
paddleA.shape("square")
paddleA.shapesize(9,1)
paddleA.color("white")
paddleA.penup()
paddleA.goto(-900,0)

#creating Paddle B, Player 2(Right Side)
paddleB = turtle.Turtle()
paddleB.speed(0)
paddleB.shape("square")
paddleB.shapesize(9,1)
paddleB.color("white")
paddleB.penup()
paddleB.goto(900,0)

#creating Ball makes it move in diagonal direction in fixed speed with ball.dx = 3 and ball.dy = 3 
#Speed depends on the power of your PC or Mac, you can change dx and dy according to ur computer speed(mine sucks so 3 is pretty fast for me)
ball = turtle.Turtle()
ball.speed(0)
ball.shape("square")
ball.color("white")
ball.shapesize(1.8,1.8)
ball.penup()
ball.goto(0,0)
ball.x = 5
ball.y = 5

#Middle Line 
#Creates multiple lines, enumerates them then checks for each enumerate with idx 
#t variable is number of turtles, a line that goes down the middle
turtles = [turtle.Turtle() for _ in range(100)]
for idx, t in enumerate(turtles):
    t.speed(0)
    t.shape("square")
    t.shapesize(2,0.1)
    t.color("white")
    t.penup()
    t.goto(0,540-10*idx)

#Movings Functions
#Paddle A Up with (w) key for player A(1)
def paddleAUp():
    y = paddleA.ycor()
    y += 20
    paddleA.sety(y)

#Paddle A Down with (s) Key for player A(1)
def paddleADown():
    y = paddleA.ycor()
    y -= 20
    paddleA.sety(y)

#Paddle B Up with (Up_Arrow) Key for player B(2)
def paddleBUp():
    y = paddleB.ycor()
    y += 20
    paddleB.sety(y)

#Paddle B Down (Down_Arrow) key for player B(2)
def paddleBDown():
    y = paddleB.ycor()
    y -= 20
    paddleB.sety(y)    

#Binding the Moving functions to Keyboard
#Pong.listen, listens to keyboard input
pong.listen()

#Moves Paddle A(1) up and down the y axis with (w) and (s) key respectively
pong.onkeypress(paddleAUp,"w")
pong.onkeypress(paddleADown,"s")

#Moves Paddle B(2) up and down the y axis with (Up_Arrow) and (Down_Arrow) key respectively
pong.onkeypress(paddleBUp,"Up")
pong.onkeypress(paddleBDown,"Down")

#Scores
AScore = 0
BScore = 0

#Scoring System for Player A(1)
ScoreSystemA = turtle.Turtle()
ScoreSystemA.pencolor("White")
ScoreSystemA.penup()
ScoreSystemA.setposition(-950,480)
ScoreSystemA.pendown()
ScoreSystemA.write(f"P1 Score: {AScore}",font=("courier",30,"normal"))
ScoreSystemA.hideturtle()

#Scoring System for Player B(2)
ScoreSystemB = turtle.Turtle()
ScoreSystemB.pencolor("White")
ScoreSystemB.penup()
ScoreSystemB.setposition(680,480)
ScoreSystemB.pendown
ScoreSystemB.write(f"P2 Score: {BScore}", font=("courier",30,"normal"))
ScoreSystemB.hideturtle()

#Main Loop To check canvas and update it each millisecond
while True:
    pong.update()

    #Makes Ball Move 
    ball.setx(ball.xcor() + ball.x)
    ball.sety(ball.ycor() + ball.y)

    #Ball Bouncing when it hits edge, Edge Detection at +y
    if ball.ycor() > 510:
        ball.sety(510)
        ball.y = ball.y * -1

    #Ball Bouncing when it hits edge, Edge Detection at -y
    if ball.ycor() < -510:
        ball.sety(-510)
        ball.y = ball.y * -1
    
    #Counting scoring system, when Ball goes Behind Paddle the Ball Resets and Goes in the Other Direction
    #Which will then update the Score Text at the upper Left Corner
    if ball.xcor() > 950:
        ball.goto(0,0)
        ball.x *= -1
        AScore = AScore + 1
        ScoreSystemA.clear()
        ScoreSystemA.write(f"P1 Score: {AScore}",font=("courier",30,"normal"))
        
    #Same As Above but for Paddle B
    if ball.xcor() < -950:
        ball.goto(0,0)
        ball.x *= -1
        BScore = BScore + 1
        ScoreSystemB.clear()
        ScoreSystemB.write(f"P2 Score: {BScore}", font=("courier",30,"normal"))

    #Winning Condition writes Player 1(A) Won and stops the Game
    if AScore == 3:
        AWin = turtle.Turtle()
        AWin.pencolor("White")
        AWin.hideturtle()
        AWin.penup()
        AWin.setposition(0,0)
        AWin.pendown()
        AWin.write("P1 Wins!",align="center",font=("courier",60,"normal"))
        turtles.clear()
        turtle.done()
        
    #Same As Above but for Paddle B
    if BScore == 3:
        BWin = turtle.Turtle()
        BWin.pencolor("White")
        BWin.hideturtle()
        BWin.penup()
        BWin.setposition(0,0)
        BWin.pendown()
        BWin.write("P2 Wins!",align="center",font=("courier",60,"normal"))
        turtles.clear()
        turtle.done()

    #Collision System first 2 conditions are for ball x coordinate to check if it's at the same x value as the paddle
    #Third if ball is less than paddle y cor for top half of the paddle
    #Fourth if ball is greater than paddle y cor for bottom half of the paddle
    if ball.xcor() < -900 and ball.xcor() > -950 and (ball.ycor() < paddleA.ycor() + 72 and ball.ycor() > paddleA.ycor() - 72):
        ball.x = ball.x * -1
    
    #Same As Above but for Paddle B
    if ball.xcor() > 900 and ball.xcor() < 950 and (ball.ycor() < paddleB.ycor() + 72 and ball.ycor() > paddleB.ycor() - 72):
        ball.x = ball.x * -1

    
    
    

    

here is the bit i'm struggling with:

#Start Click Function
def clickStart(x,y):
    if x > startGame.xcor() - 200 and x < startGame.xcor() + 200 and y > startGame.ycor() - 75 and y < startGame.ycor() + 75:
        time.sleep(0.5)
        mainTitle.clear()
        pong.bgcolor("black")  
        startGame.hideturtle()

        #Get rid of Main Menu
        mainCanvas.color("black")
        mainCanvas.goto(0,0)
        mainCanvas.begin_fill()
        #List to change canvas to desired color once clicked
        fd_list=[(90, 540), (0, 960), (270, 1080),(180,1920),(90,1080),(0,960)]
        for hd, fwd in fd_list:
             mainCanvas.setheading(hd)
             mainCanvas.fd(fwd)
        mainCanvas.end_fill()
        
        #Hide Button
        startGame.goto(0,5000)

#Key Binding Main Menu
pong.listen()
pong.onscreenclick(clickStart,1)

any help is appreciated

i tried: making a condition where if click happens then the game loop starts, when i did that and i clicked the canvas just turned all black, i think it returned none

i tried: making a second game loop, that stops when clicking the start button, that just broke

What i expected: i run code, game opens and does nothing, till i press the start button then the game starts and it plays.


Solution

  • You've made this difficult to implement by putting too much functionality at the top level of the code instead of encapsulated in functions. I've reworked your code to shoehorn in this functionality below. Your "start" button is just a giant circle as I don't have your artwork:

    from turtle import Screen, Turtle
    
    SMALL_FONT = ("Courier", 30, "normal")
    BIG_FONT = ("Courier", 60, "normal")
    MENU_FONT = ("a Absolute Empire", 60, "normal")
    
    WINDOW_WIDTH, WINDOW_HEIGHT = 1920, 1080
    
    fd_list = [(90, WINDOW_HEIGHT/2), (0, WINDOW_WIDTH/2), (270, WINDOW_HEIGHT), (180, WINDOW_WIDTH), (90, WINDOW_HEIGHT), (0, WINDOW_WIDTH/2)]
    
    def clickStart(x, y):
        if startGame.xcor() - 200 < x < startGame.xcor() + 200 and startGame.ycor() - 75 < y < startGame.ycor() + 75:
            screen.onscreenclick(None)
    
            mainTitle.clear()
            screen.bgcolor("black")
            startGame.hideturtle()
    
            # Get rid of Main Menu
            mainCanvas.clear()
            startGame.hideturtle()
    
            # Moves Paddle A(1) up and down the y axis with (w) and (s) key respectively
            screen.onkeypress(paddleAUp, "w")
            screen.onkeypress(paddleADown, "s")
            paddleA.showturtle()
    
            # Moves Paddle B(2) up and down the y axis with (Up_Arrow) and (Down_Arrow) key respectively
            screen.onkeypress(paddleBUp, "Up")
            screen.onkeypress(paddleBDown, "Down")
            paddleB.showturtle()
    
            screen.listen()
    
            play()
    
    # Movings Functions
    # Paddle A Up with (w) key for player A(1)
    def paddleAUp():
        paddleA.forward(20)
    
    # Paddle A Down with (s) Key for player A(1)
    def paddleADown():
        paddleA.backward(20)
    
    # Paddle B Up with (Up_Arrow) Key for player B(2)
    def paddleBUp():
        paddleB.forward(20)
    
    # Paddle B Down (Down_Arrow) key for player B(2)
    def paddleBDown():
        paddleB.backward(20)
    
    # Scores
    AScore = 0
    BScore = 0
    
    # Main Loop To check canvas and update it each millisecond
    def play():
        global AScore, BScore
    
        # Make Ball Move
        ball.setx(ball.xcor() + ball.x)
        ball.sety(ball.ycor() + ball.y)
    
        # Ball Bouncing when it hits edge
        if ball.ycor() > 510:
            ball.sety(510)
            ball.y *= -1
        elif ball.ycor() < -510:
            ball.sety(-510)
            ball.y *= -1
    
        # Counting scoring system, when Ball goes Behind Paddle the Ball Resets and Goes in the Other Direction
        # Which will then update the Score Text at the upper Left Corner
        if ball.xcor() > 950:
            ball.goto(0, 0)
            ball.x *= -1
            AScore += 1
            ScoreSystemA.clear()
            ScoreSystemA.write(f"P1 Score: {AScore}", align='center', font=SMALL_FONT)
        elif ball.xcor() < -950:
            ball.goto(0, 0)
            ball.x *= -1
            BScore += 1
            ScoreSystemB.clear()
            ScoreSystemB.write(f"P2 Score: {BScore}", align='center', font=SMALL_FONT)
    
        # Winning Condition writes Player 1(A) Won and stops the Game
        if AScore == 3:
            midLine.clear()
            ball.hideturtle()
            AWin = Turtle()
            AWin.hideturtle()
            AWin.pencolor("White")
            AWin.write("P1 Wins!", align="center", font=BIG_FONT)
            screen.update()
            return
    
        if BScore == 3:
            midLine.clear()
            ball.hideturtle()
            BWin = Turtle()
            BWin.hideturtle()
            BWin.pencolor("White")
            BWin.write("P2 Wins!", align="center", font=BIG_FONT)
            screen.update()
            return
    
        # Collision System first 2 conditions are for ball x coordinate to check if it's at the same x value as the paddle
        # Third if ball is less than paddle y cor for top half of the paddle
        # Fourth if ball is greater than paddle y cor for bottom half of the paddle
        if -950 < ball.xcor() < -900 and paddleA.ycor() - 72 < ball.ycor() < paddleA.ycor() + 72:
            ball.x *= -1
        elif 900 < ball.xcor() < 950 and paddleB.ycor() - 72 < ball.ycor() < paddleB.ycor() + 72:
            ball.x = ball.x * -1
    
        screen.update()
        screen.ontimer(play)
    
    screen = Screen()
    screen.title("My First Project")
    screen.bgcolor("black")
    screen.setup(WINDOW_WIDTH, WINDOW_HEIGHT)
    # screen.register_shape("start_400x150.gif")  # PNG for start button
    screen.tracer(0)
    
    # Create Paddle A, Player 1 (left Side)
    paddleA = Turtle()
    paddleA.hideturtle()
    paddleA.shape("square")
    paddleA.shapesize(1, 9)
    paddleA.color("white")
    paddleA.penup()
    paddleA.setheading(90)
    paddleA.setx(-900)
    
    # Create Paddle B, Player 2 (Right Side)
    paddleB = paddleA.clone()
    paddleB.setx(900)
    
    # Create Ball making it move in diagonal direction in fixed speed
    ball = Turtle()
    ball.shape("square")
    ball.color("white")
    ball.shapesize(1.8)
    ball.penup()
    
    ball.x = 5  # user properties
    ball.y = 5
    
    # Middle Line
    midLine = Turtle()
    midLine.hideturtle()
    midLine.color("white")
    midLine.pensize(2)
    midLine.penup()
    midLine.sety(-WINDOW_HEIGHT/2)
    midLine.pendown()
    midLine.sety(WINDOW_HEIGHT/2)
    
    # Scoring System for Player A(1)
    ScoreSystemA = Turtle()
    ScoreSystemA.hideturtle()
    ScoreSystemA.color("White")
    ScoreSystemA.penup()
    ScoreSystemA.setposition(-480, 420)
    ScoreSystemA.write(f"P1 Score: {AScore}", align='center', font=SMALL_FONT)
    
    # Scoring System for Player B(2)
    ScoreSystemB = Turtle()
    ScoreSystemB.hideturtle()
    ScoreSystemB.color("White")
    ScoreSystemB.penup()
    ScoreSystemB.setposition(480, 420)
    ScoreSystemB.write(f"P2 Score: {BScore}", align='center', font=SMALL_FONT)
    
    # Create Menu
    mainCanvas = Turtle()
    mainCanvas.hideturtle()
    mainCanvas.color("cyan")
    mainCanvas.begin_fill()
    
    for heading, distance in fd_list:
        mainCanvas.setheading(heading)
        mainCanvas.forward(distance)
    
    mainCanvas.end_fill()
    
    # Create Title
    mainTitle = Turtle()
    mainTitle.hideturtle()
    mainTitle.penup()
    mainTitle.sety(270)
    mainTitle.write("Main Menu", align="center", font=MENU_FONT)
    
    # Create Start Option
    startGame = Turtle()
    startGame.penup()
    startGame.sety(100)
    # startGame.shape("start_400x150.gif")
    startGame.shape('circle')
    startGame.shapesize(20)
    
    # Key Binding Main Menu
    screen.onscreenclick(clickStart)
    
    screen.update()
    screen.mainloop()
    

    As you can see, I've also made a number of other changes to streamline your code and make it behave properly in an event-driven environment.