I'm new to Python and trying out Turtle through Tutorials. This question turned out to be verbose, my bad.
I have designed 2 Games handled by 2 Functions (in a separate module) which are called from MAIN:
F1 : Plays an Etch a Sketch Game where user inputs are used to move the Turtle to draw
F2 : Plays a Turtle Race where 5 Turtles (I have used a list to handle 5 Objects) move at randomized paces to see who wins.
The ISSUE :
Solutions I have checked indicate the Turtle._RUNNING = True Flag which can be changed, but what is a better way to implement this without changing a Class Variable (or using Global) ? If someone can point me to the some sources (other than TURTLE DOCS) for Screen and Event Listeners to solve this, I'll do the learning myself !
Code Skeleton : I'm loading config.py into FN Module and the Game Functions from MAIN
# Module : config.py
from turtle import Turtle, Screen
squad = []
for i in range(0, 5): # List of Turtle Objects available to all Modules
raphael = Turtle()
raphael.speed(0)
squad.append(raphael)
def create_screen():
"""Creates and Returns a screen Object"""
screen = Screen()
screen.screensize(400, 400)
screen.colormode(255)
return screen
# Module : gamefunction.py
from config import squad, create_screen
def etch_sketch_game():
"""Plays the Etch a Sketch Game"""
screen_fn = create_screen()
def forwards(): # Other FNs designed but not included for this Question
squad[0].fd(30)
screen_fn.listen()
screen_fn.onkey(key="w", fun=forwards)
screen_fn.exitonclick()
def turtle_race(): # Only adde
"""Plays the Turtle Race Game"""
screen_fn = create_screen()
squad[0].setpos(-200, 30)
squad[1].setpos(-200, 20)
squad[2].setpos(-200, 0)
squad[3].setpos(-200, -20)
squad[4].setpos(-200, -30)
def forwards(): # Other FNs designed but not included for this Question
squad[0].fd(30)
screen_fn.onkey(key="w", fun=forwards)
screen.exitonclick()
Your question is somewhat answered by Python Turtle.Terminator even after using exitonclick() in that as of CPython 3.12, the only way to use exitonclick()
more than once is to mess with an internal variable _RUNNING
. If you want to work within turtle's public API, short of running subprocesses, you're sort of stuck with one screen for your whole application. This means that you'll need to move the exitonclick()
outside each game, to be a one-time call. All games will reuse the same screen.
With that in mind, Screen()
is basically a singleton factory getter for the one screen turtle gives you, so your create_screen
is a bit misleadingly named.
In your application, it seems you want to have the user click to return to the main menu, so we'll use a normal click handler for that rather than exitonclick()
. That handler is responsible for clearing out state from the previous game and prompting the user for the next game. For simplicity's sake, I'm using textinput
, but in a fancier app this would probably be a clickable turtle button for better UX. Consider this a proof of concept which will require adaptation to your use case.
I've also moved everything into one file, but you can move it back out to multiple files easily if you want.
from turtle import Screen, Turtle
def new_game():
"""Resets state and lets the user choose a new game"""
screen.onkey(key="w", fun=None)
screen.onkey(key="a", fun=None)
screen.onkey(key="d", fun=None)
for t in turtles:
t.reset()
t.hideturtle()
t.penup()
while True:
game = screen.textinput(
"Choose game",
"Choose game ('sketch', 'race' or 'quit'):"
)
if not game:
continue
elif game.lower().strip() in ("sketch", "race"):
break
elif game.lower().strip() == "quit":
return screen.bye()
if game == "sketch":
etch_sketch_game()
else:
turtle_race()
def etch_sketch_game():
"""Runs an etch a sketch game"""
screen.onkey(key="w", fun=lambda: t.forward(30))
screen.onkey(key="a", fun=lambda: t.left(30))
screen.onkey(key="d", fun=lambda: t.right(30))
screen.listen()
t = turtles[0]
t.showturtle()
t.pendown()
def turtle_race():
"""Runs a turtle race game"""
def forward():
turtles[0].forward(30)
screen.onkey(key="w", fun=forward)
screen.listen()
turtles[0].setpos(-200, 30)
turtles[1].setpos(-200, 20)
turtles[2].setpos(-200, 0)
turtles[3].setpos(-200, -20)
turtles[4].setpos(-200, -30)
for t in turtles:
t.showturtle()
t.pendown()
def initialize():
"""Performs one-time setup for entire application"""
for i in range(5):
t = Turtle()
t.speed(0)
t.hideturtle()
turtles.append(t)
screen.screensize(400, 400)
screen.colormode(255)
screen.onclick(lambda *_: new_game())
new_game()
screen.mainloop()
# global state; can encapsulate in a module or class as you see fit
turtles = []
screen = Screen()
initialize()
By the way, it's good that you've allocated turtles up front. There's currently no real way to get rid of turtles once they're created, so keeping them in a global object pool is the best approach.
As another aside, you may not actually need any global state of your own since turtle manages all of the state used in the above program. screen = Screen()
can be done per file. Screen().turtles()
can be used in place of the turtles = []
list. This might allow you to remove the config file.
See this answer if you want real-time turtle control, avoiding those pesky keyboard retriggers.