js13k y Miss City (postmortem)

Desde el 13 de agosto hasta el 13 de septiembre ha tenido lugar la competición js13kGames 2015. El objetivo es construir un juego en HTML5, en el plazo de un mes o menos que no ocupe más de 13kb. EL fichero que no debe superar los 13kb debe ser un fichero ZIP con compresión estándar que tenga un fichero index.html desde el cual arrancará el juego y todo lo necesario para su funcionamiento estará también en el fichero ZIP. JavaScript, CSS, imágenes, sonido, fuentes, etc deberán estar en el fichero ZIP que no supere los 13kb. Está explicitamente prohibido cargar algún recurso del exterior como Google Web Fonts, CDNs de JavaScript, imágenes en otro servidor, etc. Además hay un tema para los juegos, que fue anunciado el 13 de agosto. El tema ha sido Reversed.

js13k

MissCity

MissCityGameplay

El juego que he presentado se llama MissCity. El nombre viene de darle la vuelta a Sim de SimCity. Mis no existe, pero Miss sí, y es perdido. Así MissCity es ciudad perdida.

Euralia es la ciudad perfecta. Nuestra compañía desea construir un centro comercial en un solar abandonado pero el actual alcalde desea constuir una biblioteca. Dentro de poco son elecciones. ¡Debemos ganar las elecciones! Para ello puedes usar nuestro dron y repartir diversos tipos de ataques publicitarios a la población

Los controles son:

  • WASD para desplazarse (si estamos en móvil o tablet, se usa la inclinación del dispositivo)
  • VBNM para los 4 diferentes tipos de ataque (si estamos en un dispositivo táctil, aparecen cuatro botones en pantalla que realizan el mismo efecto)

Ganamos:

  • Si conseguimos suficientes votos entre el electorado

Perdemos:

  • Si pasan dos minutos
  • Si nos quedamos sin dinero (cada ataque publicitario cuesta una cantidad de dinero sin especificar)

MissCityOpera

Vamos a ver el postmortem en profundidad

Cosas que fueron bien

PathFinding

Los habitantes de Euralia son inteligentes, no van de una casilla a otra porque sí sino que tienen una ruta que realizar. El origen y el destino sí se calculan aleatoriamente, pero la ruta no, se usa un algoritmo de pathfinding. Debía encontrar una librería sencilla, pequeña, pero que implementase el algoritmo de manera limpia. Finalmente elegí EasyStar.js que es asíncrona, sin dependencias y entre sus características asegura que es pequeña (~5kb) lo cual comprimido en ZIP resulta menos de lo que esperaba. Usa licencia MIT así que perfecto. El único inconveniente que presenta es que la rejilla que usa es bidimensional y yo definí la ciudad y el sistema de renderizado con un array unidimensional que voy cortando al procesarlo. Así que el juego tiene que transformar el array unidimensional en otro bidimensional para que EasyStar lo procese correctamente. Al obtener los resultados, es necesario volver a transformarlos.

Recursos gráficos

Creía que mi juego iba a tener peores gráficos, sinceramente. Las imágenes en formato GIF ocupaban menos de lo que esperaba y pude incluir bastantes detalles. Al principio no usé imágenes, tiré de colores en CSS. Renderizar toda la ciudad fue muy sencillo. Esta no es la versión final por supuesto, pero no es muy diferente.

// city es el array unidimensional donde defino el mapa de la ciudad

var draw=city.map(function(val){
    switch(val){
      case 0: return "rgba(91,196,124,1)";
      case 1: return "rgb(76, 77, 76)";
      case 2: return "rgb(84, 230, 54)";
      case 3: return "rgb(37, 88, 219)";
      case 4: return "rgb(223, 155, 23)";
    }
  });
  var x=0,y=0;
  draw.forEach(function(cell){
	ctx.fillStyle=cell;
	ctx.fillRect(x,y,box,box);
	x+=box;
	if((x+box)>id("a").width){
	  x=0;
	  y+=box;
	}
  });

Debug en Firefox para Android

Me lo esperaba peor y realmente con WebIDE, el cable USB y ADB es muy sencillo ver la consola de JavaScript de Firefox para Android en tu ordenador.

Problemas

MissCityFirefox

Dichosas APIs de pantalla y orientación

En HTML5 siempre me torturo con los aspect ratio y demás temas relacionados con la pantalla. En HTML5 hay tantas pantallas diferentes que simplemente no sé por donde empezar. El método que he usado en este juego es diferente al usado en otras ocasiones y daría para una entrada de blog suelta. Pero también me gustaría decir que las APIs de gestión de pantalla (saber si estás en modo landscape o portrait) no funcionan entre navegadores todavía. Incluso tienen nombres incompatibles que surgen de distintas versiones del estándar. Es una cosa que las aplicaciones nativas de móviles saben hacer desde el día 1.

EasyZIP

En MissCity he usado Gulp como herramienta que se encarga de la automatización de todo (ya sabes ¡haz scripts!). Usé EasyZIP para generar el fichero ZIP y posteriormente comprobar que su tamaño seguía siendo inferior a los 13kb. Mi sorpresa vino cuando al subir el fichero ZIP provoqué un error en el servidor de js13kgames. Tuve que contactar con el administrador, hubo que borrar archivos que se habían extraído correctamente en el servidor aunque hubiese devuelto un error. La solución fue comprimirlo manualmente con File Roller y el tamaño del fichero aumentó (sin pasar los 13kb).

API de gestión de teclado

Firefox recomienda usar KeyboardEvent.key para leer el teclado y marca como obsoleta la manera antigua, que era KeyboardEvent.keyCode. Leyendo MDN uno piensa que usando KeyboardEvent.key es la solución sin más. Y efectivamente en Firefox funciona bien, pero en Chrome y Opera no. Y pudiendo usar keyCode, quién va a usar key. keyCode será obsoleto pero funciona en todos los navegadores. Finalmente implementé el teclado usando key y keyCode si no soportan key.

Juega

Si has leído hasta aquí, es un buen momento para jugar a MissCity. Hay un premio por compartir en redes sociales, si quieres ayudarme ya sabes.

Código fuente: http://github.com/AdrianArroyoCalle/miss-city

loading...

eurocookie-js

Hace ya algún tiempo que la ley europea en materia de privacidad se ha venido aplicando en España. La ley define que no se pueden almacenar datos que identifiquen al usuario con fines estadísticos (o publicitarios) a menos que se pida un consentimiento al usuario y este lo acepte. Solo lo deben cumplir aquellas personas que tengan un beneficio económico con la web. En empresas hay que aplicarlo siempre. El almacenamiento más usado para este tipo de identifición han sido las cookies, de ahí el nombre popular de “ley de cookies”.

UnionEuropea

Odisea entre las cookies

Yo uso cookies. Las uso en este blog y en otros sitios. Google Analytics requiere consentimiento, Disqus requiere consentimiento, Google AdSense requiere consentimiento. Los widgets sociales de Twitter, Facebook, Google+, etc requieren consentimiento.

¿Pero entonces todas las cookies necesitan consentimiento?

No. Sólo las que identifican al usuario con fines estadísticos, que en el caso de los gigantes de Internet es siempre. Si usamos nuestras propias cookies y no las conservamos para posterior análisis no haría falta y no hay que pedir consentimiento.

Cookies

Cookies son las más usadas, pero si usas WebStorage (localStorage) o almacenamiento de Flash también tendrás que cumplir

eurocookie-js al rescate

Basado en un pequeño plugin hecho por Google bajo licencia Apache 2.0. Se trata de un pequeño fichero JavaScript que al cargarse pedirá el consentimiento (si no ha sido preguntado antes). Este consentimiento es molesto, forzando al usuario a aceptar si quiere leer cómodamente la web. Una vez acepta el consentimiento se carga todo el JavaScript que necesitaba consentimiento. Esto último es algo que muchos plugins de consentimiento de cookies no hacen. Realmente no sé si simplemente avisando se cumple la ley, bajo mi interpretación no. Y por eso este plugin. Además eurocookie-js está traducido a una gran cantidad de idiomas de la Unión Europea (tomadas directamente de las traducciones oficiales de Google para webmasters sobre esta ley). Vamos a ver como se usa.

Instalando eurocookie-js

Instalar eurocookie-js es más simple que el mecanismo de un botijo. Hay 3 maneras:

Biscuit

Usando eurocookie-js

Identificar JavaScript que necesita consentimiento

Primero, necesitamos identificar que JavaScript necesita consentimiento. Si usa una etiqueta script es fácil. Es importante declarar el tipo MIME del script como texto plano y le añadimos la clase cookie.


<script class="cookie" type="text/plain">
 // USEMOS COOKIES FELIZMENTE, ENVIEMOS DATOS A LA NSA 
</script>

En muchos casos bastará, por ejemplo con Disqus o con Google Analytics, puesto que cargan asíncronamente los archivos. En otros casos donde usemos el atributo src de script tendremos que modificar el código que se nos provee por el siguiente.


<script src="http://servidor.com/archivo.js"></script><!-- Será sustituido por --><script class="cookie" type="text/plain">
 var file = document.createElement('script'); file.type = 'text/javascript'; file.async = true; file.src = "http://servidor.com/archivo.js"; document.getElementsByTagName('head')[0].appendChild(file); 
</script>

Aunque si el proveedor te requería usar script src posiblemente no soporte la carga asíncrona.

Activando eurocookie-js

Ahora solo nos falta activar eurocookie-js. Es fácil. Al final, antes de cerrar body tenemos que añadir lo siguiente:

Si usamos Bower o el método manual

<script>
 euroCookie("http://enlace-a-politica-de-privacidad.com"); 
</script>
Si usamos npm + browserify
 
var ec=require("eurocookie-js"); 
ec.euroCookie("http://link-to-privacy-policy.com"); 

El fichero generado por browserify podrá ser añadido directamente al HTML.

Más información

Como siempre, toda la información está en GitHub: http://github.com/AdrianArroyoCalle/eurocookie-js

Tutorial de WiX (Windows Installer MSI)

Siguiendo la estela del tutorial de CMake hoy traigo el tutorial de WiX.

¿Qué es WiX?

WiX es un proyecto opensource destinado a producir instaladores de la plataforma Windows Installer. Windows Installer es la plataforma de instalaciones preferida por Microsoft desde que comenzó su desarrollo en 1999. Los archivos MSI son los instaladores genéricos que usa Windows Installer. Provee de un entorno más seguro para modificar el sistema al de un EXE tradicional por el hecho de que Windows Installer es declarativo, no imperativo. Windows Installer es transaccional, facilita el despliegue en entornos empresariales, tiene APIs (Windows Installer API y Windows Installer SDK), permite la localización de manera sencilla, la validación de instalaciones y la gestión de reinicios. Tiene los siguientes inconvenientes: es complejo, los accesos directos, su línea de comandos, su uso del registro y sus herramientas. Así pues con WiX podemos crear paquetes MSI de Windows Installer con unos ficheros XML donde definimos la instalación.

WindowsInstaller

También conviene diferenciar los diferentes tipos de archivos que soporta Windows Installer.

  • MSI | Instalador convencional
  • MSM | Módulo de fusión
  • MSP | Módulo de parche
  • MST | Módulo de transformación

No hay que confundir estos archivos con los MSU, también usados por Microsoft pero para Windows Update y cuya estructura es diferente.

Instalando WiX

Para instalar WiX podemos usar Chocolatey. Abrimos PowerShell como administrador y ejecutamos.

choco install -y wixtoolset

Si queremos añadir las herramientas al PATH, en PowerShell.

$PATH = [Environment]::GetEnvironmentVariable("PATH")
$WIX_BIN = "C:\Program Files (x86)\WiX Toolset v3.9\bin"
[Environment]::SetEnvironmentVariable("PATH","$PATH;$WIX_BIN")

Herramientas

WiX viene con un conjunto de herramientas diseñadas para trabajar con Windows Installer.

  • candle.exe – Compila los archivos .wxs y .wxi para generar archivos objeto .wixobj
  • light.exe – Enlaza los objetos .wixobj y .wixlib produciendo el .msi
  • lit.exe – Permite unir archivos .wixobj en una librería .wixlib
  • dark.exe – Convierte un archivo ya compilado en código fuente WiX
  • heat.exe – Podemos añadir archivos al fichero WiX en masa, sin especificar manualmente
  • insignia.exe – Permite firmar archivos MSI con las mismas firmas que los CABs
  • melt.exe – Convierte un archivo MSM a un fichero código fuente de WiX
  • torch.exe – Extrae las diferencias entre objetos WiX para generar una transformación. Usado para generar parches.
  • smoke.exe – Valida ficheros MSI o MSM
  • pyro.exe – Genera un fichero de parches MSP
  • WixCop.exe – Recomienda estándares en los ficheros WiX
  • WixUnit.exe – Realiza una validación a los ficheros WiX
  • lux.exe – Usado para tests unitarios
  • nit.exe – Usado para tests unitarios

Nota: Vamos a necesitar generar GUIDs. En PowerShell podeis usar `[GUID]::NewGuid()` o `[GUID]::NewGuid().ToString()`. Si teneis instalado Visual Studio, también está disponible `uuidgen`

El fichero .wxs

El fichero WXS es la base de WiX y es un fichero XML. Para el ejemplo voy a empaquetar Lumtumo. Lumtumo contiene un ejecutable, varias DLL y varios archivos como imágenes y fuentes que están distribuidos por carpetas. He depositado la carpeta con los binarios y todos lo necesario en SourceDir.

<?xml version="1.0" encoding="utf-8"?>
<!-- Variables del preprocesador -->
<?define Platform = x86 ?>
<?if $(var.Platform) = x64 ?>
<?define ProductName = "Lumtumo (64 bit)" ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define ProductName = "Lumtumo" ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
	<!-- Códigos de idioma https://msdn.microsoft.com/en-us/library/Aa369771.aspx -->
	<Product Name="Lumtumo" Manufacturer="Adrian Arroyo Calle" Id="BAE7D874-3177-46A6-BB2D-043EFF8C59F2" UpgradeCode="457861BD-A051-4150-8752-82907E7BAF19" Language="1033" Codepage="1252" Version="1.0">
   <!-- Nunca, NUNCA, se debe cambiar el valor de UpgradeCode. Es el identificador que distingue a los productos entre versiones -->
    <Package Description="$(var.ProductName)" Platform="$(var.Platform)" Keywords="Game" Id="*" Compressed="yes" InstallScope="perMachine" InstallerVersion="300" Languages="1033" SummaryCodepage="1252" Comments="Space invaders redefined" Manufacturer="Adrian Arroyo Calle"/>
	  <!-- Generamos el listado de ficheros a instalar con heat.exe dir ..\..\build\Release\ -dr LumtumoDir -cg LumtumoComponent -gg -g1 -sf -srd -out files.wxs -->
    <!-- Es necesario actualizar Shortcut/@Target cada vez que usemos Heat para conservar la referencia al ejecutable principal válida -->
    <!-- Instalar con msiexec /i lumtumo.msi , desinstalar con msiexec /x lumtumo.msi-->
    
        <MajorUpgrade DowngradeErrorMessage="A later version of Lumtumo is already installed. Setup will now exit."/>
    <!-- Gestiona las actualizaciones posteriores. Estos MSI de actualización deben tener igual UpgradeCode, distinto Id y una versión superior-->

    <Icon Id="icon.ico" SourceFile="SourceDir/Lumtumo.exe"/>
    <!-- El icono puede estar en un ejecutable -->
    <Property Id="ARPPRODUCTICON" Value="icon.ico" />
    <!-- Este es el icono del panel de control -->
    <Property Id="ARPHELPLINK" Value="http://adrianarroyocalle.github.io" />
    <!-- Enlace de ayuda para el panel de control. Hay más propiedades con enlaces para el panel de control-->

    <MediaTemplate CabinetTemplate="LUM{0}.cab" CompressionLevel="high" EmbedCab="yes"/>
    <!-- WiX se encargará de generar los archivos Cabinet necesarios. Esto será cuando llamemos a light.exe -->
    
    <Feature Id="ProductFeature" Title="Game" Level="1">
      <ComponentGroupRef Id="LumtumoComponent"/>
      <ComponentRef Id="ApplicationShortcut" />
    </Feature>

    <!-- Solo hay una funcionalidad, el juego completo. Esta incluye todos los archivos de Lumtumo y además el acceso directo en el menú de Inicio. LumtumoComponent está definido en files.wxs -->

    <Directory Id='TARGETDIR' Name='SourceDir'>
      <Directory Id='$(var.PlatformProgramFilesFolder)'>
        <Directory Id="LumtumoDir" Name='Lumtumo'>
        </Directory>
      </Directory>
      <Directory Id="ProgramMenuFolder">
        <Directory Id="ApplicationProgramsFolder" Name="Lumtumo"/>
      </Directory>
    </Directory>
    <!-- La estructura de carpetas básica. TARGETDIR debe ser el ROOT siempre. Además, ProgramFilesFolder (en este caso usamos el preprocesador antes) y ProgramMenuFolder ya están definidos por Windows. Así que realmente creamos LumtumoDir (que usa files.wxs) y ApplicationProgramsFolder (justo abajo trabajamos con esa carpeta). Ambas se crean con el nombre de Lumtumo en el sistema de archivos -->

    <DirectoryRef Id="ApplicationProgramsFolder">
      <Component Id="ApplicationShortcut" Guid="03466DA8-7C3F-485E-85E6-D892E9F7FFE4">
        <Shortcut Id="ApplicationStartMenuShortcut" Name="Lumtumo" Description="Space Invaders redefined" Target="[#fil7BBA1E2E293173E87EC3F765BF048B16]" Icon="icon.ico" WorkingDirectory="LumtumoDir"/>
        <RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
        <!-- Nos aseguramos de borrar la carpeta con el acceso directo al desinstalar-->
        <RegistryValue Root="HKCU" Key="Software\Microsoft\Lumtumo" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
        <!-- Con esta llave del registro apareceremos en el panel de control para ser desinstalados-->
      </Component>
    </DirectoryRef>

    <Condition Message='This application only runs on Windows NT'>
      VersionNT
    </Condition>
    <!-- Un ejemplo de condiciones. En este caso pedimos que Windows tenga el núcleo NT. Se pueden especificar rangos de versiones de Windows e incluso las ediciones. Las condiciones se pueden aplicar a componentes específicos también-->
    
  </Product>
</Wix>

Ahora se genera el archivo MSI

heat.exe dir SourceDir -dr LumtumoDir -cg LumtumoComponent -gg -g1 -sf -srd -out files.wxs
# Editamos el archivo Lumtumo.wxs para actualizar la referencia al ejecutable principal en el acceso directo
candle.exe files.wxs
candle.exe Lumtumo.wxs
light.exe files.wixobj Lumtumo.wixobj -out lumtumo.msi

Este ejemplo básico está muy bien, instala todo, añade un icono en el menú de inicio (también se puede poner en el escritorio pero no me gusta), se integra con el panel de control para hacer una desinstalación limpia e incluso con modificar un valor podemos generar el paquete para 64 bits. Sin embargo faltan cosas. No se puede especificar la carpeta de instalación, de hecho, el MSI no muestra ninguna opción al usuario al instalarse. Para introducir GUI en WiX tenemos que usar la librería WiXUI.

WixUI

Para usar WixUI tenemos que cambiar el comando que usamos para llamar a light

light.exe -ext WixUIExtension files.wixobj Lumtumo.wixobj -out lumtumo.msi

Con esto ya podemos usar la librería WixUI. La manera más básica de añadir una GUI es introducir la GUI Minimal. Al final de la etiqueta Product podemos insertar

    <WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />
    <UIRef Id="WixUI_Minimal" />
    <UIRef Id="WixUI_ErrorProgressText" />

Y LICENSE.rtf en un archivo que tenemos junto a los ficheros WXS.

Si queremos un buen resultado visualmente hablando toca diseñar dos ficheros BMP.

<WixVariable Id="WixUIBannerBmp" Value="path\banner.bmp" /> <!-- 493x58 -->
<WixVariable Id="WixUIDialogBmp" Value="path\dialog.bmp" /> <!-- 493x312 -->

Aquí uso la interfaz Minimal que lo único que hace es pedir aceptar la licencia e instalarse tal como habíamos visto antes. Hay más interfaces: WixUI_Mondo, WixUI_FeatureTree, WixUI_InstallDir y WixUI_Advanced. Todas ellas permiten más ajustes que WixUI_Minimal. También podemos crear ventanas personalizadas pero en mi opinión es algo a evitar. Las instalaciones deben ser lo más simples posibles y dejar a la aplicación en sí toda la configuración que necesite. Respecto a cambiar el directorio de instalación, no soy partidario pero si quereis la cuestión es sustituir LumtumoDir por INSTALLDIR (en hate.exe y en el fichero Lumtumo.wxs) y en la etiqueta Feature añadir `Display=”expand” ConfigurableDirectory=”INSTALLDIR”`

Bundle y Bootstrap

Un archivo MSI está bien pero quizá nos gusté más un EXE que además instale las dependencias.

<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Bundle Name="Lumtumo" Version="1.0" Manufacturer="Adrián Arroyo Calle" UpgradeCode="AC44218D-B783-4DF9-B441-5AE54394DAA9" AboutUrl="http://adrianarroyocalle.github.io">
    <!-- <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense" /> -->
    <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense">
      <bal:WixStandardBootstrapperApplication LicenseUrl="" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"/>
    </BootstrapperApplicationRef>

    <Chain>
      <ExePackage Id="Dependency1" SourceFile="Dependency_package_1.exe" />
      <ExePackage Id="Dependency2" SourceFile="Dependency_package_2.exe" />

      <RollbackBoundary Id="RollBack" />

      <MsiPackage Id="MainPackage" SourceFile="lumtumo.msi" Vital="yes" />
    </Chain>
  </Bundle>
</Wix>

Y compilamos

candle.exe setup.wxs
light.exe -ext WixBalExtension setup.wxs

Y se genera el archivo `setup.exe`. Por defecto el archivo MSI se instala en modo silencioso por lo que la GUI pasa a ser la de Bootstrap.

Más sintaxis WiX

Registrar un tipo de archivo

<ProgId Id='ACME.xyzfile' Description='Acme Foobar data file' Icon="icon.ico">
    <Extension Id='xyz' ContentType='application/xyz'>
        <Verb Id='open' Command='Open' TargetFile='[#ID_DE_EJECUTABLE]' Argument='"%1"' />
    </Extension>
</ProgId>

Escribir configuración en archivo INI

<IniFile Id="WriteIntoIniFile" Action="addLine" Key="InstallDir" Name="Foobar.ini" Section="Paths" Value="[INSTALLDIR]" />

Escribir en el registro

<RegistryKey Id='FoobarRegInstallDir' Root='HKLM' Key='Software\Acme\Foobar 1.0' Action='createAndRemoveOnUninstall'>
    <RegistryValue Type='string' Name='InstallDir' Value='[INSTALLDIR]'/>
    <RegistryValue Type='integer' Name='Flag' Value='0'/>
</RegistryKey>

Borrar archivos extra en la desinstalación

<RemoveFile Id='LogFile' On='uninstall' Name='Foobar10User.log' />
<RemoveFolder Id='LogFolder' On='uninstall' />