Introducción
La primavera pasada (2010), me interesó la compatibilidad con HTML5 y las tecnologías relacionadas que aumentaba rápidamente. En ese momento, un amigo y yo nos retábamos en competencias de desarrollo de juegos de dos semanas para perfeccionar nuestras habilidades de programación y desarrollo, así como dar vida a las ideas de juegos que nos propusimos mutuamente. Por lo tanto, comencé a incorporar elementos HTML5 en mis participaciones en concursos para comprender mejor cómo funcionaban y poder hacer cosas que eran casi imposibles con especificaciones anteriores de HTML.
De las muchas funciones nuevas de HTML5, la creciente compatibilidad con la etiqueta canvas me ofreció una oportunidad emocionante para implementar arte interactivo con JavaScript, lo que me llevó a intentar implementar un juego de rompecabezas que ahora se llama Entanglement. Ya había creado un prototipo con la parte posterior de las baldosas de Settlers of Catan, por lo que, si lo usas como un tipo de modelo, hay tres partes esenciales para crear la baldosa hexagonal en el lienzo HTML5 para jugar en la Web: dibujar el hexágono, dibujar las rutas y rotar la baldosa. A continuación, se explica en detalle cómo logré cada uno de estos aspectos en su forma actual.
Cómo dibujar el hexágono
En la versión original de Entanglement, usé varios métodos de dibujo en lienzo para dibujar el hexágono, pero la forma actual del juego usa drawImage()
para dibujar texturas recortadas de una hoja de sprites.

Combiné las imágenes en un solo archivo para que sea solo una solicitud al servidor en lugar de diez. Para dibujar un hexágono elegido en el canvas, primero debemos reunir nuestras herramientas: el lienzo, el contexto y la imagen.
Para crear un lienzo, solo necesitamos la etiqueta canvas en nuestro documento HTML de la siguiente manera:
<canvas id="myCanvas"></canvas>
Le asigno un ID para que podamos incorporarlo a nuestra secuencia de comandos:
var cvs = document.getElementById('myCanvas');
En segundo lugar, debemos tomar el contexto 2D del lienzo para poder comenzar a dibujar:
var ctx = cvs.getContext('2d');
Por último, necesitamos la imagen. Si se llama "tiles.png" y se encuentra en la misma carpeta que nuestra página web, podemos obtenerlo de la siguiente manera:
var img = new Image();
img.src = 'tiles.png';
Ahora que tenemos los tres componentes, podemos usar ctx.drawImage() para dibujar el único hexágono que queremos de la hoja de sprites al lienzo:
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
destinationX, destinationY, destinationWidth, destinationHeight);
En este caso, usamos el cuarto hexágono de la izquierda en la fila superior. Además, lo dibujaremos en el lienzo en la esquina superior izquierda, manteniendo el mismo tamaño que el original. Si suponemos que los hexágonos tienen 400 píxeles de ancho y 346 píxeles de alto, en conjunto se verán de la siguiente manera:
var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';
var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
destinationX, destinationY, destinationWidth, destinationHeight);
Copiamos correctamente parte de la imagen en el lienzo, con este resultado:

Rutas de dibujo
Ahora que tenemos el hexágono dibujado en el lienzo, queremos dibujar algunas líneas en él. Primero, analizaremos la geometría de la tarjeta hexagonal. Queremos dos extremos de línea por lado, cada uno de los cuales termina 1/4 de los extremos a lo largo de cada borde y 1/2 del borde separados entre sí, de la siguiente manera:

También queremos una curva agradable, así que, con un poco de prueba y error, descubrí que, si hago una línea perpendicular desde el borde en cada extremo, la intersección de cada par de extremos alrededor de un ángulo determinado del hexágono hace un buen punto de control de Bézier para los extremos determinados:

Ahora, asignamos los extremos y los puntos de control a un plano cartesiano que corresponde a la imagen del lienzo y ya está todo listo para volver al código. Para simplificar, comenzaremos con una línea. Comenzaremos por dibujar una ruta desde el extremo superior izquierdo hasta el extremo inferior derecho. Si nuestra imagen hexagonal anterior es de 400 x 346, nuestro extremo superior tendrá 150 píxeles de ancho y 0 píxeles hacia abajo, abreviatura (150, 0). Su punto de control será (150, 86). El extremo del borde inferior es (250, 346) con un punto de control de (250, 260):

Con nuestras coordenadas en mano, ya estamos listos para comenzar a dibujar. Comenzaremos desde cero con ctx.beginPath() y, luego, pasaremos al primer extremo con lo siguiente:
ctx.moveTo(pointX1,pointY1);
Luego, podemos dibujar la línea con ctx.bezierCurveTo() de la siguiente manera:
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
Como queremos que la línea tenga un buen borde, dibujaremos esta ruta dos veces con un ancho y un color diferentes cada vez. El color se establecerá con la propiedad ctx.strokeStyle y el ancho se establecerá con ctx.lineWidth. En total, dibujar la primera línea se verá de la siguiente manera:
var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.beginPath();
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
Ahora tenemos una tarjeta hexagonal con la primera línea serpenteando:

Si ingresamos las coordenadas de los otros 10 extremos, así como los puntos de control de la curva de Bézier correspondientes, podemos repetir los pasos anteriores y crear una tarjeta como esta:

Cómo rotar el lienzo
Una vez que tengamos nuestra tarjeta, queremos poder girarla para que se puedan tomar diferentes rutas en el juego. Para lograrlo con el lienzo, usamos ctx.translate()
y ctx.rotate()
. Queremos que la tarjeta gire alrededor de su centro, por lo que nuestro primer paso es mover el punto de referencia del lienzo al centro de la tarjeta hexagonal.
Para ello, usamos lo siguiente:
ctx.translate(originX, originY);
En el que originX será la mitad del ancho de la tarjeta hexagonal y originY será la mitad de la altura, lo que nos da lo siguiente:
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
Ahora podemos rotar la tarjeta con nuestro nuevo punto central. Como un hexágono tiene seis lados, querremos rotarlo por algún múltiplo de Math.PI dividido por 3. No nos complicaremos y haremos un solo giro a la derecha con lo siguiente:
ctx.rotate(Math.PI / 3);
Sin embargo, como nuestro hexágono y las líneas usan las coordenadas (0,0) anteriores como origen, una vez que terminemos de rotar, querremos volver a traducir antes de dibujar. En total, ahora tenemos lo siguiente:
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);
Si colocamos la traducción y la rotación anteriores antes de nuestro código de renderización, este ahora renderizará la tarjeta rotada:

Resumen
Arriba, destaqué algunas de las capacidades que ofrece HTML5 con la etiqueta canvas, como renderizar imágenes, dibujar curvas de Bézier y rotar el lienzo. Usar la etiqueta de lienzo HTML5 y sus herramientas de dibujo de JavaScript para Entanglement resultó ser una experiencia agradable, y espero con ansias las muchas aplicaciones y juegos nuevos que otras personas crearán con esta tecnología abierta y emergente.
Referencia de código
Todos los ejemplos de código que se proporcionaron anteriormente se combinan a continuación como referencia:
var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';
var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);
var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
destinationX, destinationY, destinationWidth, destinationHeight);
ctx.beginPath();
var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 250;
pointY1 = 0;
controlX1 = 250;
controlY1 = 86;
controlX2 = 150;
controlY2 = 86;
pointX2 = 75;
pointY2 = 43;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 150;
pointY1 = 346;
controlX1 = 150;
controlY1 = 260;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 325;
pointY1 = 43;
controlX1 = 250;
controlY1 = 86;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 130;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 25;
pointY1 = 130;
controlX1 = 100;
controlY1 = 173;
controlX2 = 100;
controlY2 = 173;
pointX2 = 25;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();
ctx.beginPath();
pointX1 = 325;
pointY1 = 303;
controlX1 = 250;
controlY1 = 260;
controlX2 = 150;
controlY2 = 260;
pointX2 = 75;
pointY2 = 303;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();