Diva's Magic Dream: juego en menos de 1K de Javascript
Cuando se anunció la nueva edición del
concurso JS1K 2017 con el tema de Magia,
decidí participar con un juego de scroll horizontal. El resultado fue
Diva's Magic Dream.
El juego se trata de una diva que está soñando que es
perseguida por sus fans, así que usa su magia para defenderse y de paso
destruir algunas cabezas de pascua.
La forma de uso es sencilla: utilice las flechas para
moverse a la izquierda y derecha, oprima la barra espaciadora desquiciadamente
para poder salvar a la diva.
Tenía planes de desarrollar los dibujos utilizando las
funciones de curvas integras en el marcador <canvas> pero al final
recordé que podía utilizar caracteres Unicode justo como en mi
ajedrez JS1K, más reciente son los emoticones
que están integrados a todo color en los sistemas operativos más recientes.
Si bien la implementación de los emoticones Unicode es más
bien anarquica, por ejemplo, en Mac OS X la diva es rubia, en Windows tiene
el cabello negro, en Android es un negrito (misteriosamente parecido a Michael
Jackson), y en la mayoría de las versiones
de Linux todos las figuras aparecen como dibujos de línea.
Aproveché además algunas funciones de seno para realizar
pequeñas animaciones dentro del juego, como las nubes fluctuantes y el salto
de la cabeza de pascua, y la puntuación "crece" cada vez que se destruye un
enemigo.
Esta participación no entró en el top 10, siendo mi tercera
participación sin premio, así que decidí retirarme de futuras participaciones
en el concurso JS1k.
Código fuente
Note que el JS1K "provee" de algunas variables cargadas
con datos importantes, similar a esto:
<canvas id="c">
</canvas><script>
var c=document.getElementById("c");
var a=c.getContext("2d");
</script>
<script src="diva-magic-dream.js">
</script>
Aquí está mi código fuente original:
// Diva's Magic Dream por Óscar Toledo G. http://nanochess.org
//
// Creación: 16-feb-2017 4pm-9pm
// Revisión: 17-feb-2017 perfeccionamiento
// Revisión: 26-feb-2017 Mayor optimización y nuevo compresor
// Revisión: 27-feb-2017 Se agrega un sol feliz y cámaras (paparazzi)
//
// Tu diva sueña con las nubes, detén los fans a cualquier precio y ¡cuidado
// con las cabezas de Pascua!
// Utiliza las flechas izq. y der., oprime barra espaciadora para disparar magia
//
// Comprimido con https://siorki.github.io/regPack.html (gracias a @siorki)
// Matriz para el teclado
K=[];
// Matriz de personajes
o=[];
// Espera que se oprima una tecla
onkeydown=w=>K[w.which]=5;
// Espera que se suelte una tecla
onkeyup=w=>K[w.which]=0;
// r = Puntuación actual
// f = Cuadro actual
// h = Último cuadro con un fan nuevo
// Juego principal a 60hz
setInterval(w=>
{
// Sí, este juego necesita canvas
with(c)
// Sí, este juego necesita matemáticas
with(Math){
// Crea el cielo
fillStyle="#28f";
fillRect(0,0,640,240);
// Color de la puntuación o emojis si tu navegador es antiguo
fillStyle="#fff";
// Tamaño de la lista de personajes
d=o.length;
// Verifica el comienzo del juego
if(!d){
K[37]=K[39]=q=r=h=f=0;
// Pone la diva
o[o.length]={x:40,y:200,s:64,t:0,h:0};
// Pone la puntuación
o[o.length]={x:8,y:32,s:24,t:5};
// Pone las nubes
while(d<18)
o[o.length]={x:d++*40,y:260,s:96,t:6}
}
// Un sol feliz
font=80+5*sin(.1*f)+'px Emojione,"Segoe UI emoji","Lucida Grande"';
fillText("🌞",480,80);
// Ilustra personajes
while(d--)
// Trabaja directamente con el personaje
with(o[d]){
// Cada uno puede tener tamaño diferente
// Busca Emojione (Linux), Segoe UI emoji (Win7) o Lucida Grande (Mac)
font=s+'px Emojione,"Segoe UI emoji","Lucida Grande"';
// Todos los hermosos gráficos son cortesía de Unicode
// Diva,Muerte,Magia,Explosión,Bonus,Puntos,Nubes,Fan,Pascua
fillText(["💃","☠️","🌟","💥","🏅",r*100,"☁️",f&8?"🏃":"🚶","🗿","📹"][t],x,y);
// Verifica si muere
if(l=t-8?0:32,e=0,t>6&abs(x+l-o[e].x)<l+16&abs(y-o[e].y)<24)
o[e].t=1;
// 0:Movimiento de diva
[w=>{
// Izq/Der
x=min(400,max(24,x+K[39]-K[37])),
// Pequeña sinosoidal para animación de caminar
y=200+5*sin(.1*f);
// Mira si dispara magia (barra espaciadora)
if(!q&K[32])o[o.length]={x:x+32,y:y-10,s:32,t:2};
q=K[32]},
// 1:Movimiento de muerte
w=>{
// Brinco
y=200+80*sin(.1*++h);
// Reinicia juego al terminar
if(h>49)
o=[]},
// 2:Movimiento de magia
w=>{
// Adelanta estrella
x+=10;
// Busca colisiones
e=o.length;
l=0;
while(e--)
if(o[e].t>6&abs(x+l-o[e].x)<l+16&abs(y-o[e].y)<24)
// Mira cuantos choques
if(x=640,!--o[e].h)
// La puntuación "crece"
o[1].s=40,
// Convierte a explosión o bonus
r+=o[e].t-8?1:(o[o.length]={x:320,y:0,s:64,t:4},250),
o[e].t=3;
x>639&&o.splice(d,1)},
// 3:Animación de explosión
w=>{
s+=4;
s%32<1&&o.splice(d,1)},
// 4:Animación de bonus
w=>{
if(y<140)
y+=2;
++s>239&&o.splice(d,1)},
// 5:Animación de puntuación
w=>{s>24&&--s},
// 6:Desplazamiento de nubes
w=>{
// Desplaza
x=x<-80?640:--x,
// Las nubes pulsan
s=96+5*sin(.1*x)},
// 7:Animación de fan
w=>{
// Cae
if(y<200)y+=20;
// Corre
x-=8+f/1e4;
x<0&&o.splice(d,1)},
// 8:Animación de cabeza de pascua
w=>{
// Desplaza en una dirección
x-=z;
// ¡Boink!
x<z|x>400&&(z=-z);
// ¡Resorte!
y=100+100*sin(.1*f)},
// 9:Cámara
w=>{
y+=y/9;
y>280&&o.splice(d,1)
}][t]()
}
// Inserta un nuevo fan o una cámara
if(random()<.1&f-h>20){
h=f;
if(random()<.1)o[o.length]={x:o[0].x,y:1,s:64,t:9,h:1};
else o[o.length]={x:640-200*random(),y:0,s:64,t:7,h:2};
}
// Inserta una cabeza de Pascua (¿Acaso no jugaste Gradius? ;))
if(f++&&f%1500<1)
o[o.length]={x:400,y:0,s:192,t:8,h:15,z:1+f/2e4}
}
},15);
// Diva's Magic Dream por Óscar Toledo G. http://nanochess.org
//
// Creación: 16-feb-2017 4pm-9pm
// Revisión: 17-feb-2017 perfeccionamiento
//
// Tu diva está soñando con las nubes, detén los fans a cualquier precio y ¡cuidado
// con las cabezas de pascua!
//
// Utiliza las flechas izq y der del teclado, oprima espacio para disparar magia.
//
// Comprimido con http://www.iteral.com/jscrush/ (gracias al tremendo @aivopass)
// Sólo elimina espacios iniciales y comentarios en una sola línea.
// Escapes Unicode más sencillos gracias a to https://mothereff.in/js-escapes
// Arreglo del teclado
K=[];
// Arreglo de personajes
o=[];
// Verifica tecla oprimida
onkeydown=function(w){K[w.which]=1};
// Verifica tecla soltada
onkeyup=function(w){K[w.which]=0};
// r = Puntuación actual
// f = Cuadro actual
// h = Último cuadro con un fan nuevo
// Juego principal a 60hz
setInterval(function()
{
// Sí, todos los juegos necesitan Math
with(Math){
// Crea el cielo
c.fillStyle="#28f";
c.fillRect(0,0,640,240);
// Color de la puntuación y también de los emoji si tu browser es antiguo
c.fillStyle="#fff";
// Tamaño de la lista de personajes
d=o.length;
// Verifica si inicia el juego
if(!d){
K[37]=K[39]=q=r=h=f=0;
// Pone la diva
o[o.length]={x:40,y:200,s:64,t:0,h:0};
// Pone la puntuación
o[o.length]={x:8,y:32,s:24,t:5};
// Pone nubes
while(d<18)
o[o.length]={x:d++*40,y:260,s:96,t:6};
}
// Ilustra los personajes
while(d--){
// Trabaja directamente con el personaje
with(o[d]){
// Cada uno puede tener tamaño diferente
// Busca Emojione (Linux), Segoe UI emoji (Win7) o Lucida Grande (Mac)
c.font=s+'px Emojione,"Segoe UI emoji","Lucida Grande"';
// Todos nuestros bonitos gráficos cortesía de Unicode
// Diva,Muerte,Magia,Explosión,Bonus,Puntuación,Nubes,Fan,Cabeza de pascua
c.fillText(["\uD83D\uDC83","\u2620\uFE0F","\uD83C\uDF1F","\uD83D\uDCA5","\uD83C\uDFC5",r*100,"\u2601\uFE0F",f&8?"\uD83C\uDFC3":"\uD83D\uDEB6","\uD83D\uDDFF"][k=t],x,y);
// Mira si muere
if(l=t<<2&32,e=0,t>6&abs(x+l-o[e].x)<l+16&abs(y-o[e].y)<24)
o[e].t=1;
// 0:Movimiento de la diva
if(!k--)
// Izquierda/derecha
x=min(400,max(24,x+5*K[39]-5*K[37])),
// Pequeña onda para animación de caminata
y=200+5*sin(f/9),
// Checa el disparo mágico (tecla espacio)
!q&K[32]?o[o.length]={x:x+32,y:y-10,s:32,t:2}:0,q=K[32];
// 1:Movimiento al morir
if(!k--){
// Brinco
y=200+80*sin(++h/9);
// Reinicia el juego cuando termina
if(h>49)
o=[];}
// 2:Movimiento de la magia
if(!k--){
// Adelanta la estrella
x+=10;
// Busca colisiones
e=o.length;
l=0;
while(e--)
if(o[e].t>6&abs(x+l-o[e].x)<l+16&abs(y-o[e].y)<24)
// Cuenta cuantos impactos
if(x=640,!--o[e].h)
// La puntuación "crece"
o[1].s=40,
// Convierte a explosión o bonus
r+=o[e].t<8?1:(o[o.length]={x:320,y:0,s:64,t:4},250),
o[e].t=3;
x>639&&o.splice(d,1);}
// 3:Animación de explosión
if(!k--){
s+=4;
s%32<1&&o.splice(d,1);}
// 4:Animación de bonus
if(!k--){
if(y<140)
y+=2;
++s>239&&o.splice(d,1);}
// 5:Animación de puntuación
if(!k--&s>24)
--s;
// 6:Desplaza las nubes
if(!k--)
// Desplaza
x=x<-80?640:--x,
// Pulsan las nubes
s=96+5*sin(x/9);
// 7:Animación del fan
if(!k--){
// Cae
if(y<200)y+=20;
// Corre
x-=8+f/1e4;
x<0&&o.splice(d,1);}
// 8:Animación de la cabeza de pascua
if(!k--){
// Desplaza en la dirección
x-=z;
// Toca pared!
x<z|x>400&&(z=-z);
// Resorte!
y=100+100*sin(f/9);}
}
}
// Inserta un nuevo fan
if(random()<.1&f-h>20)
h=f,o[o.length]={x:640-200*random(),y:0,s:64,t:7,h:2};
// Inserta una cabeza de pascua (¿no jugaste Gradius? ;))
if(f&&f%1500<1)
o[o.length]={x:400,y:0,s:192,t:8,h:15,z:1+f/2e4};
f++;
}
},15);