PencilCoder

Teacher's Guide: Modeling Motion

Overview

This lesson introduces students to some alternative ways to direct and track sprite motion across the screen. The lesson does not introduce any additional functions or tools; rather, the focus of this lesson is soley on different techniques for modeling motion and on algorithmic thinking more generally.

More about the lesson

Pencil Code provides a variety of tools for positioning and orienting sprites about the computer screen. Early this curriculum, students were exposed to the functions fd, bk, lt, and rt. In more recent lessons, students have learned about a range of additional functions: movexy, jumpxy, moveto, jumpto, slide, and turnto. These alternative functions can be easier to work with to achieve a desired motion goal. However, as the activies in the Coordinates! lesson illustrated, successfully doing so often requires conceptualizing motion in less familiar ways.

The lesson snippet illustrates moving a turtle randomly about the screen without resorting to the functions fd, lt, and rt. There is little immediately apparent value to approaching that task in this way; this example is meant to lay the groundwork for further challenges, for which a coordinates-based approach might be much better suited.

Use of coordinates- and orientation-based functions such moveto and turnto requires a significantly different way of thinking about motion from what students are accustomed. It requires increased focus on position on the screen, direction, and change in position—but oftentimes it can result in much simpler and cleaner code and even more satisfying results.

However, as common sense dictates (and as illustred in specific examples in later sections of this document), no single tool is best for all tasks. Students need to think through the logic and puzzle out the best tools and approach to employ. Especially when first tackling such tasks, students will likely need to apply repeated trial and error before settling on the right choice.

Notes to activities

The activities in this lesson are designed to promote thinking more explicitly in terms of location and orientation when modeling motion, and to encourage students to explore some of the benefits of this alternative approach. The Shark and Sharky activities additionally aim to give student additional practice using JavaScript objects in support of modeling motion.

Good advice for all of these activities is to start simple, focusing on one aspect of motion at a time. For example, for the first exercise, Balloon, encourage students to initially code a very simple motion in which both dx and dy are constants. After they have gotten the basic program working, students can explore modeling a non-constant dx (while still keeping dy constant). An effective solution is to add small random values to dx each iteration, in a cumulative fashion similar to how dy is modeled in the Acceleration activity.

Similarly, for the Acceleration and BouncingTurtle programs, encourage students to initially focus solely on the vertical motion, adding in other features later. Note that gravity is a form of acceleration. Thus, the basic logic of the BouncingTurtle program is analagous to the Acceleration program. The challenge of the former is how to make the ball "bounce". The "how" part is relatively easy: change the sign of dy. A bit harder is to determine "when" to flip the sign on dy. Encourage students to use conditional logic based on comparison operators (i.e., if y < minY) rather than use inside or touches. Note that regardless whether the ball is going up or going down, acceleration (the change in dy) is constant—it is a negative value, making the value of dy less with each iteration. This is a key insight when modeling gravity.

As an aside, note that the goal in the BouncingTurtle program is not to model the effects of gravity exactly. While this approach is possible, it generally is overkill. In fact, trying to use an equation such as y(t) = (1/2)at2 + v0t + h0 will unnecessarily complicate things, and not likely yield a more satisfying result on screen.

The last two activities, Sharky and Sharks, illustrate how JavaScript objects can facilitate a coordinates-based program, by associating information about location and orientation with each additional sprite, rather than programs point of the activity is to use separate objects to track the data needed for each sprite, and then to iterate over the collection of those objects.

Additional activities

  • Code a program that shows many Asteroids moving across the screen. Model the motion of each sprite based on position and change in position. Use objects to record properties for each individual asteroid. At a minimum you will need to track coordinates (x and y) and rates of movement (dx and dy). "Recycle" sprites when they go off screen, making them reenter at a random positions and following a new speed and trajectory.

  • Ricochet: Revisit the eponymous additional activity in the Notes to the Comparison Operators! lesson, this time coding it using the methods suggested in this lesson. Note: adding "randomness" each time the sprite reaches a boundary can make for an interesting design, but it opens up some difficult coding challenges. So be sure to code the nonrandom version first!
  • Blank space
  • SpeedAndBearing: Code a sprite which moves about the screen randomly based on a randomly changing bearing (i.e., the direction it is heading, with 0° denoting "north", passing this value to turnto) and speed (modeled by changing a value passed to fd each turn), rather than tracking position and changes in position. However, you will likely need to reference position when reaching an edge of the screen, to determine how to respond, i.e., either continuing motion on the opposite side of the screen (a la Ateroids) or "bouncing" (as in billiards).

  • ConstantlyOnScreen: A challenge to modeling motion based on position and changes in position is that can be difficult to maintain a constant speed. For example, if the values for dx and dy both get bigger, then the movement in each iteration of a loop with be larger too. The exact result movement can be computed using important property of right triangles, the Pythagorean Theorem, which states that the square of the hypotenuse of a right triangle equals the sum of the squares of the two legs of that triangle. Students who have taken geometry are familiar with this relationship in terms of the expression a2 + b2 = c2. In terms of dx and dy , we have dx2 + dy2 = movement2; hence this theorem states that the resulting movement is equal to sqrt(dx*dx + dy*dy). In this activity, write code to move a sprite randomly about the screen at a(n approximately) constant rate, with the underlying logic based on position and changes in position. Hint: use while> loops to make sure the dx/dy combinations are neither too big nor too small. If too big, shrink both values proportionally until they are small enough; likewise, if too small, make them bigger.

Beyond the lesson

Keep Inside

