This course has already ended.

The latest instance of the course can be found at: O1: 2024

Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.

Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit. Myös suomenkielisessä materiaalissa käytetään ohjelmien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.

Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.


Chapter 2.5: Pictures and Positions

About This Page

Questions Answered: How about some more example classes? What more can I do with a picture object? How do I make it more convenient to test objects in the REPL?

Topics: Methods that create and return instances; methods that receive references to objects. The special method toString. Image manipulation: positioning, cropping, etc.

What Will I Do? Multiple programming assignments interwoven with the text.

Rough Estimate of Workload:? A couple of hours, perhaps. More if you go through the optional material at the end. There are barely any genuinely new concepts, but the programming assignments may challenge the novice programmer.

Points Available: A50.

Related Modules: Odds. We’ll re-revisit Subprograms, too.

../_images/person06.png

Our Next Objective: Locations within Pictures

../_images/pong.png

The top-left corner of a Pong paddle is located at (40,120), counting pixels from the top left. When programming graphics, it’s common to use coordinates that increase from left to right and from top to bottom.

Two-dimensional coordinates are a handy concept in many programs. You can use a pair of coordinates to represent the location of a character in a game, for example. Or, when working with images, you can use coordinates to pinpoint a specific pixel that you wish to target with an operation:

  • “Within this large image, select a square piece 400 pixels wide and high, so that its top-left corner is 150 pixels rightwards from the original’s left edge and 200 pixels down from the top edge.”
  • “Draw this small image on top of that larger one so that the small image’s center is exactly at (300,200) within the large image.”
  • “Draw a dot at the given coordinates (x,y), where x and y stand for the mouse cursor’s current position, counting from the top left.”

You’ll soon learn to issue Scala commands much like the ones expressed in English above. Towards that end, we’ll first develop a class that represents pairs of coordinates. As we do so, we’ll also apply what we learn to the Odds class from the previous chapter.

The class Pos(ition)

Let’s call our coordinate-pair class simply Pos. Perhaps it could work like this?

val first = new Pos(50, 12.5)first: Pos = Pos@3245f574
val second = new Pos(-30, 100)second: Pos = Pos@2fcad1a3
first.xres0: Double = 50.0
first.yres1: Double = 12.5
first.descriptionres2: String = (50.0,12.5)
second.descriptionres3: String = (-30.0,100.0)

Using what we know, it’s easy to implement a class that works like that:

class Pos(val x: Double, val y: Double) {

  def description = "(" + this.x + "," + this.y + ")"

}

Of course, we could just forget about Pos and represent coordinates as two separate Doubles. (Indeed, that’s what we did in Chapters 1.7 and 1.8 when we computed distances between points.) But it’s advantageous to consider positions to be a data type in their own right: a type that binds together a pair of numerical values (x and y) and provides a selection of methods for manipulating such pairs. The advantages will become increasingly clear as we go on.

Creating New Instances within a Method

Computing on Positions

It’s easy to conceive of operations that we can apply to a position to produce new positions. For instance, we might wish to compute a position that is located a given distance away from another position:

val topLeftCorner = new Pos(40, 120)topLeftCorner: Pos = Pos@2c8d4d39
val aBitToTheRight = topLeftCorner.addX(30)aBitToTheRight: Pos = Pos@25b7325c

Notice: addX returned a reference to a new Pos object that represents a location whose x coordinate is 30 units (such as pixels) greater than the position whose method was called.

All the methods of a Pos are also available on the new position object, which the variable aBitToTheRight now refers to:

aBitToTheRight.descriptionres4: String = (70.0,120.0)
val moreToTheRight = aBitToTheRight.addX(70)moreToTheRight: Pos = Pos@2d9ff490
moreToTheRight.descriptionres5: String = (140.0,120.0)

Here’s an implementation for addX:

class Pos(val x: Double, val y: Double) {

  def description = "(" + this.x + "," + this.y + ")"

  def addX(dx: Double) = {
    val relativePosition = new Pos(this.x + dx, this.y)
    relativePosition
  }

}
We create a new Pos object to represent the new position.
We compute the new position’s coordinates based on the coordinates of the this object. The parameter variable dx indicates the difference between the two positions. A simple sum produces the new x coordinate; the new position hasthe same y as the original.
We return a reference to the newly created object.

In the above version of the class, the local variable relativePosition was there merely to emphasize the two-stage process: first, we create the object; then, we return a reference that points to it. A short-and-sweet way to phrase the same is:

class Pos(val x: Double, val y: Double) {

  def description = "(" + this.x + "," + this.y + ")"

  def addX(dx: Double) = new Pos(this.x + dx, this.y)

}

When a method returns a reference, it’s up to the method’s caller to decide what to do with that reference. In our earlier example, we happened to assign the reference returned by addX to a variable (moreToTheRight), but we can just as well use the method call as part of a larger expression. For example, we can form a chain of method calls:

moreToTheRight.addX(100).descriptionres6: String = (240.0,120.0)
The Pos object’s addX method returns a reference to another Pos. This calling code doesn’t store the reference in a variable; it just immediately invokes description on the returned object, producing a return value of type String.

We can of course define an addY method as an analogue of addX. The code below additionally contains an add method that attends to both coordinates.

class Pos(val x: Double, val y: Double) {

  def description = "(" + this.x + "," + this.y + ")"

  def addX(dx: Double) = new Pos(this.x + dx, this.y)

  def addY(dy: Double) = new Pos(this.x, this.y + dy)

  def add(dx: Double, dy: Double) = new Pos(this.x + dx, this.y + dy)

}

Assume Pos is defined as above. Consider the sequence of commands below.

val myPos = new Pos(100, 200)
myPos.addX(1000)
myPos.addY(500)
myPos.add(10, 20)
println(myPos.description)

Which of the following correctly describes the class and the above commands?

We now have a decent foundation for our Pos class. We’ll add more methods to it shortly. On a more general level, we can say that we’ve defined not just a particular type of data but a set of rules that govern how new instances get created as we compute with the type.

Assignment: Odds (Part 2 of 9)

Introduction

Let’s return for a bit to the Odds program that we initiated in Chapter 2.4.

Consider two events: say, rolling a six on a die (which has 5/1 odds of happening) and a coin toss coming up tails (1/1 odds).

val six = new Odds(5, 1)six: o1.Odds = o1.Odds@60a6fd
val tails = new Odds(1, 1)tails: o1.Odds = o1.Odds@111a008

We can get the odds of not rolling a six by swapping the numbers: 1/5. A coin coming up heads has the same odds as it coming up tails, and indeed 1/1 is the same whichever way around.

More generally: if an object represents a1/a2 odds, the odds of the opposite event are a2/a1.

Task description

Add a not method to Odds. This effect-free method creates an object that represents the odds of the opposite event. It should work like this:

val somethingOtherThanSix = six.notres7: Odds = o1.Odds@56258cb3
somethingOtherThanSix.fractionalres8: String = 1/5
tails.not.fractionalres9: String = 1/1
new Odds(10, 19).not.fractionalres10: String = 19/10

Instructions and hints

  • The method must create a new Odds instance and return a reference to that object. It mustn’t return just a string or a number.
  • You need to write very little code.

A+ presents the exercise submission form here.

Parameters That Refer to Instances

Pseudocode for determining distances

Turning again to class Pos, let’s add two methods called xDiff and yDiff, which compute the distance between two positions along a single coordinate axis.

val first = new Pos(50, 12.5)first: Pos = Pos@3245f574
val second = new Pos(-30, 100)second: Pos = Pos@2fcad1a3
val differenceBetweenXCoordinates = second.xDiff(first)differenceBetweenXCoordinates: Double = 80.0
second.yDiff(first)res11: Double = -87.5
first.yDiff(second)res12: Double = 87.5
first.xDiff(new Pos(60, 20))res13: Double = 10.0

Here’s a first sketch of the method implementations. This isn’t quite Scala yet:

class Pos(val x: Double, val y: Double) {
  // ...

  def xDiff(another: Pos) = return a number that is the difference between the given
                            object’s x coordinate and your own x coordinate

  def yDiff(another: Pos) = (the same but for the objects’ y coordinates)
}

The sketch is written in pseudocode (pseudokoodi): text that resembles program code but is meant only for a human reader. In the pseudocode that we’ve used here, some parts are actual Scala but some key parts have been only outlined in English.

From pseudocode to Scala

How would you implement the above pseudocode in Scala? Stop and think. Write the required code on a piece of paper, for instance. It’s not long.

The following animation may help you answer the question. The full program code will appear at the very end of the animation. You’ll be warned before that happens, so don’t worry about spoilers.

Notice how you can refer to both the attributes of the object running the method (e.g., this.x) and the attributes of another object (here using a variable named another: another.x)?

Also note: another is just a regular variable name, not a programming-language keyword like this. We could replace the word “another” with something else without affecting the program’s behavior.

Consider these lines of code:

val first  = new Pos(50, 12.5)
val second = new Pos(-30, 100)
second.xDiff(first)

Think about what happens when those lines are executed. Consider in particular the moment when the computer has just started executing the xDiff method body. Which of the following claims hold at this time?

One more distance method

The method distance should compute the distance between two positions along a straight line:

val first = new Pos(50, 12.5)first: Pos = Pos@3245f574
val second = new Pos(-30, 100)second: Pos = Pos@2fcad1a3
second.distance(first)res14: Double = 118.55905701379376
first.distance(second)res15: Double = 118.55905701379376

The algorithm described in the following pseudocode uses a right triangle. Moreover, it makes use of the idea that an object can call its own method to “ask itself stuff”.

class Pos(val x: Double, val y: Double) {
  // ...

  def xDiff(another: Pos) = another.x - this.x

  def yDiff(another: Pos) = another.y - this.y

  def distance(somePos: Pos) = Ask yourself: what are your own xDiff and yDiff relative to the
                               other position provided as a parameter? Compute the two-dimensional
                               distance from a right triangle that has those two values as legs.
}

To make an object call its own method, we simply write this.methodName(parameters). (This is similar to how a Person commanded itself at the end of the previous chapter.)

import scala.math.hypot

class Pos(val x: Double, val y: Double) {
  // ...

  def xDiff(another: Pos) = another.x - this.x

  def yDiff(another: Pos) = another.y - this.y

  def distance(somePos: Pos) = hypot(this.xDiff(somePos), this.yDiff(somePos))

}
The object whose distance method has been called calls its own xDiff and yDiff methods.
The function hypot from Scala’s math package (Chapter 1.6) takes care of the rest.
Just to illustrate, we’ve deliberately used a different name for the parameter variable here. We could have also called it another, or something else.

On Pseudocode

Programmers use pseudocode for several purposes, primarily to sketch out solutions or to describe algorithms in a more or less programming-language-independent form.

There are many different ways to write pseudocode. In O1, we’ll continue to use the sort of pseudocode that we used above, combining actual Scala with informal natural language; in the ebook, pseudocode is highlighted in peachy boxes like the ones you just saw. We’ll often start solving a problem by drafting an overall solution in pseudocode, follow that with a more refined pseudocode, and finally express the solution in plain Scala.

You can — you should! — use pseudocode on your own, too, as you sketch out solutions to programming assignments. Use a piece of paper or an editor, as you prefer.

Describing Objects as Text

Let’s take a moment to make both Pos and Odds easier to experiment with.

Unpleasant porridge

Our earlier REPL example featured this bit:

val first = new Pos(50, 12.5)first: Pos = Pos@3245f574

Other commands serve up a similar porridge of characters:

println(first)Pos@3245f574
"The location is: " + firstres16: String = The location is: Pos@3245f574

The text Pos@3245f574 is neither pretty nor useful to us. To see a more descriptive string representation of the Pos object we created, we have to call the object’s description method. Similarly, when working with Odds objects, we’ve had to call methods such as fractional to view any actual information about the object.

It’s useful to be able to form string representations of objects. For one thing, such descriptions are convenient when testing a class (in the REPL, for example) or when searching for errors in one’s program.

Since the need is common, it should be easy to form and view descriptions. For example, wouldn’t it be nice if we could use Pos in the REPL like this?

val first = new Pos(50, 12.5)first: Pos = (50.0,12.5)
println(first)(50.0,12.5)
"The location is: " + firstres17: String = The location is: (50.0,12.5)
We wish: when we enter an expression of type Pos in the REPL, we get a proper description of the Pos object rather than Pos@3245f574.
We wish: we may pass a reference to an object as a parameter to println in order to print out a meaningful description. We don’t have to make a separate method call to produce that text, as in println(first.description).
We wish: if we use the plus operator to combine a string with an object, we get a string that contains the object’s textual description.

All our wishes are about to be fulfilled.

No more porridge: toString

Scala automatically furnishes every single object with a parameterless method named toString, which returns a string describing the object. This method is available on Pos objects, too, but unless we specify otherwise, it produces only the familiar, unhelpful text:

first.toStringres18: String = Pos@3245f574

However, we can define a class-specific way of describing objects. In other words: we can override the uninformative default toString with another implementation that works well for a specific class. Once we do that, our overriding toString implementation will be automatically called in certain circumstances. For example, the REPL always calls an object’s toString method when it has evaluated an expression that refers to an object. Similarly, if we pass an object reference to println, it invokes the object’s toString method to determine which characters it should print out.

Below is a slightly modified version of class Pos. Defined like this, the class works exactly as we wished earlier.

class Pos(val x: Double, val y: Double) {

  override def toString = "(" + this.x + "," + this.y + ")"

  // ...
}
We’ve changed the name of description to toString.
We use the override keyword to state that we’re redefining an existing method and replace the default toString defined for all object with this Pos-specific implementation.

The word override already appeared in the Superman example at the end of the previous chapter. In that earlier example, a method was generally defined for our Person class, and we overrode it on an individual object; in the present example, a method has been generally defined for all Scala objects, and we override it on all objects of type Pos. In each of the two examples, the override keyword was both appropriate and required.

Assignment: Odds (Part 3 of 9)

Develop a toString method in class Odds:

  • The method should return exactly the same string that fractional returns.
  • Don’t remove fractional, however. What you can do instead is implement toString so that it calls fractional.

See if your solution works in the REPL. When you create an Odds object, do you get a more informative printout rather than something like o1.Odds@a5e42e?

When your method works, the following command should output simply 1/1000000:

println(new Odds(1, 1000000))

Try this, too:

val six = new Odds(5, 1)
println("The odds are: " + six)
println("The reverse odds are: " + six.not)

The above example concatenates strings with object references. In practice, this means that these lines call toString on an object, combine the string returned by toString with another string, and pass on the combination to println.

A+ presents the exercise submission form here.

Mini-Assignment: Recursion and toString

Let’s define a simple function to try something out. You can try it yourself in the REPL.

def test: Int = 1 + test

This function takes no parameters. It returns an Int, which we compute by adding 1 to another number that we get by calling the test function itself.

In other words, to obtain test’s return value, the computer must add 1 to test’s return value, which is 1 plus test’s return value, which is 1 plus test’s return value, etc. We have an infinite recursion (i.e., self-references that lead to further self-references endlessly).

What does that mean in practice? Take a guess or try the code to find out. (You can copy the function definition into the REPL and try calling the function.) Remember to read the feedback you receive for your answer.

Earlier in this chapter, we implemented toString for Pos objects like this:

override def toString = "(" + this.x + "," + this.y + ")"

String interpolation is another way to get the same result:

override def toString = s"(${this.x},${this.y})"

This shorter code also works, because the this keywords are optional here (Chapter 2.2):

override def toString = s"($x,$y)"

The following, on the other hand, does not work.

override def toString = s"($this.x,$this.y)"
The reason why that doesn’t work is familiar from earlier chapters: the dollar sign “attaches” only to this, not to the entire expression this.x.

That code thus does essentially the same thing as these three equally poor implementations:

override def toString = s"(${this}.x,${this}.y)"
override def toString = "(" + this + ".x," + this + ".y)"
override def toString = "(" + this.toString + ".x," + this.toString + ".y)"

All these flawed versions rely on the same recursive logic:

Form a description of this by combining a bracket and a description of this, which is a combination a bracket and a description of this, which is a combination of a bracket and a description of this, and so on.

What does that mean in practice? How do these incorrect implementations behave?

Assignment: Odds (Part 4 of 9)

You can immediately reap toString’s benefits as you test the additional methods that you’re about to write.

Introduction

Consider, again, the events of rolling a six (5/1) and a coin coming up tails (1/1).

The odds of a single die roll and a single coin toss producing both a six and tails are 11/1. More generally: if the odds of two original events are a1/a2 and b1/b2, you can obtain the odds of both events occurring by computing (a1*b1+a1*b2+a2*b1)/(a2*b2). In our example, the original odds were 5/1 and 1/1, from which we get (5*1+5*1+1*1)/(1*1), which equals 11/1.

Either rolling a six or the coin coming up tails, or both happening, has 5/7 odds. We can get those numbers from the formula (a1*b1)/(a1*b2+a2*b1+a2*b2). In our example, the odds 5/1 and 1/1 combine to (5*1)/(5*1+1*1+1*1), which equals 5/7.

Task description

In class Odds, add two effect-free methods that determine the odds that both of two events occur (both), and the odds that at least one of two events occurs (either). The methods should work as shown in the example below.

val six = new Odds(5, 1)six: o1.Odds = 5/1
val tails = new Odds(1, 1)tails: o1.Odds = 1/1
val sixAndTails = six.both(tails)sixAndTails: o1.Odds = 11/1
six.either(tails)res19: o1.Odds = 5/7

Instructions and hints

  • Note the return values’ types. The methods return references to Odds objects, not strings or numbers.
  • All the necessary arithmetic has been provided for you above. To solve the assignment, all you need to do is:
    • Write down the formulas in Scala.
    • Create a new Odds object that stores the result and return a reference to that object.
  • All this is very similar to what you already did when you wrote not. It’s just that these formulas are longer. (This is also analogous to the add methods in class Pos, which create new Pos objects.)
  • Ask for help if you get stuck!

A+ presents the exercise submission form here.

Recall Our Plan: To Use Pos for Image Manipulation

The class Pos is available for you to use in package o1. We’ll be using it a lot.

val topLeftCorner = new Pos(0, 0)topLeftCorner: Pos = (0.0,0.0)

The class has all the methods discussed in this chapter and quite a few others to boot.

Moreover, the Pic class, which you know, has a number of methods that you don’t yet know and that use parameters of type Pos to target specific locations within images:

Positioning images onto an image

val sky = rectangle(1000, 400, LightBlue)sky: Pic = rectangle-shape
val bug = Pic("ladybug.png")bug: Pic = ladybug.png
val combination = sky.place(bug, new Pos(100, 300))combination: Pic = combined pic
show(combination)
We specify the location within the background image where we wish the other image’s center to appear.
place returns a reference to a Pic object that represents the combined image, much like above, leftOf, and other familiar methods.

Experiment with place using other numbers and images. Try placing multiple images in different places against the same background image. Like this, for instance:

val sky = rectangle(1000, 400, LightBlue)sky: Pic = rectangle-shape
val bug = Pic("ladybug.png")bug: Pic = ladybug.png
val monolith = rectangle(100, 300, Black)monolith: Pic = rectangle-shape
val bugLocation = new Pos(400, 200)bugLocation: Pos = (400,200)
val myCreation = sky.place(bug, bugLocation).place(monolith, bugLocation.addX(150))myCreation: Pic = combined pic
show(myCreation)

This is another example of how we can not only...

... nest method calls one inside another, but also...
... chain them one after the other. In our example, we place another image (of a monolith) on top of the image we got when we placed another image (of a bug).

Practice on positioning images

Open subprograms.scala in the Subprograms module. Even if it isn’t Red Nose Day today, celebrate it in spirit by writing a clownify function that works as shown.

val originalArt = Pic("defense.png")originalArt: Pic = defense.png
val adaptation = clownify(originalArt, new Pos(240, 245))adaptation: Pic = combined pic
show(adaptation)
clownify returns a new Pic where the artwork specified by the first parameter has been augmented with a red circle of fifteen pixels in diameter.
The second parameter indicates the position where the center of the red “nose” should appear.
clownify does not display the augmented picture, it merely returns it. You can display the picture by passing it to show.

As you test your function, make sure your REPL is open in the Subprograms module.

A+ presents the exercise submission form here.

In addition to place, there’s an against method that essentially does the same thing in reverse. The two method calls below produce the same result.

background.place(littlePic, position)
littlePic.against(background, position)

Use either one; it doesn’t really matter. One method may feel more natural than the other, depending on what your program does.

The against method, especially, seems very similar to onto, a method from Chapter 2.3 that we used to place an image onto the center of another image (e.g., a star at the center of a flag). And in fact, it’s possible to pass a position as an additional parameter to onto.

Experiment with onto, against, and place in the REPL, compare what they do, and mark the correct answer(s).

Try these, for example:

val trunk = rectangle(30, 250, SaddleBrown)
val foliage = circle(200, ForestGreen)
val tree = trunk.onto(foliage, new Pos(100, 225))
val trunk = rectangle(30, 250, SaddleBrown)
val foliage = circle(200, ForestGreen)
val tree = trunk.against(foliage, new Pos(100, 225))

To reiterate: you don’t have to learn the details of each method by heart. Return to this chapter or class Pic’s documentation to recap as needed.

The rest of the chapter covers a few more operations on picture objects. Knowing these methods isn’t vital for success in O1, but you may find them fun to use. Working on the optional assignments below can also build up your fluency as a programmer.

Cropping an image

The crop method

The crop method selects a section of an image, discarding the rest:

val testPic = Pic("carton.png")testPic: Pic = carton.png
show(testPic)show(testPic.crop(new Pos(20, 285), 190, 110))
The first parameter indicates the top-left corner of the rectangular cropping frame within the original.
The second and third parameter specify the cropping frame’s width and height.

Assignment: cropping left and right

Write two effect-free functions, leftSide and rightSide, that select the left and right side of the original image, respectively. Each receives a two parameters: the first is the original Pic and the second is a Double that specifies the size of the result relative to the original’s width. For example, leftSide(tree, 33.3) returns a picture that contains 33.3% of the original (the left third), and rightSide(tree, 50) returns the right half.

You may assume that the given number is between 0 and 100. Remember that Pics have width and height attributes and that the coordinates run from zero upwards. (A picture 50 pixels wide spans x coordinates from 0 to 49.)

Write your code in subprograms.scala in the Subprograms module.

A+ presents the exercise submission form here.

Solve an image puzzle

Until 2010, the back covers of MAD magazines featured topical visual jokes whose punchline was revealed by folding in the magazine so that the left- and right-hand sides of a picture covered the middle part.

Write an effect-free function that virtually folds in a picture. The function should take two parameters: the unfolded image and the percentage of the original image that will remain visible on both the left and the right.

Usage example:

val unfoldedPic = Pic("https://i.imgur.com/Rj6fcr6.png")unfoldedPic: Pic = https://i.imgur.com/Rj6fcr6.png
show(unfoldedPic)val foldedPic = foldIn(unfoldedPic, 26.2)foldedPic: Pic = combined pic
show(foldedPic)

Use leftSide and rightSide from the previous assignment. Use the tools from Chapter 2.3 to place the pieces side by side.

A+ presents the exercise submission form here.

Positioning images with ease

Anchors in pictures

Here’s the ladybug example again:

val sky = rectangle(1000, 400, LightBlue)sky: Pic = rectangle-shape
val bug = Pic("ladybug.png")bug: Pic = ladybug.png
val combination = sky.place(bug, new Pos(100, 300))combination: Pic = combined pic

In the combined picture, it’s the bug’s center that we’ve positioned at (100,300). Had we used the coordinates (0,0) instead, just the bottom right-hand corner of the bug would have appeared at the top-left corner of the background. (Try it!) It’s as if there’s a pin at the center of the bug that it attaches with. We won’t call it a pin, though, but an anchor:

bug.anchorres20: Anchor = Center
Each Pic object attaches at a particular point, which we can access through the object’s anchor variable.
Unless otherwise specified, a picture’s anchor is the middle point between its most extreme coordinates, here described as Center.
Package o1 represents anchors with a custom data type Anchor.

In many cases, an anchor at the center is what we want, but it’s also common that another option is more convenient. There are many alternatives for anchoring a picture. Let’s explore:

show(sky.place(bug, Center, new Pos(0, 0)))
The second parameter is of type Anchor and tells the method that it’s the bug’s center that we wish to place at (0,0). Notice that this value is not a string and therefore is not in quotation marks.

The command above does the same thing we did before, anchoring the Pic at its center, but it also reveals how we can change the anchoring point. Try these:

show(sky.place(bug, TopLeft, new Pos(0, 0)))show(sky.place(bug, CenterLeft, new Pos(0, 0)))

How about the next place command? Why doesn’t it “do anything”?

show(sky.place(bug, TopRight, new Pos(0, 0)))

Hare are some anchors you can use: TopLeft, TopCenter, TopRight, CenterLeft, Center, CenterRight, BottomLeft, BottomCenter, BottomRight.

Anchoring onto an anchor

In the examples above, we used absolute coordinates such as (0,0) to specify the background location where the anchor is positioned. In the example below, too, we use the coordinates (100,225) in this manner.

val trunk   = rectangle(30, 250, SaddleBrown)
val foliage = circle(200, ForestGreen)
val tree    = trunk.onto(foliage, new Pos(100, 225))

The numbers in the example aren’t arbitrary. They’ve been carefully chosen:

100 is at the center of the foliage.
225 is 125 pixels (i.e., half of the length of the trunk) lower than the center of the foliage.

By calculating these values outside the program itself, we’ve managed to place the middle point of the trunk’s top edge exactly at the center of the foliage. That took a bit of manual effort and, more importantly, if we change the size of the foliage or the trunk, we must remember and bother to recalculate the numbers. What’s more, anyone reading the program needs to reason about where those exact numbers came from.

Here’s a nicer version:

val trunk   = rectangle(30, 250, SaddleBrown)
val foliage = circle(200, ForestGreen)
val tree    = trunk.onto(foliage, TopCenter, Center)
We can use anchors to specify not only a spot in the picture being placed (the top center of the trunk) but also the target spot in the background image (the center of the foliage).

Finally, see below for a slightly longer example that uses these methods to form a scenery that’s made up of sky, ground, a tree, and a bug. Read the code, try to predict what the combined picture will be like, and run the code to see if you were right.

val sky    = rectangle(1000, 400, LightBlue)
val ground = rectangle(1000, 50,  SandyBrown)

val bug = Pic("ladybug.png")

val trunk      = rectangle(30, 250, SaddleBrown)
val foliage    = circle(200, ForestGreen)
val tree       = trunk.onto(foliage, TopCenter, Center)
val rootedTree = tree.onto(ground, BottomCenter, new Pos(500, 30))

val scenery  = sky.place(rootedTree, BottomLeft, BottomLeft)
                  .place(bug, new Pos(100, 300))

An exercise in anchoring

../_images/tsekin_lippu.png

Write an effect-free function named flagOfCzechia that:

  • receives as its only parameter a width as a Double; and
  • returns a Pic of the national flag of Czechia, whose:
    • width is determined by the parameter;
    • height is two thirds of the width;
    • left edge contains an isosceles triangle whose apex just reaches the flag’s center; and
    • whose colors are MidnightBlue, White, and Crimson.

You can use an expression of the form triangle(width, height, color) to create a triangle. Such a triangle has its base at the bottom edge and the equally long sides on the left and the right.

It’s possible to solve the assignment without anchors but this is a good opportunity to put them to use. Put them to use. Can you come up with multiple ways of anchoring the triangle in the flag?

Write this function, too, in subprograms.scala.

A+ presents the exercise submission form here.

Summary of Key Points

  • By defining methods on a class, you can establish a set of rules that governs how the values of a particular type — the instances of the class — may be combined to produce new instances.
    • In O1, Pos, Odds, and Pic are examples of classes in this vein.
    • Cf. the mathematical rules for applying operators to numbers, producing new numbers.
  • Programmers sometimes use pseudocode — text that resembles program code — for sketching out solutions, among other things.
  • You can include a method named toString in a Scala class. This method returns a string description of the object. toString methods can be convenient when testing the class, for example.
  • The class Pos, provided as part of package o1, represents pairs of coordinates. You can use it in combination with class Pic for a variety of image manipulations.
  • Links to the glossary: class, instance, reference; pseudocode; toString, to override; anchor.

Feedback

Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.

Credits

Thousands of students have given feedback that has contributed to this ebook’s design. Thank you!

The ebook’s chapters, programming assignments, and weekly bulletins have been written in Finnish and translated into English by Juha Sorva.

The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.

The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, and Aleksi Vartiainen.

The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.

The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio did the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.

The other diagrams and interactive presentations in the ebook are by Juha Sorva.

The O1Library software has been developed by Aleksi Lukkarinen and Juha Sorva. Several of its key components are built upon Aleksi’s SMCL library.

The pedagogy of using O1Library for simple graphical programming (such as Pic) is inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and Krishnamurthi and Picturing Programs by Stephen Bloch.

The course platform A+ was originally created at Aalto’s LeTech research group as a student project. The open-source project is now shepherded by the Computer Science department’s edu-tech team and hosted by the department’s IT services. Markku Riekkinen is the current lead developer; dozens of Aalto students and others have also contributed.

The A+ Courses plugin, which supports A+ and O1 in IntelliJ IDEA, is another open-source project. It was created by Nikolai Denissov, Olli Kiljunen, and Nikolas Drosdek with input from Juha Sorva, Otto Seppälä, Arto Hellas, and others.

For O1’s current teaching staff, please see Chapter 1.1.

Additional credits for this page

MAD fold-ins are the work of Al Jaffee.

The clownified painting is The Defense of the Sampo by Akseli Gallén-Kallela.

a drop of ink
Posting submission...