Archive for the '9344' Category

Programando Juegos Sociales en Línea (Parte 8) Agregando Node.Js

Thursday, January 12th, 2012

Anterior Post

En el anterior post, mostré el procesamiento del juego en el  Windows Azure Toolkit for Social Gaming (versión 1.1.1, hay una nueva 1.2.0 pero aún en beta). Ahora, es tiempo de ver cómo agregar Node.js como servidor de juego a nuestro simple juego de Ta Te Ti.

Vean que todo el código client de Game Service es agnóstico del juego, es decir, no depende del juego que se está implementando. Y vean también que ese Game Service en javascript puede ser cambiado para usar otros servicios. En este post, veremos cómo cambiar el procesamiento de las jugadas para usar una instancia del servidor Node.js.

Primero, bajarse el código a agregar de mi GitHub:

https://github.com/ajlopez/SocialGamingExtensions

Entonces:

1 – Bajar e instalar el Windows Azure Toolkit for Social Gaming versión 1.1.1, desde:

http://watgames.codeplex.com/releases/view/77091

1 – Instalar Node.js para Windows, desde http://nodejs.org/#download

2 – Cambiar al subdirectorio server de mi código, y ejecutar el comando:

npm install socket.io

La librería socket.io se bajará e instalará en el subdirectorio node_modules:

Vean que baja bastante:

3 – Iniciar el servidor de node:

node gameex.js

El servidor comienza a escuchar:

4 – Copiar el directorio client de mi código al directorio del proyecto SocialGaming.Web. Los archivos Web.config, BaseController.cs, TicTacToeController.cs serán reemplazadas. Hay nuevos archivos: Scripts\game\GameServiceNodeJs.js, Areas\Samples\Views\TicTacToe\NodeJs.cshtml.

5 – Abrir la solución de Microsoft en el Visual Studio 2010 (run as administrator) y agregar esos nuevos archivos al proyecto SocialGaming.Web. El nuevo GameService a usar para Node.js:

La nueva vista TicTacToe que usa al Node.js:

La nueva acción en el controlador TicTacToe:

Y la nueva entrada en el web.config:

El reemplazado BaseController lee esa nueva configuración, que queda disponible para ser usada por la vista:

6 – Iniciar la aplicación, debería ejecurtarse como http://127.0.0.1:81 (en puerta 81) para ser aceptada por la seguridad federada configurada en el portal de Access Control Service.

7 – Navegar a /Samples/TicTacToe/NodeJs. Ver la URL de invitación:

8 – El cliente se conecta con el servidor de Node.js. Dependiendo de las capacidades del cliente, socket.io usará WebSockets o XHR long polling:

 

9 – Abrir un nuevo explorador, en una sesión privada, y navegar a la URL provista en el paso 7

10 – Hay dos jugadores. El de la derecha (el invitado) jugó en la celda del top izquierdo. El primer jugador (el de la izquierda) recibe la nueva movida y actualiza su tablero.

11 – Ver la consola del servidor Node.js: la movida fue recibida y enviada a todos los participantes de esta instacia de juego:

Bien! Trabajo para el hogar: agreguen el Node.js para publicarse en un worker role. Vean el post de @ntotten NodeJS on Windows Azure).

Próximos temas: explicar en detalle el código del servidor que ejecuta Node.js (por ejemplo, soporta sockets TCP además de socket.io; de esta manera podemos programar un cliente en otras plataformas, desde Silverlight a IPhone a Android, sin depender de tener que usar una librería Socket.io en esos clientes).

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Programando Juegos Sociales en Línea (Parte 7) Procesamiento de las Jugadas

Wednesday, January 11th, 2012

Anterior Post

Quiero en este post presentar el procesamiento de las jugadas del ejemplo simple de Ta Te Ti. Involucra varias partes, desde la vista Razor que usa Javascript, hasta el Web Role, hasta el Azure Blob Storage.

Estos son los archivos javascript referenciados en la vista TicTacToe (en el archivo SocialGame.Web/Areas/Samples/Views/TicTacToe/Index.cshtml):

<script src="@Url.AreaContent("Scripts/jQuery.tmpl.js")" type="text/javascript"></script>
<script src="@Url.AreaContent("Scripts/knockout-1.2.1.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/game/ServerInterface.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/game/GameService.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/game/UserService.js")" type="text/javascript"></script>
<script src="@Url.AreaContent("Scripts/game/TicTacToeBoard.js")" type="text/javascript"></script>
<script src="@Url.AreaContent("Scripts/game/TicTacToeGame.js")" type="text/javascript"></script>
<script src="@Url.AreaContent("Scripts/game/TicTacToeViewModel.js")" type="text/javascript"></script>
<script src="@Url.AreaContent("Scripts/game/TicTacToeController.js")" type="text/javascript"></script>

Como escribí en el aterior post, tanto ServerInterface, GameService como UserService son agnósticos del juego, pueden reusarse para cualquier juego. Para cada nuevo tipo de juego X, lo que tenemos que programar son los XBoard, XGame con la lógica del juego, el XViewModel y el apropiado XController.

Al final de esa vista, se encuentra la creación de los servicios agnósticos del juego a usar desde el cliente:

var apiURL = "@this.ViewBag.ApiUrl";
var blobURL = "@this.ViewBag.BlobUrl";
var si = new ServerInterface();
var gs = new GameService(apiURL, blobURL, si);
var user = new UserService(apiURL, blobURL, si);

Las propiedades @this.ViewBag son llenadas en el servidor, en el controlodor de ASP.NET MVC (ver BaseController.cs)

La creación del controlador del juego:

// check for canvas, show an "Upgrade your browser" screen if they don't have it.
var canvas = document.getElementById('board');
if (canvas.getContext == null || canvas.getContext('2d') == null) {
    $("#game").toggle();
    $("#notSupported").toggle();
    return;
}
var board = new TicTacToeBoard(canvas);
var game = new TicTacToeGame();
var controller = new TicTacToeController(viewModel, gs, board, game);
controller.setGameQueueId(gameQueueId);
controller.start();


Pero la parte interesante está en el constructor del controlador:

function TicTacToeController(viewModel, gameService, board, game) {
    this.viewModel = viewModel;
    this.gameService = gameService;
    this.board = board;
    this.game = game;
    this.started = false;
    var controller = this;
    this.board.onMove = function (x, y) { controller.onMove(x, y); };
};


Notemos el this.board.onMove: el controlador se registra a sí mismo para procesar las nuevas movidas detectadas por el componente que maneja el tablero. En su método start(), el controlador se registra a sí mismo para procesar las actualizaciones detectas por el servicio de gameService:

controller.gameService.process(
        gameQueueId,
        function (queue) { controller.processGameQueue(queue); },
        function (action) { controller.processAction(action); }
        );

Examinemos el procesamiento de las jugadas.

1 – El componente del tablero detecta un click, determina la casilla, y envía una nueva movida al controlador, usando el callback onMove

2 – El controlador envía la nueva movida para la lógica del juego, para actualizar el estado (he omitido el view model en este gráfico)

3 – El controlador envía la nueva movida al game service.

4 – El game service envía un nuevo comando al web role, usando el server interface

5 – La API del web role recibe el nuevo comando

6 – La información del comando es agregada al blob que refleja el estado del juego, almacenado en el Azure Storage

Ahora, veamos el procesamiento en el otro cliente:

1 – El game service, usando un timer, va leyendo reiteradamente el stado del juego, usando la service interface

2 – La service interface, usando JSONP, recupera el estado actual del juego, desde un blob de Azure Storage

3 – Si una nueva movida es detectada en ese estado, el game service llama a una función callback provista por el controlador (el game service no tiene referencia al controlador).

4 – El controlador envía la nueva movida a la lógical de juego, que actualiza su estado (omití el view model en este gráfico)

5 – El controlador envía la nueva movida al componente de tablero, para actualizar el canvas

Un punto clave: el controllador no conoce NADA acerca de la service inteface, la API de WCF Web, el blob storage. Entonces, podemos cambiar el game service para que procese las movidas de otras maneras. Próximo post: modificar el game service para usar a Node.js como servidor de las movidas del juego.

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Programando Juegos Sociales en Línea (Parte 6) Armando y Probando el Juego y los Servicios con TDD y QUnit

Friday, December 30th, 2011

Anterior Post

En mi anterior post, comenté la nueva versión 1.1 (hay ahora una 1.2 Beta) del Windows Azure Toolkit for Social Games. Tiene juegos simples para demostrar el uso de Javascript, HTML5, procesamiento de movidas del juego, uso de Azure web roles y worker roles. Veamos de explorar en este post el armado de la lógica del juego, en Javascript, usando TDD (Test-Driven Development) y QUnit.

Hay tests en línea en:

http://watgames4.cloudapp.net/Test

Ejecutemos los test de Tic Tac Toe Game Logic:

http://watgames4.cloudapp.net/Samples/ClientTest/TicTacToeGameTest

Esta página está usando QUnit para tests en el cliente usando Javascripot. Escribí posts introductorios:

TDD with Javascript and QUnit
TDD con Javascript y QUnit

La página que visitamos está probando la lógica del juego de Ta Te Ti. Recordemos, cada juego está implementado en partes, la lógica es una de ellas:

El código cliente reside en TicTacToeGame.js dentro del proyecto SocialGames.Web. Sus primeras líneas:

TTTColor = { Empty: 0, Cross: 1, Circle: 2 };
function TicTacToeGame() {
    this.board = [
     [TTTColor.Empty, TTTColor.Empty, TTTColor.Empty],
     [TTTColor.Empty, TTTColor.Empty, TTTColor.Empty],
     [TTTColor.Empty, TTTColor.Empty, TTTColor.Empty]
     ];
}
TicTacToeGame.prototype.move = function (x, y, color) {
    this.board[x][y] = color;
};
TicTacToeGame.prototype.isEmpty = function (x, y) {
    return this.board[x][y] == TTTColor.Empty;
};
....

La vista razor (TicTacToeGameTest.cshtml) fue escrita al mismo tiempo que la lógica, usando TDD (Test-Driven Development). Veamos los primeros tests:

test("Create Empty Board", function () {
    var game = new TicTacToeGame();
    for (var x = 0; x < 3; x++)
        for (var y = 0; y < 3; y++)
            ok(game.isEmpty(x, y));
    equal(game.isTie(), false);
    equal(game.hasWinner(), false);
});
test("Valid Moves on Empty Board", function () {
    var game = new TicTacToeGame();
    for (var x = 0; x < 3; x++)
        for (var y = 0; y < 3; y++) {
            ok(game.isValid(x, y, TTTColor.Cross));
            ok(game.isValid(x, y, TTTColor.Circle));
        }
});
test("No Winner in Empty Board", function () {
    var game = new TicTacToeGame();
    equal(game.getWinner(), TTTColor.Empty);
});
test("Get Winner in First Row", function () {
    var game = new TicTacToeGame();
    game.move(0, 0, TTTColor.Cross);
    game.move(1, 0, TTTColor.Cross);
    game.move(2, 0, TTTColor.Cross);
    equal(game.getWinner(), TTTColor.Cross);
    equal(game.isTie(), false);
    equal(game.hasWinner(), true);
});

La idea es avanzar de a pequeños pasos, test por test, diseñando la API de la lógica del juego, y su conducta esperada. De esta manera, vamos avanzando sin gastar tanto tiempo en prueba manual, y menos tiempo en depuración. Esto es recomendable, pero más aún cuando se trabaja con un lenguaje dinámico como Javascript. Una batería de pruebas puede salvarnos el día en caso de un refactoreo grande. Vean que el Four In A Row siguió un camino similar.

Bien, no todo puede ser testeado fácilmente, o construido usando TDD. Algunos de los servicios agnósticos del juego (es decir, independientes del juego que se implemente) estan usando Ajax y Blob Storage, y para probarlos debemos considerar el uso de Ajax asincrónico (pueden comenzar construyendo tests sincrónicos si quieren). Pueden ver:

http://watgames4.cloudapp.net/Test/ServerInterfaceTest

(Para esta página, deben ingresar usando una cuenta de Facebook o de Windows Live ID, el ejemplo usa Federated Security y Access Control Service (ACS))

