15. Juegos

Versión para imprimir.

En esta lección se presentan técnicas para elaborar juegos.

A. Sprites

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Sprite</title>
9 <style>
10 .sprite {
11 position: fixed;
12 font-size: 3rem;
13 }
14 </style>
15</head>
16
17<body>
18 <h1>Sprite</h1>
19 <p>
20 Este ejemplo te muestra como
21 colocar una figura en
22 cualquier lugar de la
23 ventana.
24 </p>
25 <output class="sprite"
26 style="left: 100px;
27 bottom: 150px">
28 😄
29 </output>
30</body>
31
32</html>

B. Desplazamiento horizontal

Salida

Ábrelo en otraw pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>
9 Animación horizontal
10 </title>
11 <style>
12 .sprite {
13 position: fixed;
14 font-size: 3rem;
15 }
16 </style>
17</head>
18
19<body>
20 <h1>Animación horizontal</h1>
21 <output id="carita"
22 class="sprite">
23 😄
24 </output>
25 <script>
26 const REFRESCO = 5
27 const VELOCIDAD = 0.5
28 let x = 0
29 setInterval(avanza, REFRESCO)
30
31 function avanza() {
32 const y = innerHeight / 2
33 const xMáxima = innerWidth
34 carita.style =
35 `left: ${x}px; bottom: ${y}px`
36 x = (x + VELOCIDAD) % xMáxima
37 }
38 </script>
39</body>
40
41</html>

C. Recta

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Animación recta</title>
9 <style>
10 .sprite {
11 position: fixed;
12 font-size: 3rem;
13 }
14 </style>
15</head>
16
17<body>
18 <h1>Animación recta</h1>
19 <output id="carita"
20 class="sprite">
21 😄
22 </output>
23 <script>
24 const X_INICIAL = 0
25 const REFRESCO = 5
26 const VELOCIDAD = 0.5
27 let x = X_INICIAL
28 setInterval(avanza, REFRESCO)
29
30 function avanza() {
31 const xFinal = innerWidth
32 const yInicial = innerHeight / 2
33 const yFinal = innerHeight
34 const y = ((yFinal - yInicial) /
35 (xFinal - X_INICIAL)) *
36 (x - X_INICIAL) +
37 yInicial
38 carita.style =
39 `left: ${x}px; bottom: ${y}px`
40 x = (x + VELOCIDAD) % xFinal
41 }
42 </script>
43</body>
44
45</html>

D. Ondula

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Ondula</title>
9 <style>
10 .sprite {
11 position: fixed;
12 font-size: 3rem;
13 }
14 </style>
15</head>
16
17<body>
18 <h1>Ondula</h1>
19 <output id="carita"
20 class="sprite">
21 😄
22 </output>
23 <script>
24 const REFRESCO = 5
25 const VELOCIDAD = 0.5
26 const FRECUENCIA = 0.03
27 let x = 0
28 setInterval(avanza, REFRESCO)
29
30 function avanza() {
31 const xMáxima = innerWidth
32 const amplitud = innerHeight / 3
33 const yBase = innerHeight / 2
34 y = yBase +
35 amplitud *
36 Math.sin(FRECUENCIA * x)
37 carita.style =
38 `left: ${x}px; bottom: ${y}px`
39 x = (x + VELOCIDAD) % xMáxima
40 }
41 </script>
42</body>
43
44</html>

E. Girando

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Gira</title>
9 <style>
10 .sprite {
11 position: fixed;
12 font-size: 3rem;
13 }
14 </style>
15</head>
16
17<body>
18 <h1>Gira</h1>
19 <output id="mariposa"
20 class="sprite">
21 🦋
22 </output>
23 <script>
24 const REFRESCO = 120
25 const VELOCIDAD = 0.3
26 let angulo = 0
27 setInterval(gira, REFRESCO)
28
29 function gira() {
30 const xBase = innerWidth / 2
31 const amplitudX = innerWidth / 3
32 const yBase = innerHeight / 2
33 const amplitudY =
34 innerHeight / 3
35 const x = xBase +
36 amplitudX * Math.cos(angulo)
37 const y = yBase +
38 amplitudY * Math.sin(angulo)
39 mariposa.style =
40 `left: ${x}px; bottom: ${y}px`
41 angulo += VELOCIDAD
42 }
43 </script>
44</body>
45
46</html>

F. Desplazamiento con botones

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Mueve con Botones</title>
9 <style>
10 .sprite {
11 position: fixed;
12 font-size: 3rem;
13 }
14 </style>
15</head>
16
17<body>
18 <h1>Mueve con Botones</h1>
19 <p>
20 <button onclick="retrocede()">
21
22 </button>
23 <button onclick="avanza()">
24
25 </button>
26 </p>
27 <output id="carita"
28 class="sprite">
29 😄
30 </output>
31 <script>
32 let yBase
33 let xMenor
34 let xMayor
35 const VELOCIDAD = 30
36 actualiza()
37 let x = xMenor
38 dibuja()
39
40 function retrocede() {
41 actualiza()
42 if (x > xMenor) {
43 x -= VELOCIDAD
44 }
45 dibuja()
46 }
47
48 function avanza() {
49 actualiza()
50 if (x < xMayor) {
51 x += VELOCIDAD
52 }
53 dibuja()
54 }
55
56 function actualiza() {
57 yBase = innerHeight / 2
58 xMenor = innerWidth / 5
59 xMayor = 4 * innerWidth / 5
60 }
61
62 function dibuja() {
63 carita.style = `left: ${x}px;
64 bottom: ${yBase}px`
65 }
66 </script>
67</body>
68
69</html>

G. Detecta colisiones

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Choca</title>
9 <style>
10 .sprite {
11 position: fixed;
12 font-size: 3rem;
13 }
14 </style>
15</head>
16
17<body>
18 <h1>Choca</h1>
19 <output id="abrazo"
20 class="sprite"
21 style="background-color:
22 greenyellow;">
23 🤗
24 </output>
25 <output id="triste"
26 class="sprite"
27 style="background-color:
28 grey;">
29 😪
30 </output>
31 <script>
32 let x = 0
33 const REFRESCO = 5
34 setInterval(avanza, REFRESCO)
35
36 function avanza() {
37 const velocidad =
38 innerWidth / 2000
39 const x2 = innerWidth
40 const y = innerHeight / 2
41 triste.style.left =
42 `${innerWidth / 2}px`
43 triste.style.bottom = `${y}px`
44 abrazo.style.left = `${x}px`
45 abrazo.style.bottom = `${y}px`
46 if (!seTocan(abrazo, triste)) {
47 x = (x + velocidad) % x2
48 }
49 }
50
51 /**
52 * Devuelve true si 2
53 * elementos se tocan.
54 * @param {HTMLElement} e1
55 * @param {HTMLElement} e2
56 * @returns {boolean} true
57 * si los elementos se
58 * tocan.
59 */
60 function seTocan(e1, e2) {
61 const rE1 =
62 e1.getBoundingClientRect()
63 const rE2 =
64 e2.getBoundingClientRect()
65 return (rE1.right >= rE2.left
66 && rE1.left <= rE2.right
67 && rE1.top <= rE2.bottom
68 && rE1.bottom >= rE2.top)
69 }
70 </script>
71</body>
72
73</html>

H. Haz algo si chocamos

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Sonrie</title>
9 <style>
10 .sprite {
11 position: fixed;
12 font-size: 3rem;
13 }
14 </style>
15</head>
16
17<body>
18 <h1>Sonrie</h1>
19 <output id="abrazo"
20 class="sprite"
21 style="background-color:
22 greenyellow;">
23 🤗
24 </output>
25 <output id="triste"
26 class="sprite"
27 style="background-color:
28 grey;">
29 😪
30 </output>
31 <script>
32 let x = 0
33 const REFRESCO = 5
34 let intervalo =
35 setInterval(avanza, REFRESCO)
36
37 function avanza() {
38 const velocidad =
39 innerWidth / 2000
40 const x2 = innerWidth
41 const y = innerHeight / 2
42 triste.style.left =
43 `${innerWidth / 2}px`
44 triste.style.bottom = `${y}px`
45 abrazo.style.left = `${x}px`
46 abrazo.style.bottom = `${y}px`
47 if (seTocan(abrazo, triste)) {
48 triste.value = "😊";
49 triste.style.
50 backgroundColor = "pink";
51 clearInterval(intervalo);
52 } else {
53 x = (x + velocidad) % x2
54 }
55 }
56
57 /**
58 * Devuelve true si 2
59 * elementos se tocan.
60 * @param {HTMLElement} e1
61 * @param {HTMLElement} e2
62 * @returns {boolean} true
63 * si los elementos se
64 * tocan.
65 */
66 function seTocan(e1, e2) {
67 const rE1 =
68 e1.getBoundingClientRect()
69 const rE2 =
70 e2.getBoundingClientRect()
71 return (rE1.right >= rE2.left
72 && rE1.left <= rE2.right
73 && rE1.top <= rE2.bottom
74 && rE1.bottom >= rE2.top)
75 }
76 </script>
77</body>
78
79</html>

I. Muévete al azar

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Mueve Aleatorio</title>
9 <style>
10 .sprite {
11 position: fixed;
12 font-size: 3rem;
13 }
14 </style>
15</head>
16
17<body>
18 <h1>Mueve Aleatorio</h1>
19 <output id="carita"
20 class="sprite">
21 😄
22 </output>
23 <script>
24 const REFRESCO = 5
25 const RUIDO = 4
26 let velocidad = 0.5
27 let x = 0
28 setInterval(avanza, REFRESCO)
29
30 function avanza() {
31 const y = innerHeight / 2
32 const xMáxima = innerWidth
33 carita.style.left = `${x}px`
34 carita.style.bottom = `${y}px`
35 x += velocidad +
36 aleatorio(-RUIDO, RUIDO)
37 if (x < 0) {
38 x = 0
39 velocidad = -velocidad
40 } else if (x > xMáxima) {
41 x = xMáxima
42 velocidad = -velocidad
43 }
44 }
45
46 /**
47 * Devuelve un número
48 * aleatorio entre el valor
49 * menor y el valor mayor.
50 * @param {number} menor el
51 * menor valor que se puede
52 * generar
53 * @param {number} mayor el
54 * mayor valor que se puede
55 * generar
56 * @returns {number} un
57 * número aleatorio entre
58 * menor y mayor.
59 */
60 function
61 aleatorio(menor, mayor) {
62 /* Math.floor(x): elimina
63 * los decimales.
64 * Math.random(): genera
65 * un número aleatorio
66 * >= 0 y < 1. */
67 return menor +
68 Math.floor(
69 Math.random() *
70 (mayor - menor + 1))
71 }
72 </script>
73</body>
74
75</html>

J. Usa custom element

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Custom Elements</title>
9 <style>
10 figura-web {
11 position: fixed;
12 font-size: 60px;
13 }
14 </style>
15 <script>
16 class FiguraWeb
17 extends HTMLElement {
18 connectedCallback() {
19 this.x = 0
20 const attrY =
21 this.getAttribute("y")
22 const attrVel =
23 this.getAttribute("velocidad")
24 this.y = parseInt(attrY, 10)
25 this.velocidad =
26 parseInt(attrVel, 10)
27 }
28 muevete() {
29 this.style.right =
30 `${this.x}px`
31 this.style.top = `${this.y}px`
32 this.x =
33 (this.x + this.velocidad) %
34 innerWidth
35 }
36 }
37 customElements.define(
38 "figura-web", FiguraWeb)
39 </script>
40</head>
41
42<body>
43 <h1>Custom Elements</h1>
44 <figura-web id="fantasma"
45 y="0" velocidad="20">
46 👻
47 </figura-web>
48 <figura-web id="sonrisa"
49 y="100" velocidad="10">
50 😁
51 </figura-web>
52 <script>
53 setInterval(anima, 120)
54
55 function anima() {
56 fantasma.muevete()
57 sonrisa.muevete()
58 }
59 </script>
60</body>
61
62</html>

K. Asociaciones

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Asociaciones</title>
9 <script>
10 /**
11 * Devuelve un número
12 * aleatorio entre el valor
13 * menor y el valor mayor.
14 * @param {number} menor el
15 * menor valor que se puede
16 * generar
17 * @param {number} mayor el
18 * mayor valor que se puede
19 * generar
20 * @returns {number} un
21 * número aleatorio entre
22 * menor y mayor.
23 */
24 function
25 aleatorio(menor, mayor) {
26 /* Math.floor(x): elimina
27 * los decimales.
28 * Math.random(): genera
29 * un número aleatorio
30 * >= 0 y < 1. */
31 return menor +
32 Math.floor(
33 Math.random() *
34 (mayor - menor + 1))
35 }
36
37 class PerroWeb
38 extends HTMLElement {
39 connectedCallback() {
40 this.x = 0
41 this.y = 0
42 this.innerHTML = "🐕"
43 this.style.position = "fixed"
44 this.style.fontSize = "2rem"
45 this.style.right =
46 `${this.x}px`
47 this.style.bottom =
48 `${this.y}px`
49 }
50
51 muevete() {
52 this.x = (this.x + 30) %
53 innerWidth
54 this.style.right =
55 `${this.x}px`
56 }
57 }
58 customElements.define(
59 "perro-web", PerroWeb)
60
61 class AguilaWeb
62 extends HTMLElement {
63 connectedCallback() {
64 this.x = aleatorio(0,
65 Math.floor(innerWidth))
66 this.y = aleatorio(0,
67 Math.floor(innerHeight))
68 this.innerHTML = "🦅"
69 this.style.position = "fixed"
70 this.style.fontSize = "2.5rem"
71 this.style.left = `${this.x}px`
72 this.style.top = `${this.y}px`
73 }
74
75 muevete() {
76 this.y = (this.y + 10) %
77 innerHeight
78 this.style.top = `${this.y}px`
79 }
80 }
81 customElements.define(
82 "aguila-web", AguilaWeb)
83
84 class ControladorWeb
85 extends HTMLElement {
86 connectedCallback() {
87 this.muevete =
88 this.muevete.bind(this)
89 this.innerHTML = /* html */
90 `<button onclick="this.
91 parentElement.muevete()">
92 Mueve
93 </button>`
94 this.perro = new PerroWeb()
95 this.aguilas = [
96 new AguilaWeb(),
97 new AguilaWeb()]
98 this.append(this.perro)
99 for (const a of this.aguilas) {
100 this.append(a)
101 }
102 }
103
104 muevete() {
105 this.perro.muevete()
106 for (let a of this.aguilas) {
107 a.muevete()
108 }
109 }
110 }
111 customElements.define(
112 "controlador-web",
113 ControladorWeb)
114 </script>
115</head>
116
117<body>
118 <h1>Asociaciones</h1>
119 <controlador-web>
120 </controlador-web>
121</body>
122
123</html>

L. Polimorfismo

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html lang="es">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Polimorfismo</title>
9 <script>
10 function
11 aleatorio(menor, mayor) {
12 return menor +
13 Math.floor(
14 Math.random() *
15 (mayor - menor + 1))
16 }
17
18 /** @interface */
19 class SeMueve {
20 muevete() {
21 throw new Error("intf")
22 }
23 }
24
25 /** @implements {SeMueve} */
26 class PerroWeb
27 extends HTMLElement {
28 connectedCallback() {
29 this.x = 0
30 this.y = 0
31 this.innerHTML = "🐕"
32 this.style.position = "fixed"
33 this.style.fontSize = "2rem"
34 this.style.right =
35 `${this.x}px`
36 this.style.bottom =
37 `${this.y}px`
38 }
39
40 muevete() {
41 this.x = (this.x + 30) %
42 window.innerWidth
43 this.style.right =
44 `${this.x}px`
45 }
46 }
47 customElements.define(
48 "perro-web", PerroWeb)
49
50 /** @implements {SeMueve} */
51 class AguilaWeb
52 extends HTMLElement {
53 connectedCallback() {
54 this.x = aleatorio(0,
55 Math.floor(innerWidth))
56 this.y = aleatorio(0,
57 Math.floor(innerHeight))
58 this.innerHTML = "🦅"
59 this.style.position = "fixed"
60 this.style.fontSize = "2.5rem"
61 this.style.left = `${this.x}px`
62 this.style.top = `${this.y}px`
63 }
64
65 muevete() {
66 this.y = (this.y + 10) %
67 window.innerHeight
68 this.style.top = `${this.y}px`
69 }
70 }
71 customElements.define(
72 "aguila-web", AguilaWeb)
73 </script>
74</head>
75
76<body>
77 <h1>Polimorfismo</h1>
78 <p>
79 <button onclick="mueve()">
80 Mueve
81 </button>
82 </p>
83 <script>
84 const figuras = [
85 new AguilaWeb(),
86 new PerroWeb(),
87 new AguilaWeb()]
88 for (let f of figuras) {
89 document.body.append(f)
90 }
91 function mueve() {
92 for (var f of figuras) {
93 f.muevete()
94 }
95 }
96 </script>
97</body>
98
99</html>

M. Jueguito 1

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html>
3
4<head>
5 <meta charset="utf-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Jueguito 1</title>
9 <style>
10 body {
11 /* Rompe el flujo normal para
12 * poder hacer swipe hacia
13 * abajo. */
14 position: fixed;
15 top: 0px;
16 left: 0px;
17 /* ocupa todo el espacio. */
18 width: 100%;
19 height: 100%;
20 /* Elimina márgenes. */
21 margin: 0;
22 /* Evita el scroll */
23 overflow: hidden;
24 }
25
26 .sprite {
27 position: fixed;
28 }
29 </style>
30 <script>
31 //@ts-check
32 class JugadorPaloma
33 extends HTMLElement {
34 connectedCallback() {
35 this.classList.add("sprite")
36 this.innerHTML += "🕊"
37 this.style.fontSize = "60px"
38 /* Coloca el elemento a la
39 * mitad de la pantalla. */
40 const raiz =
41 document.documentElement
42 /* Obtiene las coordenadas del
43 * element. */
44 const r =
45 this.getBoundingClientRect()
46 const left =
47 (raiz.clientWidth - r.width) /
48 2
49 const top =
50 (raiz.clientHeight -
51 r.height) /
52 2
53 this.style.left = `${left}px`
54 this.style.top = `${top}px`
55 }
56
57 sube() {
58 const top =
59 this.getBoundingClientRect().
60 top -
61 20
62 this.style.top = `${top}px`
63 }
64
65 baja() {
66 const top =
67 this.getBoundingClientRect().
68 top +
69 20
70 this.style.top = `${top}px`
71 }
72
73 izquierda() {
74 const left =
75 this.getBoundingClientRect().
76 left -
77 20
78 this.style.left = `${left}px`
79 }
80
81 derecha() {
82 const left =
83 this.getBoundingClientRect().
84 left +
85 20
86 this.style.left = `${left}px`
87 }
88 }
89 customElements.define(
90 "jugador-paloma", JugadorPaloma)
91
92 class FiguraAguila
93 extends HTMLElement {
94 connectedCallback() {
95 this.classList.add("sprite")
96 this.innerHTML = "🦅"
97 this.style.fontSize = "40px"
98 const r =
99 this.getBoundingClientRect()
100 this.style.left = `${r.left}px`
101 this.style.top = `${r.top}px`
102 this.style.bottom = "auto"
103 this.style.right = "auto"
104 }
105
106 /**
107 * Mueve la figura para que se
108 * acerque al jugador, usando la
109 * ecuación de la recta.
110 * @param {HTMLElement} jugador
111 * el jugador que es
112 * perseguido.
113 */
114 muevete(jugador) {
115 const r =
116 this.getBoundingClientRect()
117 const rJ = jugador.
118 getBoundingClientRect()
119 const y2 = rJ.top
120 const y1 = r.top
121 const x2 = rJ.left
122 const x1 = r.left
123 const pendiente = x2 === x1 ?
124 0 :
125 (y2 - y1) / (x2 - x1)
126 const dirección =
127 x2 > x1 ? 1 : -1
128 const x = x1 + dirección * 5
129 const y =
130 pendiente * (x - x1) + y1
131 this.style.left = `${x}px`
132 this.style.top = `${y}px`
133 }
134 }
135 customElements.define(
136 "figura-aguila", FiguraAguila)
137 </script>
138</head>
139
140<body>
141 <div>
142 <jugador-paloma></jugador-paloma>
143 <figura-aguila
144 style="right: 0; top: 0;">
145 </figura-aguila>
146 <figura-aguila
147 style="right: 0; bottom: 0;">
148 </figura-aguila>
149 </div>
150 <script>
151 //@ts-check
152 class Juego {
153 constructor() {
154 /** @type {JugadorPaloma} */
155 this.jugador = document.
156 querySelector(
157 "jugador-paloma")
158 /** @type {FiguraAguila[]} */
159 this.figuras = Array.from(
160 document.querySelectorAll(
161 "figura-aguila"))
162 this.iniciaX = null
163 this.iniciaY = null
164 this.interval = null
165 this.activo = true
166 }
167
168 inicia() {
169 document.addEventListener(
170 "keydown",
171 evt => this.teclas(evt))
172 this.interval = setInterval(
173 () => this.mueveFiguras(), 60)
174 }
175
176 mueveFiguras() {
177 for (const f of this.figuras) {
178 f.muevete(this.jugador)
179 }
180 }
181
182 /** @param {KeyboardEvent} ev*/
183 teclas(ev) {
184 if (this.activo) {
185 switch (ev.key) {
186 case "ArrowLeft":
187 this.jugador.izquierda()
188 break
189 case "ArrowRight":
190 this.jugador.derecha()
191 break
192 case "ArrowUp":
193 this.jugador.sube()
194 break
195 case "ArrowDown":
196 this.jugador.baja()
197 break
198 }
199 }
200 }
201 }
202
203 const juego = new Juego()
204 juego.inicia()
205 </script>
206</body>
207
208</html>

N. Jueguito 2

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html>
3
4<head>
5 <meta charset="utf-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Jueguito 2</title>
9 <style>
10 body {
11 position: fixed;
12 top: 0px;
13 left: 0px;
14 width: 100%;
15 height: 100%;
16 margin: 0;
17 overflow: hidden;
18 }
19
20 .sprite {
21 position: fixed;
22 }
23 </style>
24 <script>
25 //@ts-check
26 /** @abstract */
27 class Jugador
28 extends HTMLElement {
29 izquierda() {
30 throw new Error("abstract")
31 }
32 derecha() {
33 throw new Error("abstract")
34 }
35 sube() {
36 throw new Error("abstract")
37 }
38 baja() {
39 throw new Error("abstract")
40 }
41 }
42
43 /** @abstract */
44 class Figura
45 extends HTMLElement {
46 /**
47 * @param {HTMLElement} jugador
48 * el jugador que persigue.
49 */
50 muevete(jugador) {
51 throw new Error("abstract");
52 }
53 }
54
55 class Juego2 {
56 constructor() {
57 /** @type {Jugador} */
58 this.jugador = document.
59 querySelector(".jugador")
60 /** @type {Figura[]} */
61 this.figuras = Array.from(
62 document.querySelectorAll(
63 ".figura"))
64 this.iniciaX = null
65 this.iniciaY = null
66 this.interval = null
67 this.activo = true
68 }
69
70 inicia() {
71 document.addEventListener(
72 "keydown",
73 evt => this.teclas(evt))
74 document.addEventListener(
75 "touchstart",
76 evt => this.iniciaTouch(evt))
77 document.addEventListener(
78 "touchmove",
79 evt =>
80 this.desplazaTouch(evt))
81 this.interval = setInterval(
82 () => this.mueveFiguras(), 60)
83 }
84
85 mueveFiguras() {
86 for (const f of this.figuras) {
87 f.muevete(this.jugador)
88 }
89 this.detectaColisiones()
90 }
91
92 detectaColisiones() {
93 for (const f of this.figuras) {
94 if (colisiona(
95 this.jugador, f)) {
96 this.termina()
97 break
98 }
99 }
100 }
101
102 termina() {
103 this.activo = false
104 clearInterval(this.interval)
105 alert("Jueguito 2 terminado.")
106 }
107
108 /** @param {KeyboardEvent} evt*/
109 teclas(evt) {
110 if (this.activo) {
111 switch (evt.key) {
112 case "ArrowLeft":
113 this.jugador.izquierda()
114 break
115 case "ArrowRight":
116 this.jugador.derecha()
117 break
118 case "ArrowUp":
119 this.jugador.sube()
120 break
121 case "ArrowDown":
122 this.jugador.baja()
123 break
124 }
125 this.detectaColisiones()
126 }
127 }
128
129 /** @param {TouchEvent} evt */
130 iniciaTouch(evt) {
131 if (this.activo) {
132 const toqueInicial =
133 evt.touches[0]
134 this.iniciaX =
135 toqueInicial.clientX
136 this.iniciaY =
137 toqueInicial.clientY
138 }
139 }
140
141 /** @param {TouchEvent} evt */
142 desplazaTouch(evt) {
143 if (this.activo
144 && this.iniciaX
145 && this.iniciaY) {
146 const desplazamiento =
147 evt.touches[0]
148 var desplazamientoX =
149 desplazamiento.clientX
150 var desplazamientoY =
151 desplazamiento.clientY
152 var difX = desplazamientoX -
153 this.iniciaX
154 var difY = desplazamientoY -
155 this.iniciaY
156 /* Checa que el movimiento no
157 * sea muy corto. */
158 if (Math.abs(difX)
159 + Math.abs(difY)
160 > 150) {
161 if (Math.abs(difX)
162 > Math.abs(difY)) {
163 if (difX > 70) {
164 this.jugador.derecha()
165 } else {
166 this.jugador.izquierda()
167 }
168 } else if (difY > 70) {
169 this.jugador.baja()
170 } else {
171 this.jugador.sube()
172 }
173 this.detectaColisiones()
174 // Reinicia valores.
175 this.iniciaX = null;
176 this.iniciaY = null;
177 }
178 }
179 }
180 }
181
182 /**
183 * @param {HTMLElement} e1
184 * @param {HTMLElement} e2
185 * @returns {boolean} true si los
186 * element colisionan.
187 */
188 function colisiona(e1, e2) {
189 const rE1 =
190 e1.getBoundingClientRect()
191 const rE2 =
192 e2.getBoundingClientRect()
193 return (rE1.right >= rE2.left
194 && rE1.left <= rE2.right
195 && rE1.top <= rE2.bottom
196 && rE1.bottom >= rE2.top)
197 }
198
199 customElements.define(
200 "jugador-paloma",
201 class extends Jugador {
202 connectedCallback() {
203 this.classList.add("sprite")
204 this.classList.add("jugador")
205 this.innerHTML += "🕊"
206 this.style.fontSize = "60px"
207 const raiz =
208 document.documentElement
209 const r =
210 this.getBoundingClientRect()
211 const left =
212 (raiz.clientWidth
213 - r.width) /
214 2
215 const top =
216 (raiz.clientHeight -
217 r.height) /
218 2
219 this.style.left = `${left}px`
220 this.style.top = `${top}px`
221 }
222
223 /** @override */
224 sube() {
225 const top =
226 this.getBoundingClientRect().
227 top -
228 20
229 this.style.top = `${top}px`
230 }
231
232 /** @override */
233 baja() {
234 const top =
235 this.getBoundingClientRect().
236 top +
237 20
238 this.style.top = `${top}px`
239 }
240
241 izquierda() {
242 const left =
243 this.getBoundingClientRect().
244 left -
245 20
246 this.style.left = `${left}px`
247 }
248
249 derecha() {
250 const left =
251 this.getBoundingClientRect().
252 left +
253 20
254 this.style.left = `${left}px`
255 }
256 })
257
258 customElements.define(
259 "figura-aguila",
260 class extends Figura {
261 connectedCallback() {
262 this.classList.add("sprite")
263 this.classList.add("figura")
264 this.innerHTML = "🦅"
265 this.style.fontSize = "40px"
266 const r =
267 this.getBoundingClientRect()
268 this.style.left =
269 `${r.left}px`
270 this.style.top = `${r.top}px`
271 this.style.bottom = "auto"
272 this.style.right = "auto"
273 }
274
275 /**
276 * @param {HTMLElement} jugador
277 * el jugador que es
278 * perseguido.
279 * @override
280 */
281 muevete(jugador) {
282 const r =
283 this.getBoundingClientRect()
284 const rJ = jugador.
285 getBoundingClientRect()
286 const y2 = rJ.top
287 const y1 = r.top
288 const x2 = rJ.left
289 const x1 = r.left
290 const pendiente = x2 === x1 ?
291 0 :
292 (y2 - y1) / (x2 - x1)
293 const dirección =
294 x2 > x1 ? 1 : -1
295 const x = x1 + dirección * 5
296 const y =
297 pendiente * (x - x1) + y1
298 /* Evita que las figuras se
299 * peguen, añadiendo un
300 * movimiento aleatorio. */
301 this.style.left =
302 `${desvía(x)}px`
303 this.style.top =
304 `${desvía(y)}px`
305 }
306 })
307
308 /**
309 * Obtiene una desviación
310 * aleatoria de 10 alrededor del
311 * valor i.
312 * @param {number} i valor base
313 */
314 function desvía(i) {
315 return i + 10 -
316 20 * Math.random()
317 }
318 </script>
319</head>
320
321<body>
322 <jugador-paloma></jugador-paloma>
323 <figura-aguila
324 style="right: 0; top: 0;">
325 </figura-aguila>
326 <figura-aguila
327 style="right: 0; bottom: 0;">
328 </figura-aguila>
329 <script>
330 //@ts-check
331 const juego2 = new Juego2()
332 juego2.inicia()
333 </script>
334</body>
335
336</html>

O. Jueguito 3

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

1<!DOCTYPE html>
2<html>
3
4<head>
5 <meta charset="utf-8">
6 <meta name="viewport"
7 content="width=device-width">
8 <title>Jueguito 3</title>
9 <style>
10 body {
11 position: fixed;
12 top: 0px;
13 left: 0px;
14 width: 100%;
15 height: 100%;
16 margin: 0;
17 overflow: hidden;
18 }
19
20 .sprite {
21 position: fixed;
22 }
23 </style>
24 <script>
25 //@ts-check
26 /** @abstract */
27 class Jugador
28 extends HTMLElement {
29 /** @param {number} velocidad */
30 izquierda(velocidad) {
31 throw new Error("abstract")
32 }
33 /** @param {number} velocidad */
34 derecha(velocidad) {
35 throw new Error("abstract")
36 }
37 /** @param {number} velocidad */
38 sube(velocidad) {
39 throw new Error("abstract")
40 }
41 /** @param {number} velocidad */
42 baja(velocidad) {
43 throw new Error("abstract")
44 }
45 }
46
47 /** @abstract */
48 class Figura
49 extends HTMLElement {
50 /**
51 * @param {HTMLElement} jugador
52 * el jugador que es
53 * perseguido.
54 */
55 muevete(jugador) {
56 throw new Error("abstract")
57 }
58 }
59
60 /** @interface */
61 class Fabrica {
62 /**
63 * Devuelve el único jugador,
64 * por lo que se considera
65 * Singleton.
66 * @returns {Jugador}
67 */
68 jugador() {
69 throw new Error("interface")
70 }
71 /**
72 * Devuelve un arreglo con las
73 * figuras del juego.
74 * @returns {Figura[]}
75 */
76 figuras() {
77 throw new Error("interface")
78 }
79 }
80
81 class Juego3 {
82 /** @param {Fabrica} fabrica */
83 constructor(fabrica) {
84 this.jugador =
85 fabrica.jugador()
86 this.figuras =
87 fabrica.figuras()
88 this.iniciaX = null
89 this.iniciaY = null
90 this.interval = null
91 this.activo = true
92 }
93
94 inicia() {
95 document.addEventListener(
96 "keydown",
97 evt => this.teclas(evt))
98 document.addEventListener(
99 "touchstart",
100 evt => this.iniciaTouch(evt))
101 document.addEventListener(
102 "touchmove",
103 evt =>
104 this.desplazaTouch(evt))
105 this.interval = setInterval(
106 () => this.mueveFiguras(), 60)
107 }
108
109 mueveFiguras() {
110 for (const f of this.figuras) {
111 f.muevete(this.jugador)
112 }
113 this.detectaColisiones()
114 }
115
116 detectaColisiones() {
117 for (const f of this.figuras) {
118 if (colisiona(
119 this.jugador, f)) {
120 this.termina()
121 break
122 }
123 }
124 }
125
126 termina() {
127 this.activo = false
128 clearInterval(this.interval)
129 this.jugador.innerHTML = "💥"
130 }
131
132 /** @param {KeyboardEvent} evt*/
133 teclas(evt) {
134 if (this.activo) {
135 switch (evt.key) {
136 case "ArrowLeft":
137 this.jugador.izquierda(20)
138 break
139 case "ArrowRight":
140 this.jugador.derecha(20)
141 break
142 case "ArrowUp":
143 this.jugador.sube(20)
144 break
145 case "ArrowDown":
146 this.jugador.baja(20)
147 break
148 }
149 this.detectaColisiones()
150 }
151 }
152
153 /** @param {TouchEvent} evt */
154 iniciaTouch(evt) {
155 if (this.activo) {
156 const toqueInicial =
157 evt.touches[0]
158 this.iniciaX =
159 toqueInicial.clientX
160 this.iniciaY =
161 toqueInicial.clientY
162 }
163 }
164
165 /** @param {TouchEvent} evt */
166 desplazaTouch(evt) {
167 if (this.activo
168 && this.iniciaX
169 && this.iniciaY) {
170 const desplazamiento =
171 evt.touches[0]
172 var desplazamientoX =
173 desplazamiento.clientX
174 var desplazamientoY =
175 desplazamiento.clientY
176 var difX = desplazamientoX -
177 this.iniciaX
178 var difY = desplazamientoY -
179 this.iniciaY
180 if (Math.abs(difX)
181 + Math.abs(difY)
182 > 150) {
183 if (Math.abs(difX)
184 > Math.abs(difY)) {
185 if (difX > 70) {
186 this.jugador.derecha(40)
187 } else {
188 this.jugador.izquierda(40)
189 }
190 } else if (difY > 70) {
191 this.jugador.baja(40)
192 } else {
193 this.jugador.sube(40)
194 }
195 this.detectaColisiones()
196 this.iniciaX = null
197 this.iniciaY = null
198 }
199 }
200 }
201 }
202
203 /**
204 * @param {HTMLElement} e1
205 * @param {HTMLElement} e2
206 * @returns {boolean} true si los
207 * element colisionan.
208 */
209 function colisiona(e1, e2) {
210 const rE1 =
211 e1.getBoundingClientRect()
212 const rE2 =
213 e2.getBoundingClientRect()
214 return (rE1.right >= rE2.left
215 && rE1.left <= rE2.right
216 && rE1.top <= rE2.bottom
217 && rE1.bottom >= rE2.top)
218 }
219
220 customElements.define(
221 "jugador-paloma",
222 class extends Jugador {
223 connectedCallback() {
224 this.classList.add("sprite")
225 this.innerHTML += "🕊"
226 this.style.fontSize = "60px"
227 const raiz =
228 document.documentElement
229 const r =
230 this.getBoundingClientRect()
231 const left =
232 (raiz.clientWidth
233 - r.width) /
234 2
235 const top =
236 (raiz.clientHeight -
237 r.height) /
238 2
239 this.style.left = `${left}px`
240 this.style.top = `${top}px`
241 }
242
243 /**
244 * @param {number} velocidad
245 * @override
246 */
247 sube(velocidad) {
248 const top =
249 this.getBoundingClientRect().
250 top -
251 velocidad
252 this.style.top = `${top}px`
253 }
254
255 /**
256 * @param {number} velocidad
257 * @override
258 */
259 baja(velocidad) {
260 const top =
261 this.getBoundingClientRect().
262 top +
263 velocidad
264 this.style.top = `${top}px`
265 }
266
267 /**
268 * @param {number} velocidad
269 * @override
270 */
271 izquierda(velocidad) {
272 const left =
273 this.getBoundingClientRect().
274 left -
275 velocidad
276 this.style.left = `${left}px`
277 }
278
279 /**
280 * @param {number} velocidad
281 * @override
282 */
283 derecha(velocidad) {
284 const left =
285 this.getBoundingClientRect().
286 left +
287 velocidad
288 this.style.left = `${left}px`
289 }
290 })
291
292 customElements.define(
293 "figura-aguila",
294 class extends Figura {
295 connectedCallback() {
296 this.classList.add("sprite")
297 this.innerHTML = "🦅"
298 this.style.fontSize = "40px"
299 const r =
300 this.getBoundingClientRect()
301 this.style.left =
302 `${r.left}px`
303 this.style.top = `${r.top}px`
304 this.style.bottom = "auto"
305 this.style.right = "auto"
306 }
307
308 /**
309 * @param {HTMLElement} jugador
310 * el jugador que persigue.
311 * @override
312 */
313 muevete(jugador) {
314 const r =
315 this.getBoundingClientRect()
316 const rJ = jugador.
317 getBoundingClientRect()
318 const y2 = rJ.top
319 const y1 = r.top
320 const x2 = rJ.left
321 const x1 = r.left
322 const pendiente = x2 === x1 ?
323 0 :
324 (y2 - y1) / (x2 - x1)
325 const dirección =
326 x2 > x1 ? 1 : -1
327 const x = x1 + dirección * 5
328 const y =
329 pendiente * (x - x1) + y1
330 this.style.left =
331 `${desvía(x)}px`
332 this.style.top =
333 `${desvía(y)}px`
334 }
335 })
336
337 function desvía(i) {
338 return i + 10 -
339 20 * Math.random()
340 }
341 </script>
342</head>
343
344<body>
345 <jugador-paloma></jugador-paloma>
346 <figura-aguila
347 style="right: 0; top: 0;">
348 </figura-aguila>
349 <figura-aguila
350 style="right: 0; bottom: 0;">
351 </figura-aguila>
352 <script>
353 //@ts-check
354 /** @implements {Fabrica} */
355 class MiFabrica {
356 constructor() {
357 /** @type {Jugador} */
358 this.jugadorSingleton =
359 document.querySelector(
360 "jugador-paloma");
361 }
362 /**
363 * Devuelve el único jugador,
364 * por lo que se considera
365 * Singleton.
366 * @returns {Jugador}
367 */
368 jugador() {
369 return this.jugadorSingleton
370 }
371 /**
372 * Devuelve un arreglo con las
373 * figuras del juego.
374 * @returns {Figura[]}
375 */
376 figuras() {
377 return Array.from(
378 document.querySelectorAll(
379 "figura-aguila"))
380 }
381 }
382 const juego =
383 new Juego3(new MiFabrica())
384 juego.inicia()
385 </script>
386</body>
387
388</html>

P. Resumen