Índice de entradas
Esta serie de artículos han sido traducidos a partir del documento "How to
Write ZX Spectrum Games" con permiso de su autor, Jonathan Cauldwell, un gran
desarrollador de juegos para el Spectrum, os recomiendo
visitar su Web donde está
el texto original. El documento original, y por tanto esta traducción, tiene
© Jonathan Cauldwell y solo puede duplicarse con permiso expreso por escrito
de su autor.
ACTUALIZADO 16/02/2018: Se añaden fuentes verificados para su descarga. Ver nuevo apéndice B
ACTUALIZADO 20/09/2022: Se actualizan los fuentes, ahora ejecutables según el nuevo apéndice C
ACTUALIZADO 16/02/2018: Se añaden fuentes verificados para su descarga. Ver nuevo apéndice B
ACTUALIZADO 20/09/2022: Se actualizan los fuentes, ahora ejecutables según el nuevo apéndice C
Control del teclado y el Joystick
Una tecla a la vez
A condición de que no hayas deshabilitado las interrupciones o te hayas entrometido con ellas, por defecto la ROM del Spectrum leerá automáticamente el teclado y actualizará
varias variables del sistema situadas en la posición de memoria 23552 cincuenta
veces por segundo. La forma más sencilla de comprobar si hay una
pulsación de tecla es primero cargar la dirección 23560 con un valor nulo,
y luego interrogar esta ubicación hasta que cambie, el resultado es el valor
ASCII de la tecla pulsada. Esto es muy útil para aquellas situaciones de "presione cualquier
tecla para continuar", para la elección de elementos de un menú, o para la entrada del nombre en las rutinas de puntuación más alta. Una rutina de este tipo podría tener este aspecto:
ld hl,23560 ; variable del sistema LAST K. ld (hl),0 ; poner un valor nulo aquí. loop ld a,(hl) ; nuevo valor de LAST K. cp 0 ; ¿sique a cero? jr z,loop ; si, no se ha pulsado nada. ret ; tecla pulsada.
Pulsación de varias teclas
Las pulsaciones de teclas individuales son raras de usar para la rápida
acción de los juegos arcade, en su lugar necesitamos detectar más
de una pulsación simultánea, y aquí es donde las cosas se ponen un poco más complicadas.
En lugar de leer direcciones de memoria tenemos que leer uno de ocho
puertos, cada uno de los cuales corresponde a una fila de cinco teclas. Por
supuesto, la mayoría de modelos de Spectrum parecen tener muchas mas teclas ¿como se conectan? Bueno, en realidad no lo hacen. La distribución
del teclado del Spectrum original consistía en sólo cuarenta teclas, dispuestas
en ocho grupos de filas de cinco teclas. Con el fin de acceder a algunas de las funciones era necesario presionar ciertas combinaciones de teclas al mismo tiempo, por ejemplo para borrar la combinación requerida eran CAPS SHIFT y 0 a la vez.
Sinclair añadió estas teclas extra cuando llegó el Spectrum Plus a escena
en 1985, y funcionan mediante la simulación de la pulsación simultánea de las combinaciones de pulsaciones
de teclas necesarias en los modelos de teclado de goma original.
La distribución del teclado original se separa en estas agrupaciones:
Puerto Teclas 32766 B, N, M, Symbol Shift, Space 49150 H, J, K, L, Enter 57342 Y, U, I, O, P 61438 6, 7, 8, 9, 0 63486 5, 4, 3, 2, 1 64510 T, R, E, W, Q 65022 G, F, D, S, A 65278 V, C, X, Z, Caps Shift
Para descubrir qué teclas están siendo presionadas leemos el número del puerto
adecuado, cada tecla en la fila está asignanda a uno de los cinco bits bajos D0-D4 (valores 1, 2, 4, 8 y 16) donde D0 representa la tecla exterior y D4 la más
interna. Curiosamente, cada bit está en alto (1) cuando no se presiona, y en bajo (0) cuando lo está, al contrario de lo que cabría esperar.
Para leer una fila de cinco teclas simplemente cargamos el número de puerto
en el par de registros BC, a continuación realizar la instrucción in a,(c). Como sólo queremos los bits de menor valor podemos ignorar los bits que no
deseamos, ya sea con una and 31 o mediante la rotación de los bits de salida del
acumulador usando la bandera de acarreo mediante cinco instrucciones rra: call c,(dirección).
Si esto es difícil de entender revisa el siguiente ejemplo:
ld bc,63486 ; fila del teclado 1-5/puerto de joystick 2. in a,(c) ; ver que tecla se ha pulsado. rra ; bit externo = key 1. push af ; recordar el valor. call nc,mpl ; si se ha pulsado, moverse a la izquierda. pop af ; restaurar el acumulador. rra ; siguiente bit (valor 2) = key 2. push af ; recordar el valor. call nc,mpr ; se ha pulsado, moverse a la derecha. pop af ; restaurar el acumulador. rra ; siguiente bit (valor 4) = key 3. push af ; recordar el valor. call nc,mpd ; se ha pulsado, moverse hacia abajo. pop af ; restaurar el acumulador. rra ; siguiente bit (valor 8) reads key 4. call nc,mpu ; se ha pulsado, moverse hacia arriba.
Ver mas abajo apartado "un juego simple" para un ejemplo de esto
Joysticks
Los puertos de joystick Sinclair 1 y 2 fueron simplemente asignados a cada una de
las filas de las teclas numéricas, lo que se puede ver fácilmente desde el editor de BASIC, usando el joystick para escribir números.
El puerto 1 (interface 2) se asigna a las teclas 6, 7, 8, 9 y 0, el puerto 2 (interface
1) a las teclas 1, 2, 3, 4 y 5. Para detectar la entrada de joystick leemos el
puerto del mismo modo que para la lectura del teclado. Los joysticks Sinclair utilizan
los puertos 63486 (interface 1 / puerto 2) y 61438 (interface 2 / puerto 1), los bits
D0-D4 darán un 0 para el presionado, 1 para no presionado.
El popular formato de joystick Kempston no está asignada al teclado y se puede
leer en su lugar mediante el puerto 31. Esto significa que podemos usar un sencillo
in a,(31). Una vez más, se utilizan los valores de los bits D0-D4, aunque esta
vez los valores son como se podría esperar, con un valor alto si se está aplicando el joystick en una dirección particular. Los valores
de los bits resultantes serán 1 para presionado, 0 para no presionado.
; Ejemplo de rutina de control del joystick. joycon ld bc,31 ; puerto del joystick Kempston. in a,(c) ; leer la entrada. and 2 ; verificar el bit "izquierda". call nz,joyl ; moverse a la izquierda. in a,(c) ; leer la entrada. and 1 ; verificar el bit "derecha". call nz,joyr ; moverse a la derecha. in a,(c) ; leer la entrada. and 8 ; verificar el bit "arriba". call nz,joyu ; moverse arriba. in a,(c) ; leer la entrada. and 4 ; verificar el bit "abajo". call nz,joyd ; moverse abajo. in a,(c) ; leer la entrada. and 16 ; verificar el bit de disparo. call nz,fire ; disparo pulsado.
Un juego simple
Ahora podemos ir un paso más allá y, poniendo en práctica lo que ya hemos cubierto,
escribir la sección de control principal para un juego básico. Esta será la
base de una simple variante del Centipede (ciempiés) que vamos a desarrollar en los próximos
capítulos. No hemos cubierto todo lo necesario para un juego todavía, pero
podemos hacer un comienzo con un pequeño bucle de control que permite al jugador manipular una pequeña base de disparo por la pantalla. Te lo advierto, este programa no tiene salida al BASIC así que asegúrate de haber guardado
una copia del código fuente antes de ejecutarlo.
; Queremos una pantalla en negro. ld a,71 ; tinta blanca (7) en fondo negro (0), ; con brillo (64). ld (23693),a ; establecer nuestros colores de pantalla. xor a ; forma rápida de cargar el acumulador con cero. call 8859 ; establecer el colore del borde permanente. ; Configurar los gráficos. ld hl,blocks ; dirección de los datos para los UDG. ld (23675),hl ; apuntar los UDG hacia aquí. ; De acuerdo, vamos a empezar el juego. call 3503 ; rutina ROM - borra la pantalla, abre el canal 2. ; Inicializar coordenadas. ld hl,21+15*256 ; cargar el par hl con las coordenadas iniciales. ld (plx),hl ; fijar las coordenadas del jugador. call basexy ; establecer las posiciones x e y del jugador. call splayr ; mostrar símbolo de la base del jugador. ; Este es el bucle principal. mloop equ $ ; Borrar el jugados. call basexy ; establecer las posiciones x e y del jugador. call wspace ; mostrar un espacio sobre el jugador. ; Ahora hemos eliminado el jugador y lo movemos antes de volverlo a mostrar ; en sus nuevas coordenadas. ld bc,63486 ; fila del teclado 1-5/joystick puerto 2. in a,(c) ; ver que teclas están pulsadas. rra ; bit mas externo = tecla 1. push af ; recordar el valor. call nc,mpl ; si está está siendo pulsada, moverse a la izquierda. pop af ; restaurar el acumulador. rra ; siguiente bit (valor 2) = tecla 2. push af ; recordar el valor. call nc,mpr ; si está está siendo pulsada, moverse a la derecha. pop af ; restaurar el acumulador. rra ; siguiente bit (valor 4) = tecla 3. push af ; recordar el valor. call nc,mpd ; si está está siendo pulsada, moverse hacia abajo. pop af ; restaurar el acumulador. rra ; siguiente bit (valor 8) = tecla 4. call nc,mpu ; si está está siendo pulsada, moverse hacia arriba. ; Ahora que se ha movido podemos volver a mostrar al jugador. call basexy ; establecer las posiciones x e y del jugador. call splayr ; mostrar al jugador. halt ; retardo. ; Saltar de nuevo al principio del bucle principal. jp mloop ; Mover al jugador a la izquierda. mpl ld hl,ply ; recordar, ¡y es la coordenada horizontal! ld a,(hl) ; ¿cuál es el valor actual? and a ; ¿es cero? ret z ; sí - no podemos seguir hacia la izquierda. dec (hl) ; restar 1 a la coordenada y. ret ; Mover al jugador a la derecha. mpr ld hl,ply ; recordar, ¡y es la coordenada horizontal! ld a,(hl) ; ¿cuál es el valor actual? cp 31 ; ¿está en el borde derecho (31)? ret z ; sí - no podemos seguir hacia la derecha. inc (hl) ; sumar 1 a la coordenada y. ret ; Mover al jugador hacia arriba. mpu ld hl,plx ; recordar, ¡x es la coordenada vertical! ld a,(hl) ; ¿cuál es el valor actual? cp 4 ; ¿está en el límite superior (4)? ret z ; sí - no podemos seguir hacia arriba. dec (hl) ; restar 1 a la coordenada x. ret ; Mover al jugador hacia abajo. mpd ld hl,plx ; recordar, ¡x es la coordenada vertical! ld a,(hl) ; ¿cuál es el valor actual? cp 21 ; ¿está en el límite inferior (21)? ret z ; sí - no podemos seguir hacia abajo. inc (hl) ; sumar 1 a la coordenada x. ret ; Configurar las coordenadas X e Y de la posición de la base del jugador, ; se le llama antes de la visualización y supresión de la base. basexy ld a,22 ; código para AT. rst 16 ld a,(plx) ; coordenada vertical del jugador. rst 16 ; fijar la posición vertical del jugador. ld a,(ply) ; coordenada horizontal del jugador. rst 16 ; fijar la posición horizontal del jugador. ret ; Mostrar al jugador en la posición de impresión actual. splayr ld a,69 ; tinta cían (5) en fondo negro (0), ; brillante (64). ld (23695),a ; establecer nuestros colores temporales de pantalla. ld a,144 ; código ASCII para el UDG 'A'. rst 16 ; dibujar jugador. ret wspace ld a,71 ; tinta blanca (7) en fondo negro (0), ; brillante (64). ld (23695),a ; establecer nuestros colores temporales de pantalla. ld a,32 ; carácter de ESPACIO. rst 16 ; dibujar espacio. ret plx defb 0 ; coordenada x del jugador. ply defb 0 ; coordenada y del jugador. ; gráficos UDG. blocks defb 16,16,56,56,124,124,254,254 ; base del jugador.
Rápido, ¿no es así? De hecho, hemos reducido el ritmo con
una instrucción de interrupción, pero todavía funciona a unos veloces 50 cuadros
por segundo, lo que es probablemente un poco demasiado rápido. No te preocupes,
ya que seguiremos añadiendo más características al código que comenzarán
a reducir la velocidad. Si te sientes cómodo puedes intentar adaptar
el programa anterior para trabajar con un joystick Kempston. No es difícil, requiere simplemente cambiar el puerto 63486 por el puerto 31, y sustituir los cuatro posteriores call nc,(dirección) por call c,(dirección)
(¿recuerdas que los bits se invierten?)
Usar teclas redefinibles es un poco más complicado. Como probablemente sabes,
el teclado del Spectrum original se divide en 8 filas de 5 teclas cada uno, leyendo el puerto asociado con una determinada fila de teclas, y a continuación
probando los bits D0-D4 podemos saber si se pulsa una tecla en particular. Si vas a sustituir ld bc,31 en el fragmento de código anterior con ld bc,49150
podría acceder a saber si la fila de teclas se ha usado, aunque esto no lo hace una buena rutina de teclas redefinibles. Afortunadamente, hay otra manera
de hacer las cosas.
Podemos establecer el puerto necesario para cada fila de teclas utilizando la
fórmula del manual del Spectrum . Donde n es el número de fila 0-7, la dirección
del puerto será 254 + 256 * (255 - 2n). Hay una rutina ROM en la dirección
654 que hace un montón de trabajo duro para nosotros, devolviendo el número
de la tecla pulsada en el registro e, en el rango de 0-39. Un valor 0-7 corresponde
a la tecla más interna de cada fila (eso es B, H, Y, 6, 5, T, G y V), un valor
8-15 a la siguiente tecla a lo largo de cada fila, y así hasta el 39 para la tecla externa
en la última fila (CAPS SHIFT). El estado de la tecla de maúsculas a su vez también se devuelve en el registro d. Si no se pulsa ninguna tecla retorna en e el valor 255.
La rutina de la ROM sólo puede devolver un único número de tecla, lo que no es bueno
para la detección de más de una tecla a la vez. Para determinar si una tecla específica está siendo presionada o no en cualquier momento, es necesitamos convertir el número de nuevo en un puerto y, a continuación, leer ese
puerto y comprobar el bit individual por nosotros mismos. Hay una rutina muy
útil que utilizo para mis trabajo, y es la única rutina en mis juegos que no
he escrito yo mismo. El crédito por eso debe ir a Stephen Jones, un programador que escribió excelentes artículos para el Spectrum Club Discovery
hace muchos años. Para utilizar su rutina, cargar el acumulador con el número
de la tecla que deseas probar, llama a ktest, a continuación, comprueba la bandera
de acarreo. Si se ha establecido (1) no está siendo presionada la tecla, si no hay
acarreo (0) es que se pulsa la tecla. Si eso es demasiado confuso y parece
como una forma incorrecta de funcionar, pon una instrucción ccf justo antes
del ret.
; rutina de prueba del teclado del Sr. Jones. ktest ld c,a ; tecla a probar en c. and 7 ; mascara de bits d0-d2 para la fila. inc a ; en el rango de 1-8. ld b,a ; colocarlo en b. srl c ; dividir c por 8, srl c ; encontrar la posición dentro de la fila. srl c ld a,5 ; sólo 5 teclas por fila. sub c ; restar de la posición. ld c,a ; ponerlo en c. ld a,254 ; byte alto del puerto a leer. ktest0 rrca ; rotar en su posición. djnz ktest0 ; repetir hasta que hemos encontrado la fila correspondiente. in a,(254) ; leer el puerto (a=alto, 254=bajo). ktest1 rra ; rotar el bit fuera del resultado. dec c ; bucle del contador. jp nz,ktest1 ; repetir hasta que el bit se posicione por acarreo. ret
No hay comentarios:
Publicar un comentario