Table of Contents

Turtle

Introduction

Python Season 3 needs something interesting, so we will begin by designing a script language we can use for other things. This is an important concept due to it's wide range of applications in fields such as AI and robotics, system design and so forth.

Game.py

We will begin with the Game class. main.py and Window.py are unchanged from a Basic Pygame Program style boilerplate.

import pygame
from Turtle import *
from TurtleProgram import *

class Game:
    def __init__(self, window):
        self.window = window
        self.screen = window.screen
        self.logo = window.logo
        self.font = window.font

        # Game Data
        self.FPS = 60
        self.clock = pygame.time.Clock()

        self.turtle = Turtle(self.window.width,
                             self.window.height)
        self.turtle.x = self.window.width//2
        self.turtle.y = self.window.height//2
        self.d = 0

        self.t = TurtleProgram(self.turtle)
        self.t.load_file("flower.t")
        for line in self.t.program:
            print(line)

        quit()

    def start(self):
        while True:
            self.checkEvents()
            self.update()
            self.draw()
            self.clock.tick(self.FPS)

    def draw(self):
        self.screen.fill((0, 0, 0))  # Clear the screen
        self.turtle.draw(self.screen)
        pygame.display.flip()

    def update(self):
        self.t.run()


    def checkEvents(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.quit()
                return

            if event.type == pygame.KEYDOWN:
                # key down event, process keys.

                if event.key == pygame.K_q:
                    self.quit()

    def drawText(self, at_x, at_y, text, color):
        text_surface = self.font.render(text, False, color)
        x = self.window.fontwidth * at_x
        y = self.window.fontheight * at_y
        self.screen.blit(text_surface, (x + 2, y))

    def quit(self):
        quit()
        exit()

Here drawText() could be removed. The new code revolves around this:

        self.turtle = Turtle(self.window.width,
                             self.window.height)
        self.turtle.x = self.window.width//2
        self.turtle.y = self.window.height//2
        self.d = 0

        self.t = TurtleProgram(self.turtle)
        self.t.load_file("flower.t")

        for line in self.t.program:
            print(line)

First we instantiate the Turtle class and give it our window information so it can create it's Turtle data. We set x and y to be the window height and width – but this can and should be moved into the Turtle constructor.

A second part includes loading an example turtle script from a file – but this could be done in the Game constructor by using self.t.add(“reset”) and so forth.

You can also remove the for loop which just prints out the program to console.

Turtle.py

The gameworld here is the Turtle world, which is built on top of Game.py.

import pygame
import math

class Turtle:
    def __init__(self, w, h):
        # Set width and height
        self.width = w
        self.height = h

        self.color = (255, 255, 255)
        self.line_width = 1

        # Create an image surface
        self.image = pygame.Surface((w, h))

        # Reset to baseline
        self.reset()

    def draw(self, screen):
        screen.blit(self.image, (0, 0))

We begin the constructor by defining the Turtle world and the turtle itself. These two concepts could theoretically be split into two classes. But, we will have them combined for the sake of brevity, for now.

The turtle world is the self.image = pygame.Surface(). This is an image which represents what was drawn by the turtle. Our draw function just blits this to the screen. Therefore the minimum framerate is 1, i.e. we do not need a high framerate as most of the time it will display a static image. This also implies that the turtle does not need to repeatedly draw itself every frame, but this is a change that will be made later in Game.py, and is left as an exercise to the reader (for now).

Turtle Commands

In this next section of the code, we define what the turtle can do. These are the “turtle commands”. Think of them like opcodes on a CPU, with the turtle world itself (self.image) being the “computer system” such as it's memory.


    ########################################
    # Turtle Commands                      #
    ########################################

    def clear(self):
        self.image.fill((0,0,0))

    def reset(self):
        self.pen = False
        self.clear()

        self.x = self.width//2
        self.y = self.height//2
        self.d = 0

        self.turnLeft(90)

    def penUp(self):
        self.pen = False

    def penDown(self):
        self.pen = True

    def turnLeft(self, deg):
        self.d = self.d - deg

        if self.d < 0:
            self.d = self.d % 360

    def turnRight(self, deg):
        self.d = self.d + deg

        if self.d > 360:
            self.d = self.d % 360

    def forward(self, distance):
        # Get angle in radians
        a = math.radians(self.d)

        # Calculate rise and run
        rise = distance * math.sin(a)
        run = distance * math.cos(a)

        x1 = self.x
        y1 = self.y

        x2 = x1 + run
        y2 = y1 + rise

        if self.pen:
            pygame.draw.line(self.image,
                             self.color,
                             (x1, y1), (x2, y2),
                             self.line_width)
        self.x = x2
        self.y = y2

The turtle has a small number of basic commands, as follows:

Thus there are three basic movement commands (go, turn left, turn right), and three system related commands (up, down, reset).

This class clearly and succinctly defines the turtle world and the position and velocity of the turtle.

TurtleProgram.py

The turtle program is a way to store and deal with turtle programs (i.e. '.t files').

class TurtleProgram:
    def __init__(self, turtle):
        self.turtle = turtle
        self.program = []
        self.pc = 0

    # Erase the program
    def reset(self):
        self.program = []
        self.pc = 0

    def add(self, s):
        self.program.append(s)

    def run(self):
        self.pc = 0
        while self.pc < len(self.program):
            self.do(self.program[self.pc])
            self.pc += 1

In this beginning part of the program we define what a program is (an array of lines) and we allow for lines to be added and cleared from the program. There is also the concept of running the program. The addition of the run() command as a stub leads us to part II, the do command. Think of this like an instruction fetch cycle for a CPU, with the do() method being the actual lookup and execution of the command.

opcode execution: do()

    def do(self, statement):
        # Create cmd and para based on statement.
        if ' ' in statement:
            cmd, para = statement.split(' ', 1)  # one space
            try:
                para = float(para)  # make sure it's a number.
            except:
                para = 0
        else:
            cmd, para = statement, 0  # one space

        #print(cmd, para) # for debugging

        # Comment lines -- do nothing.
        if cmd == "#":
            pass
            return

        # RESET
        if cmd == "reset":
            self.turtle.reset()
            return

        # GO
        if cmd == "go":
            self.turtle.forward(para)
            return

        # LEFT
        if cmd == "left":
            self.turtle.turnLeft(para)
            return

        # RIGHT
        if cmd == "right":
            self.turtle.turnRight(para)
            return

        # PEN UP
        if cmd == "up":
            self.turtle.penUp()
            return

        # PEN UP
        if cmd == "down":
            self.turtle.penDown()
            return

The purpose of do() is to control the turtle. It creates the idea of an automatic turtle which works on the statements of a turtle program. This program could be added manually with .add() method calls or by loading a .t file as shown below:

Loading a .t file

This is the third part of Class TurtleProgram.

    def load_file(self, filename):
        file = open(filename, "r")
        program = file.readlines()
        file.close()

        # Clean up the file.
        self.program = []
        for line in program:
            s = line.strip()
            if len(s) > 0:
                self.program.append(s)

Originally I didn't have the code which cleaned up the program and it wouldn't work. After adding this quick syntax fixer the program worked.

Future Development

1. Refactor Turtle.py

We encourage refactoring Turtle.py into two separate classes.

Since we are dealing with a conceptual turtle, there is no reason no to force it into a separate class.

Conceptually, TurtleWorld.py could be simply a surface in pygame. So it might not need full treatement in TurtleWorld.py class, but to keep things simple during a phase 1 refactoring we will not merge it with Game.py in the same breath as splitting it into two distinct classes.

On that note, it is easy to see a bit of a conflict in the idea of a GameWorld (in Game.py). TurtleWorld (in a theoretical TurtleWorld.py) is itself a discrete thing, which is not necessarily a game. So a good argument for leaving Game.py alone and making a TurtleWorld.py is that TurtleWorld is just data and Game.py is the projector of that world onto the screen. However in this theory Game.py would only contain calls to classes during it's lifecycle maintenance (checkEvents(), update(), render(), rinse and repeat.)

2. Editing

We discourage in-suite editing and saving of .t files.

There is no need to do that since they are just text files and text files are a well-established idiom on desktop PCs.

3. Expanding

Expanding the commands of the turtle could be interesting. As a next-step project, depending on the student, we could add commands for color (and maybe alpha), then we could add commands for line width and maybe line patterns. We could also add commands for polygons, arcs, and circles.

Beyond those basic drawing ideas the fun gets in when you add line numbers and GOTOs, and then flow control like IF and so forth. Some fundamental questions to ask the student are, what changes (or what new commands) would we need to write in order to do something like the PONG game, but entirely in T (Turtle)? (Ex: A delay command, a “sheet” command, a switch-sheet command for animation, etc). What about reading the state of the pixel? Or a “goto (x,y)” command for the turtle? How interesting and complex can this become? There are many ideas and possibilities to explore!