Difference between revisions of "W1512 Colorful Turtles"
Line 204: | Line 204: | ||
What would happen if we add just one line of code that rotates the turtle a few degrees after we draw each square? | What would happen if we add just one line of code that rotates the turtle a few degrees after we draw each square? | ||
<syntaxhighlight lang="swift" highlight="12"> | <syntaxhighlight lang="swift" highlight="12"> | ||
override func | override func render(canvas:Canvas) { | ||
if let canvasSize = canvas.canvasSize, !didDraw { | if let canvasSize = canvas.canvasSize, !didDraw { | ||
Revision as of 21:17, 10 November 2019
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.
Experiment[edit]
Getting Started[edit]
Begin a new project
Create a new Igis shell project within your "Experiences" directory.
cd ~/Experiences
git clone https://github.com/TheCoderMerlin/IgisShellD W1512
Enter into the Sources/IgisShellD directory of the new project.
cd W1512/Sources/IgisShellD/
Run the project.
./run.sh
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.)
Helpful hint: It's useful to bookmark this page in your browser.
First Steps[edit]
Let's draw a square. Edit file "main.swift":
emacs main.swift
Edit the file by finding the definition of the Painter class. Before the init constructor, add the following property:
var didDraw = false
This will enable us to keep track of whether or not the turtle completed its drawing mission.
Now, add an render method 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.paint(turtle)
didDraw = true
}
}
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:
- Tell the turtle to move forward 100 steps
- Turn right (clockwise) 90 degrees
- Tell the turtle to move forward 100 steps
- Turn right (clockwise) 90 degrees
- Tell the turtle to move forward 100 steps
- Turn right (clockwise) 90 degrees
- Tell the turtle to move forward 100 steps
- Turn right (clockwise) 90 degrees
Because the turtle is facing up (north) initially, our turtle first moves upward.
Run the project.
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:
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.paint(turtle)
didDraw = true
}
}
Run the project. View the results in the browser as you did earlier.
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 update.
func paintSquare(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)
paintSquare(turtle:turtle)
canvas.paint(turtle)
didDraw = true
}
}
Run the project. View the results in the browser as you did earlier.
Let's add a parameter to the function enabling us to make a square of any color and any size.
func paintSquare(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)
paintSquare(turtle:turtle, color:Color(.red), width:50)
canvas.paint(turtle)
didDraw = true
}
}
Run the project. View the results in the browser as you did earlier.
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
}
}
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?
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.
Run the project. View the results in the browser as you did earlier.
Was your hypothesis correct? If not, why not?
Exercises[edit]
- 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]
- 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