EL JUEGO
Vuelven los invasores del espacio!!!
En el año 1977 intentaron invadir las ciudades de la tierra, y fracasaron.
Ahora vuelven con un malvado plan: destruir las centrales nucleares para dejar el planeta inhabitable.
Con la ayuda de nuestro tanque debemos evitar que cumplan con su objetivo.
Cada nivel lo componen 30 invasores, y al superarlo aparecerá una central más grande y los invasores deberán recorrer menos distancia para llegar a ella.
Destruye el UFO que aparece por la parte superior para conseguir puntos extras.
Pulsa “RETURN” en la pantalla de presentación para empezar la partida.
Controles:
Pulsa “X” para mover el tanque a la derecha y “Z” para mover el tanque a la izquierda.
Pulsa RETURN para disparar.
Descargar código fuente (PAS) y ejecutable (CMD):
BLOQUES
En PASCAL los programas se suelen construir a base de PROCEDURES, que son procedimientos encargados de hacer una tarea concreta.
Para el programa se han definido 13 PROCEDURES:
Pause: Hace una pausa de un tiempo dado. Un valor “100” hace una pausa de 1 segundo aproximadamente.
BorrarPantalla: Borra la pantalla hasta la fila indicada. Respeta el marcador superior.
PulsarTecla: Queda en espera hasta que se pulsa una tecla.
Marcador: Muestra un valor numérico de 4 cifras en la zona de los marcadores. Indicar posición horizontal y el valor.
Presentacion: Muestra la pantalla de presentación y la pantalla de inicio de partida.
InicioPrograma: Procedimiento que se ejecuta la primera vez que se ejecuta el programa. Define ciertas matrices y el récod.
ResetVariables: Inicializa las variables antes de iniciar un nivel o perder una vida.
ZonaDeJuego: Al iniciar un nivel muestra el tanque, los invasores y la central nuclear.
MoverTanque: Controla el movimiento de nuestro tanque.
ControlDisparo: Controla todo lo relacionado con el disparo: movimiento e impacto.
MoverInvasor: Controla el movimiento de los invasores.
MoverUFO: Controla el UFO.
PerderVida: Decrementa el contador de vidas y controla el final de la partida.
Al final hay el nudo principal de desarrollo formado por 3 bucles REPEAT:
1) Se va repitiendo partida tras partida, mostrando la presentación primero de todo, y engloba los otros dos. Es un bucle infinito, del que solo se puede salir pulsando CTRL+C o ESC. Este último lo controla el programa y fuerza un HALT.
2) Controla la repetición cada vez que superamos un nivel o perdemos una vida. Este bucle finaliza al perder las tres vidas.
3) Controla todo el desarrollo a lo largo de la partida. Engloba el movimiento del tanque, enemigos, UFO, etc. Este bucle finaliza cuando perdemos una vida o pasamos de nivel.
EL LISTADO
Código: Seleccionar todo
PROGRAM NuclearInvaders;
{ *** Definición de Variables y Constantes Globales *** }
CONST
invader_graf : ARRAY [1..10] OF STRING[3] = ('w8w','"8"','/Y\','\Y/','/Y\','\Y/','qXp','dXb','qXp','dXb');
base1 : STRING[27] = '+-+-+-+-+-+-+-+-+-+-+-+-+-+';
base2 : STRING[27] = '|*|*|*|*|*|*|*|*|*|*|*|*|*|';
valor : ARRAY [1..10] OF INTEGER = (30,20,20,10,10,30,20,20,10,10);
VAR
tecla : CHAR;
muerto, disparo : BOOLEAN;
f, i, pos_ini, pos_fin : INTEGER;
puntos, puntos_max, nivel, vidas : INTEGER;
tanque_x, laser_x, laser_y, indice : BYTE;
ufo_cont, ufo_x, animar, flota : BYTE;
invader_x, invader_y : ARRAY [1..10] OF INTEGER;
invader_vel, invader_num : ARRAY [1..10] OF INTEGER;
invader_mov : ARRAY [1..10,1..2] OF STRING[4];
{ *** Rutina de Pausa *** }
PROCEDURE Pausa(ciclos:INTEGER);
BEGIN
FOR f:=1 TO ciclos DO Delay(15);
END;
{ *** Borrado parcial de la pantalla *** }
PROCEDURE BorrarPantalla(linea:INTEGER);
BEGIN
GotoXY(1,3);
FOR f := 3 TO linea DO
Writeln(' ');
END;
{ *** Pausar hasta que se pulsa una tecla *** }
PROCEDURE PulsarTecla;
BEGIN
REPEAT
UNTIL KeyPressed;
IF KeyPressed THEN read(KBD,tecla);
END;
{ *** Mostrar las Puntuaciones del Marcador *** }
PROCEDURE Marcador(x,y,numero:INTEGER);
VAR
result : STRING[8];
BEGIN
Str(numero,result); result:=ConCat('0000',result);
GotoXY(x,y); Writeln(Copy(result,Length(result)-3,4));
END;
{ *** Pantalla de Presentacion del juego *** }
PROCEDURE Presentacion;
BEGIN
ClrScr;
Writeln(' PLAYER<1> HI-SCORE PLAYER<2>');
Marcador(8,2,puntos); Marcador(39,2,puntos_max);
GotoXY(39,5); Write('PLAY');
GotoXY(33,7); Write('NUCLEAR INVADERS');
GotoXY(29,10); Write('* SCORE ADVANCE TABLE *');
GotoXY(34,12); Write('<*> =? MYSTERY');
GotoXY(34,14); Write('w8w =30 POINTS');
GotoXY(34,16); Write('/Y\ =20 POINTS');
GotoXY(34,18); Write('qXp =10 POINTS');
GotoXY(3,21); Write('(C) SCAINET SOFT, 2014');
GotoXY(55,21); Write('<= Z - X => : RETURN = |');
GotoXY(6,24); Write('oxIxo oxIxo');
GotoXY(69,24); Write('CREDIT 00');
PulsarTecla;
BorrarPantalla(23);
GotoXY(8,2); Write('0000');
GotoXY(34,12); Write('PLAY PLAYER<1>');
Pausa(300);
GotoXY(3,23); Writeln('____________________________________________________________________________');
Write(' 3');
END;
{ *** Inicio del Programa *** }
PROCEDURE InicioPrograma;
BEGIN
puntos_max:=0;
FOR f:=1 TO 5 DO BEGIN
invader_mov[f,1]:=invader_graf[f*2-1];
invader_mov[f,2]:=invader_graf[f*2];
invader_mov[f+5,1]:=invader_mov[f,1];
invader_mov[f+5,2]:=invader_mov[f,2];
END;
END;
{ *** Reset de las Variables *** }
PROCEDURE ResetVariables;
BEGIN
tanque_x:=5; disparo:=FALSE;
muerto:=FALSE; indice:=1;
ufo_x:=0; ufo_cont:=200;
animar:=1; flota:=30;
FOR f:=1 TO 5 DO BEGIN
invader_y[f]:=f*2+5; invader_y[f+5]:=f*2+5;
invader_x[f]:=4; invader_x[f+5]:=75;
invader_vel[f]:=0; invader_vel[f+5]:=0;
invader_num[f]:=3; invader_num[f+5]:=3;
END;
END;
{ *** Mostrar la Zona de Juego *** }
PROCEDURE ZonaDeJuego;
BEGIN
i:=nivel*4+5; pos_ini:=42-Round(i/2); pos_fin:=pos_ini+i-1;
GotoXY(pos_ini,6); Write(Copy(base1,1,i));
FOR f:=7 TO 11 DO BEGIN
GotoXY(pos_ini,f); Write(Copy(base2,1,i));
GotoXY(pos_ini,f+1); Write(Copy(base1,1,i));
f:=f+1
END;
FOR f:=1 TO 10 DO BEGIN
GotoXY(invader_x[f],invader_y[f]); Write(invader_mov[f,1]);
END;
GotoXY(tanque_x,22); Write('oxIxo');
END;
{ *** Mover el Tanque *** }
PROCEDURE MoverTanque;
BEGIN
read(KBD,tecla); tecla:=UpCase(tecla);
IF (tecla='Z') AND (tanque_x>5) THEN BEGIN
tanque_x:=tanque_x-1;
GotoXY(tanque_x,22); Write('oxIxo ');
END;
IF (tecla='X') AND (tanque_x<72) THEN BEGIN
GotoXY(tanque_x,22); Write(' oxIxo');
tanque_x:=tanque_x+1;
END;
IF (tecla=chr(13)) AND (disparo=FALSE) THEN BEGIN
disparo:=TRUE;
laser_x:=tanque_x+2; laser_y:=22;
END;
IF (tecla=chr(27)) THEN Halt;
END;
{ *** Controlar el Disparo del Tanque *** }
PROCEDURE ControlDisparo;
BEGIN
IF (laser_y<22) THEN BEGIN
GotoXY(laser_x,laser_y); Write(' ');
END;
laser_y:=laser_y-1;
IF (laser_y=2) OR ((laser_x>=pos_ini) AND (laser_x<=pos_fin) AND (laser_y<17)) THEN
disparo:=FALSE
ELSE BEGIN
GotoXY(laser_x,laser_y); Write('|');
FOR f:=1 TO 10 DO
IF (laser_y=invader_y[f]) AND (laser_x>=invader_x[f]) AND (laser_x<=invader_x[f]+2) THEN BEGIN
GotoXY(invader_x[f],invader_y[f]); Write(' ');
invader_vel[f]:=0;
IF (f>5) THEN invader_x[f]:=75 ELSE invader_x[f]:=4;
invader_num[f]:=invader_num[f]-1;
If (invader_num[f]>0) THEN BEGIN
GotoXY(invader_x[f],invader_y[f]); Write(invader_mov[f,1]);
END;
disparo:=FALSE; flota:=flota-1;
puntos:=puntos+valor[f]; Marcador(8,2,puntos);
END;
IF (disparo=TRUE) THEN
IF (ufo_x<>0) AND (laser_y=4) THEN
IF (laser_x>=ufo_x+1) AND (laser_x<=ufo_x+3) THEN BEGIN
f:=(Random(3)+1)*50;
GotoXY(ufo_x+1,4); Write(f,' ');
Pausa(30);
GotoXY(ufo_x+1,4); Write(' ');
puntos:=puntos+f; Marcador(8,2,puntos);
disparo:=FALSE; ufo_x:=0; ufo_cont:=200;
END;
END;
END;
{ *** Mover a los Invasores *** }
PROCEDURE MoverInvasor;
BEGIN
IF (invader_num[indice]>0) THEN BEGIN
IF (invader_vel[indice]=0) THEN BEGIN
IF (Random(10)>7) THEN BEGIN
invader_vel[indice]:=1;
IF (indice>5) THEN invader_vel[indice]:=-invader_vel[indice];
END;
END
ELSE BEGIN
GotoXY(invader_x[indice],invader_y [indice]); Write(' ');
invader_x[indice]:=invader_x[indice]+invader_vel[indice];
GotoXY(invader_x[indice],invader_y[indice]); Write(invader_mov[indice,animar]);
IF (indice<6) AND (invader_x[indice]>pos_ini-2) THEN muerto:=TRUE;
IF (indice>5) AND (invader_x[indice]<pos_fin) THEN muerto:=TRUE;
END;
END;
indice:=indice+1;
IF (indice>10) THEN BEGIN
indice:=1;
IF (animar=1) THEN animar:=2 ELSE animar:=1;
END;
END;
{ *** Mover el Platillo Volante *** }
PROCEDURE MoverUFO;
BEGIN
IF (ufo_x=0) THEN BEGIN
ufo_cont:=ufo_cont-1;
IF (ufo_cont=0) THEN ufo_x:=3;
END
ELSE BEGIN
ufo_x:=ufo_x+1;
GotoXY(ufo_x,4);
IF (ufo_x<75) THEN Write(' <*>')
ELSE BEGIN
Writeln(' ');
ufo_x:=0; ufo_cont:=200;
END;
END;
END;
PROCEDURE PerderVida;
BEGIN
vidas:=vidas-1;
IF (vidas>0) THEN BEGIN
GotoXY(4,24); Write(vidas);
GotoXY(vidas*6,24); Write(' ');
END
ELSE BEGIN
GotoXY(35,19); Write(' GAME OVER ');
IF (puntos_max<puntos) THEN puntos_max:=puntos;
Pausa(300);
END
END;
{ *** Nudo Principal de Desarrollo *** }
BEGIN
InicioPrograma;
{ *** Bucle Inicio Partida *** }
REPEAT
Presentacion;
nivel:=1; vidas:=3; puntos:=0;
{ *** Bucle Iniciar Nivel *** }
REPEAT
ResetVariables;
BorrarPantalla(22);
ZonaDeJuego;
{ *** Bucle Principal *** }
REPEAT
IF (KeyPressed) THEN MoverTanque;
IF (disparo=TRUE) THEN ControlDisparo;
MoverInvasor;
MoverUFO;
Pausa(5);
UNTIL (flota=0) OR (muerto=TRUE);
IF (muerto=FALSE) THEN BEGIN
IF (nivel<4) THEN nivel:=nivel+1;
END
ELSE PerderVida;
Pausa(300);
UNTIL (vidas=0);
UNTIL FALSE;
END.
APUNTES FINALES
No tocaba el lenguaje PASCAL desde el año 1993, en que conseguí una copia “ilegal” del programa TURBO Pascal de Borland para MS-DOS. Es un lenguaje que me gustó porque es potente, estructurado y bastante fácil de aprender. Pero la verdad es que hice un par de proyectos de los míos y no lo toqué más. También en su día conseguí una copia del HiSoft PASCAL para el ZX-Spectrum pero me limité a hacer alguna sencilla prueba.
Para CP/M-80 tengo el PASCAL MT+, pero no me acaba de gustar. Hace un tiempo conseguí el Turbo PASCAL para CP/M-86 y una vez bien configurado descubrí todo su potencial. Lleva un completo editor incorporado y puedes ejecutar el programa o generar un ejecutable con extensión CMD.
Poco después conseguí un manual de instrucciones en formato PDF y saltando algunas cosas, acabó impreso en
láser a doble cara y metido en un archivador para poder ser consultado cómodamente.
Y el descubrir que incorpora el comando “GotoXY”, que permite escribir en cualquier posición de la pantalla, me animó a desarrollar mi primer juego de acción. Y que mejor que una versión del “Nuclear Invaders”... en modo texto !!!
Pongámonos en ambiente
La verdad es que esta ha sido una versión muy especial para mi.
Este es el escenario:
Un AMSTRAD PC-1515-SD, con un 8086 a 8MHz y 512 KB de RAM, monitor monocromo de fósforo blanco, una disquetera de 5.25” DD de 360 KB y un teclado de 83 teclas.
Un disquete con una copia del sistema operativo CP/M-86, otro disquete con una copia del Turbo PASCAL y un manual de este compilador.
Y para finalizar, un proyecto: Nuclear Invaders.
Con este cóctel era imposible que no acabara bien.
El desarrollo
En un principio he estructurado el programa basándome un poco en la versión del Jupiter ACE, aunque está más simplificado.
Aquí, en lugar de palabras he definido procedimientos.
Como siempre he empezado por lo más fácil, para poco a poco irme sumergiendo en el desarrollo de las partes más complejas.
Así, he empezado con la pantalla de presentación, el movimiento de nuestro tanque y del disparo, el movimiento de los invasores y del UFO. He dejado para el final el control de lo que toca el disparo.
EL CP/M no permite, de una forma estándar, consultar lo que hay en una posición determinada de la pantalla con lo que esta parte se ha modificado respecto al resto de versiones. Así, al avanzar el disparo va comprobando si coincide con la posición de alguno de los 10 invasores, del UFO e incluso de la central nuclear. Como va compilado, el rendimiento es perfecto.
Para el tema de los gráficos, me he basado en el juego “ALIENS” del CP/M-80. El uso de letras no da para mucho, pero las animaciones han quedado más que bien.
Y los problemillas…
El programa se ha desarrollado en un equipo a 8 MHz, y la velocidad es perfecta en él, pero con el programa 22DSK he pasado el fichero con el código fuente al PC moderno, y al ejecutarlo allí, en mi simulador de CP/M-86 me encuentro que va a una velocidad brutal. En resumen, es injugable. Si el simulador lo ejecuto desde el DOS-Box, la velocidad baja, pero sigue siendo muy alta.
Para hacer la pausa uso la intrucción “Delay(n)” donde “n” indica los milisegundos que ha de esperar. El manual ya indica que no es perfecto, y doy fe que no lo es.
He intentado ver si es posible acceder al reloj del sistema, pero no he sido capaz de descubrir si es posible. Esto iría bien para montar una rutina que calculara la velocidad del equipo y la ajustara automáticamente. Seguiremos investigando.
Por otro lado, el control de la nave no es muy bueno porque al pulsar una tecla espera a que entre en modo auto-repetición para mover nuestro tanque continuamente. A pesar de ello, el juego es jugable y he adaptado la dificultad a ello.
En el juego “ALIENS” lo que hacen es que al mover la nave en una dirección no para hasta que pulsas una determinada tecla. No me ha gustado la solución y lo he dejado como estaba.
Con todo, me ha hecho mucha gracia programar esta versión. Programar el juego entero me ha llevado 3 noches, aprovechando los ratitos de tren para documentarme y planificar cosas.
Para terminar, decir que este programa ha sido desarrollado en un AMSTRAD PC-1512-SD bajo CP/M-86, el listado lo he impreso con una matricial y he usado el 22DSK para pasar el programa a un disquete de 3.5" y de allí a un PC moderno con disquetera USB.
Os invito a probarlo.