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
El ejemplo anterior no muestra la creación de las colecciones
shipsytorpedoes. 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
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
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.
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