Difference between revisions of "W1512 Colorful Turtles"

From Coder Merlin
Line 8: Line 8:
== Background ==
== Background ==
Turtle graphics enable us to not only lift and drop the pen, but also to change the pen color and thickness.  Using these tools and prior knowledge, we can generate a wide variety of colorful and complex patterns.
Turtle graphics enable us to not only lift and drop the pen, but also to change the pen color and thickness.  Using these tools and prior knowledge, we can generate a wide variety of colorful and complex patterns.
== Prepare ==
{{ScenesShellPrepare|W1512}}


== Experiment ==
== Experiment ==
{{StopProgram|Stop the running program.
Return to the ''console'' and press {{SpecialKey|CONTROL|C}}
}}


=== Getting Started ===
=== First Steps ===
[[File:Breathe-document-new.svg|left|50px|link=|Breathe-document-new]] Begin a '''new''' project
Open the file {{Pathname|Background.swift}} in emacs.   


Create a new Igis shell project within your "Experiences" directory.
Add a new property to the {{SwiftClass|Background}} class:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="swift" highlight="3">
cd ~/Experiences
class Background : RenderableEntity {
git clone https://github.com/TheCoderMerlin/IgisShellD W1512
</syntaxhighlight>


Enter into the Sources/IgisShellD directory of the new project.
    var didDraw = false
<syntaxhighlight lang="bash">
cd W1512/Sources/IgisShellD/
</syntaxhighlight>


[[File:Start button green arrow.svg|left|link=|Start button green arrow]] Run the project.
    init() {
<br>
        // Using a meaningful name can be helpful for debugging
<syntaxhighlight lang="bash">
        super.init(name:"Background")
./run.sh
    }
</syntaxhighlight>
}
 
Open a browser (or use a new tab on an already-open browser).
Go to the URL:  http://www.codermerlin.com/users/user-name/dyn/index.html
 
NOTE:  You MUST change '''user-name''' to your actual user name.  For example, http://www.codermerlin.com/users/john-williams/dyn/index.html
 
You'll know your successful if you see the title bar change to "Coder Merlin: IGIS".  (The browser window will be blank because we haven't added any graphics yet.)
 
{{notice|[[File:Oxygen480-actions-help-hint.svg|frameless|30px]]|Helpful hint:  It's useful to bookmark this page in your browser.}}
 
=== First Steps ===
Let's draw a square.  Edit file "main.swift":
<syntaxhighlight lang="bash">
emacs main.swift
</syntaxhighlight>
 
Edit the file by finding the definition of the '''Painter''' class.  Before the '''init''' constructor, add the following property:
<syntaxhighlight lang="swift">
    var didDraw = false
</syntaxhighlight>
</syntaxhighlight>


This will enable us to keep track of whether or not the turtle completed its drawing mission.
This will enable us to keep track of whether or not the turtle completed its drawing mission.


Now, add an '''render''' method as follows:
Add a new method (below {{SwiftIdentifier|init}}) as follows:
<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
     override func render(canvas:Canvas) {
     override func render(canvas:Canvas) {
Line 67: Line 49:
             turtle.forward(steps:100)
             turtle.forward(steps:100)
             turtle.right(degrees:90)
             turtle.right(degrees:90)
             canvas.paint(turtle)
             canvas.render(turtle)


             didDraw = true
             didDraw = true
Line 74: Line 56:
</syntaxhighlight>
</syntaxhighlight>


The method creates a new Turtle.  (Remember that the initial position of the turtle is ''home''.  In the ''home'' position, the turtle is located in the center of the screen and pointed up (northward). We then:
The conditional that we added will evaluate to true only if the size of the canvas is available and we didn't yet draw with the turtle.  If the consequent is executed, the very last statement will set the flag '''didDraw''' to true, ensuring that the consequent executes only once.
 
The method creates a new Turtle.  (Remember that the initial position of the turtle is ''home''.  In the ''home'' position, the turtle is located in the center of the canvas and pointed up (northward). We then:
# Tell the turtle to move forward 100 steps
# Tell the turtle to move forward 100 steps
# Turn right (clockwise) 90 degrees
# Turn right (clockwise) 90 degrees
Line 84: Line 68:
# Turn right (clockwise) 90 degrees
# Turn right (clockwise) 90 degrees


Because the turtle is facing up (north) initially, our turtle first moves upward.
Remember to save the file, then suspend emacs.
 
{{RunProgram|Run the program and view in a browser before continuing.}}


[[File:Start button green arrow.svg|left|link=|Start button green arrow]] Run the project. <br>View the results in the browser as you did earlier.


It appears that several of those steps were repeated. As we've learned, a better option to organize this code would be to use a loop.  Let's refactor our code as follows:
It appears that several of those steps were repeated. As we've learned, a better option to organize this code would be to use a loop.  Let's refactor our code as follows:
Line 98: Line 83:
                 turtle.right(degrees:90)
                 turtle.right(degrees:90)
             }
             }
             canvas.paint(turtle)
             canvas.render(turtle)


             didDraw = true
             didDraw = true
Line 105: Line 90:
</syntaxhighlight>
</syntaxhighlight>


[[File:Start button green arrow.svg|left|link=|Start button green arrow]] Run the project. View the results in the browser as you did earlier.
Remember to save the file, then suspend emacs.


{{RunProgram|Run the program and view in a browser before continuing.}}


While this is an improvement, we can do better.  Let's refactor some more and move the interesting functionality to a separate function and invoke that function from '''render'''.
 
While this is an improvement, we can do better.  Let's refactor some more and move the interesting functionality to a separate function and invoke that function from {{SwiftIdentifier|render}}.
<syntaxhighlight lang="swift" highlight="1-6,12">
<syntaxhighlight lang="swift" highlight="1-6,12">
     func paintSquare(turtle:Turtle) {
     func renderSquare(turtle: Turtle) {
         for _ in 1 ... 4 {
         for _ in 1 ... 4 {
             turtle.forward(steps:100)
             turtle.forward(steps: 100)
             turtle.right(degrees:90)
             turtle.right(degrees: 90)
         }
         }
     }
     }
Line 120: Line 107:
         if let canvasSize = canvas.canvasSize, !didDraw {
         if let canvasSize = canvas.canvasSize, !didDraw {


             let turtle = Turtle(canvasSize:canvasSize)
             let turtle = Turtle(canvasSize: canvasSize)
             paintSquare(turtle:turtle)
             renderSquare(turtle: turtle)
             canvas.paint(turtle)
             canvas.render(turtle)


             didDraw = true
             didDraw = true
Line 128: Line 115:
     }
     }
</syntaxhighlight>
</syntaxhighlight>
[[File:Start button green arrow.svg|left|link=|Start button green arrow]] Run the project. View the results in the browser as you did earlier.
Remember to save the file, then suspend emacs.


Remember to save the file, then suspend emacs.


{{RunProgram|Run the program and view in a browser before continuing.}}




Let's add a parameter to the function enabling us to make a square of any color and any size.
Let's add a parameter to the function enabling us to make a square of any color and any size.
<syntaxhighlight lang="swift" highlight="1-2,4,13">
<syntaxhighlight lang="swift" highlight="1-2,4,13">
     func paintSquare(turtle:Turtle, color:Color, width:Int) {
     func renderSquare(turtle: Turtle, color: Color, width: Int) {
         turtle.penColor(color:color)
         turtle.penColor(color:color)
         for _ in 1 ... 4 {
         for _ in 1 ... 4 {
             turtle.forward(steps:width)
             turtle.forward(steps: width)
             turtle.right(degrees:90)
             turtle.right(degrees: 90)
         }
         }
     }
     }
Line 147: Line 136:


             let turtle = Turtle(canvasSize:canvasSize)
             let turtle = Turtle(canvasSize:canvasSize)
             paintSquare(turtle:turtle, color:Color(.red), width:50)
             renderSquare(turtle:turtle, color:Color(.red), width:50)
             canvas.paint(turtle)
             canvas.render(turtle)


             didDraw = true
             didDraw = true
Line 154: Line 143:
     }
     }
</syntaxhighlight>
</syntaxhighlight>
[[File:Start button green arrow.svg|left|link=|Start button green arrow]] Run the project. View the results in the browser as you did earlier.
 
{{RunProgram|Run the program and view in a browser before continuing.}}
 


=== First Pattern ===
=== First Pattern ===

Revision as of 22:03, 3 September 2020

Within these castle walls be forged Mavens of Computer Science ...
— Merlin, The Coder
Hawaii Turtle

Prerequisites[edit]

Research[edit]

Background[edit]

Turtle graphics enable us to not only lift and drop the pen, but also to change the pen color and thickness. Using these tools and prior knowledge, we can generate a wide variety of colorful and complex patterns.

Prepare[edit]

Create a new Scenes shell project within your Experiences directory:

ty-cam@codermerlin:~$  cd ~/Experiences

ty-cam@codermerlin:~/Experiences$  git clone https://github.com/TheCoderMerlin/ScenesShellBasic W1512


Enter the Sources/ScenesShell directory of the new project:

ty-cam@codermerlin:~/Experiences$  cd W1512/Sources/ScenesShell/


Start button green arrow
Run the program.

ty-cam@codermerlin:~/Experiences/W1512/Sources/ScenesShell$  run


Ensure that you are logged on to the wiki. Then, click on the Tools menu followed by right-clicking on IGIS and selecting the menu item Open in New Window or Open in New Tab.

You'll know you're successful if you see the title bar change to "Coder Merlin: IGIS". (The browser window will be blank because we haven't added any graphics yet.)

Hint.pngHelpful Hint
It's useful to bookmark this page in your browser.

Experiment[edit]

Stop button red ex
Stop the running program.

Return to the console and press CONTROL-C

First Steps[edit]

Open the file Background.swift in emacs.

Add a new property to the Background class:

class Background : RenderableEntity {

    var didDraw = false

    init() {
        // Using a meaningful name can be helpful for debugging
        super.init(name:"Background")
    }
}

This will enable us to keep track of whether or not the turtle completed its drawing mission.

Add a new method (below init) as follows:

    override func render(canvas:Canvas) {
        if let canvasSize = canvas.canvasSize, !didDraw {

            let turtle = Turtle(canvasSize:canvasSize)
            turtle.forward(steps:100)
            turtle.right(degrees:90)
            turtle.forward(steps:100)
            turtle.right(degrees:90)
            turtle.forward(steps:100)
            turtle.right(degrees:90)
            turtle.forward(steps:100)
            turtle.right(degrees:90)
            canvas.render(turtle)

            didDraw = true
        }
    }

The conditional that we added will evaluate to true only if the size of the canvas is available and we didn't yet draw with the turtle. If the consequent is executed, the very last statement will set the flag didDraw to true, ensuring that the consequent executes only once.

The method creates a new Turtle. (Remember that the initial position of the turtle is home. In the home position, the turtle is located in the center of the canvas and pointed up (northward). We then:

  1. Tell the turtle to move forward 100 steps
  2. Turn right (clockwise) 90 degrees
  3. Tell the turtle to move forward 100 steps
  4. Turn right (clockwise) 90 degrees
  5. Tell the turtle to move forward 100 steps
  6. Turn right (clockwise) 90 degrees
  7. Tell the turtle to move forward 100 steps
  8. Turn right (clockwise) 90 degrees

Remember to save the file, then suspend emacs.

Start button green arrow
Run the program and view in a browser before continuing.


It appears that several of those steps were repeated. As we've learned, a better option to organize this code would be to use a loop. Let's refactor our code as follows:

    override func render(canvas:Canvas) {
        if let canvasSize = canvas.canvasSize, !didDraw {

            let turtle = Turtle(canvasSize:canvasSize)
            for _ in 1 ... 4 {
                turtle.forward(steps:100)
                turtle.right(degrees:90)
            }
            canvas.render(turtle)

            didDraw = true
        }
    }

Remember to save the file, then suspend emacs.

Start button green arrow
Run the program and view in a browser before continuing.


While this is an improvement, we can do better. Let's refactor some more and move the interesting functionality to a separate function and invoke that function from render.

    func renderSquare(turtle: Turtle) {
        for _ in 1 ... 4 {
            turtle.forward(steps: 100)
            turtle.right(degrees: 90)
        }
    }

    override func render(canvas:Canvas) {
        if let canvasSize = canvas.canvasSize, !didDraw {

            let turtle = Turtle(canvasSize: canvasSize)
            renderSquare(turtle: turtle)
            canvas.render(turtle)

            didDraw = true
        }
    }

Remember to save the file, then suspend emacs.

Remember to save the file, then suspend emacs.

Start button green arrow
Run the program and view in a browser before continuing.


Let's add a parameter to the function enabling us to make a square of any color and any size.

    func renderSquare(turtle: Turtle, color: Color, width: Int) {
        turtle.penColor(color:color)
        for _ in 1 ... 4 {
            turtle.forward(steps: width)
            turtle.right(degrees: 90)
        }
    }

    override func render(canvas:Canvas) {
        if let canvasSize = canvas.canvasSize, !didDraw {

            let turtle = Turtle(canvasSize:canvasSize)
            renderSquare(turtle:turtle, color:Color(.red), width:50)
            canvas.render(turtle)

            didDraw = true
        }
    }
Start button green arrow
Run the program and view in a browser before continuing.


First Pattern[edit]

Let's make good use of our function and invoke it in a loop, drawing 50 squares of different sizes.

    override func render(canvas:Canvas) {
        if let canvasSize = canvas.canvasSize, !didDraw {

            let turtle = Turtle(canvasSize:canvasSize)
            for i in 1 ... 50 {
                let width = i * 10
                paintSquare(turtle:turtle, color:Color(.red), width:width)
            }
            canvas.paint(turtle)

            didDraw = true
        }
    }
Start button green arrow

Run the project. View the results in the browser as you did earlier.


Now, let's change the colors in a specific pattern. To do so, we'll define an array to hold some colors, and then select each color in turn.

    override func render(canvas:Canvas) {
        if let canvasSize = canvas.canvasSize, !didDraw {

            let turtle = Turtle(canvasSize:canvasSize)
            let colors = [Color(.palegreen), Color(.mediumspringgreen), Color(.limegreen), Color(.lime)]
            var colorIndex = 0
            for i in 1 ... 50 {
                let width = i * 10
                let color = colors[colorIndex]
                paintSquare(turtle:turtle, color:color, width:width)
                colorIndex = (colorIndex + 1) % colors.count
            }
            canvas.paint(turtle)

            didDraw = true
        }
    }

Take careful note of how the colors are selected in turn. What prevents us from running off the right edge of the array?

Start button green arrow

Run the project. View the results in the browser as you did earlier.

More Interesting Patterns[edit]

What would happen if we add just one line of code that rotates the turtle a few degrees after we draw each square?

    override func render(canvas:Canvas) {
        if let canvasSize = canvas.canvasSize, !didDraw {

            let turtle = Turtle(canvasSize:canvasSize)
            let colors = [Color(.palegreen), Color(.mediumspringgreen), Color(.limegreen), Color(.lime)]
            var colorIndex = 0
            for i in 1 ... 50 {
                let width = i * 10
                let color = colors[colorIndex]
                paintSquare(turtle:turtle, color:color, width:width)
                colorIndex = (colorIndex + 1) % colors.count
                turtle.right(degrees:5)
            }
            canvas.paint(turtle)

            didDraw = true
        }
    }

Before running the program, give the above change some thought. What do you predict will happen? Draw your hypothesis on graph paper before proceeding.

Start button green arrow

Run the project. View the results in the browser as you did earlier.


Was your hypothesis correct? If not, why not?

Exercises[edit]

  1. Produce a new pattern by repeatedly drawing a polygon (of no fewer than five sides) of various sizes at various angles. Define your own color scheme and rotate through the colors.

Supplemental Exercises[edit]

  1. Before drawing your pattern, paint the canvas with an aesthetic background using the drawing primitives that were covered in earlier projects (e.g. Ellipses, Rectangles).

Key Concepts[edit]

  • Beautiful patterns may be generated using simple polygons
    • Polygons may be drawn in various sizes
    • After drawing each polygon, the turtle's starting position and/or rotation is slightly modified, so that the subsequent polygon is subtly different
    • As more and more polygons are drawn, a pattern will emerge
  • Defining functions to contain repeated code greatly aids comprehension
  • A series of colors may be represented in an array
    • The expression index = (index + 1) % count is a very common paradigm for rotating through a series of elements in an array