Whenever you need to deal with more than one element of the same
nature – instances of the same class – it is a clue to use a
collection to hold them. Moreover, when these elements are of a fixed
quantity, it indicates more precisely you want to use an Array
instance. An Array
is a collection of fixed size. It can not
grow nor shrink.
When this quantity is variable, you want to use an
OrderedCollection
instance. It is a collection of variable
size, it can grow or shrink.
SpaceWar! is a two-player game, there will always be two players
and two spaceships. We use an Array
instance to keep a
reference to each spaceship.
Each player can fire several torpedoes; therefore the gameplay holds
zero or more torpedoes – hundreds if we decide so. The torpedoes
quantity is variable, we want to use an OrdredCollection
instance to keep track of them.
In the SpaceWar
class, we already defined two instance
variables ships
and torpedoes
. Now, we want an
initializeActors
method to set up the game with the involved
actors – central star, ships, etc. Part of this initialization is to
create the necessary collections.
See below an incomplete implementation of this method:
SpaceWar>>initializeActors centralStar := CentralStar new. ../.. ships first position: 200 @ -200; color: Color green. ships second position: -200 @ 200; color: Color red
The example above does not show the creation of the
ships
andtorpedoes
collections. Replace “../..” with lines of code where these collections are instantiated and if necessary populated.
The spaceship and the torpedo objects are responsible for their
internal states. They understand the #update:
message to recompute
their position according to the mechanical laws.
A fired torpedo has a constant velocity, no external forces are applied
to it. Its position is linearly updated according to the time
elapsed. The t
parameter in the #update:
message is
this time interval.
Torpedo>>update: t "Update the torpedo position" position := velocity * t + position. ../..
A spaceship is put under the strain of the star’s gravity pull and the acceleration of its engines. Therefore its velocity and position change according to the mechanical laws of physics.
SpaceShip>>update: t "Update the ship position and velocity" | ai ag newVelocity | "acceleration vectors" ai := acceleration * self direction. ag := self gravity. newVelocity := (ai + ag) * t + velocity. position := (0.5 * (ai + ag) * t squared) + (velocity * t) + position. velocity := newVelocity. ../..
Remember that Smalltalk does not follow the mathematics precedence of arithmetic operators. These are seen as ordinary binary messages which are evaluated from the left to the right when there is no parenthesis. For example, in the code fragment
...(velocity * t)...
, the parenthesis are mandatory to get the expected computation.
Observe in this previous method how the direction and the gravity are defined in two specific methods.
The #direction
message asks for the unit vector representing the
nose direction of the spaceship:
SpaceShip>>direction "I am a unit vector representing the nose direction of the mobile" ^ Point rho: 1 theta: self heading
The #gravity
message asks for the gravity vector of the spaceship
is subjected to:
SpaceShip>>gravity "Compute the gravity acceleration vector" | position | position := self morphPosition. ^ [-10 * self mass * self starMass / (position r raisedTo: 3) * position] on: Error do: [0 @ 0]
Observe the message #starMass
sent to the spaceship herself.
We, as a spaceship, have not yet figured out how to ask the central star
for its stellar mass. Our starMass
method can just return, for
now, the number 8000.
The gameplay is the responsibility of a SpaceWar
instance. At
a regular interval of time, it refreshes the states of the game
actors. A stepAt:
method is called at a regular interval of time
determined by the stepTime
method:
SpaceWar>>stepTime "millisecond" ^ 20 SpaceWar>>stepAt: millisecondSinceLast ../.. ships do: [:each | each unpush]. ../..
In the stepAt:
method, we intentionally left out the details to
update the ship and torpedo positions. Note: each ship is sent
regularly an #unpush
message to reset its previous #push
acceleration.
Replace the two lines “../..” with code to update the ships and the torpedoes’ positions and velocities.
Among other things, the gameplay handles the collisions between the various protagonists. Enumerators are very handy for this.
Ships are held in an array of size 2, we just iterate it with a #do:
message and a dedicated block of code:
SpaceWar>>collisionsShipsStar ships do: [:aShip | (aShip morphPosition dist: centralStar morphPosition) < 20 ifTrue: [ aShip flashWith: Color red. self teleport: aShip] ]