Calvi
Calvi

Reputation: 35

Python Game Too Slow

My game became too slow after I created code under #sensors comments (see code below, it has a lot of iterations in its for-loops). I have made controls for a human to move the red dot, but the game is supposed to be played by itself, by computer.

My question is:

  1. Is my macbook pro 15" from 2015 too slow (will this code work with another computer)? or
  2. Is Python language too slow (will this code work with another language)? or
  3. Is Python module (turtle) wrong for this kind of a task? or
  4. Is my code just horrible (it wouldn't work with any language nor any computer)?

or something else?

This is my code:

import turtle
import math

#Set up screen
wn = turtle.Screen()
wn.bgcolor("lightyellow")
score = 0

#Draw border
mypen = turtle.Turtle()
mypen.penup()
mypen.setposition(-300, -300)
mypen.speed(0)
mypen.pendown()
mypen.pensize(3)
for side in range(4):
    mypen.forward(600)
    mypen.left(90)
mypen.hideturtle()

#Draw obstacle
myObstacle = turtle.Turtle()
myObstacle.penup()
myObstacle.setposition(-150, -150)
myObstacle.speed(0)
myObstacle.pendown()
myObstacle.pensize(3)
for side in range(4):
    myObstacle.forward(300)
    myObstacle.left(90)
myObstacle.hideturtle()

#Create player turtle
player = turtle.Turtle()
player.penup()
player.speed(0)
player.setposition(-200, -200)
player.color("red")
player.shape("circle")


#Set speed variable
speed = 1


#define functions
def turnleft():
    player.left(30)
def turnright():
    player.right(30)
def increasespeed():
    global speed
    speed += 1
def decreasespeed():
    global speed
    if speed > 1:
        speed -= 1


#Set keyboard bindings
turtle.listen()
turtle.onkey(turnleft, "Left")
turtle.onkey(turnright, "Right")
turtle.onkey(increasespeed, "Up")
turtle.onkey(decreasespeed, "Down")


#bounderies
def merge(list1, list2): 
    merged_list = [(list1[i], list2[i]) for i in range(0, len(list1))] 
    return merged_list 

bounderies = merge([-300] * 601, list(range(-300,301)))
bounderies.extend(merge([300] * 601, list(range(-300,301))))
bounderies.extend(merge(list(range(-300,301)), [-300] * 601))
bounderies.extend(merge(list(range(-300,301)), [300] * 601))

bounderies.extend(merge([-150] * 301, list(range(-150,151))))
bounderies.extend(merge([150] * 301, list(range(-150,151))))
bounderies.extend(merge(list(range(-150,151)), [-150] * 301))
bounderies.extend(merge(list(range(-150,151)), [150] * 301))

def scoreset():
        global score
        score += 1
        scorestring = "Score: %s" %score
        mypen.undo()
        mypen.penup()
        mypen.setposition(-340, 310)
        mypen.pendown()
        mypen.color("green")
        mypen.write(scorestring, False, align = "left", font=("ariel", 16, "bold"))




        

#sensors
def forwardDistance():
    forwardDistance = []
    minForwDist = 0
    tupleCoordinate = (0,0)
    yCoordinate = 0
    xCoordinate = 0
    position = (int(player.xcor()), int(player.ycor()))
    heading = player.heading()
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus
    
    for alpha in range(1000):
        if (heading < 45 and heading >= 0) or (heading < 360 and heading >= 315):
            xCoordinate = position[0] + alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))
            
        elif (heading < 315 and heading >= 225):
            yCoordinate = position[1] - alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 225 and heading >= 135):
            xCoordinate = position[0] - alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 135 and heading >= 45):
            yCoordinate = position[1] + alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        if tupleCoordinate in bounderies:
            forwardDistance.append(player.distance(tupleCoordinate))
            minForwDist = min(forwardDistance)
            #print("Forward distance: ", int(minForwDist))
    return minForwDist

def leftDistance():
    forwardDistance = []
    minForwDist = 0
    tupleCoordinate = (0,0)
    yCoordinate = 0
    xCoordinate = 0
    position = (int(player.xcor()), int(player.ycor()))
    if player.heading() + 90 >= 360:
        heading = player.heading() + 90 - 360
    else:
        heading = player.heading() + 90
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus
    
    for alpha in range(1000):
        if (heading < 45 and heading >= 0) or (heading < 360 and heading >= 315):
            xCoordinate = position[0] + alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))
            
        elif (heading < 315 and heading >= 225):
            yCoordinate = position[1] - alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 225 and heading >= 135):
            xCoordinate = position[0] - alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 135 and heading >= 45):
            yCoordinate = position[1] + alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        if tupleCoordinate in bounderies:
            forwardDistance.append(player.distance(tupleCoordinate))
            minForwDist = min(forwardDistance)
            #print("Left distance: ", int(minForwDist))
    return minForwDist

def leftForwardDistance():
    forwardDistance = []
    minForwDist = 0
    tupleCoordinate = (0,0)
    yCoordinate = 0
    xCoordinate = 0
    position = (int(player.xcor()), int(player.ycor()))
    if player.heading() + 45 >= 360:
        heading = player.heading() + 45 - 360
    else:
        heading = player.heading() + 45
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus
    
    for alpha in range(1000):
        if (heading < 45 and heading >= 0) or (heading < 360 and heading >= 315):
            xCoordinate = position[0] + alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))
            
        elif (heading < 315 and heading >= 225):
            yCoordinate = position[1] - alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 225 and heading >= 135):
            xCoordinate = position[0] - alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 135 and heading >= 45):
            yCoordinate = position[1] + alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        if tupleCoordinate in bounderies:
            forwardDistance.append(player.distance(tupleCoordinate))
            minForwDist = min(forwardDistance)
            #print("Left-forward distance: ", int(minForwDist))
    return minForwDist

def rightDistance():
    forwardDistance = []
    minForwDist = 0
    tupleCoordinate = (0,0)
    yCoordinate = 0
    xCoordinate = 0
    position = (int(player.xcor()), int(player.ycor()))
    if player.heading() < 90:
        heading = 360 - (90 - player.heading())
    else:
        heading = player.heading() - 90
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus
    
    for alpha in range(1000):
        if (heading < 45 and heading >= 0) or (heading < 360 and heading >= 315):
            xCoordinate = position[0] + alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))
            
        elif (heading < 315 and heading >= 225):
            yCoordinate = position[1] - alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 225 and heading >= 135):
            xCoordinate = position[0] - alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 135 and heading >= 45):
            yCoordinate = position[1] + alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        if tupleCoordinate in bounderies:
            forwardDistance.append(player.distance(tupleCoordinate))
            minForwDist = min(forwardDistance)
            #print("Right distance: ", int(minForwDist))
    return minForwDist

def rightForwardDistance():
    forwardDistance = []
    minForwDist = 0
    tupleCoordinate = (0,0)
    yCoordinate = 0
    xCoordinate = 0
    position = (int(player.xcor()), int(player.ycor()))
    if player.heading() < 45:
        heading = 360 - (45 - player.heading())
    else:
        heading = player.heading() - 45
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus
    
    for alpha in range(1000):
        if (heading < 45 and heading >= 0) or (heading < 360 and heading >= 315):
            xCoordinate = position[0] + alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))
            
        elif (heading < 315 and heading >= 225):
            yCoordinate = position[1] - alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 225 and heading >= 135):
            xCoordinate = position[0] - alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 135 and heading >= 45):
            yCoordinate = position[1] + alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        if tupleCoordinate in bounderies:
            forwardDistance.append(player.distance(tupleCoordinate))
            minForwDist = min(forwardDistance)
            #print("Right-forward distance: ", int(minForwDist))
    return minForwDist
#finished sensors


while True:
    rightForwardDistance()
    rightDistance()
    leftForwardDistance()
    leftDistance()
    forwardDistance()
    sensors = {'left': leftDistance(), 'left forward': leftForwardDistance(), 'forward': forwardDistance(), 'right forward': rightForwardDistance(), 'right': rightDistance()}
    changeDirectionTo = max(sensors, key=sensors.get)
    
    player.forward(speed)

    #change Direction To
    if changeDirectionTo == 'left':
        player.left(90)
    elif changeDirectionTo == 'left forward':
        player.left(45)
    elif changeDirectionTo == 'right forward':
        player.right(45)
    elif changeDirectionTo == 'right':
        player.right(90)
    
    #when hitting the boundary
    if (int(player.position()[0]),int(player.position()[1])) in bounderies:
        scoreset()
        
    if player.xcor() > 300 or player.xcor() < -300:
        player.right(30)
        
    if player.ycor() > 300 or player.ycor() < -300:
        player.right(30)
    
    if player.position() == myObstacle.position():
        player.right(30)
        
    if player.xcor() > -150 and player.xcor() < 150 and player.ycor() > -150 and player.ycor() < 150:
        player.right(30)

Upvotes: 0

Views: 92

Answers (3)

cdlane
cdlane

Reputation: 41872

There are several problems with your code. First is the bounderies [sic] set issue that @KirkStrauser nicely addresses. But the problem with sensors is even greater than @LucasBelfanti suggests. Instead of doing the math (i.e. geometry) and finding the distance to the target, you test every possible point along every element of a vector towards the target. Short of fixing the geometry, since you're only looking along one vector at a time, the first intercept should be the point you want, and you can break out of the sensor and avoid the next 500 or so tests.

If we combine that with using math.tan() instead of math.sin()/math.cos(), and use modular arithmetic on the angle, for one of your sensors we get something like:

from math import radians, tan

def rightDistance():
    minForwDist = 0
    tupleCoordinate = (0, 0)
    x, y = int(player.xcor()), int(player.ycor())
    heading = (player.heading() - 90) % 360
    tangent = tan(radians(heading))

    for alpha in range(1000):
        if 0 <= heading < 45 or 315 <= heading < 360:
            xCoordinate = x + alpha
            yCoordinate = xCoordinate * tangent + (y - x * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))
        elif 225 <= heading < 315:
            yCoordinate = y - alpha
            xCoordinate = (yCoordinate - (y - x * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))
        elif 135 <= heading < 225:
            xCoordinate = x - alpha
            yCoordinate = xCoordinate * tangent + (y - x * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))
        elif 45 <= heading < 135:
            yCoordinate = y + alpha
            xCoordinate = (yCoordinate - (y - x * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        if tupleCoordinate in boundaries:
            return player.distance(tupleCoordinate)

    return minForwDist

Upvotes: 1

Kirk Strauser
Kirk Strauser

Reputation: 30937

You have quite a few places like:

if something in bounderies: ...

Problem is, bounderies is a list so that lookup is a O(n) operation. And since the most common case is that something not in bounderies, it's usually going to have to examine the entire list to see that your coordinates aren't in it.

Adding a single line:

...
bounderies.extend(merge(list(range(-150,151)), [-150] * 301))
bounderies.extend(merge(list(range(-150,151)), [150] * 301))
bounderies = set(bounderies)  # <--

turns that super expensive - and frequent! - lookup from O(n) to O(1), and on my computer made the whole program run about 18x faster.

There are still plenty of other things you can do to make this faster, but this is a super easy and effective optimization.

Upvotes: 2

Lucas Belfanti
Lucas Belfanti

Reputation: 283

I have copied and ran your code and let me first answer the questions:

  1. No, your computer is ok.
  2. That shouldn't be a problem on this case.
  3. I don't believe so. Check the documentation.
  4. I'd say it could be improved.

The main place where the code is making a lot of processing is in the wile True condition. There you call 10 functions:

rightForwardDistance()
rightDistance()
leftForwardDistance()
leftDistance()
forwardDistance()
sensors = {'left': leftDistance(), 'left forward': leftForwardDistance(), 'forward': forwardDistance(),
           'right forward': rightForwardDistance(), 'right': rightDistance()}

where each one of them has a for loop of a range of 1000, removing/commenting the first 5 would make the game a little faster.

rightForwardDistance()
rightDistance()
leftForwardDistance()
leftDistance()
forwardDistance()

Besides that, the code could be improved in different ways, for example:

    for alpha in range(1000):
    if (heading < 45 and heading >= 0) or (heading < 360 and heading >= 315):
        xCoordinate = position[0] + alpha
        yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
        tupleCoordinate = (int(xCoordinate), int(yCoordinate))

    elif (heading < 315 and heading >= 225):
        yCoordinate = position[1] - alpha
        xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
        tupleCoordinate = (int(xCoordinate), int(yCoordinate))

    elif (heading < 225 and heading >= 135):
        xCoordinate = position[0] - alpha
        yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
        tupleCoordinate = (int(xCoordinate), int(yCoordinate))

    elif (heading < 135 and heading >= 45):
        yCoordinate = position[1] + alpha
        xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
        tupleCoordinate = (int(xCoordinate), int(yCoordinate))

    if tupleCoordinate in bounderies:
        forwardDistance.append(player.distance(tupleCoordinate))
        minForwDist = min(forwardDistance)
    # print("Left distance: ", int(minForwDist))

That code is repeated 5 times and can be moved to a function to avoid repeating it. The code also has unused variables, that could be removed.

yCoordinate = 0
xCoordinate = 0

After those changes, the code would be more readable and a little faster:

import turtle
import math

# Set up screen
wn = turtle.Screen()
wn.bgcolor("lightyellow")
score = 0

# Draw border
mypen = turtle.Turtle()
mypen.penup()
mypen.setposition(-300, -300)
mypen.speed(0)
mypen.pendown()
mypen.pensize(3)
for side in range(4):
    mypen.forward(600)
    mypen.left(90)
mypen.hideturtle()

# Draw obstacle
myObstacle = turtle.Turtle()
myObstacle.penup()
myObstacle.setposition(-150, -150)
myObstacle.speed(0)
myObstacle.pendown()
myObstacle.pensize(3)
for side in range(4):
    myObstacle.forward(300)
    myObstacle.left(90)
myObstacle.hideturtle()

# Create player turtle
player = turtle.Turtle()
player.penup()
player.speed(0)
player.setposition(-200, -200)
player.color("red")
player.shape("circle")

# Set speed variable
speed = 1


# define functions
def turnleft():
    player.left(30)


def turnright():
    player.right(30)


def increasespeed():
    global speed
    speed += 1


def decreasespeed():
    global speed
    if speed > 1:
        speed -= 1


# Set keyboard bindings
turtle.listen()
turtle.onkey(turnleft, "Left")
turtle.onkey(turnright, "Right")
turtle.onkey(increasespeed, "Up")
turtle.onkey(decreasespeed, "Down")


# bounderies
def merge(list1, list2):
    merged_list = [(list1[i], list2[i]) for i in range(0, len(list1))]
    return merged_list


bounderies = merge([-300] * 601, list(range(-300, 301)))
bounderies.extend(merge([300] * 601, list(range(-300, 301))))
bounderies.extend(merge(list(range(-300, 301)), [-300] * 601))
bounderies.extend(merge(list(range(-300, 301)), [300] * 601))

bounderies.extend(merge([-150] * 301, list(range(-150, 151))))
bounderies.extend(merge([150] * 301, list(range(-150, 151))))
bounderies.extend(merge(list(range(-150, 151)), [-150] * 301))
bounderies.extend(merge(list(range(-150, 151)), [150] * 301))


def scoreset():
    global score
    score += 1
    scorestring = "Score: %s" % score
    mypen.undo()
    mypen.penup()
    mypen.setposition(-340, 310)
    mypen.pendown()
    mypen.color("green")
    mypen.write(scorestring, False, align="left", font=("arial", 16, "bold"))


# sensors
def forwardDistance():
    position = (int(player.xcor()), int(player.ycor()))
    heading = player.heading()
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus

    return doMath(heading, position, tangent)



def leftDistance():
    position = (int(player.xcor()), int(player.ycor()))
    if player.heading() + 90 >= 360:
        heading = player.heading() + 90 - 360
    else:
        heading = player.heading() + 90
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus

    return doMath(heading, position, tangent)


def leftForwardDistance():
    position = (int(player.xcor()), int(player.ycor()))
    if player.heading() + 45 >= 360:
        heading = player.heading() + 45 - 360
    else:
        heading = player.heading() + 45
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus

    return doMath(heading, position, tangent)


def rightDistance():
    position = (int(player.xcor()), int(player.ycor()))
    if player.heading() < 90:
        heading = 360 - (90 - player.heading())
    else:
        heading = player.heading() - 90
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus

    return doMath(heading, position, tangent)


def rightForwardDistance():
    position = (int(player.xcor()), int(player.ycor()))
    if player.heading() < 45:
        heading = 360 - (45 - player.heading())
    else:
        heading = player.heading() - 45
    sinus = math.sin(math.radians(heading))
    cosinus = math.cos(math.radians(heading))
    tangent = sinus / cosinus

    return doMath(heading, position, tangent)


def doMath(heading, position, tangent):
    forwardDistance = []
    minForwDist = 0
    tupleCoordinate = (0, 0)

    for alpha in range(1000):
        if (heading < 45 and heading >= 0) or (heading < 360 and heading >= 315):
            xCoordinate = position[0] + alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 315 and heading >= 225):
            yCoordinate = position[1] - alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 225 and heading >= 135):
            xCoordinate = position[0] - alpha
            yCoordinate = xCoordinate * tangent + (position[1] - position[0] * tangent)
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        elif (heading < 135 and heading >= 45):
            yCoordinate = position[1] + alpha
            xCoordinate = (yCoordinate - (position[1] - position[0] * tangent)) / tangent
            tupleCoordinate = (int(xCoordinate), int(yCoordinate))

        if tupleCoordinate in bounderies:
            forwardDistance.append(player.distance(tupleCoordinate))
            minForwDist = min(forwardDistance)

    return minForwDist

# finished sensors


while True:
    sensors = {'left': leftDistance(), 'left forward': leftForwardDistance(), 'forward': forwardDistance(),
               'right forward': rightForwardDistance(), 'right': rightDistance()}
    changeDirectionTo = max(sensors, key=sensors.get)

    player.forward(speed)

    # change Direction To
    if changeDirectionTo == 'left':
        player.left(90)
    elif changeDirectionTo == 'left forward':
        player.left(45)
    elif changeDirectionTo == 'right forward':
        player.right(45)
    elif changeDirectionTo == 'right':
        player.right(90)

    # when hitting the boundary
    if (int(player.position()[0]), int(player.position()[1])) in bounderies:
        scoreset()

    if player.xcor() > 300 or player.xcor() < -300:
        player.right(30)

    if player.ycor() > 300 or player.ycor() < -300:
        player.right(30)

    if player.position() == myObstacle.position():
        player.right(30)

    if player.xcor() > -150 and player.xcor() < 150 and player.ycor() > -150 and player.ycor() < 150:
        player.right(30)

Upvotes: 3

Related Questions