Kinetica Code&Beer #4: Juegos con HTML5

Siguiendo con los eventos de Code&Beer en las oficinas, ayer se habló sobre desarrollo de video juegos con HTML5 y JavaScript.

Enfoque

Iniciamos con una introducción al concepto de desarrollo de video juegos, donde hablamos de los siguientes puntos:

  • Tag <canvas> como lienzo de dibujo en HTML5
  • Bucle principal de un juego
  • Sprites y Sprite Sheets
  • Double buffering para optimización de performance

Los puntos anteriores sirvieron como puntapié de las actividades mínimas necesarias que involucra la creación de un video juego.

El objetivo

Mediante la utilización de un micro framework existente y algunos sprites, nos pusimos en la tarea de crear un juego simple, con una base común, pero aplicando cada uno de los asistentes sus propias variantes.

 

 

El micro framework

Este micro framework, con solo 88 líneas de JavaScript, fue el encargado de manejar la lógica principal del juego. Proveer el lienzo de dibujo a los distintos objetos del juego, manejar el bucle de dibujo e interactuar con el mecanismo de double buffering.

Un poco de la estructura del framework a continuación:

this.init = function (canvasName) {
canvas = document.getElementById(canvasName);
context = canvas.getContext(“2d”);

setInterval(runGame, 1000 / 30);
}

function runGame() {
    …
    backBuffer.fillStyle = “rgb(255,255,255)”;
    backBuffer.fillRect(0, 0, canvas.width, canvas.height);

    for (var i = 0; i < gameObjects.length; i++) {
        gameObjects[i].update(delta);
        if (gameObjects[i].draw && gameObjects[i].visible()) {
            …
        }
    }
    context.drawImage(backBufferCanvas, 0, 0);
};

Los objetos del juego

Cada entidad en el juego fue representada por un objeto que manejaría el micro framework. Para respetar la estructura y funcionamiento, cada objeto necesitaría 4 funciones (Init, Visible, Update y Draw).

function Cavernicola() {
var self = this;
var cavernicola = new Image();
cavernicola.src = “prota.png”;


this.init = function() {
//Inicializamos el objeto

}

this.visible = function() {
return true;
}

this.update = function(delta) {
//Lógica de actualización del objeto

}

this.draw = function(context) {
//Se dibuja el objeto en el lienzo de dibujo

}
}

Pueden descargar el código desde esta dirección.

Code&Beer: Chain of responsability Design Pattern

¡Hola!

La semana pasada en Kinetica celebramos nuevamente un Code&Beer. En esta oportunidad quisimos probar un Coding Dojo al estilo Randori.

La actividad consistía en agregar funcionalidad a un código sencillo existente. Este contaba con varios tests sobre la funcionalidad que ya había y se pretendía agregar muchos más usando TDD.

La aplicación modelaba una aprobación de la orden de un ítem a través de una cadena jerárquica de empleados. Cada empleado tenía la chance de rechazar el pedido por algún motivo y si lo aprobaba era evaluado por el siguiente en la cadena jerárquica.

El código original era más o menos así (C#):

public void ProcesarCompra(Compra compra)
{
	if (compra.Costo > 500)
	{
		compra.Estado = Compra.EstadoCompra.Rechazada;
		compra.Razón = "Lean: El costo unitario es muy alto";
	}

	if (compra.Costo * compra.Cantidad > 10000)
	{
		compra.Estado = Compra.EstadoCompra.Rechazada;
		compra.Razón = "Fer: Demasiado dinero";
	}

	// Más reglas...
}

Arrancamos con 2 personas, cuando pasaban 6 minutos yo daba una señal, una salía y otra ocupaba su lugar. Les fui agregando nuevo requerimientos de a uno para que notaran cómo se complicaba el diseño:

  • Se contrata una persona en el nivel más bajo de la cadena: Luís. Luís no acepta compras de más de 15 unidades.
  • Parece que el desempeño de Luís ha sido excepcional por lo que es promovido a CEO. Ahora es el primero en juzgar las compras.
  • Luís nos recomienda –y, como es el CEO, lo obedecemos- que contratemos a Adriana. Ella acepta solo algunos pedidos, como máximo 3.

IMG_2406

Los chicos se dieron cuenta rápido que no era bueno tener todo en el mismo método y crearon un modelo con personas y reglas. Cambiando el código a algo así:

Persona lean = new Persona(new CostoUnitarioMayorA500());
Persona fer = new Persona(new CostoMayorA1000());
Persona luis = new Persona(new CantidadMayorA15());

(...)

public void ProcesarCompra(Compra compra)
{

    if (!lean.ProcesarCompra(compra)) return;

    if (!fer.ProcesarCompra(compra)) return;

    if (!luis.ProcesarCompra(compra)) return;
}

La implementación estaba bastante bien y me dejó con pocos motivos para cambiarlo por la implementación con el patrón Chain of responsability. Lo que yo tenía en mente era algo así:

public abstract class PersonaJerarquica
{
    public PersonaJerarquica SiguienteEnMando { get; set; }

    public abstract void ProcesarCompra(Compra compra);
}

public class Lean : PersonaJerarquica
{
    public Lean(PersonaJerarquica subdito)
    {
        this.SiguienteEnMando = subdito;
    }

    public override void ProcesarCompra(Compra compra)
    {
        if (compra.Costo > 500)
        {
            compra.Estado = Compra.EstadoCompra.Rechazada;
            compra.Razón = "Lean: El costo unitario es muy alto";
        }

        if (this.SiguienteEnMando != null)
        {
            this.SiguienteEnMando.ProcesarCompra(compra);
        }
    }
}

(...)

PersonaJerarquica lean = new Lean(new Fernando(new Luis(null)));

public void ProcesarCompra(Compra compra)
{
	lean.ProcesarCompra(compra);
}

La clave de esta implementación es el SiguienteEnMando y la construcción de la cadena de mando. El SiguienteEnMando desentiende un ProcesarCompra de otro. Es decir, encapsula cada persona.

Pero para mi es clave que la construcción de la cadena sea simple. En el ejemplo yo iba agregando reglas para que entendiera que es una parte del sistema flexible. Esta implementación hace muy fácil cambiar el orden de ejecución de la cadena de mando. Incluso, si lo deseáramos podemos hacerlo por configuración.

Resumiendo ventajas de esta implementación con el patrón:

  1. Encapsula las reglas.
  2. Es muy flexible ante cambios en la cadena.

¡Saludos!