Mine-Field 1K para ZX-81
Publicado: 17 Jul 2013, 20:40
EL JUEGO
El objetivo del juego consiste en descubrir todas las casillas del tablero de juego y localizar las 10 minas que contiene.
Cuando introducimos el número de fila y de columna el programa nos indica la cantidad de minas que hay alrededor de esa casilla. Si añadimos un carácter más, marcaremos la casilla con un asterisco, como “sospechosa” de contener una mina.
BLOQUES
He dividido el listado en 4 bloques:
- Línea REM usada como matriz numérica donde guardamos el estado de las 100 casillas.
- Borrado de la matriz y generar el campo minado.
- Introducir las coordenadas.
- Revisar la celda y las de alrededor.
COMO FUNCIONA
A continuación detallo, línea por línea, el funcionamiento del programa.
Se utilizan las siguientes variables:
F – Variable para bucles.
I – Variable para bucles.
X – Coordenada horizontal de la casilla introducida.
Y – Coordenada vertical de la casilla introducida.
U – Variable que contiene el valor “1”.
P – Puntero al primer carácter después del REM en la línea 5. Se usa como matriz numérica.
A – Variable de apoyo.
A$ - Variable donde se guarda las coordenadas de la casilla.
5 – Línea REM que contiene 100 caracteres.
10 – Activamos el modo FAST.
15 – Guardamos el valor 1 en la variable “U”. Se usará a lo largo del programa y permite ahorrar memoria.
20 – Se muestra el identificador de las casillas horizontales.
25 – Guardamos el valor 16514 en la variable “P”. Apunta al primer carácter después del REM de la línea 5.
30 – Inicio de un bucle entre los valores 0 y 99.
35 – Ponemos los caracteres de la línea REM a 0 mediante un POKE.
40 – Final del bucle. Ya tenemos la matriz inicializada.
50 – Inicio de un bucle entre los valores 0 y 9.
55 – Guardamos en “A” un valor entre 0 y 99, ya que “I” ha quedado con valor 100 al finalizar el bucle anterior.
60 – Si esa posición en la línea REM vale 1 vuelve a la línea 55.
65 – Hacemos un POKE a la línea REM y ponemos el valor de la posición a 1.
70 – Imprimimos una tira de guiones en la zona de juego y su posición vertical.
75 – Final del bucle.
80 – Activamos el modo SLOW.
100 – Introducimos la casilla en la variable “A$”.
105 – Guardamos en “Y” el primer carácter de “A$”, que es la fila.
110 – Guardamos en “X” el segundo carácter de “A$”, que es la columna.
115 – Situamos el cursor del PRINT en la posición Y,X más una casilla.
120 – Si la longitud de “A$” es 2 salta a la línea 135.
125 – Se considera que se está marcando una casilla y se pone un “*” en la posición indicada.
130 – Salta a la línea 80 para volver a introducir otra casilla.
135 – Si la casilla está a 1 pone una “X” y fuerza un STOP provocando un ERROR al usar la variable inexistente “Z”.
140 – Se activa el modo FAST.
200 – Se inicializa la variable “A” con 0.
205 – Inicio del bucle de control vertical desde la fila anterior a la siguiente de la indicada.
210 – Inicio del bucle de control horizontal desde la columna anterior a la siguiente de la indicada.
215 – Se suma 1 a “A” si la casilla está a 1 y entre las coordenadas 0 y 9, tanto vertical como horizontalmente.
220 – Final del bucle horizontal.
225 – Final del bucle vertical.
230 – Si “A” es distinto de 0 pone el número de minas en la casilla que hemos introducido.
235 – Si “A” es igual a 0 pone un espacio en la casilla indicada.
240 – Salta a la línea 80 para activar el modo SLOW y volver a introducir otro número.
EL PROGRAMA
APUNTES FINALES
Hacía 29 años que no programaba en un ZX-81 con 1 KB, y que mejor que re-estrenarse con un juego.
Asumiendo que en este sistema los juegos de acción no suelen quedar muy bien si están hechos en BASIC, que mejor que hacer un rompe-cocos. Siempre nos queda el modo FAST.
Así que me he decidido a hacer una versión del “Campo Minado” que había programado en M-BASIC bajo CP/M-86.
Desarrollar este juego ha sido todo un reto, y por poco no lo consigo…
Peleando con la memoria:
- Un ZX-81 básico dispone de 1024 bytes de RAM libres.
- Las variables de sistema ocupan 125 bytes.
- La memoria de video para un tablero de juego de 10x10 más los indicadores de fila y columna ocupan un total de 11x11=121 bytes, más los 25 bytes de final de línea, ocupan 146 bytes. Más unos 5 ó 6 de la zona del INPUT.
Total unos 150 bytes menos.
Necesito 7 variables numéricas, que ocupan 7 bytes cada una y una alfanumérica que no debería ocupar más de 7 bytes, pero que depende de lo que se introduzca en el INPUT. Así que, en principio, las variables consumen unos 50 bytes más.
Total que 1024-125-150-50=699 bytes para el programa.
Y para rematar, hay que guardar una matriz de 100 posiciones para guardar la posición de las bombas:
- Si se usa una matriz numérica ocupa más de 500 bytes. Inviable.
- Si se usa una variable de cadena de 100 posiciones ocupa unos 105 bytes pero complica la gestión posterior.
¿Como se hace?
El programa se ha desarrollado en un TIMEX SINCLAIR 1000 con 2 KB para evitar el engorro de programar con 1 KB y su molesta “escalada” de líneas en pantalla mientras programas. Así daba uso a este equipo, con el que nunca había programado.
Durante el desarrollo existía una línea:
9999 PRINT (PEEK 16396+256*PEEK 16397)-16509
... que me ha permitido controlar lo que ocupaba el programa. Este no debe ocupar más de 600 Bytes.
Una vez el programa funcionaba correctamente se ha cargado en un SINCLAIR ZX-81 para verificar que funciona con 1KB y hacer los correspondientes ajustes en caso de error. Evidentemente solo ejecutarlo ha dado un error de memoria llena.
¿Como ahorrar memoria?
Mi primera decisión ha sido usar como matriz donde guardar la posición de las bombas, una línea REM (línea 5) con 100 caracteres. Con esto he ahorrado unos bytes ya que solo ocupa espacio como línea de programa pero no en el área de variables, y encima facilita la gestión posterior. Así, cada vez que se ejecuta el programa cambia el contenido de la línea. Aparecen unos espacios donde no hay bomba, y un cuadradito donde hay la bomba.
Como usaré el valor 1 varias veces, he declarado una variable “U” en la línea 15 con ese valor. Se ha declarado mediante un VAL porque el valor ocupa 7 bytes en la zona de variables, pero en la zona del programa ocupa 3 bytes menos que de la forma tradicional y queda más claro que con un CODE. Y con “U+U” puedo obtener un 2.
Declaro la variable P como puntero al primer carácter después del REM en la línea 5, ya que se usará varias veces. El programa se empieza a guardar a partir de la dirección 16509 y el primer carácter después del REM está en 16514.
Como sustitutivo del valor 0 uso NOT PI ya que solo ocupa 2 bytes en lugar de los 5 habituales.
En las líneas 30-40 hay un bucle que usa la variable “I”. La estructura del bucle ocupa 18 bytes. De todas formas, el bucle va de 0 a 99, y al finalizar I=100. En la línea 55 uso este valor para calcular la posición aleatoria de las bombas y ahorro meter un valor numérico.
Las casillas de las bombas tienen un valor de 0 (vacía) o 1 (bomba). Esto me ha permitido usar IF sin signos de condición, como se puede ver en las líneas 60, 135 y en la suma de 215. Si hubiera usado una variable de cadena no lo podría haber hecho, o tendría que hacer un VAL.
En la línea 215 controlo las bombas que hay alrededor de la casilla indicada. Para ello hago un doble bucle que comprueba 3x3 casillas, y suma 1 cada vez que encuentra una bomba y la casilla está en una posición entre 0 y 9. Esto es así para evitar las casillas cerca de los bordes del tablero. Esta línea me daba errores “4” por falta de memoria durante la ejecución.
He evitado el uso de GOSUB-RETURN y otras cosas y en todo momento el programa se ejecuta de arriba a abajo.
Para forzar la detención del programa y ahorrar un STOP si tocamos una bomba, en la línea 135 hago un PRINT de la variable Z, que no está definida. Esto provoca un error 2 y detiene el programa.
La ejecución en 1 KB
Ni que decir que al probar el programa en el ZX-81 con 1 KB me ha dado un problema de memoria. Esto me ha obligado a sustituir algunos VAL por CODE y a depurar todavía más el programa, a parte de eliminar la línea 9999.
También encontré la forma de simplificar ciertos cálculos, como en la línea 135, a declarar la variable “U” para almacenar el 1, y alguna otra cosilla.
He activado el modo FAST al hacer los cálculos para conseguir que funcione de una forma rápida.
Por desgracia no he podido validar que el valor introducido es correcto por temas de memoria, así que si se introduce un valor incorrecto nos aparecerá un error 3, y se detendrá el programa.
Tampoco he podido hacer que descubra las casillas colindantes a la indicada hasta que encuentra una mina, pero para ello debería haber dispuesto de unos 250 bytes más. Por ello hay que ir descubriendo casilla a casilla.
Con todo, el programa ocupa unos 600 bytes, funciona perfectamente y con una buena velocidad, así que, al final…
Prueba superada !!!
Os invito a probarlo.