Through previous lessons, students have had many opportunities to make sprites move randomly about the screen. A common desire when writing such programs—which, in this curriculum, likely first arose very early on in the For Loops! lesson when they coded their first Erratic Turtle—is to keep the sprite from wandering offscreen.

Conditional logic is essential for this task. If the goal is to simply ensure that the sprite stays in the visible window, the following script will get the job done:

while true
  await done defer()
  if not(inside(window))
    turnto 0,0 
  else 
    rt -5+random(11) 
  fd 3  

Though effective at keeping the sprite in the screen, this algorithm yields unsatisfactory movements at the boudaries:

It's common to desire either a more life-like "bounce" each time the sprite reaches the edge of the screen, such as what happens when a ball hits the edge of a billiards table; or a continuation of the movement on the opposite side of the screen, such as in the video games Asteroids and PacMan.

Given that we already have some code to create a randomly moving sprite, it's tempting to try to tweak it to get the desired results, such as by simply replacing turnto 0,0 with something that gives a more satisfactory turn at the boundaries. However, the required replacement cocde turns out to be quite complicated. Students who took on the Ricochet activity in the Notes to the Comparison Operators! lesson have likely already discovered this; they likely wrote some relatively complicated code that computed turn angles based on output of the direction function, but with that computation dependent on which edge of the screen was reached. Which is to say, the solution requires quite a bit of mathematical manipulations embedded in a web of conditional logic (i.e, if/else statements). After much puzzling and simplifying, we might get something like this:

while true
  fd 5 
  await done defer()
  [x,y] = getxy()
  if y>h/2 or y<-h/2 or x>w/2 or x<-w/2
    heading = direction()
    if y>h/2 or y<-h/2  
      if -w/2<x<w/2 #(top or bottom) AND NOT(left or right)
        if heading>=0
          heading = 180 - heading 
        else        #(top or bottom) AND (left or right)
          heading = -(180 + heading)
      else
        heading = 180 + heading
    else 
      if heading>=0 # NOT(top or bottom) AND right
        heading = -heading 
      else          # NOT(top or bottom) AND left
        heading = 360 - heading 
    turnto heading   

Effective, yes, but difficult to write and thereful prone to coding bugs.

This lesson aims to encourage students to approach coding motion in different ways and seek an alternative that could avoid much of the complexity of the original Ricochet program. In this case, the goal is to think a bit more in terms of position and change of position, rather than from the inherent relative positioning standpoint inherent in heavy use of the functions fd, lt, and rt

We begin anew from scratch, beginning with a simple random motion algorithm, similar to the example provided in the lesson:

[x,y] = [0,0]
dx = 1
dy = 1
while true
  await done defer()
  dx = dx + random(normal)/3
  dy = dy + random(normal)/3
  x = x+dx 
  y = y+dy
  turnto x,y
  moveto x,y

This code is certainly more complicated than the example given at the beginning of this section, which relied on fd, rt, and lt. However, the benefits to this approach become apparent when we add code to keep the sprite on the screen.

Because we are always keeping track of the sprite's coordinates, we don't need calls to inside or getxy to test if the sprite is onscreen. Instead, with each iteration, we check the coordinates to see if a motion will result in the sprite moving off screen, as illustrated in the following example. The conditional logic prevents the sprites from going out of bounds by simply changing the value of either dx or dy (or both) to its opposite, which yields a "billiards bounce" effect.

[w,h] = sizexy()
[x,y] = [0,0]
dx = 1
dy = 1
while true
  await done defer()
  dx = dx + random(normal)/3
  dy = dy + random(normal)/3
  if x+dx>w/2 or x+dx<-w/2
    dx = -dx
  if y+dy>h/2 or y+dy<-h/2
    dy = -dy
  x = x+dx 
  y = y+dy
  
  turnto x,y
  moveto x,y

Alternatively, we can employ a PacMan/Asteroids effect with just a slight variation of the previous example:

[w,h] = sizexy()
[x,y] = getxy()
dx = 1
dy = 1
while true
  await done defer()
  dx = dx + random(normal)/3
  dy = dy + random(normal)/3
  if x+dx>w/2 or x+dx<-w/2
    x = -x
    jumpto x,y
  if y+dy>h/2 or y+dy<-h/2
    y = -y
    jumpto x,y
  x = x+dx 
  y = y+dy
  
  turnto x,y
  moveto x,y

Constant speed

A drawback to the movement algorithms discussed thus far in this seciton is that the sprites no longer move at constant speed, as they did in programs that moved the sprite based on fd, rt, and lt. A conceptually straightforward approach to maintain a constant speed when using the coordinates-based movement logic is to apply the Pythagorean Theorem. Students are encouraged to explore this concept in the Additional Activity ConstantlyOnScreen, above.

In short, one can compute randomly compute values for dx and dy, and then proportionately scale them up or down until the sums of their squares equals a given constant, which equals the square of a given "speed" parameter. Though conceptually straightforward, this application of the Pythagorean Theorem gets complicated by the fact that dx and dy can take negative values. A detailed discussion is omitted here; however, these ideas are explored in the following three scripts:

Pedagogy

Tools mediate thinking

A long-recognized principle of the learning sciences literature is that tools mediate thinking. This concept, in its most abstract sense associated with the Russian scholar Vygotsky, recognizes that how we think about a problems depends on the tools (conceptual as well as physical) with which we are equipped to tackle that problem.

This lesson illustrates Vygotsky's concept in the context of algorithmic thinking. Previously in this curriculum, students learned a range of coordinates-based movement funtions, and were given a taste of how they might be used. This lesson encourages them to further explore the opportunities to use these additional functions, which in some cases can yield more concise, reliable, efficient, and/or elegant solutions to coding challenges.