I made a program to generate lines in random directions in Python turtle. The way I did it is very repetitive, and it also crashes. My code recurses through functions repetitively. I have found it always crashes at about the 3215th recursion. I don't know if that's relevant. I'm asking if anyone knows why it's crashing and how to stop it. When it crashes, the turtle graphics window and cmd window both randomly close. My code:
import turtle
import random
import sys
sys.setrecursionlimit(100000)
rminlength = 1
rmaxlength = 15
gminlength = 1
gmaxlength = 30
bminlength = 1
bmaxlength = 45
rminangle = 45
rmaxangle = 45
gminangle = 90
gmaxangle = 90
bminangle = 135
bmaxangle = 135
drawspeed = 10000
global recurse
recurse = 0
r = turtle.Turtle()
r.color('red')
r.pensize(3)
r.shape('circle')
r.speed(drawspeed)
r.hideturtle()
g = turtle.Turtle()
g.color('green')
g.pensize(3)
g.shape('circle')
g.speed(drawspeed)
g.hideturtle()
b = turtle.Turtle()
b.color('blue')
b.pensize(3)
b.shape('circle')
b.speed(drawspeed)
b.hideturtle()
#Movement
def rmove():
if(random.randint(1,2) == 1):
r.left(random.randint(rminangle,rmaxangle))
if(random.randint(1,2) == 1):
r.forward(random.randint(rminlength,rmaxlength))
else:
r.backward(random.randint(rminlength,rmaxlength))
else:
r.right(random.randint(rminangle,rmaxangle))
if(random.randint(1,2) == 1):
r.forward(random.randint(rminlength,rmaxlength))
else:
r.backward(random.randint(rminlength,rmaxlength))
global recurse
recurse+=1
print(recurse)
gmove()
def gmove():
if(random.randint(1,2) == 1):
g.left(random.randint(gminangle,gmaxangle))
if(random.randint(1,2) == 1):
g.forward(random.randint(gminlength,gmaxlength))
else:
g.backward(random.randint(gminlength,gmaxlength))
else:
g.right(random.randint(gminangle,gmaxangle))
if(random.randint(1,2) == 1):
g.forward(random.randint(gminlength,gmaxlength))
else:
g.backward(random.randint(gminlength,gmaxlength))
global recurse
recurse+=1
print(recurse)
bmove()
def bmove():
if(random.randint(1,2) == 1):
b.left(random.randint(bminangle,bmaxangle))
if(random.randint(1,2) == 1):
b.forward(random.randint(bminlength,bmaxlength))
else:
b.backward(random.randint(bminlength,bmaxlength))
else:
b.right(random.randint(bminangle,bmaxangle))
if(random.randint(1,2) == 1):
b.forward(random.randint(bminlength,bmaxlength))
else:
b.backward(random.randint(bminlength,bmaxlength))
global recurse
recurse+=1
print(recurse)
rmove()
rmove()
input('Crashed')
Whenever you call a function, you're using memory in a finite region of data allocated to the program called the stack. If these functions never resolve and continue calling other functions, the stack memory is never reclaimed. Eventually, your program runs out of memory. This is called a stack overflow.
Here's the actual error message from your program:
...
File "a.py", line 100, in bmove
rmove()
File "a.py", line 64, in rmove
gmove()
File "a.py", line 82, in gmove
bmove()
File "a.py", line 99, in bmove
print(recurse)
MemoryError: stack overflow
You've attempted to solve the problem by increasing the recursion limit that was set by Python, but this only postpones the inevitable. Even if some calls did resolve, increasing this number is an unsafe way to write code because it makes assumptions about stack size rather than rewriting program logic to ensure calls resolve and that the stack doesn't grow out of control.
Because recursion is unnecessary to obtain the sequential turtle movement you're going for, let's re-write your program to use loops rather than function calls to direct the turtles:
import turtle
import random
import sys
rminlength = 1
rmaxlength = 15
gminlength = 1
gmaxlength = 30
bminlength = 1
bmaxlength = 45
rminangle = 45
rmaxangle = 45
gminangle = 90
gmaxangle = 90
bminangle = 135
bmaxangle = 135
drawspeed = 10000
r = turtle.Turtle()
r.color('red')
r.pensize(3)
r.shape('circle')
r.speed(drawspeed)
r.hideturtle()
g = turtle.Turtle()
g.color('green')
g.pensize(3)
g.shape('circle')
g.speed(drawspeed)
g.hideturtle()
b = turtle.Turtle()
b.color('blue')
b.pensize(3)
b.shape('circle')
b.speed(drawspeed)
b.hideturtle()
#Movement
def rmove():
if(random.randint(1,2) == 1):
r.left(random.randint(rminangle,rmaxangle))
if(random.randint(1,2) == 1):
r.forward(random.randint(rminlength,rmaxlength))
else:
r.backward(random.randint(rminlength,rmaxlength))
else:
r.right(random.randint(rminangle,rmaxangle))
if(random.randint(1,2) == 1):
r.forward(random.randint(rminlength,rmaxlength))
else:
r.backward(random.randint(rminlength,rmaxlength))
def gmove():
if(random.randint(1,2) == 1):
g.left(random.randint(gminangle,gmaxangle))
if(random.randint(1,2) == 1):
g.forward(random.randint(gminlength,gmaxlength))
else:
g.backward(random.randint(gminlength,gmaxlength))
else:
g.right(random.randint(gminangle,gmaxangle))
if(random.randint(1,2) == 1):
g.forward(random.randint(gminlength,gmaxlength))
else:
g.backward(random.randint(gminlength,gmaxlength))
def bmove():
if(random.randint(1,2) == 1):
b.left(random.randint(bminangle,bmaxangle))
if(random.randint(1,2) == 1):
b.forward(random.randint(bminlength,bmaxlength))
else:
b.backward(random.randint(bminlength,bmaxlength))
else:
b.right(random.randint(bminangle,bmaxangle))
if(random.randint(1,2) == 1):
b.forward(random.randint(bminlength,bmaxlength))
else:
b.backward(random.randint(bminlength,bmaxlength))
while 1: # loop infinitely
rmove()
gmove()
bmove()
Specifically, the recursive calls were removed and a while 1:
infinite loop was added.
As you mention, there is a lot of repetition in the code. Writing a class to encapsulate your turtle logic offers a significant cleanup opportunity and makes the program easily extensible to handle arbitrary numbers of additional turtles:
import turtle
from random import choice
from random import randint
class Turtle:
def __init__(
self, color, min_len, max_len, angle, speed=10, pensize=3
):
self.min_len = min_len
self.max_len = max_len
self.angle = angle
self.turt = turtle.Turtle()
self.turt.color(color)
self.turt.pensize(pensize)
self.turt.speed(speed)
self.turt.hideturtle()
def move(self):
choice((self.turt.left, self.turt.right))(self.angle)
dir_func = choice((self.turt.forward, self.turt.backward))
dir_func(randint(self.min_len, self.max_len))
if __name__ == "__main__":
turtles = [
Turtle("red", 1, 15, 45),
Turtle("green", 1, 30, 90),
Turtle("blue", 1, 45, 135)
]
while 1:
for turt in turtles:
turt.move()
Happy turtling!