User Tools

Site Tools


turtle

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
turtle [2024/01/12 03:03] – created appledogturtle [2024/01/12 03:37] (current) appledog
Line 5: Line 5:
  
 == Game.py == Game.py
 +We will begin with the Game class. main.py and Window.py are unchanged from a [[Basic Pygame Program]] style boilerplate.
 +
 +<Code:Python>
 +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()
 +</Code>
 +
 +Here drawText() could be removed. The new code revolves around this:
 +
 <Code> <Code>
 +        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)
 +</Code>
 +
 +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.
 +
 +<Code:Python>
 +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))
 +
 +</Code>
 +
 +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.
 +
 +<Code:Python>
 +
 +    ########################################
 +    # 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
 +
 +</Code>
 +
 +The turtle has a small number of basic commands, as follows:
 +
 +* reset -- This command //clears the screen,// lifts the //pen//, and resets the //turtle position.//
 +* up -- this raises the //pen// thus turning off drawing during //turtle movement.//
 +* down -- thus lowers the //pen// thus turning on drawing during //turtle movement.//
 +* go -- This moves the //turtle// forward, drawing a line if the //pen// is //down.//
 +* left -- This causes the //turtle// to turn a number of degrees to the left.
 +* right -- This causes the //turtle// to turn to the right, similar to the //left// command.
 +
 +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').
 +
 +<Code:Python>
 +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
 +</Code>
 +
 +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()
 +<Code:Python>
 +    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
 +</Code>
 +
 +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.
 +
 +<Code:Python>
 +    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)
 +
 </Code> </Code>
 +
 +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!
turtle.1705028604.txt.gz · Last modified: 2024/01/12 03:03 by appledog

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki