3.6 Spacewar! States and Behaviors

3.6.1 The game states

After defining the classes involved in the game design, we now define several states of these classes:

We need to explain the mathematical nature of these states, and then discuss their object representation in the instance variables of our classes.

 note In the following sections, to ease reading we will write “the variable myVar is a String” instead of the correct but cumbersome “the instance variable myVar is a reference to a String instance”.

SpaceWar

This object is the entry into the game. We want a meaningful class name. Its instance variables are the involved protagonists of the game:

CentralStar

Its unique instance variable, mass, is a number, most likely an Integer.

SpaceShip

The spaceship is the most complex object, some clarifications regarding its variables are needed.

A few words regarding the Euclidean coordinates: the origin of our orthonormal frame is the central star, its first vector is oriented toward the right of the screen, and the second one towards the top of the screen. This choice eases the computation of the ship’s acceleration, velocity and position. More on this below.

Torpedo

A torpedo is launched or “fired” from a ship with an initial velocity related to the ship’s velocity. Once the torpedo life span counter reaches zero, it self-destructs.

3.6.2 Instance variables

In the previous chapter, we explained how to define the four classes SpaceWar, CentralStar, SpaceShip and Torpedo. In this section, we will add to these definitions the instance variables – states – discussed above.

To add the variables to the Torpedo class, from the Browser, select this class. Next, add the variable names to the instanceVariableNames: keyword, separated by one space character. Finally, save the updated class definition with Ctrl-s shortcut:

Object subclass: #Torpedo
   instanceVariableNames: 'position heading velocity lifeSpan'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Spacewar!'

Example 3.14: Torpedo class with its instance variables

 CuisLogo Add the instance variables we discussed earlier to the SpaceWar, CentralStar and SpaceShip classes.

Exercise 3.9: Instance variables of the Spacewar! protagonists

3.6.3 Behaviors

Some of these states need to be accessed from other entities:

To write these behaviors in the Browser, first select the class then the method category you want – when none, select -- all --.

In the code pane below appears a method template:

messageSelectorAndArgumentNames
   "comment stating purpose of message"
   | temporary variable names |
   statements

Example 3.15: Method template

It describes itself as:

  1. Line 1. It is a mandatory method name, the same as the message.
  2. Line 2. An optional comment surrounded by double quote.
  3. Line 3. An optional list of variables local to the method, surrounded by pipe characters.
  4. Line 4. A subsequent list of message sendings and assignments.

The getter mass on SpaceShip is written as:

SpaceShip>>mass
   ^ mass

The SpaceShip>> part is not valid code and should not be written in the Browser. It is a text convention to inform the reader the subsequent method is from the SpaceShip class.

 CuisLogo Write the SpaceShip getter messages for its position, velocity and mass attributes.

Exercise 3.10: SpaceShip getter message

Some instance variables need to be set from another entity, so a setter keyword message is necessary. To set the name of a space ship we add the following method:

SpaceShip>>name: aString
   name := aString

The := character is an assignment, it means the name instance variable is bound to the aString object. To type in this symbol type _ then space, Cuis-Smalltalk will turn it into the left arrow symbol. Alternatively write name := aString. One might pronounce := as “gets”.

Since name is an instance variable, each instance method knows to use the box for the name. The meaning here is that we are placing the value of the aString argument into the instance’s box called name.

Since each instance variable box can hold an object of any class, we like to name the argument to show that we intend that the name variable should hold a string, an instance of the String class.

 CuisLogo Ship position and velocity, as well as torpedo heading will need to be set at game start-up or when a ship jumps in hyperspace. Write the appropriate setters.

Exercise 3.11: SpaceShip setter messages

Observe how we do not have a setter message for the spaceship mass attribute. Indeed, it does not make sense to change the mass of a ship from another object. In fact, if we consider both player ships to be of equal mass, we should remove the mass variable and edit the mass method to return a literal number:

SpaceShip>>mass
   ^ 1

Example 3.16: A method returning a constant

On the other hand, we could also consider the mass to depend on the consumed fuel and torpedoes. After all, 93 % of Saturn V rocket’s mass was made up of its fuel. We will discuss more about that later in the chapter about refactoring.

Controls

A spaceship controlled by the player understands messages to adjust its direction and acceleration17:

Direction. The ship’s heading is controlled with the #left and #right messages. The former decrements the heading by 0.1 and the latter increments it by 0.1.

 CuisLogo Write two methods named left and right to shift the ship heading of 0.1 according to the indications above.

Exercise 3.12: Methods to control ship heading

Acceleration. When the #push message is sent to the ship, the engines are ignited, and an internal acceleration of 10 units of acceleration are applied to the ship. When the #unpush message is sent, the acceleration stops.

 CuisLogo Write two methods named push and unpush to adjust the ship’s inner acceleration according to the indications above.

Exercise 3.13: Methods to control ship acceleration

3.6.4 Initializing

When an instance is created, for example, SpaceShip new, it is automatically initialized: the message #initialize is sent to the newly created object and its matching initialize instance side method is called.

The initializing process is useful to set the default values of the instance variables. When we create a new space ship object we want to set its default position, speed, and acceleration:

SpaceShip>>initialize
   super initialize.
   velocity := 0 @ 0.
   position := 100 @ 100.
   acceleration := 0

Example 3.17: Initialize the spaceship

In the method Example 3.17, observe the first line super initialize. When a message is sent to super, it refers to the superclass of the class’s method using super. So far, the SpaceShip parent class is Object, therefore the Object>>initialize method is called first for initialization.

When created, a spaceship is positioned to the top and right of the central star. It has no velocity nor internal acceleration – only the gravity pull of the central star. Its nose points in direction of the top of the game display.

You probably noticed there is no code to initialize the heading, something like heading := Float halfPi negated to orient the ship in the direction of the top of the screen. The truth is we don’t need the heading attribute, this information will be provided by the class Morph used later as a parent class of SpaceShip and Torpedo. At this time, the heading variable will be removed and we will define the heading behavior with the appropriate heading and heading: methods.

 CuisLogo Write the method to initialize the central star with 8000 units of mass.

Exercise 3.14: Initialize central star


Footnotes

(17)

The velocity is a consequence of the accelerations applied to the spaceship.