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.

Kieli vaihtuu A+:n sivujen yläreunan painikkeesta. Tai tästä: Vaihda suomeksi.


Chapter 3.6: Decisions, Decisions

About This Page

Questions Answered: How about some more practice on if expressions? How can I make my game more unpredictable? How can I write a game or simulation where I can move a character about freely?

Topics: More selection with if. Randomness. Private methods. Optionally: the type o1.Direction.

What Will I Do? Program. There are a few required assignments and quite a bunch of optional ones.

Rough Estimate of Workload:? The main assignments will probably take you less than an hour. The optional assignments are somewhat harder and take a substantial amount of additional time.

Points Available: A25.

Related Modules: FlappyBug, Odds. The optional assignments require MoreApps (new).

../_images/person05.png

Assignment: FlappyBug (Part 13 of 17: An Obstinate Obstacle)

Foreword

In our current version of FlappyBug, we have only a single obstacle that glides across the view once. This is not quite satisfactory. It would be better if the bug had to continue dodging more obstacles.

Here are two alternative approaches:

  1. Once the obstacle has moved out of sight beyond the left edge and is no longer “active”, we create another Obstacle object on the right-hand side of the view. We replace the obstacle that we had previously linked to the Game with this new obstacle. The old obstacle object is no longer needed.

  2. Once the obstacle has moved out of sight beyond the left edge and is no longer “active”, we instantly move it to the right-hand side of the view simply by changing its coordinates. In a technical sense, the bug will keep encountering the same obstacle object over and over again, but in practice, it will look like new obstacles keep arriving from the right.

Neither approach is unambiguously better than the other. We will now pick the second approach, which is easy to implement.

Task description

Begin by adding an effect-free method isActive in class Obstacle. This method takes no parameters. It returns true if the obstacle’s right edge is located to the right of the screen’s left edge, or if it’s exactly at the screen’s left edge. Otherwise, the method returns false. For example, if an obstacle has a radius of 50 and an x coordinate of -50, the obstacle still just counts as active, but if the coordinate was any less, it wouldn’t.

Then modify the approach method in the same class. It currently looks like this:

def approach() =
  this.currentPos = this.currentPos.addX(-ObstacleSpeed)

Edit the method so that it either shifts the obstacle left or teleports it back to the right, depending on circumstances. More specifically:

  • If the obstacle was active when the method is called, it moves leftwards as before.

  • If the obstacle wasn’t active, it gets a new x coordinate that is equal to the width of the visible game world plus 250 (i.e., 1250) and a new y coordinate that is equal to half the height of the game world (i.e., 200). What this means is that the obstacle will teleport a bit to the right of the visible game world, halfway between the top and the bottom edge, and resume its leftward movement there.

A+ presents the exercise submission form here.

Afterword

Now we effectively have multiple obstacles but they always appear at the same spot. This is not quite satisfactory. It would be better if the obstacles arrived at unpredictable heights.

How can we pick random coordinates for an obstacle?

This is a problem in its own right. Let us first consider randomness more generally.

Randomness

Generate a list of 30 fully random numbers between 0 and 9. As you pick each number, each of the ten alternatives must have exactly the same probability of being picked. You must do this mentally and cannot use any external aids.

Class Random

Good news: Scala gives us a class called Random that easily generates random numbers.

import scala.util.Randomval generator = Random(74534161)generator: scala.util.Random = scala.util.Random@1413cb7
generator.nextInt(10)res0: Int = 3
generator.nextInt(10)res1: Int = 8
generator.nextInt(10)res2: Int = 7
generator.nextInt(10)res3: Int = 2
generator.nextInt(10)res4: Int = 3
generator.nextInt(10)res5: Int = 2

An object of type Random is a “random number generator”. The class is located in package scala.util. (That package contains an assortment of miscellaneous tools. Its name is short for “utilities”.)

A Random object has an assortment of methods for generating random numbers. One that is useful particularly often is nextInt(n: Int), which returns a random number between zero and n-1.

For example, when we pass in 10, we get numbers between 0 and 9.

What’s that in the brackets, then? We’ll get to that momentarily.

You can try entering the above commands in the REPL. If you do, you’ll get exactly the same list of random numbers from nextInt: 3, 8, 7, 2, 3, 2, etc.

That makes little sense. After all, the numbers were supposed to be random.

Pseudorandom numbers

The generation of random numbers is too important to be left to chance.

—Robert Coveyou

The assignment “Pick 30 fully random numbers between 0 and 9.” is easier said than done. One practical option is to use a “good enough” mathematical algorithm for computing a sequence of numbers. For example:

  • Start by picking some number N, say N=20.

  • Take the Nth decimal of π. Let that be the first “random number”.

  • Determine the next “random number” by taking the another digit from π at 2*N.

  • Follow with 3*N, 4*N, and so on.

Randomness is hard not just to us humans but computers, too. A computer can’t simply pluck true randomness out of thin air.

The numbers produced by such an algorithm are pseudorandom numbers (näennäissatunnaisluku). Given the same “starting number”, known as a seed (siemen), we always get the same sequence of pseudorandom numbers. In our example algorithm, N was the seed; ever time we apply the algorithm with an N of 20, we get the same result.

Mathematicians have developed various algorithms that generate pseudorandom numbers. Scala’s Random class uses one such algorithm (which we won’t inspect more closely in O1). We pass the seed as a constructor parameter (e.g., 74534161 above)

Given a particular seed, an algorithm always produces the same pseudorandom numbers. This can be either a blessing (if we wish to replicate a sequence that was originally chosen at random) or a curse (if we want a distinct sequence every time).

Seeding from the clock

There are different ways of picking a seed. In some cases, it can make sense to ask the end user to supply a seed number. Another approach is to take some “random enough” characteristic in the program’s environment; a classic trick is to take the computer’s current time and date to the precision of, say, a nanosecond, and seed the generator with that.

In many applications, it’s not terribly important to know which exact seed number is being used. For that reason, package scala.util provides not just the class Random but also a singleton object of the same name. With this object, you can conveniently generate clock-based pseudorandom numbers without explicitly instantiating class Random:

import scala.util.RandomRandom.nextInt(10)res6: Int = 8
Random.nextInt(10)res7: Int = 6
Random.nextInt(10)res8: Int = 2

If you try these commands yourself you will (very probably) get different return values.

Assignment: FlappyBug (Part 14 of 17: An Unpredictable Obstacle)

Groundwork: randomLaunchPosition

Turning back to class Obstacle, let’s add a method that picks a random starting position beyond the right edge of the view. Here’s some starter code:

private def randomLaunchPosition() =
  val launchX = Add up the view’s width (1000), the obstacle’s radius, and a random integer between 0 and 499.
  val launchY = Pick a random integer that is at least 0 and less than the view’s height (400).
  Pos(launchX, launchY)

A couple of expressions have been left for you to fill in.

This method is private. It’s meant for use within the Obstacle class only and isn’t part of the class’s public interface. (Cf. private variables; Chapter 3.2.)

Task description

  1. Implement randomLaunchPosition. Use the given template and fill in the missing parts.

  2. Modify the approach method so that it no longer places an inactive obstacle in a standard position. Instead, the method should call randomLaunchPosition to obtain a new position for the obstacle.

  3. Edit class Obstacle so that we no longer supply a starting location as a constructor parameter whenever we instantiate the class. Instead, we’ll pass in only the radius, and the new instance should call its own randomLaunchPosition method to place itself at a random starting location.

  4. Make a small edit to class Game so that it’s compatible with the above change: pass just one constructor parameter to the new Obstacle.

Instructions and hints

  • Use the clock-seeded singleton object Random so that you get different results every time the game is played.

  • You can also try creating a Random instance with a specific seed. If you do, you’ll give the obstacle the same “random” behavior every time.

  • randomLaunchPosition is a parameterless, effectful method, so we write the empty brackets (), as per Chapter 2.6. (The method counts as effectful, because it affects the state of the underlying random-number generator.)

A+ presents the exercise submission form here.

Try some scary graphics

Discard rockPic, which we’ve thus far used in the GUI to produce a picture of the obstacle. Use Pic("obstacle.png") instead and scale it to the right size with scaleTo(diameterOfObstacle). Play if you dare.

More Assignments

Assignment: Odds (Part 9 of 9)

Return to class Odds in the module of the same name. Add an eventHappens method that randomly determines whether an event occurs, assuming its likelihood is represented by the Odds object:

val dieRollOfSix = Odds(5, 1)dieRollOfSix: Odds = 5/1
dieRollOfSix.eventHappens()res9: Boolean = false
dieRollOfSix.eventHappens()res10: Boolean = false
dieRollOfSix.eventHappens()res11: Boolean = true

Whenever we call the method, we get either true or false at random. In our die-rolling example, we get false pretty much exactly five times as often as we get true.

Because it invokes the effectful method nextInt, eventHappens is effectful, too, needs the empty brackets as a parameter list (Chapter 2.6). nextInt counts as effectful because it changes the random number generator’s internal state.

Remember to import Random from scala.util. And remember that Random.nextInt(n) returns a number between zero and n-1.

A+ presents the exercise submission form here.

Using the keyboard to trot about

../_images/trotter.png

Package o1.trotter in module MoreApps contains a small application where you can make a “trotter” — a virtual horse — run about against a background grid. The trotter moves from one square to another as the clock ticks; it changes direction when you press the arrow keys on your keyboard. Well, that’s what it’s supposed to do, anyway.

Within the package, you’ll find the class TrotterGame, which we’ll use as the domain model. Feel free to study the whole class if you want, but for this assignment, here’s what you need to know about it:

val game = TrotterGame(5, 40)game: o1.trotter.TrotterGame = a 5-by-5 grid
game.horseHeadingres12: o1.Direction = Direction.Right
game.horseHeading = Direction.Up

In this example, we create a small world (pictured) for the trotter to horse about in. It has five times five squares, and the horse steps 40 pixels, or one square, at a time.

The variable horseHeading is of type Direction. This type is part of O1Library and represents directions in the sort of two-dimensional coordinate system that is familiar to us from class Pos.

It’s a var, so we can assign a new value to it. The following values are useful here: Direction.Up, Direction.Down, Direction.Right, and Direction.Left.

In TrotterApp.scala, you can find a GUI that creates a larger grid. Try running the app. The horse moves. Pressing the arrow keys, you’ll notice that you can only turn the horse upwards.

The given GUI already contains the code that produces the graphics as well as an onTick method. There’s also a good start for the onKeyDown event handler, whose job it is to interpret keys (Key) as headings of the horse (Direction). Here is the code as given:

override def onKeyDown(key: Key) =
  if key == Key.Up then
    game.horseHeading = Direction.Up

There are two separate concepts called Up here:

Key.Up is a constant that corresponds to the upward arrow key on the keyboard. Direction.Up, on the other hand, means the upward direction towards smaller y coordinates.

Your official task is just to complete this method so that it turns the horse also in the other three main directions (Right, Down, Left). You can do this simply by adding similar if commands (even though this makes the code repetitive). If you want, you can also experiment with other modifications to the given code as you please.

A+ presents the exercise submission form here.

Using the keyboard to accelerate

Package o1.charger in MoreApps contains a similar application where you can direct a “charger” in a flat, two-dimensional world. Holding down a key adds to a charger’s speed.

The given version of the program fails to get the charger moving. Fix that:

  1. Flesh out method onKeyDown much like you did in the previous assignment: the GUI should react to four arrow keys by calling the charger’s accelerate method and passing in the appropriate Direction parameter.

  2. Study class Charger. Its intended behavior is described in the comments within the code. For the most part, the implementation’s already there, but some parts are missing.

  3. Complete the branches of the if in accelerate so that they adjust the charger’s speed and direction as specified.

  4. Complete move so that the method changes the charger’s position based on its speed and direction.

    • The given code demonstrates that each Direction object has a dx variable that indicates how much the x coordinate changes if an object moves a single unit in that direction. So Direction.Right has a dx of +1 and Left has a dx of -1, while Up and Down have a dx of zero. Direction objects similarly have a dy attribute.

    • With that information, move is simpler to implement. (If you need a hint, you can return to the previous assignment and study TrotterGame.)

  5. Test your program.

  6. It would be nice if the charger’s image reflected its direction of movement. This is easily accomplished. We already know that class Pic has the methods clockwise and counterclockwise that rotate an image (Chapter 2.3). Moreover, every Direction object has a method named toDegrees that you can use as shown below.

    Direction.Up.toDegreesres13: Double = 90.0
    Direction.Left.toDegreesres14: Double = 180.0
    

    Loosely speaking, what the method does is answer the question: if we’re facing in this direction, how many degrees would we have to turn clockwise in order to face rightwards? For example, Direction.Up is 90 degrees counterclockwise from Direction.Right.

    Edit the method makePic in the GUI so that it rotates the charger’s image to point towards its heading.

A+ presents the exercise submission form here.

Adopting class Velocity

In the previous assignment, we used two separate variables to represent the charger’s speed (i.e., how much its position changes per step) and direction. Together, speed and direction constitute velocity.

It’s both natural and convenient to represent the concept of velocity as a class. And you don’t have to roll your own: velocity is a useful concept in many applications and there’s a class for it in O1’s library:

val test = Velocity(Direction.Up, 12.3)testVelocity: o1.world.Velocity = Velocity(Direction.Up,12.3)
test.directionres15: o1.world.Direction = Direction.Up
test.speedres16: Double = 12.3

A velocity consists of a speed and a direction.

The class has many methods, some of which we’ll find useful later and some of which we’ll use right now:

  1. Take a look at the Scaladocs of class Velocity. Note in particular the methods faster, noFasterThan, and nextFrom.

  2. Refactor class Charger (that is, rework its code without changing what it does): Replace the separate variables angle and speed with a single variable of type Velocity. Adjust the methods heading, accelerate, and move so that they use the new variable.

    • Don’t touch the class’s public interface Each method must still do what it did before.

A+ presents the exercise submission form here.

Using the keyboard to glide smoothly

Our charger program is lacking in many ways. For one thing, the charger’s movements are unpleasantly rigid. Moreover, the way we’ve handled key presses so far is unsatisfactory from a technical point of view:

  • The runtime environment of the program invokes onKeyDown in the event of the user pressing a key. The method is also intermittently reinvoked as long as the key remains down. However, the number of these subsequent onKeyDown invocations isn’t standard across the different environments where we may run our program. This means that the program’s behavior doesn’t rest on a solid foundation.

  • Also due to the nature of onKeyDown events, pressing down multiple keys at once doesn’t work as we might wish: try hitting one arrow key while holding another down, and the application will “forget” the held-down key. This doesn’t feel natural to the user and doesn’t bode well for our future endeavors such as using a combination of two keys for diagonal movement.

Let’s adopt a slightly different approach to tracking held-down keys. This other approach is a touch more complex but makes it much easier to program smooth movements.

Package o1.glider contains starter code for a program that controls a “glider”.

  1. Familiarize yourself with the GUI in GliderApp.scala. Note at least the following.

    • The application bears a clear resemblance to the charger app.

    • The event handler onTick simply commands the glider to glide on each tick.

    • The primary difference compared to the charger app is in the onKeyDown method: it doesn’t actually increase the glider’s speed when the up arrow is pressed but simply records that ”the gas pedal is down”.

    • onKeyUp similarly handles keys being released.

  2. Look at class Glider. Note at least the following.

    • A glider has a Velocity.

    • It also has three instance variables of type Boolean that indicate whether the “gas pedal” is down and whether the “steering wheel” has been turned left or right.

    • The method glide is responsible for the glider’s movement. It adjusts the glider’s velocity based on the controls and the glider’s position as per its current velocity.

    • The glider doesn’t have brakes, but “friction” slows it down slightly at the end of each movement step so that the glider eventually comes to rest unless accelerated.

  3. The code that actually changes the glider’s position and applies friction has been given. What you should do is enable the glider to change speed and direction; this goal is detailed in the comment that precedes glide.

    • glide delegates this subtask to the private method adjustVelocity. You can make the necessary changes there.

  4. Now you can give GliderApp a spin. The glider should start moving when you press the up arrow and gradually slow down by itself. It doesn’t turn yet.

  5. Fill in the missing code in GliderApp.scala’s event handlers so that they inform the Glider object when it should turn.

A+ presents the exercise submission form here.

Another glider

If you wish, you can try adding another glider to the view. Make it respond to the keys W, A, and D, for instance.

If you do this, and consequently end up with code that works but looks repetitive, you can reflect on why that happened. In future chapters, we’ll come up with more ways to eliminate redundant code.

More keys

What would be an easy way to respond to a Shift-click of the mouse? Or to determine if one of the special keys Shift, Ctrl, or Alt is down while the user pressed another key?

One of the optional assignments of Chapter 3.1 featured an event handler that received not just the click’s location but an event object of type MouseClicked. Other event handlers, too, can be defined so that they receive a reference to an event object as a parameter. Such an event object can provide various kinds of information about the GUI event that occurred, such as whether a particular special key was down at the time. Here are a couple of examples:

override def onClick(mouseEvent: MouseClicked) =
   println(mouseEvent.isControlDown)
override def onKeyDown(keyPressEvent: KeyPressed) =
  println(keyPressEvent.isAltDown)

Note the types of the parameter variables.

You can try adding those methods to one of the preceding programs. You can also check out the other event handler described in View’s documentation. Perhaps you have an idea of your own for a small app that responds to the special keys?

Further Reading on Randomness

Applications of randomness

Random.org’s introductory article on Randomness already came up above. That’s a good place to start.

Randomness gives me gray hairs since it doesn’t actually exist. Sort of.

Where do you need randomness that’s more random than what a computer can produce?

In online poker, for example.

Large-scale computer simulations, such as those used for predicting weather, may work poorly if the distribution of random numbers is even slightly off.

Pseudo-randomness may compromise information security. If a random-number generator is used for security purposes but is predictable, hackers may exploit it for nefarious purposes.

For more information, see Applications of Randomness on Wikipedia.

Summary of Key Points

  • Producing a truly random number is anything but trivial.

  • The Scala API includes tools for producing pseudorandom numbers that are often “random enough”.

  • You can write private methods that help you implement a class.

  • Links to the glossary: if, pseudorandom number, random seed; private.

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 and so 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, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Anna Valldeoriola Cardó, 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 has been designed and implemented by various students in collaboration with O1’s teachers.

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

Additional credits appear at the ends of some chapters.

a drop of ink
Posting submission...