En esta lección se presentan técnicas para elaborar juegos.
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
| 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> |
En esta lección se revisaron los elementos principales para elaborar formularios y se presentaron algunos ejemplos.