Esta vez, el sistema bajo prueba es el Service Interface:

Hay algunos trucos en el código de prueba (ServerInterfaceTest.cshmlt), un fragmento:

test("Call User/Verify", function () {
    var success = function (result) { ok(true); start(); };
    var error = ajaxGetError;
    stop(10000);
    expect(1);
    si.sendAjaxGet(apiURL + "user/verify", success);
});

expect es una función provista por QUnit que prepara al framework para recibir un ok(true) alguna vez durante la ejecución de la prueba. Esa confirmación se incluye en la función callback success que será llamada luego de la ejecución exitosa de la llamada asincrónica .sendAjaxGet. La vida Async no es fácil 😉

Más análisis del código en próximos posts. Debería adaptar algún juego para que use Node.js para procesar sus jugadas.

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Desarrollando un juego en la nube con HTML5, JavaScript y Node.js

Friday, December 16th, 2011

Es el título de la próxima charla gratuita que @woloski y yo daremos en el Microsoft User Group de Buenos Aires, Argentina. Ver detalles en

http://www.mug.org.ar/Eventos/3715.aspx

Será el próximo miércoles, 21 de Diciembre, a las 18:30hs. Tienen que visitar ese link si quieren inscribirse (hay inscripción previa).

La idea a presentar está basada en desarrollos que hicimos para:

Windows Azure Toolkit for Social Games

Ya escribí sobre Azure y Social Games (con Tankster y los nuevos ejemplos simples) en:

Social Games Programming
Programando Juegos Sociales

Para la charla visitaremos:

– HTML5, en especial su uso de Canvas para dibujar un simple tablero

– Javascript, por ejemplo Knockout para tener un ViewModel en el cliente que al cambiar refresca elementos de la página

– Uso de TDD en Javascript, con QUnit

– Socket.IO cliente para comunicarse con un servidor

– Node.js para levantar un servidor de jugadas, usando Socket.IO (quiero también escribir un ejemplo con sockets TCP)

– Elementos de Tankster (seguridad federada en Azure, etc…) aunque lo principal de la charla se basa en los nuevos ejemplos simples (Tankster puede ser una aplicación muy grande para estudiar como primer ejemplo).

Si quieren leer sobre Tankster y los ejemplos simples nuevos, les recomiendo los posts en los enlaces que mencioné arriba.

Sobre temas de HTML5, Node.js, Javascript, TDD ya tengo escritos:

HTML5: Links, News and Resources
Javascript: Links, News and Resources
Node.js: Links, News and Resources
TDD With Javascript and QUnit
My posts about QUnit

Y estos son los enlaces que fui descubriendo mientras estudiaba estos temas:

http://www.delicious.com/ajlopez/javascript
http://delicious.com/ajlopez/html5
http://delicious.com/ajlopez/socketio
http://www.delicious.com/ajlopez/nodejs

Mañana Sábado Diciembre 17, el bueno de @cprieto dará una VAN (reunión virtual) en AltNet Hispano:

http://cprieto.com/2011/12/13/proxima-van-html5-para-los-no-iniciados/

El bueno de @theprogrammer ya dio una VAN sobre Node.js:

http://altnethispano.org/wiki/van-2011-08-06-nodejs.ashx

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Programando Juegos Sociales en Línea (Parte 5) Nuevo Azure Toolkit

Thursday, December 1st, 2011

Anterior Post 
Siguiente Post 

Dos semanas atrás, fue publicada una nueva versión del Windows Azure Toolkit for Social Games. Vean los posts de @ntotten:

Windows Azure Toolkit for Social Games Version 1.1.1
Windows Azure Toolkit for Social Games Version 1.1

La nueva versión implementa dos juegos simples en HTML5: Ta Te Ti, y Cuatro en Raya, usando vistas ASP.NET MVC, servicios WCF Web API, seguridad federada usando ACS, y Azure Storage. Pueden jugar en línea en:

http://watgames4.cloudapp.net/

Pienso que éste es un ejemplo más claro que el anterior (Tankster) que era una gran aplicación pero algo excesiva ;-). Veamos de entrar en algunos detalles de implementación de este nuevo ejemplo.

Totten escribió:

The biggest change we have made in this release is to separate the core toolkit from the Tankster game. After we released the Tankster sample game we received a lot of feedback asking for a simpler game that developers could use to learn. To meet this need we developed two simple games, Tic-Tac-Toe and Four in a Row, and included in the toolkit. The Tankster game is now available separately as a sample built on top of the toolkit.

While the new games included in the toolkit are much simpler than Tankster, they still show the same core concepts. You can easily use these samples as a starting point to build out any number of types of games. Additionally, you will find that many of the core components of the game such as the leaderboard services, game command services can be used without any modification to the server side or client side code.

En mi anterior post, mencioné un pequeño pero importante cambio en el proceso de acciones de juego: toda la lógica fue removida del código del servidor. Adoptando este camino, podemos escribir nuevos juegos sin cambiar el código del servidor. Podemos seguir agregando código en el servidor si lo necesitamos (por ejemplo, para agregar control al juego, detectar operaciones inválidas enviadas desde algún cliente, etc) pero es interesante tener una base de código que sea agnóstica del juego.

Abriendo la solución en Visual Studio, encontraremos archivos Javascript usados por los dos juegos. Podemos escribir un nuevo juego, reusando estos archivos sin cambios:

Los juegos están implementados como áreas:

Podríamos escribir nuevos juegos y publicarlos como paquetes NuGet.

Visualmente, el código Javascript cliente está organizado de esta manera:

Cada juego X (X = Ta Te Ti, Cuatro en Raya, uno nuestro) tiene:

XGame: la lógica del juego

XBoard: para dibujar el tablero en un elemento canvas de HTML5, y para detectar eventos de click en el mismo.

XViewModel: contiene el jugador actual y otros datos para ser usandos en el armado de la vista (los ejemplos usan  knockout.js, como MVC en Javascript)

XController: para procesar nuevos eventos y para coordinar los elementos de arriba.

La parte genérica:

UserService: métodos relacionados a usuarios: login, lista de amigos, etc…

GameService:  jugar las movidas, recibir movidas de otros jugadores, otras acciones (por ejemplo, se podrían enviar mensajes de chat).

ServerInterface: Llamadas Ajax (usando GET, POST, JSONP, Azure storage….) que son usados por la implementación de User y Game Service.

Temas para próximos posts: analizar el código Javascript, el uso del Canvas, tests de Javascript usando QUnit, comunicación con el servidor usando Ajax, cambio del Game Service (en Javascript) para usar un Node.js como servidor que reciba y reparta las acciones de juego.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Jugando con NodeJs (1) En Windows (y en Azure)

Friday, September 2nd, 2011

Hasta unas semanas atrás, la única forma de ejecutar NodeJs en Windows era bajar el código desde el repositorio en Github y compilarlo usando CygWin (otro caso de para tener la banana, hay que llevarse al mono también ;-). Ver:

Building node.js on Cygwin (Windows)
How to install Node.js on Windows

Pero ahora hay una alternativa: el equipo de NodeJs ha publicado una versión para Windows precompilada (0.5.4 y 0.5.5):

http://nodejs.org/#download
http://nodejs.org/dist/
http://nodejs.org/dist/v0.5.4/node.exe
http://nodejs.org/dist/v0.5.5/node.exe

(Hay una nueva versión http://nodejs.org/dist/v0.5.5/node-186364e.exe)

Estoy usando la 0.5.4. Luego de bajarme el exe, ejecuto un simple programa de demo que usa Socket.IO:

Node puede extenderse con módulos escritos en Javascript. El mensaje de arriba me avisa que me falta en módulo SocketIO. El probrama npm es el manejador de paquetes más usado de NodeJs, pero no hay una versión Windows (aún):

how can i do npm on windows!

Así que me bajé el módulo y sus dependencias, como dice:

Node.js on Windows: Who Needs NPM?

¿Dónde están las URLs de las dependencias y sus repos? Pueden ver en:

http://search.npmjs.org/

Me bajé algunos módulos, quedando ahora:

Están en el subdirectorio donde reside el node.exe. El archivo principal a leer y estudiar en cada módulo es su declaración Package.json. Ejemplo, éste es el manifiesto para Socket.IO:

{
    "name": "socket.io"  , 
    "version": "0.7.9"  ,
    "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"  ,
    "homepage": "http://socket.io"  , 
    "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]  , 
    "author": "Guillermo Rauch <guillermo@learnboost.com>"  , 
    "contributors": [        
	{ "name": "Guillermo Rauch", "email": "rauchg@gmail.com" }      , 
        { "name": "Arnout Kazemier", "email": "info@3rd-eden.com" }      ,
        { "name": "Vladimir Dronnikov", "email": "dronnikov@gmail.com" }
     ]  ,
     "repository":{
	    "type": "git"      , 
        "url": "https://github.com/LearnBoost/Socket.IO-node.git"    
     }  ,
     "dependencies": {
        "socket.io-client": "0.7.9"      ,
        "policyfile": "0.0.4"      , 
        "redis": "0.6.6"    
     }  ,
     "devDependencies": {
        "expresso": "0.7.7"      ,
        "should": "0.0.4"      , 
        "assertvanish": "0.0.3-1"
    }  ,
     "main": "index"  , 
     "engines": { "node": ">= 0.4.0" }
}

Cuando tuve todos los módulos, puse el valor de la variable NODE_PATH apuntando al directorio padre de todos los módulos, y ejecuté la aplicación de nuevo:

Triunfo de la mente sobre la materia! 😉 Este fue mi primer manera de ejecutar un programa. Pero recomendaría la alternativa: poner todos los múdulos en un subdirectorio llamado node_modules:

De esta forma, no necesitamos la variable NODE_PATH. Yo uso esa variable porque alguno de mis módulos están en desarrollo en otros directorios. Pero si tienen un subdirectorio node_modules, NodeJs (pienso que desde versión >0.5) busca en ese subdirectorio los módulos requeridos Y TAMBIEN en el NODE_PATH. Entonces puedo usar node_modules directamente y poner adicionalmente NODE_PATH=c:\Git apuntando donde está mi módulo AjFabriq en desarrollo. NodeJs encontrará Socket.IO en el subdirectorio node_modules, y a mi AjFabriq en c:\Git\ajfabriq.

Si tienen problemas con las dependencias, lean el post de @cibrax:

Running the “Express” web development framework on Node for Windows

Ahí explica cómo usar un clon de npm escrito en Python para instalar nuevos módulos y dependencias de NodeJs.

Hasta hay una manera de ejecutar NodeJs dentro de IIS. Vean el post de @shanselman:

Installing and Running node.js applications within IIS on Windows – Are you mad?

Una alternativa interesante a NodeJs que ejecuta en ASP.NET:

Asynchronous scalable web applications with real-time persistent long-running connections with SignalR

Bueno, ¿y si necesitamos ejecutar NodeJs en Azure? Me gustaría escribir un post sobre el tema, pero ….  @ntotten ya me robó la idea ;-). Lean:

NodeJS on Windows Azure

Otros posts sobre NodeJs y Azure:

Node.js, Ruby, and Python in Windows Azure: A Look at What’s Possible | MIX11 | Channel 9

http://channel9.msdn.com/Events/MIX/MIX11

Node.js, Ruby, and Python in Windows Azure: MIX Talk

http://blog.smarx.com/posts/node-js-ruby-and-python-in-windows-azure-my-mix-talk

Posts relacionados sobre NodeJs:

Node.Js: Links, news, Resources (2)

Node.Js: Links, news, Resources (1)

Playing with Node.js, Ubuntu, Sqlite3 and node-Sqlite

Jugando con Node.js, Ubuntu, Sqlite3 y node-Sqlite

Mis enlaces:

http://www.delicious.com/ajlopez/nodejs

Mas jugando con NodeJs en futuros post.

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Programando Juegos Sociales en Línea (Parte 4) Procesando Acciones De Juego Arbitrarias

Wednesday, August 10th, 2011

Anterior Post 
Siguiente Post 

Hay un nuevo release del Windows Azure Toolkit for Social Games:

http://watgames.codeplex.com/releases/view/70342

Examinemos un cambio clave en esta versión. Hay un nuevo punto de entrada en Tankster.GamePlay\Services\IGameService.cs:

[WebInvoke(Method = "POST", UriTemplate = "command/{gameId}")]
HttpResponseMessage Command(Guid gameId, HttpRequestMessage request);
[WebInvoke(Method = "POST", UriTemplate = "command/v2/{gameId}")]
HttpResponseMessage Command2(Guid gameId, HttpRequestMessage request);

Hay DOS puntos de entrada para enviar comandos de juego desde los clientes. ¿Por qué? Al tener dos URIs el servidor puede ser usado por los clientes antiguos y por los nuevos. Noten el uso de  “v2” en el UriTemplate.

El nuevo código (parcial) de Command2 en su implementación (GameService.cs):

// Add gameAction
var gameAction = new GameAction
{
    Id = Guid.NewGuid(),
    Type = commandType,
    CommandData = commandData,
    UserId = this.CurrentUserId,
    Timestamp = DateTime.UtcNow
};
// Cleanup game actions lists
for (int i = 0; i < game.GameActions.Count(); i++)
{
    if (game.GameActions[i].Timestamp < DateTime.UtcNow.AddSeconds(-10))
    {
        game.GameActions.RemoveAt(i);
        i--;
    }
}
game.GameActions.Add(gameAction);
this.gameRepository.AddOrUpdateGame(game);
return SuccessResponse;

Si comparamos éste con el viejo código (que sigue estando en el método Command) con el código de arriba, la principal diferencia es:

– El servidor no maneja la lógica de “quién es el próximo jugador en la ronda de turnos”

– El servidor no distribuye los comandos recibidos usando un GameActionProcessor

Y otro cambio: el tipo de una acción de juego es ahora un entero, un int. En la anterior versión del ejemplo era un enum, reflejando un conjunto fijo de tipos.

Todavía actualiza el blob de estado del juego agregando el nuevo comando (un comando puede ser un mensaje de chat, o un disparo). De esta nueva manera, el CODIGO DEL SERVIDOR es más independiente de la lógica del juego: el código cliente tiene la responsabilidad de elegir el próximo jugador. La noción de turno se deja al desarrollo del código cliente. Ahora, el código servidor puede ser usado para OTROS juegos multi-jugador. Y podríamos tener nuestro propio “Social Game”! 😉

Entonces, los roles web y worker de Windows Azure están a cargo de la autenticación federada, la formación de nuevos juegos (al sumar jugadores que quieran participar), etc. Pero el motor es más “game agnostic” en este nuevo release.

Repasando como queda ahora. El código cliente envía comandos:

Pasos:

1- El código cliente envía una nueva Game Action en JSON (por ejemplo, información de un nuveo disparo (posición, ángulo, fuerza)).

2- Una de las instancias del web role recibe el Game Action y la agrega a la lista de acciones en el correspondiente blobg del juego (guardando solamente las acciones de los últumos 10 segundos)

3- Los otros jugadores están “poleando” el estado del juego, notando cuándo una game action implica un cambio de turno. Y si reciben disparos, los van mostrando en cada browser.

El código cliente podría manejar también el caso de un jugador que pasa a “offline”, mediante un “timeout”.

Pros de esta nueva forma de manejar las acciones:

– El código del servidor es más genérico, y puede ser usado para otros juegos

Cons:

– El código cliente puede ser engañado (no hay controles, validaciones en el servidor)

– Más complejidad a implementar en el código cliente: manejo de turnos, “timeouts”, latencia, control de “cheat”.

La prueba ácida: implementar un nuevo juego usando el mismo código servidor. Algunos puntos para pulir: el modo Skirmish (cada jugador entra en un juego, esperando a que otros también entren) está todavía “hardcodeado” a un máximo de 5 jugadores; la ventana de 10 segundos para las acciones es arbitraria. Posibles soluciones: cada cliente de juego crea un nuevo juego (esperando a otros jugadores, o invitándolos) especificando el número mínimo, máximo de jugadores, el “timeout” en segundos,  y la ventana de tiempo para mantener las acciones.

Pero recuerden: es todavía una beta. Una nueva versión está en camino.

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Programando Juegos Sociales en Línea (Parte 3) Tankster, Blob Storage y JSONP

Wednesday, August 3rd, 2011

Anterior Post
Siguiente Post

En mi anterior post escribí sobre el Windows Azure Social Gaming Toolkit, y su ejemplo de juego en línea, para varios jugadores. Ver:

http://watgames.codeplex.com/
http://www.tankster.net/

(El lunes 1ro de Agosto, salió una nueva beta: http://watgames.codeplex.com/releases/view/70342)

Hay dos post interesantes de @ntotten acerca de los “internals” del juego y las decisiones que se tomaron:

Architecture of Tankster – Introduction to Game Play (Part 1)
Architecture of Tankster– Scale (Part 2)

Vale la pena mencionar acá el fragmento de Nathan:

Before we begin with an explanation of the implementation, it is important to know what our goals where for this game. We had three primary goals when building this game.

  1. The game must scale to handle several hundred thousand concurrent users.*
  2. The architecture must be cost effective.
  3. The architecture must be flexible enough for different types of games.

* This is the goal, but there are still some known limitations in the current version that would prevent this. I will be writing more about that later.

Ahora, en este post, quiero escribir sobre el uso e implementación de blob storage en Tankster.

Primero: ¿por qué usar blob storage? Si jugamos Tankster en modo de práctica, el cliente Javascript no necesita usar el backend de Azure. El código Javascript puede acceder al web role y al blob storage usando llamadas Ajax/JSON. Y en juego multi-jugador (en el modo de juego de Tankster llamado Skirmish), cada cliente “pollea” el blob storage para conseguir el estado del juego (los disparos de los tanques, mensajes de chat, otro estado). Pero de nuevo: ¿por qué blob storage? Hay razones monetarias (costo de acceder a la Web Role API vs el costo de leer un blob), pero no conozco los precios de Azure (ya saben, “el maestro no toca la plata” ;-). Pero hay otra razón: repartir el trabajo, dando algo de aire a las instacias de web role. En vez de hacer todo con la API alojada en el web role, se leen blobs desde Azure storage.

Hay una clase AzureBlobContainer en el proyecto de librería de clases Tankster.Common:

public class AzureBlobContainer<T> : IAzureBlobContainer<T>
{
    private readonly CloudBlobContainer container;
    private readonly bool jsonpSupport;
    private static JavaScriptSerializer serializer;
    public AzureBlobContainer(CloudStorageAccount account)
        : this(account, typeof(T).Name.ToLowerInvariant(), false)
    {
    }
    public AzureBlobContainer(CloudStorageAccount account, bool jsonpSupport)
        : this(account, typeof(T).Name.ToLowerInvariant(), false)
    {
    }
    public AzureBlobContainer(CloudStorageAccount account, string containerName)
        : this(account, containerName.ToLowerInvariant(), false)
    { 
    }
	//....

 

La clase usa generics. El tipo T puede ser un Game, GameQueue u otro tipo .NET. Pueden reusar esta clase en otros proyectos: es “game agnostic”, no está escrita para este proyectos, sino que se puede usar en otros. Los varios constructores le agregan flexibilidad para tests.

Este es el código para grabar un objeto tipado en un blob:

public void Save(string objId, T obj)
{
    CloudBlob blob = this.container.GetBlobReference(objId);
    blob.Properties.ContentType = "application/json";
    var serialized = string.Empty;
    serialized = serializer.Serialize(obj);
    if (this.jsonpSupport)
    {
		serialized = this.container.Name + "Callback(" + serialized + ")";
    }
    blob.UploadText(serialized);
}

El objeto (tipo T) es serializado a JSON. Y no sólo JSON: noten que hay un Callback (podríamos remover esto, si nuestro proyecto no lo necesita). Entonces, un objeto juego se graba como (he robado…. eh… tomado prestado 😉 este código del post de Nathan):

gamesCallback(
    {"Id":"620f6257-83e6-4fdc-99e3-3109718934a6"
    ,"CreationTime":"\/Date(1311617527935)\/"
    ,"Seed":1157059416
    ,"Status":0
    ,"Users":[
        {"UserId":"MxAb1iZtey732BGsWsoMcwx3JbklW1xSnsxJX9+KanI="
        ,"UserName":"MxAb1iZtey732BGsWsoMcwx3JbklW1xSnsxJX9+KanI="
        ,"Weapons":[]
        },
        {"UserId":"ZXjeyzvw7WTdP8/Uio4P6cDZ8jmKvCXCDp7JjWolAOY="
        ,"UserName":"ZXjeyzvw7WTdP8/Uio4P6cDZ8jmKvCXCDp7JjWolAOY="
        ,"Weapons":[]
        }]
    ,"ActiveUser":"MxAb1iZtey732BGsWsoMcwx3JbklW1xSnsxJX9+KanI="
    ,"GameRules":[]
    ,"GameActions":[]
    })

¿Por qué el callback? Para soportar JSONP:

http://en.wikipedia.org/wiki/JSONP

JSONP or “JSON with padding” is a complement to the base JSON data format, a pattern of usage that allows a page to request data from a server in a different domain. As a solution to this problem, JSONP is an alternative to a more recent method called Cross-Origin Resource Sharing.

Under the same origin policy, a web page served from server1.example.com cannot normally connect to or communicate with a server other than server1.example.com. An exception is the HTML <script> element. Taking advantage of the open policy for <script> elements, some pages use them to retrieve Javascript code that operates on dynamically-generated JSON-formatted data from other origins. This usage pattern is known as JSONP. Requests for JSONP retrieve not JSON, but arbitrary JavaScript code. They are evaluated by the JavaScript interpreter, not parsed by a JSON parser.

Si Ud. no conoce JSON y JSONP, acá hay un tutorial con ejemplo usando JQuery (la misma librería usada por el código de Tankster):

Cross-domain communications with JSONP, Part 1: Combine JSONP and jQuery to quickly build powerful mashup

Pero (ya saben, siempre hay un “pero” en todo proyecto de software) el blob storage de Azure no soporta el uso de JSONP URLs (que tienen un parámetro indicando el id de un callback generado al azar):

Query JSON data from Azure Blob Storage with jQuery

Hay una solución propuesta en un hilo de Stack Overflow. El agente Maxwell Smart diría: ah! el “viejo truco” de tener el callback ya especificado en el blob!

dataCallback({"Name":"Valeriano","Surname":"Tortola"})

Pueden leer una más detallada descripción del problema y solución con código de ejemplo en el post de @woloski’s post (Buen práctica Matías! escribir un post con código!):

Ajax, Cross Domain, jQuery, WCF Web API or MVC, Windows Azure

Un problema a tener en cuenta: vean que el nombre del callback está “hardcodeado” en el blob. El gamesCallback presente en el blob de Game Status debe ser el nombre de una función global. Pero pueden cambiar el texto del blob a cualquier Javascript válido.

¿Y del lado cliente? Pueden estudiar el código de gskinner en el proyecto Tankster.GamePlay, en src\ui\net\ServerDelegate.js:

(function (window) {
    goog.provide('net.ServerDelegate');
    goog.require('net.ServerRequest');
    
    var ServerDelegate = function () {
        throw new Error('ServerDelegate cannot be instantiated.');
    };
    
    var p = ServerDelegate;
    
    p.BASE_API_URL = "/";
    p.BASE_BLOB_URL = "http://tankster.blob.core.windows.net/";
    p.BASE_ASSETS_URL = "";
    p.load = function (type, data, loadNow) {        
        var req;
        if (p.CLIENT_URL == null) {
            p.CLIENT_URL = window.location.toString();
        }
//....

There is an interesting switch:

switch (type) {
    //Local / config calls
    case 'strings':
        req = new ServerRequest('locale/en_US/strings.js', 
		   null, 'jsonp', null, 'stringsCallback'); break;
    //Game calls
    case 'endTurn':
        req = new ServerRequest(apiURL+'Game/EndTurn/', 
		    null, 'xml'); break;
    case 'leaveGame':
        req = new ServerRequest(apiURL+'Game/Leave/'+data.id, 
		    {reason:data.reason}, 'xml', ServerRequest.POST); break;
    case 'playerDead':
        req = new ServerRequest(apiURL+'Game/PlayerDead/', 
		    null, 'json'); break;
    case 'gameCreate':
        req = new ServerRequest(apiURL+'Game/Queue', data, 
		    'xml', ServerRequest.POST); break;
    case 'usersGame':
        req = new ServerRequest(blobURL+'sessions/'+data, 
		    null, 'jsonp', null, 'sessionsCallback'); break;
    case 'gameStatus':
        req = new ServerRequest(blobURL+'games/'+data, 
		    null, 'jsonp', null, 'gamesCallback'); break;
    case 'gameQueueStatus':
        req = new ServerRequest(blobURL+'gamesqueues/'+data, 
		    null, 'jsonp', null, 'gamesqueuesCallback'); break;
    case 'notifications':
        req = new ServerRequest(blobURL+'notifications/'+data, 
		    null, 'jsonp'); break;
    
    //User calls
    case 'verifyUser':
//...

Vean el ‘gameStatus’: usa ‘jsonp’ como formato. Pienso que el  ‘gamesCallback’ como parámetro no es necesario: pueden usar cualquier otro nombre, la función a llamar reside en el contenido del blob.

Yo pienso que hay alternativas a esta forma de mantener el estado del juego. El blob refleja a la entidad Game (ver Tankster.Core\Entities\Game.cs). Pero podría ser implementado en dos blobs por juego:

– Uno de los blobs, conteniendo el estado inicial (jugadores, posiciones, armas…) y la lista de TODA la historia del juego (pienso que todo esto se puede reusar para juegos de tablero, como ajedrez o go, donde puede ser interesante tener la historia de las movidas del juego).

– Otro blob, conteniendo las “novedades” (los últimos 10 segundos de las acciones del juego). De esta forma, los clientes “pollean” este blob, que debería ser más corto que el actual.

El precio a pagar: el servicio en el Web Role que atiende las acciones de los juegos DEBE actualizar LOS DOS blobs. Pero esta actualización sólo ocurren cuando un jugador envía una movida (disparo, chat..). La consulta reiterada desde cada cliente se dispara, digamos, cada un segundo. Actualmente, el blob del juego mantiene los últimos 10 segundos de las acciones Y ADEMAS el estado inicial, jugadores, armas, etc… CADA CLIENTE lee reiteradamente esa información. En la implementación que sugiero, los datos a leer cada vez son menos. En el caso que un cliente se desconecte y reconecte, podría leer el blob que contiene el juego completo, para volver a sincronizar su estado.

La separación de esta información en dos blobs podría mejorar la escalabilidad de la solución. Pero ya saben: premature optimization is the root of all evil ;-). Si tomamos ese camino para almacenar y recuperar los juegos, deberíamos ejecutar algunos tests de carga para realmente conocer las consecuencias de esa decisión.

Tengo que seguir con más análisis de código de Tankster, patrones y desarrollo de juegos sociales en general.

Algunos enlaces:

WAT is to Social Gaming, as Windows Azure is to… | Coding4Fun Blog | Channel 9
Gracias! “Vieron a luz” y mencionan mi post 😉
Episode 52 – Tankster and the Windows Azure Toolkit for Social Games | Cloud Cover | Channel 9
Microsoft tailors Azure cloud services to social game developers | VentureBeat
Windows Azure Toolkit for Social Games Released to CodePlex – ‘Tankster’ Social Game Built for Windows Azure
Tankster, a Social Game Built for Windows Azure | Social Gaming on Windows Azure | Channel 9
Social Gaming on Windows Azure | Channel 9
Build Your next Game with the Windows Azure Toolkit for Social Games
Microsoft delivers early build of Windows Azure toolkit for social-game developers | ZDNet

http://www.delicious.com/ajlopez/tankster

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Programando Juegos Sociales en Línea (Parte 2) Tankster y Windows Azure Toolkit For Social Games

Friday, July 22nd, 2011

Anterior Post
Siguiente Post

El miércoles pasado, 20 de Julio, Microsoft liberó una versión “preview” del Windows Azure Toolkit for Social Games, y publicó una versión beta (con código) de un primer juego de demostración.

Pueden bajar el código desde Codeplex: http://watgames.codeplex.com/

Pueden jugar el juego en línea en: http://www.tankster.net/

La solución actual tiene estos proyectos:

Tankster.GamePlay es un Web Role. Hay un único WorkerRole. Tankster.Core es una librería de clases. Hay código interesante en Tankster.Common: utilitarios Azure para acceder a repositorios, un “job engine”, y más; todo ese código es independiente del juego, podría usarse en cualquier proyecto Azure.

Estos son mis primeros cortos comentarios sobre el código y la implementación de las “features” de este juego (recuerden, es un beta! Algunas de estas implementaciones podrían cambiar en el próximo “release”):

– Tecnología cliente: HTML5, Javascript, EaselJs (para programación del canvas, que permite implementar la parte gráfica).
– Tecnología servidor: ASP.NET MVC 3, algunas vistas Razor para pruebas (tema interesante: ¿Cómo probar el juego cuando sin usar el juego REAL), WCF Web API (otro tema interesante: ver tecnologías alternativas para manejar la actividad del juego. Es importante: un tema a cuidar en este tipo de aplicaciones es cómo responder a una gran carga desde el cliente)

– Hay un modelo en el cliente y entidades en Javascript. Vean See src/model, src/game.

– Hay un modelo del juego There is a server game model (see Tankster.Core class library project)

 

– Pueden jugar en modo “single player” o pueden elegir “multi-player online”. En este caso, el juego usa el ACS Portal para permitir la autenticación usando Federated Authentication:

– El código cliente reside en una sola página: index.html (con montones de archivos javascript referenciados desde ahí)
– El código cliente envía JSON (comandos) a los WCF Web API endpoints, usando Ajax/JQuery. Hay varios servicios publicados, exponiendo una interfaz tipo REST

routes.MapServiceRoute<GameService>("game");
routes.MapServiceRoute<AuthService>("auth");
routes.MapServiceRoute<UserService>("user");
routes.MapServiceRoute<TestService>("test");

– Mucha de la actividad del juego es enviado al servicio game/command. El servicio (en el web role) actualiza el estado del juego guardándolo en un blob en el Azure Storage. Fragmento de código:

// Add gameAction
var gameAction = new GameAction
{
    Id = Guid.NewGuid(),
    Type = commandType,
    CommandData = commandData,
    UserId = this.CurrentUserId,
    Timestamp = DateTime.UtcNow
};
// Cleanup game actions lists
for (int i = 0; i < game.GameActions.Count(); i++)
{
    if (game.GameActions[i].Timestamp < DateTime.UtcNow.AddSeconds(-10))
    {
        game.GameActions.RemoveAt(i);
        i--;
    }
}
game.GameActions.Add(gameAction);
this.gameRepository.AddOrUpdateGame(game);

– El estado del juego es “poleado” por los clientes javascript consultando directamente el blob storage. De esta forma, el Web Role ASP.NET MVC tiene menos carga.
– El blob reside en el mismo dominio, así que no son necesarias llamadas Ajax cross-domain. Pero el juego está preparado para ese tipo de llamadas, reemplezando el objeto XmlHttpRequest por un componente Flash.
– El modo de juego Skirmish (cinco jugadores en una batalla) está hecho encolando los jugadores hasta llegar a cinco. Ese trabajo lo hace el Worker Role. Va publicando el estado en un blob que es “poleado” por los clientes.
– Las estadísticas son procesadas en el worker role: los clientes javascrip envían comando al Web Role, éste selecciona algunos para estadísticas y los envía a una cola Azure. No se ocupa de las estadísticas en el momento, para no afectar al cliente. Finalmente, esos datos para estadísticas se manejan en el Worker Role.
– User status, Game status, Skirmish Game Queue status son blobs.
– Las estadísticas se guardan en SQL Azure.
– El único worker role tiene un Job Engine (motor de trabajos) para procesar varias tareas, ejemplo “fluent”:

// Game Queue for Skirmish game
Task.TriggeredBy(Message.OfType<SkirmishGameQueueMessage>())
    .SetupContext((message, context) =>
    {
         context.Add("userId", message.UserId);
    })
    .Do(
        new SkirmishGameQueueCommand(userRepository, 
            gameRepository, workerContext))
    .OnError(logException)
    .Start();

Hay varios puntos para comentar y discutir, alimento para futuros posts. Seguramente la solución actual evolucionará y nuevas versiones serán publicadas (¿esta semana? ¿la próxima?). Pero es interesante ver publicado un juego en línea y su código disponible para analizarlo.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Ejecutando AjSharp en Azure

Tuesday, June 14th, 2011

El code kata de este pasado fin de semana fue algo que estaba pensando desde el último año: ejecutar AjSharp (mis post en español de AjSharp) dentro de un Worker Role de Azure. Pero desde antes que pienso: un intérprete como AjSharp puede servir de lenguaje de scripting en sistemas distribuidos. Esta incursión en Azure es una prueba de concepto interesante.

La idea es: una instancia de worker role recibe un texto via una cola de mensaje. El texto contiene código AjSharp y lo ejecuta. La salida (digamos, los PrintLine) se captura como texto y se envía como mensaje a otra cola.

El resultado del ejercicio está dentro de mi AjCodeKata project: tienen que bajarse trunk\Azure\AzureAjSharp Y TAMBIEN trunk\AjLanguage (donde está el proyecto AjSharp).

La solución:

Los proyectos:

AzureAjSharp.WorkerRole: un worker role de ejemplo, con estas líneas agregadas:

CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
Processor processor = new Processor(account);
processor.Start();

Azure.AjSharp: la librería de clases principal del proyecto. Contiene una clase Processor. Los constructores necesitan una cuenta de cloud y los nombres (opcionales, hay valores “default”): cola de requests, responses y un blob container. La cola de request contiene mensajes con código AjSharp para ejecutar (recuerden que AjSharp puede acceder a clases y objetos .NET: pueden invocar código compilado tranquilamente). La cola de response tiene el texto de salida de esas ejecuciones. El código de arriba processor.Start() inicia la lectura y ejecución de esos mensajes AjSharp.

AzureAjSharp.Console: Es el programa que lee líneas de la consola, y cuando ingresamos una línea que contiene sólo “send”, el texto ingresado se convierte en un mensaje Azure que va a la cola de requests. Este programa también va leyendo (en un Thread aparte) los mensajes que le llegan por la cola de responses, imprimiendo el resultado.

AzureLibrary: Clases auxiliares que usé en otros ejemplos de Azure.

AjSharpVS2010, AjLanguageVS2010: Implementación de AjSharp .

Cuando ejecuto el programa de consola, puedo enviar código AjSharp para ejecutar en un worker role, y recibir el resultado:

Vean que uso clases .NET como DirectoryInfo.

Y hay más: AjSharp soporta el Include(“nombredearchivoaserincluido”); donde el archivo contiene más código AjSharp. Modifiqué el lanzamiento de la máquina AjSharp para que tenga una versión cambiada de Include: ahora busca el contenido en un blob. Eso permite incluir y reusar código que no quepa en un mensaje, desde un blob container

Un gráfico:

Entonces, subí algo de código (los archivos originales están en el directorio Examples del proyecto Azure.AjSharp) en el contenedor blob llamado ajsfiles (para esta prueba en mi DevStorage):

(Estoy usando Neudesic Azure Storage Explorer, pero podría usar también CloudBerry Explorer for Azure Storage: soporta el manejo de carpetas en árbol).

Esta es el resultado de la ejecución de código con include de HelloWorld.ajs, y ForOneToTen.ajs:

Próximos pasos:

– Escribir más código utilitario en AjSharp, para ser incluido en otros programas, como: utilitarios de manejo de File y Directory, download y upload de blobs, envío y recepción de mensajes en cola, mensajes a todos los worker role intances, bajada y carga de assemblies, objetos a compartir entre request y request de AjSharp, etc. El cielo es el límite! 😉

Entonces, podemos (nosotros o un programa) enviar tareas dinámicamente y recibir resultados. Algo para tener: Guids para identificar tareas y sus resultados; una interfaz web; resultados almacenados como blobs; cache (y flush) de los archivos incluidos, etc…

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez