Recargas de combustible – parte 2

Seguimos con la segunda parte de las “recargas de combustible”. En la primer parte vimos como implementar un generador de números aleatorios, algo que es trivial en cualquier lenguaje de programación, pero en ensamblador es un pequeño dolor de cabeza.
En esta segunda parte vamos a ver el código para que cada cierto intervalo de tiempo caiga un tanque de combustible desde el cielo, que puede ser tomado por cualquiera de los jugadores o destruido a tiros, para que no la agarre nadie.

Vamos a hacer, como en anteriores ocasiones, una máquina de estados, la cual tendrá 5 ‘status’ (status0 – status4). Esta máquina utilizará las siguientes variables/posiciones de memoria (que definiremos convenientemente en vars.asm):

; direcciones de fuel, sprite 4
sprcolorfuel = $d02b
sprpointfuel = $07fc
sprxfuel = $d008          
spryfuel = $d009
FUEL      = $10       ; 16, constante sprite 4 (00010000) 
cantFUEL  = 10        ; cantidad de combustible de cada fuel 
statusFuel = $c0a0
fuelCounter = $c0a1

;255 mas grande el valor, mas tiempo tarda en aparecer
delayEntreFuels = 32    

Para que haya mas claridad en la explicación primero voy a hacer un “resumen” de la maquina de estados, y luego voy a desglosar cada parte.

@status0
    decrementa el contador 'fuelCounter', cuando llega a 0 pasa a status1

@status1
    inicializa el 'fuel' (obtiene posicion x aleatoria, posiciona en y, etc

@status2
    caida libre hasta que llega al piso

@status3
    espera en el piso, hasta que alguien lo agarre o lo destruyan

@status4
    apaga el sprite, reinicia contadores, cambia a status0

Status 0

Aqui solamente esperamos un cierto tiempo, para ello decrementamos un contador ‘fuelCounter‘, con la particularidad que lo haremos cada 64 frames (para ello usaremos un ‘generador de ticks’ que previamente usamos en otra parte). Cuando llegue a 0 pasamos a status1

@status0                
          ; si statusFuel == 0 sigue, si no pasa al siguiente
          ldx            statusFuel
          cpx            #0        
          bne            @status1  

          ; Si hay un tick64 entonces decremento en 1 el fuelCounter
          lda            tick64 
          cmp            #0        
          bne            @decrementFuelCounter
          jmp            @exitFuel 
          
@decrementFuelCounter          
          dec            fuelCounter
          ; Si el fuelCounter llego a 0 cambio a status1
          beq            @setStatus1
          jmp            @exitFuel 
          
@setStatus1
          ldx            #1        
          stx            statusFuel

Status 1

Cuando el fuelCounter llega  a 0 cambiamos de status,  e inicializamos el tanque de combustible. Para ello  activo el sprite 4, pongo en 0 la posición y, borro el bit8 (ya que puede haber quedado seteado de un tanque anterior), obtengo un numero aleatorio y le sumo 48, para centrarlo un poco. Esto es porque nuestro generador tira números de 0 a 255, pero nuestra pantalla tiene 320 px de ancho.
Para no complicar demasiado todo, y considerando que de 0 a 24  y de 320 en adelante el tanque no se mostraría en pantalla, decidí simplemente restringirlo a un área centrada de la pantalla, de  255 pixels, que abarca la posición entre 48 y 303.
Luego que inicializé todo paso a status2.

@status1                
          ldx            statusFuel
          cpx            #1        
          bne            @status2  

          ; Pongo posicion y en 0
          ldy            #0        
          sty            spryfuel  

          ; activo sprite 4
          lda            spractive 
          ora            #FUEL
          sta            spractive 
          
          ; posicion x fuel (random entre 48 y 303)
          ; borro el bit8 del sprxfuel (por si quedo seteado anteriormente)
          lda            sprxBit8   
          and            #%11101111
          sta            sprxBit8  
          
          ; obtengo un numero del generador random
          jsr            randomGenerator
          clc
          lda            random    

          ; lo centro un poco
          adc            #48       
          sta            sprxfuel  
          ; si tengo carry, entonces voy a tener que setear Bit8
          bcs            @setBit8  
          
          jmp            @setStatus2  
@setBit8                           
          lda            sprxBit8  
          ora            #%00010000
          sta            sprxBit8  

@setStatus2
          ; finalmente paso a status2
          ldx            #2
          stx            statusFuel

Status 2

Aqui comienza la caída libre, incrementando la posición y del sprite cada cuatro frames (usamos el mismo tick4 que previamente definimos para la gravedad de los jetpacks). Una vez que llega al piso pasamos a status3

@status2                
          ldx            statusFuel
          cpx            #2
          bne            @status3  

          ; incremento cada 4 frames
          lda            tick4
          cmp            #0        
          bne            @incrementFuel_y
          jmp            @exitFuel 

@incrementFuel_y
          inc            spryfuel  
          ldy            spryfuel  

          ; si llego al piso (un poquito mas, ya que es mas corto el sprite)
          cpy            #floorPosition + 3
          beq            @setStatus3
          jmp            @exitFuel 
          
@setStatus3          
          ldx            #3
          stx            statusFuel

Status 3

Este status no hace nada, ni me molesto en poner codigo, el sprite se queda en el piso hasta que, por detección de colisiones, lo agarre alguien o lo destruyan de un tiro.

Status 4

A este status se llega desde la detección de colisiones (que lo explicaré en la próxima entrega). Básicamente reinicializa el contador fuelCounter, apaga el sprite y pone el status en 0, para que comience nuevamente todo.

@status4                
          ;reinicializa statusFuel a 0
          ldx            #0        
          stx            statusFuel

          ; inicializa fuelCounter con el valor de delayEntreFuels
          ldx            #delayEntreFuels 
          stx            fuelCounter

          ; apago sprite 4
          lda            spractive 
          and            #255 - FUEL
          sta            spractive 

Y no me tengo que olvidar de inicializar algunas de las variables previamente definidas, en initVars.asm

          ldx            #0        
          stx            statusFuel
          
          ldx            #delayEntreFuels
          stx            fuelCounter

Y con esto ya estamos…
En la próxima entrega vamos a finalizar esta parte de las recargas, vamos a editar el detectCollision.asm para que detecte cuando un jugador agarra el combustible, o cuando lo destruyen.

Como siempre, el codigo completo lo pueden bajar desde el repositorio:
https://github.com/moonorongo/jp_wars.git
Hasta la próxima!

Recargas de combustible – parte 1

Vamos a incluir los tanques de combustible que nos permitirán recargar los jetpacks. Como todo esto tiene una complejidad extra voy a dividir la entrada en varias partes. La idea es que cada cierto tiempo caigan desde el cielo tanques de combustible (bien espaciados, cosa que sean escasos, y obligue a los jugadores a luchar por ellos).
Ademas, dichos tanques pueden ser destruidos con los disparos… esto tiene un doble filo, ya que permite destruir un tanque que el otro jugador esté a punto de tomar, o también puede ocurrir que lo destruyamos por disparar a lo loco.

Pero antes… un video de lo que quedó:

Los tanques caerán aleatoriamente desde diferentes posiciones X de la pantalla. Para esto necesitamos un generador de números aleatorios, en nuestro caso algo muy sencillo, que tire números de un byte de longitud, es decir, entre 0 y 255 valores diferentes. Si bien la pantalla de la C64 tiene 320 pixels, para simplificar un poco la cuestión vamos a hacer que los tanques caigan entre la posición 48 y la posición 303.
El código lo tome de codebase64.org, concretamente aquí. Es una pequeña subrutina, que llamamos en cada loop, y lo que hace es guardar un numero aleatorio de 1 byte de longitud en la posición de memoria random.

randomGenerator
          lda            random      ; cargo en A el contenido de random
          beq            @doEor      ; si es 0 hace un EOR con $1d, y lo guarda en random nuevamente
          asl                        ; si no, hago un shift left 
          beq            @noEor      ; si la op es 0, guarda en random
          bcc            @noEor      ; si hay carry, guarda en random
@doEor    eor            #$1d        ; y si llego aca, hace un eor 00011101 (para 'randomizarlo' un poco)
@noEor    sta            random      ; finalmente guarda el valor generado
          rts

El código es muy básico, y no demasiado aleatorio… genera los números aleatorios multiplicando por 2 (asl) y/o aplicando un eor con un numero (en este caso $1d). Viendo la rutina en acción veo que hay una tendencia a que genere números altos, los tanques tienden a caer del lado derecho de la pantalla, pero a los efectos que necesitamos en el juego esta… mas que bien.

Para que la rutina funcione correctamente es necesario inicializar random. Para ello vamos a utilizar una dirección de memoria de la C64, concretamente la posición de memoria $a2, que contiene el byte menos significativo de la variable TI del sistema, concretamente es un timer interno de la computadora, lo que nos dará una inicialización mas o menos aleatoria. El código de inicialización lo ponemos en initVars.asm:

          ldx            $a2         ; inicializamos el generador de numeros aleatorios
          stx            random      ; con un valor de la variable TI (que esta en $a2)

Como siempre, el codigo completo lo pueden bajar desde el repositorio:
https://github.com/moonorongo/jp_wars.git
En la próxima entrega vamos a implementar la maquina de estados para que caiga un tanque de combustible cada cierto intervalo de tiempo.
Hasta la proxima!