viernes, 29 de enero de 2016

Ejemplo de marquesina rotante en BASIC

Bueno, es simplemente un texto que va de derecha a izquierda en pantalla, aparece y desaparece en bucle. 

Como nosotros leemos de izquierda a derecha, lo conveniente es que el texto aparezca de derecha a izquierda. 

Puedes descargar el código fuente en formato TAP de este enlace.


Abajo el código BASIC.
Es muy sencillo 

10 CLS
20 PRINT AT 0,0;"Pulsa q para terminar"
30 LET M$="MARQUESINA"
40 LET C=1
50 FOR I=31 TO 0 STEP -1
60 PRINT AT 20,I;M$(1 TO C)
70 IF C<LEN (M$) THEN LET C=c+1
80 IF (31-i)>=LEN (m$) THEN PRINT AT 20,i+LEN (m$);" "
90 GO SUB 170
100 NEXT I
110 FOR i=1 TO LEN (m$) STEP 1
120 PRINT AT 20,0;m$(i TO LEN (m$))
130 PRINT AT 20,LEN (m$)-i;" "
140 GO SUB 170
150 NEXT i
160 GO TO 40
170 LET i$=INKEY$
180 IF i$="q" THEN STOP
190 RETURN
 


martes, 26 de enero de 2016

ZX Spectrum, pasando un "misil" por en medio de la pantalla en BASIC, parte 2

En el ejemplo de "ZX Spectrum, pasando un "misil" por en medio de la pantalla en BASIC" habíamos visto como acometer el pintado sobre lo que hubiese en pantalla, de una línea recta, sobre lo que hubiese en pantalla.

En este ejemplo, hemos ampliado para que, dado: 
  • Unas coordenadas de origen (X,Y) en modo carácter.
  • Número de celdas para el desplazamiento.
  • Una dirección (arriba, abajo y diagonales).


... nos pinte la línea de 8 bits, a modo de misil y atraviese lo que esté en el camino, respetando sus propiedades de dibujo, color y fondo. 

Dado que tenemos un límite de 2 colores, por celda, veremos el efecto de como va cambiando de color a medida que el misil va pasando por encima del contenido.

Los efectos más deseables, se logran con el fondo "blanco" y sin brillo ni intermitencia. En el ejemplo se usan estos modos, para que se vea como el programa es capaz de respetar las propiedades de cada cosa. 

Veamos el programa en acción: 


Con este ejemplo, podemos extrapolar subrutinas, para poder implementar el lanzamiento de misiles en el juego Retro8ogue

Además, para optimizar al máximo el paso a código máquina, usando el compilador HiBasic, he usado las directivas de compilación apropiadas para marcar (casi) todas las variables como enteros positivos, además de las longitudes máximas de cada variable cadena usada. 

El compilador de BASIC funciona de forma óptima, cuando trabaja con enteros, y más, si son enteros positivos. 

Todas estas optimizaciones permiten dejar el compilado con solo 278 bytes para variables, además de presentar el mejor rendimiento. 

Puedes descargar el código BASIC de este enlace.

El ejemplo en código máquina se encuentra en este enlace.



jueves, 21 de enero de 2016

ZX Spectrum, pasando un "misil" por en medio de la pantalla en BASIC

Como hemos visto en la entrada que ejemplifica como pasar de coordenadas de caracteres a coordenadas de alta resolución en pantalla del Spectrum, se pueden mezclar instrucciones que usan coordenadas de alta resolución como PLOT e instrucciones que usan las coordenadas de modo "caracter" como PRINT AT en BASIC.

¿Que pasa si queremos, por ejemplo, pasar un "misil" (linea recta de 8 bits) por en medio de caracteres o UDG's que ya están en pantalla ?. Es decir lo que queremos conseguir es una animación que se solape por encima de lo que hay en pantalla. 

Bueno, lo primero que tenemos que tener en cuenta son cuatro particularidades del Spectrum, que condicionarán nuestro trabajo:
  • Solo hay dos colores por "celda" de 8x8 en pantalla. Es decir, cuando "pisamos" una celda, solo podemos gestionar dos colores a la vez, el de fondo (PAPER) y el del carácter o gráfico (INK). 
  • La ordenación de las direcciones en memoria, no son "lineales". Es decir, hay que calcular posiciones en memoria sabiendo que hay "saltos raros" de memorias en función de la posición en pantalla. 
  • No hay una manera directa de pasar de decimal a binario. Hay que hacer una subrutina que nos lo haga, dado que necesitamos saber el "dibujo" en pantalla por donde pasamos. PEEK nos devuelve un decimal y para dibujar en pantalla, necesitamos saber que es lo que estamos "pisando". 
  • Los efectos FLASH y BRIGHT no afectan a los colores y se pueden usar libremente. Es decir, no nos complican la vida.


Acordémonos que lo que hay en cada celda, es un dibujo de 8x8 bits.

En el siguiente vídeo, vemos ejemplificado el paso de un misil, de izquierda a derecha, que además pasa por encima de letras, y cuando sale, las deja como estaban. 





Se observa el efecto de los colores, cuando pasamos por una letra. Dado que el misil es negro, cuando escribimos un punto con PLOT toda la celda se pasa a color negro. 

Para hacerlo funcionar correctamente tanto en las celdas "vacias" (que solo tienen un espacio en blanco y color de fondo) y recomponer las letras y los colores cuando el misil pasa por encima de ella usamos dos instrucciones importantes, OVER e INVERSE.

Cuando el misil pasa, imprimimos un punto usando OVER 1. Cuando vamos "borrando" el misil y recuperando lo que había, usamos INVERSE en función de lo que había en esa posición, un 0 o un 1. 

También tenemos en cuenta si es el último pixel, para devolverle el color a la celda. 

IF r$(x)="0" AND X<8 THEN PLOT OVER 1;(px-8)+(x-1),py+4: GO TO 390
IF r$(x)="1" AND X<8 THEN PLOT INVERSE 0; FLASH f; BRIGHT bold; FLASH f; INK co;(px-8)+(x-1),py+4: GO TO 390
IF R$(X)="0" AND X=8 THEN PLOT INVERSE 1; INK co; BRIGHT bold; FLASH f;(px-8)+(x-1),py+4: GO TO 390
IF R$(X)="1" AND X=8 THEN PLOT INVERSE 0; INK co; BRIGHT bold; FLASH f;(px-8)+(x-1),py+4

También usamos ATTR y un mecanismo para calcular la posición en memoria y el contenido.

Es todo un poco lioso, pero viendo el ejemplo en código se entiende mucho mejor. 

Como siempre el código BASIC es bastante lento, pero una vez que lo pasamos a código máquina usando el compilador HiSoft (y habiendo optimizado el código BASIC) el rendimiento es aceptable.

He dejado el código BASIC del ejemplo un este enlace

En este otro enlace que dejado el mismo ejemplo pasado a código máquina. 



ZX Spectrum. De decimal a binario y viceversa.

En todas las operaciones de lectura de memoria usando PEEK, el Spectrum nos devuelve un valor decimal.

Cuando lo que estamos leyendo de memoria, es un gráfico, por ejemplo una letra, si queremos usar el contenido necesitamos saber la composición en binario de dicho valor. 

Este pequeño programa BASIC, pide una letra para imprimirla en pantalla, localiza la dirección que ocupa en pantalla (es decir, su "celda") para luego interpretar su contenido en binario y pintarla en pantalla usando ceros y unos. 

Si queremos usar un gráfico que hay en pantalla , o manipularlo, debemos hacerlo usando su valor en binario.

Recordemos que cada carácter es una plantilla de 8x8 bits, dibujada usando ceros y unos, igual que cuando usamos los User Defined Graphics (UDG's)



















En este vídeo observamos como se introduce una letra, la imprimimos en pantalla, luego la analizamos y la volvemos a pintar en pantalla, pero usando ceros y unos. 

El código fuente lo puedes descargar en formato TAP de este enlace.









martes, 19 de enero de 2016

ZX Spectrum BASIC, pasar de coordenadas de baja a alta resolución.

Es muy cómodo trabajar con las coordenadas X e Y de baja resolución, para utilizar comandos como PRINT AT y SCREEN$ en pantalla. 

Las coordenadas de "baja resolución" son perfectas para manejarnos en modo "carácter"  y con UDG's de 8x8 básicos. 

El modo de "alta resolución" de nuestro Spectrum son 255 pixels horizontales por 175 pixels verticales.

Cuando queremos crear formas geométricas, o simplemente pintar una línea recta usamos comandos pomo DRAW y PLOT, que usan las coordenadas de alta resolución. 

Es una gran ventaja el poder combinar el uso de ambos modos gráficos ; simplemente usando el comando y las coordenadas que nos interesen a cada fin.

Por ejemplo, el juego Retro8ogue utiliza el modo carácter para casi todo, usando las coordenadas del modo de "baja resolución" (comandos PRINT AT y SCREEN$ básicamente).

Para poder pintar, por ejemplo, una pequeña línea, que viaje a través de la pantalla (es decir, una flecha) parece más apropiado usar el comando PLOT que crear varios UDG's; uno por cada uno de las 4 direcciones en las que la "flecha" se podría desplazar. 

Entonces, ¿como combinamos el uso de ambos modos gráficos, en función de lo que hay en pantalla?.

Pues lo más sencillo es , dado un "carácter" o UDG en pantalla (posiciones X e Y de modo "baja resolución") calcular cuales son sus coordenadas X e Y en modo "alta resolución". 

Para ello, usamos dos funciones BASIC en nuestro programa: 

Para calcular la coordenada X: 

DEF FN x(x)=x*8

El valor inicial de la posición X es la coordenada X por 8. Como cada "celda" en el modo de baja resolución es de 8x8, esta función nos devolverá la coordenada X donde se encuentra el primer pixel, en alta resolución. 

Para calcular la coordenada Y: 

DEF FN y(y)=ABS ((y*8)-168)

Dado que la coordenada Y en el modo de baja resolución, va "al revés" que en el modo de alta resolución (en baja se cuenta de arriba a abajo y en alta de abajo a arriba, en pantalla) sacamos el valor absoluto de la coordenada Y multiplicado por 8, y restando la última posición inicial de las celdas en vertical, que es 168.

Hay que recordar que estas fórmulas nos devuelven los valores iniciales de X e Y. 

La celda "completa" que representan las coordenadas de modo carácter que le hemos pasado a las funciones, son las coordenadas X e Y de alta resolución más 8 (bits).

En el código de ejemplo de abajo, calculamos las coordenadas de alta resolución, y pintamos una X en la "celda" a la que pertenecen. 

El código fuente en BASIC, del ejemplo también lo puedes descargar aquí

Después del código hay un vídeo ilustrativo.

10 CLS : LET lx=0: LET ly=0: LET hy=0: LET hx=0
20 DEF FN x(x)=x*8
30 DEF FN y(y)=ABS ((y*8)-168)
40 INPUT "Entra coor. Y :";ly
50 INPUT "Entra coor. X :";lx
60 IF lx<0 OR lx>31 OR ly<0 OR ly>21 THEN PRINT AT 2,2;"Val. no val. 0>X<32. 0>Y<22": GO TO 40
70 PRINT AT 2,2;"              ": PRINT AT 2,2;"Lo X: ";lx
80 PRINT AT 3,2;"              ": PRINT AT 3,2;"Lo Y: ";ly
90 LET hx=FN x(lx): LET hy=FN y(ly)
100 PRINT AT 4,2;"              ": PRINT AT 4,2;"Hi X: ";hx
110 PRINT AT 5,2;"              ": PRINT AT 5,2;"Hi Y: ";hy
120 INPUT "Pintar X ?";z$
130 IF z$="s" THEN GO SUB 200
140 INPUT "salir ? s/n ";z$
150 IF z$="s" THEN GO TO 180
160 PRINT AT 2,2;"                           "
170 GO TO 10
180 STOP
190 RETURN
200 FOR a=0 TO 7
210 PLOT hx+a,hy+a
220 PLOT hx+a,(hy+7)-a
230 NEXT a
240 RETURN 




miércoles, 13 de enero de 2016

Algoritmo de búsqueda de camino en ZX BASIC (Path Finding)

Una de las cosas más comunes en los juegos es implementar un algoritmo que lleve a un personaje del punto A al punto B. En el camino, siempre, obstáculos por donde no se puede pasar.

Estos algoritmos, denominados "Path Finding" o "Búsqueda de Camino", están muy bien documentados y hay muchísimos ejemplos en Internet para entornos de programación actuales.

Bueno, ¿ Que pasa si queremos implementar esta funcionalidad en Sinclair BASIC ?. 

Nos encontramos con varios problemas:
  • El uso de recursos de lenguaje de programación como colas, objectos , etc.
  • Las limitaciones propias del Sinclair BASIC. Tenemos muy poca memoria donde debe residir todo el programa o juego, no solo el algoritmo y un numero muy limitado de espacio para variables.
  • La velocidad de proceso en Sinclair BASIC, sobre todo dentro de bucles. 


Todo esto comienza como parte del desarrollo del juego Retro8ogue. Estoy en la fase de implementar el movimiento de los monstruos en pantalla, pero con sentido (o lo que llaman también I.A., Inteligencia Artificial). 

Una vez dentro del rango de visión / oído del monstruo, éste debe encontrar el camino hacia el personaje, independientemente del mapa y los obstáculos que se pueda encontrar. Como es un juego rogue - like, el mapa es generado aleatoriamente y nunca es igual. 

Dadas las limitaciones, partimos de las siguientes premisas:


Es decir, simplificar al máximo las posibilidades de búsqueda para minimizar el código y las necesidades de proceso. Añadir un poquito de aleatoriedad en la búsqueda del mejor camino. 

Veamos el ejemplo en pantalla de nuestro intento: 























Hay una escalera que el personaje tiene que encontrar. 

El mapa es aleatorio, pero no muy poblado de bloques para facilitar la labor. 

Lo primero que definimos es que hay 8 direcciones posibles para el personaje. 

4 5 6
3@7
2 1 8

La idea es que, de las 8 posibles direcciones, ¿cual es la que está mas cerca del objetivo?. 

Todo lo hacemos con coordenadas X e Y, para lo que la pantalla del Spectrum es perfecta. 

Esto lo calculamos de la siguiente manera: 

d = ABS(destino_x - origen_x)+ABS(destino_y-origen_y) 

Si calculamos esto sobre las 8 posibles direcciones que podemos seguir, sabemos cual es la que mas cerca nos deja del objetivo. Si esa dirección está ocupada por algo que no nos deja pasar, nos movemos de forma aleatoria 1 vez. 

Hay muchas maneras más efectiva de hacer esto, pero la utilizada nos garantiza que: 
  • Eventualmente, lleguemos al objetivo. 
  • El monstruo se comporta de vez en cuando de forma aleatoria en su movimiento, lo que añade jugabilidad al juego. 
  • Es muy fácil de implementar en BASIC y de optimizar para usar el mínimo de código, variables y proceso. 


El resultado en BASIC es aceptable en términos de rendimiento, y una vez pasado a código máquina, funciona de forma excelente. 

Si al ejemplo le quitamos el uso de detección de UDG's , irá aún más rápido. He utilizado los UDG's para hacer el ejemplo más completo y aplicable al desarrollo de Retro8ogue

Hay otras optimizaciones hechas en el movimiento aleatorio para que no se pierda tiempo, pero esas las puedes ver en el propio código (como no intentar otra vez un lado que está obstruido en el mismo ciclo).

Puedes descargar una cinta en formato TAP con el código Sincalir Basic aquí.

Puedes descargar una cinta en formato TAP con el programa en código máquina aquí

Puedes ver el vídeo del ejemplo funcionando: