Tutorial de ZX Basic + Fourspriter #21: Estupidez Artificial

Vamos a implementar ahora las tres rutinas de IA diferentes. Como hemos dicho, enType, que valdrá 0, 1 o 2 para cada enemigo, decidirá que IA aplica. Cada IA la encapsularemos en una subrutina que modifique el bicharraco “i”. Por lo pronto, ya podemos empezar a “poblar” la rama del IF de la máquina de estados correspondiente al estado activo. Básicamente, miramos qué vale enType, y llamamos a una rutina u otra:

        ElseIf enState (i) = BICHESTACTIVO Then
            ' Llamamos a una subrutina que contenga la IA
            ' Según el valor de enType (i), y le pasamos
            ' "i", el número de enemigo que estamos procesando.
            If enType (i) = BICHTIPOREBOTANTE Then
                iaRebotante (i)
            ElseIf enType (i) = BICHTIPOACOSADOR Then
                iaAcosador (i)
            Else
                iaPasoDeTuCulo (i)
            End If

Bien, ahora toca el turno de implementar estas tres subrutinas: iaRebotante, iaAcosador y iaPasoDeTuCulo. Vamos a ello. Empecemos por el principio…

Bicharracos rebotantes

Esta IA es muy sencilla. Se basa en mover al bicharraco sumando a sus coordenadas un 1 o un -1 en cada frame, con lo que se moverá en diagonal. Si llegamos al borde de la pantalla o nos chocamos con un obstáculo, cambiaremos el sentido del movimiento en el eje que haya producido el choque. Esto se ve más fácilmente en código que explicándolo de palabra, creo. Como tenemos que detectar colisiones con el escenario, usaremos nuestra función getCharBehaviourAt.

Como en muchas otras ocasiones, el tratamiento del eje X y del eje Y se hacen por separado. Primero, el eje X:

Sub iaRebotante (i as uByte)
    If enMx (i) = 1 Then
        If enX (i) = MAPSCREENWIDTH + MAPSCREENWIDTH - 2 _
            Or getCharBehaviourAt (enX (i) + 2, enY (i), nPant) >= 4 _
            Or getCharBehaviourAt (enX (i) + 2, enY (i) + 1, nPant) >= 4 Then
            enMx (i) = -1
        End If
    ElseIf enMx (i) = -1 Then
        If enX (i) = 0 _
            Or getCharBehaviourAt (enX (i) - 1, enY (i), nPant) >= 4 _
            Or getCharBehaviourAt (enX (i) - 1, enY (i) + 1, nPant) >= 4 Then
            enMx (i) = 1
        End If
    End If
    
    enX (i) = enX (i) + enMx (i)

Veamos qué estamos haciendo aquí: en primer lugar comprobamos si nos estamos moviendo a la derecha (enMx = 1) o a la izquierda (enMx = -1), principalmente para saber qué comprobaciones tenemos que hacer. Recordemos el diagramilla de las colisiones para guiarnos:

Si nos movemos a la derecha (enMx = 1), tenemos que comprobar una colisión a la derecha del muñeco. Tendremos, pues, que comprobar las casillas en amarillo del diagrama. Además, comprobaremos que enX no se sale de la pantalla. Si ocurre cualquiera de estas dos cosas (que haya un obstáculo en cualquiera de las casillas amarillas o que estemos en el borde de la pantalla) lo que hacemos es cambiar el sentido: enMx = -1. Hacemos lo mismo al movernos a la izquierda (enMx = -1), pero comprobando esta vez, como es lógico, que enX esté en el borde izquierdo o que alguna las casillas rojas sea un obstáculo. Si algo de eso se cumple, cambiamos el sentido: enMx = 1.

Después de las comprobaciones, simplemente hacemos avanzar al bicharraco en el sentido correcto (con enX (i) = enX (i) + enMx (i)).

Luego se hace exactamente lo mismo para el eje vertical. En este caso entran en juego los bordes superior e inferior de la pantalla y las casillas verdes y azules del diagramilla:

    If enMy (i) = 1 Then
        If enY (i) = MAPSCREENHEIGHT + MAPSCREENHEIGHT - 2 _
            Or getCharBehaviourAt (enX (i), enY (i) + 2, nPant) >= 4 _
            Or getCharBehaviourAt (enX (i) + 1, enY (i) + 2, nPant) >= 4 Then
            enMy (i) = -1
        End If
    ElseIf enMy (i) = -1 Then
        If enY (i) = 0 _
            Or getCharBehaviourAt (enX (i), enY (i) - 1, nPant) >= 4 _
            Or getCharBehaviourAt (enX (i) + 1, enY (i) - 1, nPant) >= 4 Then
            enMy (i) = 1
        End If
    End If
    
    enY (i) = enY (i) + enMy (i)

Por último, y para terminar, animamos el sprite cambiando de un frame a otro. enFrame debería ir cambiando de 0 a 4 para ir seleccionando los bloques correctos en cada frame. Como enFrame inicialmente valdrá 0, es muy sencillo conseguirlo con un “one-liner“:

    enFrame (i) = 4 - enFrame (i)
End Sub

Bicharracos acosadores

Los bicharracos acosadores son también muy sencillos: lo que hay que hacer es que enX y enY se “acerquen” a pX y pY en cada frame… siempre que no haya un obstáculo de por medio. Una vez más, separamos los ejes horizontal y vertical. Además, sólo nos moveremos si enHl = 0. Si recordáis, enHl va cambiando de 0 a 1 continuamente en cada frame. Así, por tanto, conseguiremos que el enemigo se mueva a la mitad de la velocidad que nuestro protagonista.

Otra cosa que queremos conseguir es que el sprite sólo se anime (cambie de frame) si nos hemos movido. Para ello vamos a recordar los valores iniciales de enX y enY para que al final, si ha cambiado cualquiera de ellos, cambiemos de frame. Hagamos esto lo primero:

Sub iaAcosador (i as uByte)
    Dim enCx, enCy as uByte
    
    enCx = enX (i): enCy = enY (i)

Empecemos. Vamos a tratar primero el eje horizontal. Dependiendo de la relación entre pX y enX intentaremos movernos a la derecha (si pX > enX) o a la izquierda (si pX < enX). Para ello (tirando de diagramilla) habrá que comprobar que las casillas correspondientes sean traspasables:

    If enHl (i) = 1 Then
        If pX > enX (i) _
            And getCharBehaviourAt (enX (i) + 2, enY (i), nPant) < 4 _
            And getCharBehaviourAt (enX (i) + 2, enY (i) + 1, nPant) < 4 Then
            enX (i) = enX (i) + 1
        ElseIf pX < enX (i) _
            And getCharBehaviourAt (enX (i) - 1, enY (i), nPant) < 4 _
            And getCharBehaviourAt (enX (i) - 1, enY (i) + 1, nPant) < 4 Then
            enX (i) = enX (i) - 1
        End If

Seguidamente, hacemos lo mismo con el eje vertical:

        If pY > enY (i) _
            And getCharBehaviourAt (enX (i), enY (i) + 2, nPant) < 4 _ 
            And getCharBehaviourAt (enX (i) + 1, enY (i) + 2, nPant) < 4 Then
            enY (i) = enY (i) + 1
        ElseIf pY < enY (i) _
            And getCharBehaviourAt (enX (i), enY (i) - 1, nPant) < 4 _ 
            And getCharBehaviourAt (enX (i) + 1, enY (i) - 1, nPant) < 4 Then
            enY (i) = enY (i) - 1
        End If
    End If

Finalmente, comprobamos si enX o enY han cambiado para animar el sprite (cambiar de frame):

    If enCx <> enX (i) Or enCy <> enY (i) Then
        enFrame (i) = 4 - enFrame (i)
    End If
End Sub

Bicharracos que pasan de tu culo

Estos bicharracos se moverán enCt pasos en la dirección que indique enDir. Hemos decidido que enDir = 0 significa que no se moverá, enDir = 1 será hacia arriba, 2 hacia abajo, 3 hacia la izquierda, y 4 hacia la derecha. enCt se decrementará en cada frame del juego y cuando llegue a 0 se volverá a elegir una dirección en enDir y una nueva cuenta en enCt. Como enCt comienza valiendo 0, esto será precisamente lo primero que se haga. El nuevo valor de enCt será un número aleatorio entre BICHMINPASOS y BICHMAXPASOS, como dijimos anteriormente.

Lo que hay que hacer, por tanto, es ver si enCt vale 0, en cuyo caso habrá que elegir nueva dirección en enDir y una nueva cuenta en enCt, o vale distinto de 0, en cuyo caso habrá que avanzar en la dirección de enDir y decrementar enCt:

Sub iaPasoDeTuCulo (i as uByte)
    If enCt (i) = 0 Then
        enCt (i) = Int (Rnd * (BICHMAXPASOS - BICHMINPASOS)) + BICHMINPASOS
        enDir (i) = Int (Rnd * 5)

La expresión en enCt sirve para calcular un número al azar entre BICHMINPASOS y BICHMAXPASOS. Apuntároslo: es un truco BASIC muy util.

En la otra rama del IF, simplemente decrementaremos enCt y posteriormente, según el valor de enDir, intentaremos avanzar en la dirección correspondiente siempre que sea posible (de nuevo: comprobando las colisiones según el diagramilla):

    Else 
        enCt (i) = enCt (i) - 1
        If enDir (i) = 1 Then
            ' Arriba
            If enY (i) > 0 _
                And getCharBehaviourAt (enX (i), enY (i) - 1, nPant) < 4 _
                And getCharBehaviourAt (enX (i) + 1, enY (i) - 1, nPant) < 4 Then
                enY (i) = enY (i) - 1
            Else
                enCt (i) = 0
            End If
        ElseIf enDir (i) = 2 Then
            ' Abajo
            If enY (i) < MAPSCREENHEIGHT + MAPSCREENHEIGHT - 2 _
                And getCharBehaviourAt (enX (i), enY (i) + 2, nPant) < 4 _
                And getCharBehaviourAt (enX (i) + 1, enY (i) + 2, nPant) < 4 Then
                enY (i) = enY (i) + 1
            Else
                enCt (i) = 0
            End If
        ElseIf enDir (i) = 3 Then
            ' Izquierda
            If enX (i) > 0 _
                And getCharBehaviourAt (enX (i) - 1, enY (i), nPant) < 4 _
                And getCharBehaviourAt (enX (i) - 1, enY (i) + 1, nPant) < 4 Then
                enX (i) = enX (i) - 1
            Else
                enCt (i) = 0
            End If
        ElseIf enDir (i) = 4 Then
            ' Derecha
            If enX (i) > 0 _
                And getCharBehaviourAt (enX (i) + 2, enY (i), nPant) < 4 _
                And getCharBehaviourAt (enX (i) + 2, enY (i) + 1, nPant) < 4 Then
                enX (i) = enX (i) + 1
            Else
                enCt (i) = 0
            End If
        End If

Como vemos, las comprobaciones de colisiones/borde de la pantalla avanzan al muñeco en la dirección correcta si es posible y, en caso contrario, ponen enCt a 0 para volver a elegir una dirección.

Finalmente tendremos que animar al sprite (cambiar de frame). Esto lo haremos solo si la dirección en enDir es diferente de 0, ya que si es 0 no nos estaremos moviendo:

        If enDir (i) <> 0 Then
            enFrame (i) = 4 - enFrame (i)
        End If
    End If
End Sub

¡Y listo! Ya tenemos la inteligencia artificial lista y funcionando. Cuando lo probéis, os daréis cuenta de que existe cierto parpadeo de los sprites si hay al menos tres y si están en la parte superior de la pantalla. Eso queda feo. Pero no os preocupéis: en el siguiente capítulo explicaremos por qué ocurre esto y modificaremos la biblioteca fourspriter para solucionarlo.

Pulsa aquí para descargar el paquetito de este capítulo.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: