4.5 Colecciones de SpaceWar!

4.5.1 Instanciar colecciones

Siempre que tengas que manejar más de un elemento de la misma naturaleza (instancias de la misma clase), es recomendable utilizar una colección para almacenarlos. Además, cuando estos elementos son de cantidad fija, es más preciso utilizar una instancia Array. Un Array es una colección de tamaño fijo. No puede crecer ni reducirse.

Cuando esta cantidad es variable, se recomienda utilizar una instancia de OrderedCollection. Se trata de una colección de tamaño variable, que puede crecer o reducirse.

SpaceWar! es un juego para dos jugadores, siempre habrá dos jugadores y dos naves espaciales. Utilizamos una instancia Array para mantener una referencia a cada nave espacial.

Cada jugador puede disparar varios torpedos; por lo tanto, el juego admite cero o más torpedos, cientos si así lo decidimos. La cantidad de torpedos es variable, queremos usar una instancia OrdredCollection para llevar un registro de ellos.

En la clase SpaceWar, ya hemos definido dos variables de instancia: ships y torpedoes. Ahora queremos un método initializeActors para configurar el juego con los actores involucrados: estrella central, naves, etc. Parte de esta inicialización consiste en crear las colecciones necesarias.

A continuación se muestra una implementación incompleta de este método:

SpaceWar>>initializeActors
   centralStar := CentralStar new.
   ../..
   ships first 
      position: 200 @ -200;
      color: Color green. 
   ships second
      position: -200 @ 200;
      color: Color red

Ejemplo 4.17: Incomplete game initialization

 CuisLogo El ejemplo anterior no muestra la creación de las colecciones ships y torpedoes. Reemplaza «../..» por líneas de código en las que se instancien estas colecciones y, si es necesario, se rellenen.

Ejercicio 4.17: Colecciones para contener las naves y torpedos

4.5.2 Colecciones en acción

La nave espacial y los objetos torpedo son responsables de sus estados internos. Entienden el mensaje #update: para recalcular su posición de acuerdo con las leyes mecánicas.

Un torpedo disparado tiene una velocidad constante, no se le aplican fuerzas externas. Su posición se actualiza linealmente según el tiempo transcurrido. El parámetro t en el mensaje #update: es este intervalo de tiempo.

Torpedo>>update: t
"Update the torpedo position"
   position := velocity * t + position.
   ../..

Ejemplo 4.18: Mecánicas del torpedo

Una nave espacial está sometida a la fuerza gravitatoria de la estrella y a la aceleración de sus motores. Por lo tanto, su velocidad y posición cambian según las leyes mecánicas de la física.

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.
   ../..

Ejemplo 4.19: Mecánicas de la nave espacial

 note Recuerda que Smalltalk no sigue la precedencia matemática de los operadores aritméticos. Estos se consideran mensajes binarios normales que se evalúan de izquierda a derecha cuando no hay paréntesis. Por ejemplo, en el fragmento de código ...(velocity * t)..., los paréntesis son obligatorios para obtener el cálculo esperado.

Observa que en ese método anterior cómo la dirección y la gravedad están definidas en dos métodos específicos.

El mensaje #direction solicita el vector unitario que representa la dirección de la proa de la nave espacial:

SpaceShip>>direction
"I am a unit vector representing the nose direction of the mobile"
   ^ Point rho: 1 theta: self heading

Ejemplo 4.20: Método dirección de la nave espacial

El mensaje #gravity solicita el vector de gravedad al que está sometida la nave espacial:

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]

Ejemplo 4.21: Gravedad de la nave espacial

Observa el mensaje #starMass enviado a la propia nave espacial. Nosotros, como nave espacial, aún no hemos descubierto cómo preguntarle a la estrella central cuál es su masa estelar. Nuestro método starMass solo puede devolver, por ahora, el número 8000.

El juego es responsabilidad de una instancia de SpaceWar. A intervalos regulares, actualiza los estados de los actores del juego. Se llama al método #stepAt: a intervalos regulares determinados por el método #stepTime:

SpaceWar>>stepTime
"millisecond"
   ^ 20

SpaceWar>>stepAt: millisecondSinceLast
   ../..
   ships do: [:each | each unpush].
   ../..

Ejemplo 4.22: Actualización periódica del juego

En el método stepAt:, hemos omitido intencionadamente los detalles para actualizar las posiciones de la nave y el torpedo. Nota: cada nave recibe periódicamente un mensaje #unpush para restablecer su aceleración #push anterior.

 CuisLogo Reemplaza las dos líneas «../..» con código para actualizar las posiciones y velocidades de las naves y los torpedos.

Ejercicio 4.18: Actualizar todas las naves y torpedos

Entre otras cosas, el juego gestiona las colisiones entre los distintos protagonistas. Los enumeradores son muy útiles para esto.

Los barcos se guardan en una matriz de tamaño 2, simplemente la iteramos con un mensaje #do: y un bloque de código dedicado:

SpaceWar>>collisionsShipsStar
   ships do: [:aShip | 
      (aShip morphPosition dist: centralStar morphPosition) < 20 ifTrue: [
         aShip flashWith: Color red.
         self teleport: aShip]
   ]

Ejemplo 4.23: Colisión entre las naves y el Sol