After defining the classes involved in the game design, we now define several states of these classes:
SpaceWar instance representing the gameplay needs to
know about the centralStar, the ships, the
fired torpedoes, and its color.
CentralStar has a mass state. It is
necessary to compute the gravity force applied to a given ship.
SpaceShip instance knows about its name,
its position coordinates, its heading angle,
its velocity vector, its fuel gauge, its
count of the available torpedoes, its mass
and its acceleration engine boost.
Torpedo has position,
velocity and lifeSpan states.
We need to explain the mathematical nature of these states, and then discuss their object representation in the instance variables of our classes.
In the following sections, to ease reading we will write “the variable
myVaris aString” instead of the correct but cumbersome “the instance variablemyVaris a reference to aStringinstance”.
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 is the unique CentralStar of
the game play. We need to know about it to request its mass.
ships is a collection of the two player ships. It
is an Array instance, its size is fixed to two elements.
torpedoes is a collection of the fired torpedoes
in the gameplay. As this quantity is variable, a dynamic
OrderedCollection makes sense.
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.
name is a String.
position is a 2D screen coordinate, a
location. Smalltalk uses the Point class to represent
such objects. It understands many mathematics operations as
operations on vectors; very useful for mechanical calculations.
A point is easily instantiated with the binary message #@ send
to a number with another number as its argument: 100 @
200 returns a Point instance representing the
coordinates (x;y) = (100;200).
The ship’s position is regularly recomputed according to
the law of the Galilean reference frame. The computation depends on
the ship’s velocity, it’s current engine boost, and the gravity pull
of the central star.
heading is an angle in radians, the direction
where the ship’s nose is pointing. It is therefore a Float
number. At the game’s start, the ships are oriented to the top, the
heading value is then -pi/2 radians; the oy axis is
oriented from the top to the bottom of the screen.
velocity is the vector representing the
instantaneous speed of the ship. It is a Point instance.
fuel is the gauge, as long as it is not zero, the
player can ignite the ship’s rocket engine to provide acceleration to move
around and to counter the central star’s gravity pull. It is an
integer number.
torpedoes is the quantity of available torpedoes
the player can fire. It is an Integer number.
mass is an Integer representing the ship
mass.
acceleration is the intrinsic ship acceleration
norm provided when the ship’s rockets are ignited. It is therefore
an Integer number.
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.
position is a 2D screen coordinate, a
Point instance. Unlike the ship, it does not accelerate based
on the gravity pull of the central star. Indeed, a torpedo does not
come with a mass state. For our purposes, it is essentially
zero. Its position over time only depends on the torpedo velocity
and its initial acceleration.
heading is an angle in radians, the direction
where the torpedo nose is pointing. Its value matches the ship
heading when fired, it is therefore a Float number too.
velocity is a vector representing the
instantaneous speed of the torpedo. It is constant over the torpedo’s
lifespan. Again velocity is kept as a Point instance.
lifeSpan is an integer number counter, when it
reaches zero the torpedo self-destructs.
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
Add the instance variables we discussed earlier to the
SpaceWar,CentralStarandSpaceShipclasses.
Exercise 3.9: Instance variables of the Spacewar! protagonists
Some of these states need to be accessed from other entities:
ship name: 'The needle'.
star mass * ship mass.
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:
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.
Write the
SpaceShipgetter messages for itsposition,velocityandmassattributes.
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.
Ship
positionandvelocity, as well as torpedoheadingwill 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.
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.
Write two methods named
leftandrightto 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.
Write two methods named
pushandunpushto adjust the ship’s inner acceleration according to the indications above.
Exercise 3.13: Methods to control ship acceleration
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.
Write the method to initialize the central star with 8000 units of mass.
Exercise 3.14: Initialize central star