Guía para el desarrollo de juegos blockchain

Escrito por Ikeh Akinyemi ✏️

El desarrollo de blockchain ha crecido y evolucionado rápidamente en los últimos años y ahora se está adoptando en varias esferas del desarrollo de software. Desde aplicaciones descentralizadas (DApps), hasta software financiero descentralizado (DeFi), pasando por NFTs y DAOs, la tecnología blockchain se ha infiltrado en una amplia gama de industrias y sirve para muchos casos de uso.

En este tutorial, exploraremos la tendencia emergente del desarrollo de juegos blockchain. Los juegos basados en blockchain también se conocen como juegos en cadena. Una vez que entiendas la estructura básica involucrada en la escritura de un contrato inteligente y su despliegue en una blockchain, puedes utilizar las herramientas disponibles dentro del espacio criptográfico para construir juegos.

Construiremos un juego de lotería para demostrar cómo funciona el desarrollo de juegos en blockchain. También revisaremos la estructura básica para implementar transacciones dentro de un juego en blockchain. Luego, lo desplegaremos en una red de testnet.



¿Qué es blockchain?

La estructura de datos subyacente de una blockchain es una cadena de listas enlazadas, o “bloques” únicos. Cada bloque que se añade a la cadena está automáticamente vinculado al bloque anterior añadido, y el bloque anterior también apunta a su predecesor.

Esta cadena de listas enlazadas es a su vez una lista de transacciones. El proceso a través del cual se acuerdan estos bloques antes de añadirlos a la estructura de datos de la lista enlazada constituye la innovación clave que nos han proporcionado las cadenas de bloques: un protocolo. Este protocolo ayuda a la red a decidir cómo se añaden los bloques a la cadena.

Este proceso de decisión dio lugar a la naturaleza descentralizada de blockchain. La prueba de trabajo (PoW), la prueba de toma (PoS) y la prueba de autoridad (PoA) son mecanismos descentralizados a través de los cuales se toman estas decisiones y se acuerdan antes de añadir un bloque a la cadena.

Las criptomonedas que han surgido a través de estas cadenas de bloques son un medio para incentivar a las personas a ejecutar el software que asegura las redes alrededor de estas cadenas de bloques.

Las plataformas de blockchain como NEAR proporcionan una plataforma criptográficamente segura para almacenar, actualizar y eliminar datos de una blockchain utilizando contratos inteligentes.



Desarrollo de juegos web3

Web3, en el contexto de las cadenas de bloques, se refiere a las aplicaciones descentralizadas que se ejecutan en la cadena de bloques. Son apps que permiten a cualquier persona participar sin monetizar sus datos personales. Con un buen conocimiento de un lenguaje de programación que sea soportado por cualquiera de estas blockchains, podemos empezar a escribir contratos inteligentes para construir aplicaciones de juegos como DApps en la blockchain.

A medida que el ecosistema blockchain evoluciona, surgen nuevos paradigmas. Inspirándose en el ecosistema De-Fi, el ecosistema de juegos blockchain también ha evolucionado hacia algo conocido como GameFi. GameFi, también conocido como play to earn, introduce una nueva forma de jugar convirtiendo a sus usuarios habituales en una fuerza que gobierna las principales decisiones dentro de la industria del juego.

GameFi facilita una economía propia de los jugadores a la hora de comerciar con objetos de valor, así como de generar ingresos adicionales con tokens y tokens no fungibles. Esto supone la construcción de comunidades en torno a un juego concreto, y los usuarios de estos juegos pueden ganar criptodivisas o activos valiosos dentro del metaverso del juego (y también fuera de él).



Escribiendo contratos inteligentes en la blockchain NEAR

Para este tutorial, demostraremos cómo construir juegos en la blockchain NEAR mediante la construcción de un proyecto de juego de ejemplo.

git clone https://github.com/IkehAkinyemi/lottery-smart-contract.git

Una vez ejecutado con éxito el comando anterior, cambia el directorio a la carpeta lottery-smart-contract. Puedes abrirla en cualquier editor de texto; para este tutorial, usaremos Visual Studio Code.

Desde el terminal, ejecuta el comando código . dentro del directorio de la carpeta.

La imagen anterior muestra la estructura básica de carpetas para un proyecto NEAR que utiliza AssemblyScript para su contrato inteligente.

La carpeta script contiene el archivo fuente del shell para compilar y desplegar el contrato inteligente en la blockchain. La carpeta src contiene la carpeta lottery, dentro de la cual escribiremos el código necesario para nuestro contrato inteligente.

El resto de archivos son de configuración que AssemblyScript necesita para entender algunos de los tipos definidos en Near. La librería near-sdk-as es una colección de paquetes utilizados para desarrollar contratos inteligentes NEAR en AssemblyScript.



Cómo construir un juego de lotería en el blockchain de NEAR

.Con este juego, exploraremos algunos de los conceptos básicos de la escritura de contratos inteligentes en la blockchain de Near utilizando AssemblyScript.

yarn install o npm install para instalar la biblioteca near-sdk-as y cualquier dependencia necesaria.

A continuación, cree una carpeta llamada assembly. Dentro de esta carpeta, crea dos archivos: index.ts y model.ts. El archivo model.ts contiene los diferentes tipos de objetos que utilizaremos a lo largo de nuestro código en el archivo index.ts. El archivo model.ts contiene lo siguiente:

import { RNG } from "near-sdk-as";

@nearBindgen
export class Lottery {
  private luckyNum: u32 = 3;

  constructor() {
    const randGen = new RNG<u32>(1, u32.MAX_VALUE);
    this.id = "LO-" + randGen.next().toString();
  }
}
 

En el código anterior, definimos un tipo Lotería. Este representa la estructura del tipo de juego de lotería. Dentro de él definiremos las diferentes interfaces que queremos que estén disponibles, tanto las públicas como las privadas, al igual que la variable privada luckyNum que es un entero sin signo.

Utilizando el objeto RNG (generador de números aleatorios), inicializamos la variable this.id del juego a un número aleatorio. Y en la variable randGen, sólo estamos inicializando el objeto RNG, mientras que con la función randGen.next, estamos generando un número aleatorio utilizando los valores de la semilla, <u32>(1, u32.MAX_VALUE), que se le pasaron.



Definiendo interfaces de función

Ahora vamos a definir la función play de nuestro juego. Esta contendrá el fragmento de código responsable de generar un número aleatorio dentro de un rango de enteros establecido.

import { RNG, logging } from "near-sdk-as";

@nearBindgen
export class Lottery {
  ...

  play(): bool {
    const randGen = new RNG<u32>(1, u32.MAX_VALUE);
    const pickNum = randGen.next();

    logging.log("You picked: " + pickedNum.toString());

    return pickedNum === this.luckyNum
  }
}
 

Con la función play, cualquier jugador puede llamarla para generar un número aleatorio utilizando el objeto RNG. Luego, importamos el objeto logging, que nos da acceso a los valores de salida en la consola nativa – que es la terminal de nuestra máquina local.

La función play devuelve un valor bool, y este valor true o false es el resultado de comparar el pickedNum contra this.luckyNum para determinar si el número adivinado es igual al luckyNum definido en el juego de lotería.

A continuación, definiremos la función reset. Como su nombre indica, nos permitirá restablecer el this.luckyNum a un nuevo número aleatorio:

...
@nearBindgen
export class Lottery {
  ...

  reset(): string {
    const randGen = new RNG<u32>(1, u32.MAX_VALUE);
    const randNum = randGen.next();
    assert(randNum !== this.luckyNum, "Rerun this function to generate a new random luckyNum");

    this.luckyNum = randNum;
    return "The luckyNum has been reset to another number";
  }
}
 

En el código anterior, generamos otro nuevo número aleatorio. Usando la función assert, lo comparamos con el valor actual de this.luckyNum.

Si la comparación evalúa true, entonces el resto del código de la función continúa ejecutándose. Si no es así, la función se detiene en ese punto y devuelve el mensaje de aserción, Rejecutar esta función para generar un nuevo luckyNum aleatorio.

.Cuando el assert es verdadero, asignamos la variable this.luckyNum al nuevo número generado, randNum.



Definiendo el objeto Jugador

Para cada jugador del juego de la lotería, definiremos una estructura de tipo básico. Esta estructura presenta al jugador dentro de nuestro juego.

Actualiza el archivo model.ts con el siguiente código:

import { RNG, logging, PersistentVector, Context } from "near-sdk-as";

export type AccountID = string;

@nearBindgen
export class Lottery {
  ...
}

@nearBindgen
export class Player {
  id: AccountId;
  guesses: PersistentVector<bool>;

  constructor(isRight: bool) {
    this.id = Context.sender;
    this.guesses = new PersistorVector<bool>("g"); // choose a unique prefix per account

    this.guesses.push(isRight);
  }
}
 

El tipo de objeto Player contiene dos interfaces: la variable this.id, que es de tipo AccountID, y this.guesses, que es una matriz de valores booleanos.

La estructura de datos PersistentVector es un tipo de datos de matriz. Durante la inicialización, utilizamos el objeto Context para obtener el llamador actual de este contrato inteligente a través de la función Context.sender. Luego, lo asignamos a this.id.

Para this.guesses, inicializamos un nuevo PersistentVector objeto y lo asignamos a this.guesses. Luego, utilizando la interfaz de la función push disponible en PersistorVector, añadimos un nuevo valor booleano, isRight, en la variable this.guesses.

Definamos otros tipos y variables que utilizaremos al definir las funciones principales en la siguiente sección:

...
exsport const TxFee = u128.from("500000000000000000000000");
export const WinningPrize = u128.from("100000000000000000000000");
export const Gas: u64 = 20_000_000_000_000;

...

export const players = new PersistentMap<AccountID, Player>("p")
...
 



Definir las funciones del núcleo del juego

Crea un archivo index.ts dentro de la carpeta assembly. Aquí es donde definiremos las funciones principales de nuestro juego de lotería.

Dentro del archivo index.ts, define una función pickANum, como se muestra a continuación:

import { TxFee, Lottery, Player, players } from "./model";
import { Context, u128 } from "near-sdk-as";

export function pickANum(): void {
  verifyDeposit(Context.attachedDeposit);
  const game = new Lottery();
  const guess = game.play();
  let player;

  if (players.contains(Context.sender)) {
    player = players.get(Context.sender) as Player;
    player.guesses.push(guess);
    players.set(Context.sender, player);
  } else {
    player = new Player(guess);
  }
}

function verifyDeposit(deposit: u128): void {
  assert(deposit >= TxFee, "You need 0.5 NEAR tokens to pick a number");
}
 

En la función anterior, estamos verificando un depósito de 0,5 tokens NEAR antes de que cualquier jugador del juego de lotería pueda invocar cualquier llamada para jugar una partida en el contrato inteligente. De esta manera, nuestros jugadores están pagando una cierta cantidad de dinero antes de jugar el juego. Además, una vez que un jugador juega, actualizamos el perfil de ese jugador en la estructura de datos de los jugadores.

A continuación, vamos a definir la función que se encargará de pagar a un jugador ganador generando aleatoriamente el número correcto que sea igual al luckyNum:

import { TxFee, Lottery, Player, players, Gas, WinningPrize } from "./model";
import { Context, u128, ContractPromiseBatch, logging } from "near-sdk-as";

function on_payout_complete(): string {
  logging.log("This winner has successfully been paid");
}

export function payout(): void {
  const player = players.get(Context.sender) as Player;

  for (let x = 0; x < players.guesses.length; x++) { 
    if (player.guesses[x] === true) {
      const to_winner = ContractPromiseBatch.create(Context.sender);
      const self = Context.contractName;

      to_winner.transfer(WinningPrize);
      to_winner
        .then(self)
        .function_call("on_payout_complete", "{}", u128.Zero, Gas)
    }
  }
}

Las funciones anteriores nos ayudan a realizar transacciones de transferencia a los ganadores del juego de la lotería. Con el objeto ContratoPromesaLote, creamos y configuramos una transacción de transferencia a la dirección que pasamos como argumento al método crear. Luego, con la función transferir, hacemos una transacción por valor del token, PremioGanador, que se le pasó.

Utilizando la función function_call, programamos una llamada a la función para cuando la transacción haya sido enviada con éxito. Para este juego, la función que pretendemos llamar en una transacción exitosa es la función on_payout_complete.

Para el propósito de este tutorial, no nos centraremos en la configuración de una Testnet NEAR o Monedero Testnet, pero te animo a que consultes los enlaces para saber más sobre las distintas redes que existen en el ecosistema NEAR.

Para esta demostración, construiremos nuestro juego de lotería para generar el archivo en formato binario .wasm, y luego usaremos el comando near dev-deploy para desplegar el contrato inteligente.



Construir y desplegar contratos inteligentes

Primero construiremos el contrato inteligente utilizando el comando asb:

yarn asb

Este es un comando alias para el comando yarn asb --verbose --nologo, como se define en el archivo package.json ubicado en el directorio raíz.

Después de haber generado con éxito una carpeta build que contiene un archivo lottery.wasm dentro de la carpeta build/release/, podemos ejecutar el siguiente comando para desplegarlo:

near dev-deploy ./build/release/lottery.wasm 

Esto desplegará el contrato inteligente y nos proporcionará el nombre o ID del contrato, que podemos utilizar para interactuar con él en el frontend o a través de un archivo shell.

$ near dev-deploy ./lottery.wasm   
Starting deployment. Account id: dev-1635968803538-35727285470528, node: https://rpc.testnet.near.org, helper: https://helper.testnet.near.org, file: ./lottery.wasm
Transaction Id 4TWTTnLEx7hpPsVMfK31DDX3gVmG4dsqoMy7sA7ypHdo
To see the transaction in the transaction explorer, please open this url in your browser
https://explorer.testnet.near.org/transactions/4TWTTnLEx7hpPsVMfK31DDX3gVmG4dsqoMy7sA7ypHdo
Done deploying to dev-1635968803538-35727285470528



Probando nuestro juego blockchain

He escrito dos pruebas unitarias para confirmar que nuestra aplicación es realmente funcional. Estas dos sencillas pruebas crearán un juego de lotería y además resetearán la variable luckyNum a un nuevo número aleatorio.

La carpeta /src/lottery/__test__ contiene el archivo de pruebas. Ejecuta el conjunto de pruebas con el siguiente comando:

$ yarn test:unit
[Describe]: Checks for creating account

 [Success]: ✔ creates a new game
 [Success]: ✔ create and reset the luckyNum of a new game

    [File]: src/lottery/__tests__/index.unit.spec.ts
  [Groups]: 2 pass, 2 total
  [Result]: ✔ PASS
[Snapshot]: 0 total, 0 added, 0 removed, 0 different
 [Summary]: 2 pass,  0 fail, 2 total
    [Time]: 19.905ms
  [Result]: ✔ PASS
   [Files]: 1 total
  [Groups]: 2 count, 2 pass
   [Tests]: 2 pass, 0 fail, 2 total
    [Time]: 13907.01ms
Done in 14.90s.
 



Conclusión

En este tutorial, hemos demostrado cómo crear aplicaciones de juegos en plataformas blockchain. Los juegos basados en blockchain pueden jugarse como juegos multijugador o en solitario.

También puedes ampliar el concepto de los juegos blockchain para incluir un metaverso -un mundo digital- alrededor de tu juego. El metaverso es un mundo en el que los jugadores pueden formar equipos, crear un gobierno e incluso crear monedas como medio de intercambio de valor. Puedes acuñar NFT o formar DAO dentro de un mundo de juego digital.

Consulta los documentos de NEAR para ver cómo construir un frontend para consumir el contrato inteligente del juego creado en este tutorial. El código base completo del contrato inteligente está disponible en GitHub.

.




LogRocket: Visibilidad total en tus aplicaciones web

LogRocket es una solución de monitorización de aplicaciones frontales que le permite reproducir los problemas como si ocurrieran en su propio navegador. En lugar de adivinar por qué se producen los errores, o pedir a los usuarios capturas de pantalla y volcados de registro, LogRocket le permite reproducir la sesión para entender rápidamente lo que salió mal. Funciona perfectamente con cualquier app, independientemente del framework, y tiene plugins para registrar el contexto adicional de Redux, Vuex y @ngrx/store.

Además de registrar las acciones y el estado de Redux, LogRocket registra los registros de la consola, los errores de JavaScript, los stacktraces, las solicitudes/respuestas de red con cabeceras + cuerpos, los metadatos del navegador y los registros personalizados. También instrumenta el DOM para registrar el HTML y el CSS de la página, recreando vídeos de píxeles perfectos incluso de las aplicaciones más complejas de una sola página.

Pruébalo gratis.

Categorías : # Blockchain, # desarrollo de juegos

Deja una respuesta

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