Montar un servidor REST (II)

Hace unos meses empecé a explicar cómo montar un servidor REST sencillo para servir datos a una aplicación móvil (o de cualquier otro tipo) de forma rápida y reusable. En esa segunda parte voy por fin a completar la explicación entrando en la parte más interesante del código. Aprovecho para aclarar algo que se me pasó en la anterior entrega: parte de este código y las ideas de cómo estructurar todo el servicio en si provienen de este estupendo artículo.

Nos quedamos por ver la clase que debía procesar la petición. En nuestro proyecto, para cada recurso que necesitemos manejar deberemos crear una clase RecursoController, derivada de la clase RESTcontroller . También será necesario crear una clase RecursoModel, hija de RESTmodel,  que se encargará de la interacción con la base de datos. Pero lo veremos después.

La clase RESTcontroller comienza con las definición de unas pocas propiedades y cuatro métodos  abstractos que deberemos implementar para cada entidad.

abstract class RESTcontroller
{
	protected $classname;
	protected $data;
	public $ID;

	abstract function post(&$error);
	abstract function put(&$error);
	abstract function get(&$error);
	abstract function delete(&$error);

 	.
	.
	.
}

La propiedad $data se utiliza para almacenar los datos recibidos en la petición. $classname se utiliza para que la clase hija indique a RESTController qué tipo de entidad maneja. Se utiliza para crear el objeto JSON o XML adecuadamente. La propiedad pública $ID sirve para identificar el recurso que se está tratando. Cuando se trabaja con una colección de recursos esta propiedad vale NULL.

Los métodos abstratos son los que debemos completar en cada clase derivada para responder a cada unas de las 4 operaciones REST básicas. Deben devolver un array de pares clave-valor (un diccionario hablando con mayor propiedad), cuyas claves son los nombres de las propiedades y los valores los obtenidos en la operación. Si el resultado de la operación es un conjunto de entidades (lo cual ocurre generalmente en las operaciones get en las que no especificamos un ID), estos métodos deben devolver un array de diccionarios como los que he descrito. Si queremos notificar algún error, lo haremos a través del parámetro por referencia $error. Por ahora esto puede resultar demasiado abstracto, pero espero que quede más claro al final de esta entrada.

Como vimos en la anterior entrada, index.php, entrega el control al método processRequest de la clase recursoController:

public function processRequest($request_method, $content_type, $data)
{	
	// Copiamos los parámetros recibidos 
	// para usarlos en la case derivada.
	$this->data = $data;
	// Obtenemos el nombre del recurso
	// a partir de la propiedad o del 
	// nombre de la clase
	$classname = isset($this->classname)?$this->classname:get_class($this);

	// En función del tipo de petición
	// llamamos a un método u otro
	// (definidos en la clase derivada)
	switch($request_method) {		
	case 'get':
		$error = "";
		$result = $this->get($error);
		.	
		.
		.
		break;	
	case 'post':
	case 'put':	
		$error = "";
		if ($request_method == 'post') {
			$result = $this->post($error);
		} else {
			$result = $this->put($error);
		}	
		.
		.
		.
		break;
	case 'delete':
		if($this->delete($error)) {
			.
			.
			.
		}
		break;
	default:
		RESTcontroller::sendError(500, "Unkwon request method", $content_type);
	}
}

El código de arriba está explicado básicamente en sus comentarios. Simplemente en función del método solicitado por el cliente se llama a un método u otro (que debemos implementar en la case derivada). Si os fijáis, utilizo el mismo caso del switch para los métodos putpost, porque como vamos a ver el procesamiento de la respuesta es el mismo. En caso de que el método pedido no sea reconocido, se devuelve un error mediante un método de clase que no tiene mucho misterio, y el poco que tiene será desvelado cuando terminemos con este.

Las clases derivadas de RESTController nos van a devolver los resultados  como diccionarios (pares clave-valor) si se tratan de un solo recurso o bien un array de estos diccionarios si es un conjunto de resultados. En el caso de que haya habido un error, devuelven el valor null. Para devolver esos datos al cliente REST, debemos darles el formato adecuado, bien sea XML o JSON. La conversión de un diccionario en JSON es trivial en PHP usando json_encode($array). Para hacer la conversión a XML necesitamos apoyarnos en una pequeña función recursiva como se muestra en esta respuesta de stackoverflow. Así es como quedaría el tratamiento de las peticiones post y put, en las que si todo es correcto devolvemos al cliente el único registro insertado o modificado, respectivamente.

$error = "";
if ($request_method == 'post') {
	$result = $this->post($error);
} else {
	$result = $this->put($error);
}

if($result) {
	if ($content_type == 'json')
	{
		RESTcontroller::sendResponse(200, json_encode($result), 'application/json');
	}
	else 
	{
		$xml = new SimpleXMLElement("<?xml version="1.0"?><$classname/>");
		RESTcontroller::array_to_xml($result, $xml);					
		RESTcontroller::sendResponse(200, $xml->asXML(), 'application/xml');
	}
} else {
	RESTcontroller::sendError(500, $error, $content_type);
}

Y este sería el código de nuestro método protegido para convertir un array en un documento XML:

protected static function array_to_xml($data, &$xml) {
	    foreach($data as $key => $value) {
	        if(is_array($value)) {
	            if(!is_numeric($key)){
	                $subnode = $xml->addChild("$key");
	                RESTcontroller::array_to_xml($value, $subnode);
	            }
	            else{
	                RESTcontroller::array_to_xml($value, $xml);
	            }
	        }
	        else {
	            $xml->addChild("$key","$value");
	        }
	    }
	}

El tratamiento que hacemos de la respuesta de la clase derivada de RESTController es ligeramente diferente, pues tenemos que devolver correctamente tanto registros individuales como conjunto de registros. La forma que tenemos de diferenciar ambos casos es preguntando si está definida una clave llamada ID en el diccionario. Si existe (en el primer ‘nivel’ de profundidad) estamos tratando un registro individual. En caso contrario se trata de un conjunto de ellos.

case 'get':
$error = "";
$result = $this-&gt;get($error);

if ($result) {
	if ($content_type == 'json')
	{
		 // Conjunto de conjunto de resultados		
		if (!array_key_exists("ID", $result)) {
			$result = array("ArrayOf$classname" =&gt; $result);
		}
		RESTcontroller::sendResponse(200, json_encode($result), 'application/json');
	}
	else
	{	
		// Consulta de un sólo elemento			
		if (array_key_exists("ID", $result)) {
			$xml = new SimpleXMLElement("<!--?xml version="1.0"?-->&lt;$classname/&gt;");
			RESTcontroller::array_to_xml($result, $xml);
		} else { // Conjunto de conjunto de resultados
			$xml = new SimpleXMLElement("<!--?xml version="1.0"?-->&lt;ArrayOf$classname/&gt;");
			foreach($result as $object) {
      				$subnode = $xml-&gt;addChild("$classname");
      				RESTcontroller::array_to_xml($object, $subnode);
			}					
		}

		RESTcontroller::sendResponse(200, $xml->asXML(), 'application/xml');
	}	
} else {
	RESTcontroller::sendError(500, $error, $content_type);
}
break;

En realidad, podríamos tratar peticiones get, post y put en un solo caso del switch, pero por claridad los vamos a mantener separado por ahora.

Por último, en las peticiones de tipo delete se devolverá un mensaje de OK si se ha completado la operación con éxito, y el error en caso contrario.

if($this->delete($error)) {
	if ($content_type == 'json')
	{
		RESTcontroller::sendResponse(200, json_encode(array("message" => "OK")), 'application/json');
	}
	else 
	{						
		RESTcontroller::sendResponse(200, "<?xml version="1.0"?><message>OK</message>", 'application/xml');
	}
	RESTcontroller::sendResponse(200);
} else {
	RESTcontroller::sendError(500, $error, $content_type);
}

El código de los métodos de clase sendResponse y getStatusCodeMessage es exactamente el mismo que el del artículo que mencioné arriba.

¿CÓMO SE UTILIZA ESTA CLASE ABSTRACTA?

Cuando diseñamos un servicio REST, normalmente servirá para proporcionar servicios para la manipulación de datos persistentes, almacenados en bases de datos SQL o no SQL. En ambos casos tendremos recursos que se corresponderán con tablas o colecciones. En cualquier caso, tendremos que proporcionar un recurso con un nombre determinado. Si queremos proporcionar servicios sobre un recurso llamado User, lo primero que debemos hacer es crear un fichero user.php que tendrá el siguiente aspecto inicial:

<?PHP
require_once('RESTcontroller.php');

class UserController extends RESTcontroller {
	function UserController() {
		$this->classname="User";
	}

	function get(&$error) {
		$error = '';
		$response = '':

		return $response;
	}

	function post(&$error) {
		$error = '';
		$response = '':

		return $response;
	}

	function put(&$error) {
		$error = '';
		$response = '':

		return $response;
	}

	function delete(&$error) {
		$error = '';
		$response = '':

		return $response;
	}
}

?>

En este fichero definimos una clase UserController heredada de la clase RESTController. Partiendo de esa plantilla, tendremos que escribir nuestro código en función de la lógica de nuestra aplicación y del motor de datos que tengamos debajo, en parte encapsulado por una clase RESTModel. Para continuar con esta entrada, vamos a suponer que trabajamos con una base de datos MySQL. De esta forma también podremos presentar la clase RESTModel. El código competo de nuestra clase UserController quedaría así:

<?PHP
require_once('RESTcontroller.php');
require_once('RESTmodel.php');

class UserModel extends RESTmodel {
	function UserModel() {
		$this->tablename = "Users";
		$this->properties["ID"] = 0;
		$this->properties["nick_name"] = "";
		$this->properties["password"] = "";
	}
}

class UserController extends RESTcontroller {
	function UserController() {
		$this->classname="User";
	}

	function get(&$error) {
		$model = new UserModel();
		$error = '';

		// Leer los datos de usarios, conocido su ID
		if (isset($this->ID)) {
			$model->properties["ID"] = $this->ID;
			$response = $model->select();	
			if (!$response) {
				$error = "Not found User with ID ".$this->ID; 
			}
		// Login
		} else if (isset($this->data["nick_name"]) && isset($this->data["password"])) {
			$nick_name = $this->data["nick_name"];
			$password = $this->data["password"];
			$response = $model->select("password='$password' AND nick_name='$nick_name'");
			if (!$response) {
				$error = "Authentication failed"; 
			}
		// Usuarios con nick similar a un nombre dado
		} else if (isset($this->data["name"]) ) {
			unset($model->properties["password"]);
			$name = $this->data["name"];
			$response = $model->selectAll("`nick_name` LIKE '%$name%'");
			if (!$response) {
				$error = "No users found"; 
			}	
		// Petición no reconocida
		} else {
			$response = false;
			$error = "No parameters";
		}

		return $response;
	}

	function post(&$error) {
		// Validamos los datos
		if (isset($this->data["password"]) && $this->data["password"] != "" &&
			isset($this->data["nick_name"]) && $this->data["nick_name"] != "") {	

			$model = new UserModel();					

			// Comprobamos que no exista ya un usuario con ese nick
			$response = $model->selectAll("`nick_name`='".$this->data["nick_name"]."'");	
			if ($response && count($response)>0) {
				$error = "User nick already exits";
				return false;
			} 

			// Copiamos los datos en las propiedades del modelo
			$model->properties["nick_name"] = $this->data["nick_name"];
			$model->properties["password"] = $this->data["password"];	

			$response = $model->insert();
			if ($response) {
				$error = "";
			} else {
				$response = false;
				$error = "Database Insert Error";
			}

		} else {
			$response  = false;
			$error = "Imcomplete User data";
		}	

		return $response;
	}

	function put(&$error) {
		$model = new UserModel();

		// Comprobamos si existe el usuario
		// y de paso precargamos los datos existentes
		$model->properties["ID"] = $this->ID;
		$found = $model->select();

		if ($found) {		
			// Cmabiamos los datos que hayamos recibido					 
			if(isset($this->data["password"]) && $this->data["password"] != "") {						
				$model->properties["password"] = $this->data["password"];	
			}

			$response = $model->update();
			if (!$response) {
				$error = "Database Update Error";
			}	
		} else {
			$response = false;
			$error = "Not found resource with ID ".$this->ID; 
		}
		return $response;
	}

	function delete(&$error) {
		$model = new UserModel();
		$model->properties["ID"] = $this->ID;
		$result = $model->delete();
		if ($result) {
			$error = "";
		} else {
			$error = "Error deleting User ".$this->ID;
		}

		return $result;
	}
}

En cada método he añadido los comentarios necesarios para entender el código. En el método get, en función de los parámetros recibidos se realiza una consulta u otra al modelo. En el método post, realizamos una validación de los datos obligatorios para hacer la inserción. En el método put, primero comprobamos que exista el registro, cargando de paso sus datos en las propiedades del modelo, y luego modificamos los datos que hayamos recibido en la petición, manteniendo el resto intacto. El método delete tiene poco que comentar.

Al principio del fichero hemos definido una clase derivada de la clase RESTModel. En ella definimos una propiedad llamada properties, que contiene un diccionario con los nombres de los campos de nuestra base de datos. Definimos además otra propiedad tablename que indica el nombre de la tabla que vamos a manipular. Mediante estas propiedades podemos crear de forma automatizada las consultas SELECT, INSERT, UPDATE y DELETE en la clase RESTModel. Esta clase además expone una serie de métodos que permite hacer estas operaciones básicas:

<?php

abstract class RESTmodel
{
	public $properties = array();
	protected $tablename;

	function update() {

	}

	function insert() {

	}

	function select($where = "") {

	}

	function selectAll($where = "") {

	}

	function delete() {

	}
}

?>

El funcionamiento de la clase RESTModel da para un futuro artículo. Antes de finalizar no hay que olvidar incluir el fichero user.php en index.html.

require_once('user.php');

Que aproveche.

Wanderer

Hace un tiempo hice el experimento de crear un juego en un rato. Estuve publicando mis progresos en twitter, pero de ahí se lo lleva el viento. Como quiero probar Calabash como framework para hacer TDD, he pensado que podría utilizar ese mini juego para ello. Aprovecho la ocasión para liberar con licencia MIT el código que resultó del experimento y anunciarlo aquí, junto con el vídeo que grabé para demostrar su funcionamiento.

A veces me aburro…

En realidad no. Si me aburro hago tontunadas como generar un gráfico estadístico aleatorio usando la librería libgd.

 

Posición inicial del juego
Use para su estadística inventada

Edito: Añado el código fuente:

<?php
$padding = 10;
$width = 500;
$height = 400;
$hor_div = 10;
$ver_div = 12;

header("Content-Type: image/png");
$im = @imagecreate($width, $height)
    or die("Cannot Initialize new GD image stream");
$color_fondo = imagecolorallocate($im, 230, 230, 255);

$color_texto = imagecolorallocate($im, 0, 0, 0);

imagerectangle ($im, $padding, $padding, $width - $padding, $height - $padding, $color_texto);

$step = ($height - $padding*2) / $ver_div;
for($y=$padding; $y<$width - $padding; $y+=$step) {
	imageline($im, $padding, $y, $width - $padding, $y, $color_texto);
}

$step = ($width - $padding*2) / $hor_div;
for($x=$padding; $x<$width - $padding; $x+=$step) {
	imageline($im, $x, $padding, $x, $height - $padding, $color_texto);
}

$color_graph = imagecolorallocate($im, 255, 0, 0);
$prev = rand($padding, $height- $padding);
for($x=$padding+$step; $x<=$width - $padding; $x+=$step) {
	$curr = rand($padding, $height- $padding);
	imageline($im, $x-$step, $prev, $x, $curr, $color_graph);
	$prev = $curr;
}

imagepng($im);
imagedestroy($im);
?>

Un juego en un rato

Hace mucho tiempo vi un juego en un parque de juego que me pareció curioso. Se trata de un juego de 4 en raya, en el que cada jugador mueve una de sus fichas por turno, sin límite de cambios de dirección o distancia. El juego se llama “Vagabundo” y es así:

Juego del vagabundo
Juego del vagabundo

En la foto se ve que se puede mover cómo la rejilla por la que se mueven las bolas es de 6×6 ranuras. Sin embargo en las instrucciones dibujan una rejilla de 8×8.

Pues bien, desde el primer momento pensé que sería divertido hacer un juego para móviles así. Esto fue hace cosa de un año. Hace unas semanas hice un prototipo rápido de la interfaz para iOS. Empleé una par de horas de una tutoría en las que me aburría mucho. En principio solo se podía mover una bola. Un par de ratos más, incluido el echado esta misma tarde, y ya muevo todas las bolas evitando que colisionen y todo:

Posición inicial del juego
Posición inicial del juego

Pues bien, me he propuesto hacer el experimento de realizar el juego “en un rato” y contarlo. No parto de cero, porque ya tengo código previo que reutilizaré de otros proyectos, incluido el del servidor (porque será un juego para dos jugadores on-line) que empecé a explicar hace ya unos meses.

Algunas bolas movidas
Algunas bolas movidas

A ver cómo sale el experimento. Podéis seguirlo en mi cuenta de twitter que también tengo algo abandonada.

Montar un servidor REST (I)

BLA, BLA, BLA, …

Cuando uno se quiere a poner a programar un juego para móviles, social, con partidas para varios jugadores y de mucho éxito (¡ja¡), lo primero que uno quiere hacer es ponerse a programar la lógica del juego, el tablero, la interacción con este, etc. Apetece algo menos desarrollar el flujo entre pantallas, la pantalla de configuración, el login,… es decir, la fontanería. No es tan “cool”. En lo último que un se piensa meter es el alcantarillado sucio y oscuro del servidor que dará servicio a nuestro juego de éxito. Si uno busca en el diccionario de la programación de juegos en antónimo de “cool”, aparece la palabra “servidor”.

Pero en seguida que uno se pone a programar ese gran éxito, te das cuenta que apenas puedes avanzar si no tienes un servidor contra el que validar usuarios, crear partidas, retar adversarios, configurar notificaciones PUSH, … Un auténtica lata. Así que aquí me encuentro, abandonando la chachi-interfaz de mi super-exito para buscar la mejor forma de escribir un servidor, de la forma más rápida posible y, ya que estamos, que me sirva para cualquier plataforma del mundo-mundial (™) y que pueda reaprovechar para futuros hits. Puedo uno inventarse una personalísima interfaz, con un protocolo propio, o puedo uno aprovechar que ya existe la rueda y montar un servidor REST(casiful). El problema es que puede que usted, como yo, se encuentre con que no tiene ni paj pueñetera idea de cómo montar eso. Después de curiosear por los blogs de otros, ver librerías, framework y más frameworks, uno decide la tontería de escribirse a pelo un mini-framework (o mini-plantilla en realidad) en PHP desde cero; ¡como un machote! Evidentemente como novato habré cometido mil barbaridades, estoy abierto a correcciones y estoy deseando que me las hagan.

Lo primero es explicar rápidamente y sin aburrir de que va eso del REST. Representational State Transfer es un conjunto de convenciones que nos permite interactuar con recursos (Datos, entidades, objetos, …) por medio del protocolo HTTP en un entorno cliente servidor. Es un protocolo no basado en estados por lo que no hay que gestionar sesiones y en el que cada recurso individual o conjunto de recursos puede ser accedido mediante una URI. Para que no entendamos, si quiero leer un conjunto de partidas en mi super-juego, tendré algo así:

http://api.superhitdelamuerte.com/partida

Y si quiero obtener los datos de la partida cuyo ID es 3454, accedería  a la dirección:

http://api.superhitdelamuerte.com/partida/3454

En función de la operación a la que quiera acceder, enviaremos los parámetros en la URL o en el cuerpo de la petición y haremos esta por unos de los métodos contemplado en el protocolo HTTP. Más adelante veremos de nuevo esto.

La respuesta de un servidor REST normalmente es dada en JSON, XML, HTML o algo similar. El cliente debe conocer el formato de la respuesta para poder manejarla.

Hasta aquí una explicación simple y probablemente insuficiente para tus ansias de conocimiento. Te invito a leer más sobre el tema

DÓNDE CARJ PUÑETAS ESTÁ EL CÓDIGO.

Bueno, vamos a la implementación que para eso estamos aquí. Lo primero que vamos a necesitar es escribir un .htaccess (lo siento, no me he puesto a mirarlo para IIS) que haga que toda las peticiones a nuestro servidor sean redirigidas hacia nuestro index.php. Desde allí iremos contruyendo toda nuestra infraestructura. Yo voy a crear subdominios específicos para las api de cada mega-hit que escriba del tipo api.mega-hit.com, de modo que tenga bien separado la API del resto de la web del proyecto, en una carpeta física separada, con sus reglas mod-rewrite particulares. Mi .htaccess tiene este aspecto:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase / 
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA]
RewriteRule .* - [E=HTTP_CONTENT_TYPE:%{HTTP:Content-Type},L] 
</IfModule>

Las primeras 5 reglas se encargan de decirle a Apache que redirija todas las peticiones HTTP al index.php. La última regla nos permite acceder al campo de cabecera “Content-Type” desde PHP mediante $_SERVER[‘HTTP_CONTENT_TYPE’].

Una vez que toda petición llega a index.php, debemos analizar la URL de estas para saber que recurso nos están solicitando y si nos están pidiendo una en concreto o un conjunto (recibimos un ID, aunque la desición de hacer una cosa u otra no la tomamos aquí). Debemos averiguar también que tipo de petición es (GET, POST, SET, DELETE) y el formato (content-type) en el que debemos devolver la respuesta, si decidimos que vamos a aceptar más de uno. Yo he escrito mi código para responder con datos en JSON o XML, ya que solo está pensado para que estos sean consumidos por programas y no por humanos.

<?php
// Separamos la URI de los parámetros
$uri= explode('?', $_SERVER['REQUEST_URI']);
// Troceamos la URI: [0] => dominio, [1] => Recurso, [2] => ID
$parts = explode('/', $uri[0]);

// Tipo de petición: GET, POST, PUT, DELETE
$request_method = strtolower($_SERVER['REQUEST_METHOD']);
// Respuesta que vamos a dar.
$content_type = (strpos($_SERVER['HTTP_CONTENT_TYPE'], 'json')) ? 'json' : 'xml';
$data = array();
// Si la petición ha sido de tipo PUT o POST, los datos vienen
// en el cuerpo de la petición. En caso contrario vinen en la
// misma URI.
if ($request_method == "post" || $request_method == "put") 
{
   $values = file_get_contents('php://input');
   parse_str($values, $data);
} 
else 
{
   $data = $_GET;
}

// Si hemos recibido un nombre de recurso...
if(count($parts)>1) {
   // Creamos un objeto de la clase "recursoController"
   // (Ya veremos cómo es esta clase y qué cosas hace)
   $classname = $parts[1]."Controller"; 
   $object = new $classname();

   // Si hemos recibido el ID, lo pasamos al objeto.
   // En caso contrario le pasamos NULL
   $object->ID = isset($parts[2])?$parts[2]:NULL;
   // Pedimos al objeto que procese la petición
   $object->processRequest($request_method, $content_type, $data);
} // No hacemos nada en caso contrario (else)
?>

Así de simple. La parte más dura del trabajo la realiza los objetos de las clases “recursoController” y “recursoModel”. De eso nos ocuparemos en próximas entregas. Lo breve, si bueno, dos veces breve.

Mis primeros pinitos desarrollando para iOS

Este fin de semana he dedicado unas horas a introducirme en el desarrollo de aplicaciones para iOS. Ya había hecho algún intento, pero no me había puesto a ello porque tanto en el trabajo como en casa tengo (tenía) Mac OS X Leopard y las versiones del SDK que se pueden descargar de la web Apple solo son válidas para Lion o Snow Leopard. Buscando por internet pude encontrar en el blog Drop the Nerd el enlace al SDK de iOS para Leopard, en la misma web de Apple. A continuación me bajé algún ejemplo ya hecho y descubrí que necesita firmar de alguna forma las aplicaciones para poderlas probar en un iPhone (aunque de todas formas aún no tengo ninguno). Por suerte hay formas de crearse uno mismo una firma digital para ello.

Con todo listo, me puse a buscar cursos que me permitieran aprender tanto Objetive-C como a desarrollar para iOS, que no son la misma cosa. Una de las páginas que más útil me ha resultado es esta en la que se enseña rápidamente las características de Objetive-C comparándolas con las equivalentes en otros lenguajes orientados a objetos como C++ o Java. Tras leer ese breve tutorial ya podía ir entendiendo código escrito por otros en Objetive-C. Tras leer lo que ya considera suficiente sobre Objetive-C decidí ponerme manos a la obra (lo que más nos gusta a los programadores) y busque un curso que ensañará paso a paso a programar un juego para iOS (en el fondo lo que queremos todos es hacernos ricos con el nuevo Angry Birds). He encontrado un curso muy básico pero que cubre la mayoría de los puntos más básicos del desarrollo un juego (bucle del juego, detección simple de colisiones, IA, pantalla inicial y sonido). Hay que tener un poco de paciencia con el curso porque se ve que tras un traslado o migración ha perdido la maquetación y algunos enlaces, pero es bastante productivo:

Y he aquí mi criatura: