Crear videojuegos NPC utilizando un contrato con estado y transferencias atómicas en Algorand

Utilizando un contrato con estado y transferencias atómicas, realizaremos un seguimiento de una variable de salud y dejaremos que los usuarios decidan si ayudan o engañan al personaje NPC por una pequeña cantidad de ALGO’s. Una vez que la salud haya alcanzado el máximo o se ha reducido a nada, el juego termina.

Desde una perspectiva de DevOps, mostraré cómo realizar un juego que puede ponerse en marcha en cuestión de segundos.

Requisitos

  1. La abstracción python3 de TEAL llamada pyteal.
  1. Herramienta de línea de comandos de meta.

Fondo

Este tutorial sólo leerá/escribirá, desde/hacia el estado global. Sin embargo, los contratos inteligentes de Algorand son poderosos porque también puede almacenar el estado localmente en la cuenta del usuario (por ejemplo, para verificar si votaron o no).

Para almacenar algunos datos en la cuenta local de un usuario, tiene que utilizar la aplicación y al salir del contrato invocando un procedimiento “CloseOut”, de donde el “Stateful Contract” (estado del contrato), puede modificar sus datos de participación (o no).

No abordaremos el estado local, ya que no es necesario para que el juego sea funcional. Si también está interesado en el almacenamiento local, eche un vistazo a este documento.

Pasos

  1. Anatomía del programa de contratos
  1. Implementación de la secuencia de comandos
  1. Pruebas de la aplicación
  1. Seguir desarrollando

1. Anatomía del programa de contratos

Usaremos la siguiente plantilla de contrato con estado. Contiene una función approval que devolverá nuestra parte de aprobación del contrato con estado. También contiene una función clear_state la cual devolverá la parte CloseOut . Aquí puede eliminar los datos de participación del usuario o realizar alguna otra lógica de «exclusión voluntaria». Finalmente, la función principal compila ambos programas para producir los archivos fuente. 

# contract.py
from pyteal import *

def approval():
    # create program here
    return program

def clear_state():
    program = Seq([Return(Int(1))])
    return program

if __name__ == "__main__":
    with open('approval.teal', 'w') as f:
        compiled = compileTeal(approval(), mode=Mode.Application, version=2)
        f.write(compiled)

    with open('clear_state.teal', 'w') as f:
        compiled = compileTeal(clear_state(), mode=Mode.Application, version=2)
        f.write(compiled)

Para el constructor, usaremos la dirección del creador del contrato como la dirección de nuestro NPC, George. También estableceremos la variable de salud de nuestro NPC en 5. Para almacenar y leer una variable desde/hacia el estado que usamos, utilizaremos las funciones:  App.globalPut(name, value) App.globalGet(name), respectivamente.

on_init = Seq([
    App.globalPut(Bytes("george"), Txn.sender()),   # save the creator's address
    App.globalPut(Bytes("health"), Int(5)),         # set an integer to 5
    Return(Int(1)),
])

A continuación, hay dos condiciones que deben reutilizarse. Creamos dos valores booleanos que almacenarán estos valores. La primera condición verifica si la transacción atómica eventualmente enviará la cantidad requerida a la billetera de George (Creador del contrato). El segundo comprobará que la variable de salud aún se encuentra dentro de nuestro umbral deseado.

Usamos una variable incorporada llamada Gtxn, que almacena transacciones agrupadas en una lista. La segunda transacción debería estar en índice 1. Solicitaremos al usuario que envíe al menos 100 micro ALGO’s, para aliviar la congestión al trabajar en redes públicas. Desde la perspectiva de los juegos, es mucho más eficiente minimizar la cantidad de transacciones que utiliza su aplicación.

# check if 2nd tx amount is correct and that george receives the payment
correct_amt_for_george = If(Or(Gtxn[1].amount() < Int(100), # Check for at least 100 microalgos
                               Gtxn[1].receiver() != App.globalGet(Bytes("george"))),
                               Return(Int(0)))

# check if npc is dead or fully replenished
alive_or_dead = If(Or(App.globalGet(Bytes("health")) >= Int(10), 
        App.globalGet(Bytes("health")) <= Int(0)), Return(Int(0)))

Las funciones injure y heal comprueban si George ha trascendido (alcanzado la salud completa) o ha muerto antes de actualizar la variable de salud (ya sea sumando o restando 1). El juego termina cuando la salud del NPC se vuelve 0 o máxima (10), ya que todas las transacciones posteriores se revertirán.

on_injure = Seq([
    alive_or_dead,
    correct_amt_for_george,
    App.globalPut(Bytes("health"), App.globalGet(Bytes("health")) - Int(1)),
    Return(Int(1)),
])

on_heal = Seq([
    alive_or_dead,
    correct_amt_for_george,
    App.globalPut(Bytes("health"), App.globalGet(Bytes("health")) + Int(1)),
    Return(Int(1)),
])

La lógica de actualización y eliminación comprobará si el usuario es propietario del contrato antes de ejecutarlo. Finalmente, combinamos todos estos métodos en un solo programa.

program = Cond(
    [Txn.application_id() == Int(0), on_init],                                  # init
    [Txn.on_completion() == OnComplete.DeleteApplication, Return(is_creator)],  # delete
    [Txn.on_completion() == OnComplete.UpdateApplication, Return(is_creator)],  # update
    [Txn.application_args[0] == Bytes("heal"), on_heal],                        # heal
    [Txn.application_args[0] == Bytes("damage"), on_injure],                    # injure
)

La ejecución del script producirá los archivos necesarios durante la implementación. Asegúrese de estar ejecutando Python versión 3 o superior.

python contract.py

Si todavía tiene python2 en su sistema, considere usar el comando para especificar python3.

python3 contract.py

2. Implementación de la secuencia de comandos

Esta sección requiere que tenga una red privada en ejecución y que tenga el comando goal instalado en su sistema. Si prefiere usar una red diferente, como TestNet, puede actualizar los comandos para usar el directorio de datos de esa red en su lugar.

De lo contrario, para ejecutar un nodo privado, Scale-It tiene un inicio rápido de nodo privado que puede instalar con los siguientes comandos:

git clone https://github.com/scale-it/algo-builder
cd algo-builder/infrastructure
make create-private-net
export ALGO_PVTNET_DATA=/path/to/algo-builder/infrastruture/node_data/PrimaryNode

Para ver información sobre las cuentas y carteras disponibles, puede utilizar los siguientes comandos:

goal wallet list -d $ALGO_PVTNET_DATA
goal account list -d $ALGO_PVTNET_DATA

Usemos un “Makefile” para facilitarnos la interacción con George a través de comandos como make heal. Comenzamos estableciendo las variables que probablemente reutilizaremos en la parte superior del archivo.

GEORGE_ACCOUNT      := creatorAddress
ALGO_PVTNET_DATA    := /path/to/algo-builder/infrastructure/node_data/PrimaryNode
ALGO_TESTNET_DATA   := ~/.testnet-algod
ALGO_MAINNET_DATA   := ~/.mainnet-algod

# pick a net
ALGO_DATA           := $(ALGO_PVTNET_DATA)

# during test we'll use a different account to interact with George
FROM_ACCOUNT        := differentAddress
TO_ACCOUNT          := $(GEORGE_ACCOUNT)

Para implementar la aplicación (o darle vida a George), usamos el siguiente comando CLI. Para nuestro estado global de contrato usamos una cadena (dirección del propietario) y un entero (variable de salud). No almacenamos nada en la cuenta del usuario, por lo que el espacio de estado local tendrá 0 enteros y cadenas.

deploy:
    goal app create --creator $(GEORGE_ACCOUNT) \
        --approval-prog ./approval.teal \
        --clear-prog ./clear_state.teal \
        --local-byteslices 0 \
        --local-ints 0 \
        --global-byteslices 1 \
        --global-ints 1 \
        -d $(ALGO_(DATA)

Ahora es tan simple como correr “make deploy”. Una vez implementado con éxito, puede guardarlo como App ID en el entorno, para facilitar su uso dentro de los próximos comandos shell. Esto también facilita el cambio de la aplicación activa, sin editar el propio “Makefile”.

export APP_ID=42

3. Probar la aplicación

Ahora podemos usar la aplicación y consultar su estado. Por ejemplo, si desea leer los valores del estado global, use el siguiente comando:

read:
    goal app read --app-id $(APP_ID) \
        --global -d $(ALGO_DATA) \
        --guess-format

Debería ver la dirección de George y el valor de su salud en formato json. por ej: make read

{
  "george": {
    "tb": "6HZOQLMJCMKNVNMEJVTI3BD3FZIO2EJGTINJKT4NOYMR577YI4VZHP3NHM",
    "tt": 1
  },
  "health": {
    "tt": 2,
    "ui": 5
  }
}  

Actualmente está establecido en 5, ya que ese es nuestro valor inicial durante la creación del contrato. Interactuemos con George para cambiarlo. Necesitaremos crear una transacción atómica que:

  1. Realice una llamada a la aplicación. (Para ello, invocamos la lógica de recuperación del contrato).
  1. Envíe la cantidad mínima requerida a “George’s Wallet”.

El siguiente fragmento muestra cómo se verá el comando en el “Makefile”. Primero hacemos una llamada a la aplicación, luego enviamos al menos 100 micro ALGO’s a George. El resultado de esto son dos archivos de transacciones tx1y tx2.

heal-cmd:
    goal app call --app-id $(APP_ID) \
        --app-arg 'str:heal'  \
        -f $(FROM_ACCOUNT) \
        -d $(ALGO_DATA) \
        -o tx1

amount:
    goal clerk send -a 100 \
        -t $(TO_ACCOUNT) \
        -f $(FROM_ACCOUNT) \
        -d $(ALGO_DATA) \
        -o tx2

Ahora necesitamos crear y firmar una transacción grupal para que el contrato pueda interrogarla. Y luego, finalmente, enviamos la transacción atómica a la red.

group-send:
    cat tx1 tx2 > ctx
    goal clerk group -i ctx -o gtx -d $(ALGO_DATA)
    goal clerk sign -i gtx -o stx -d $(ALGO__DATA)
    goal clerk rawsend -f stx -d $(ALGO_DATA)

El comando completo para enviar la transferencia atómica debe ahora ser simplemente: heal-cmd amount group-send.

heal: heal-cmd amount group-send

Si todo se hizo correctamente, al volver a leer el estado, la variable de salud debería haberse actualizado. George también debería tener una pequeña propina.

$ make heal
$ make read
{
  "george": {
    "tb": "6HZOQLMJCMKNVNMEJVTI3BD3FZIO2EJGTINJKT4NOYMR577YI4VZHP3NHM",
    "tt": 1
  },
  "health": {
    "tt": 2,
    "ui": 6
  }
}

Ahora vamos a herirlo dos veces. Su variable de salud debería haber disminuido a 4.

$ make injure
$ make injure
$ make read
{
  "george": {
    "tb": "6HZOQLMJCMKNVNMEJVTI3BD3FZIO2EJGTINJKT4NOYMR577YI4VZHP3NHM",
    "tt": 1
  },
  "health": {
    "tt": 2,
    "ui": 4
  }
}

El envío a la dirección incorrecta en la segunda transacción del grupo hará que el programa de aprobación devuelva un error, lo que provocará que falle toda la transferencia atómica. Para que una transferencia atómica sea exitosa, todas las transacciones adjuntas deben ser exitosas.

Para retirar la aplicación, llamamos al método app delete desde CLI:

delete:
  goal app delete --app-id $(APP_ID) -f $(GEORGE_ACCOUNT) -d $(ALGO_DATA)

Y luego puedes llamar a make delete desde la línea de comando.

4. Mayor desarrollo

Ahora puede construir una interfaz que, por ejemplo, devolverá un mensaje de bienestar durante el estado heal o un comentario desagradable mientras está en mal estado con injure.

El proyecto completo se puede encontrar en github.


Este artículo ha sido escrito originalmente por Dooke en «Tutoriales» del portal para desarrolladores de Algorand  y traducido por AlgoLatam.

Original Article: https://developer.algorand.org/tutorials/simple-npc-game-interactions-using-a-stateful-contract-and-atomic-transfers/

Deja una respuesta

Tu dirección de correo electrónico no será publicada.