N. Jueguito 2

Salida

Ábrelo en otra pestaña.

Revísalo en gilpgedit.

<!DOCTYPE html>
<html>

<head>
 <meta charset="utf-8">
 <meta name="viewport"
   content="width=device-width">
 <title>Jueguito 2</title>
 <style>
  body {
   position: fixed;
   top: 0px;
   left: 0px;
   width: 100%;
   height: 100%;
   margin: 0;
   overflow: hidden;
  }

  .sprite {
   position: fixed;
  }
 </style>
 <script>
  //@ts-check
  /** @abstract */
  class Jugador
   extends HTMLElement {
   izquierda() {
    throw new Error("abstract")
   }
   derecha() {
    throw new Error("abstract")
   }
   sube() {
    throw new Error("abstract")
   }
   baja() {
    throw new Error("abstract")
   }
  }

  /** @abstract */
  class Figura
   extends HTMLElement {
   /**
    * @param {HTMLElement} jugador
    *   el jugador que persigue.
    */
   muevete(jugador) {
    throw new Error("abstract");
   }
  }

  class Juego2 {
   constructor() {
    /** @type {Jugador} */
    this.jugador = document.
     querySelector(".jugador")
    /** @type {Figura[]} */
    this.figuras = Array.from(
     document.querySelectorAll(
      ".figura"))
    this.iniciaX = null
    this.iniciaY = null
    this.interval = null
    this.activo = true
   }

   inicia() {
    document.addEventListener(
     "keydown",
     evt => this.teclas(evt))
    document.addEventListener(
     "touchstart",
     evt => this.iniciaTouch(evt))
    document.addEventListener(
     "touchmove",
     evt =>
      this.desplazaTouch(evt))
    this.interval = setInterval(
     () => this.mueveFiguras(), 60)
   }

   mueveFiguras() {
    for (const f of this.figuras) {
     f.muevete(this.jugador)
    }
    this.detectaColisiones()
   }

   detectaColisiones() {
    for (const f of this.figuras) {
     if (colisiona(
      this.jugador, f)) {
      this.termina()
      break
     }
    }
   }

   termina() {
    this.activo = false
    clearInterval(this.interval)
    alert("Jueguito 2 terminado.")
   }

   /** @param {KeyboardEvent} evt*/
   teclas(evt) {
    if (this.activo) {
     switch (evt.key) {
      case "ArrowLeft":
       this.jugador.izquierda()
       break
      case "ArrowRight":
       this.jugador.derecha()
       break
      case "ArrowUp":
       this.jugador.sube()
       break
      case "ArrowDown":
       this.jugador.baja()
       break
     }
     this.detectaColisiones()
    }
   }

   /** @param {TouchEvent} evt */
   iniciaTouch(evt) {
    if (this.activo) {
     const toqueInicial =
      evt.touches[0]
     this.iniciaX =
      toqueInicial.clientX
     this.iniciaY =
      toqueInicial.clientY
    }
   }

   /** @param {TouchEvent} evt */
   desplazaTouch(evt) {
    if (this.activo
     && this.iniciaX
     && this.iniciaY) {
     const desplazamiento =
      evt.touches[0]
     var desplazamientoX =
      desplazamiento.clientX
     var desplazamientoY =
      desplazamiento.clientY
     var difX = desplazamientoX -
      this.iniciaX
     var difY = desplazamientoY -
      this.iniciaY
     /* Checa que el movimiento no
      * sea muy corto. */
     if (Math.abs(difX)
      + Math.abs(difY)
      > 150) {
      if (Math.abs(difX)
       > Math.abs(difY)) {
       if (difX > 70) {
        this.jugador.derecha()
       } else {
        this.jugador.izquierda()
       }
      } else if (difY > 70) {
       this.jugador.baja()
      } else {
       this.jugador.sube()
      }
      this.detectaColisiones()
      // Reinicia valores.
      this.iniciaX = null;
      this.iniciaY = null;
     }
    }
   }
  }

  /**
   * @param {HTMLElement} e1
   * @param {HTMLElement} e2
   * @returns {boolean} true si los
   *    element colisionan.
   */
  function colisiona(e1, e2) {
   const rE1 =
    e1.getBoundingClientRect()
   const rE2 =
    e2.getBoundingClientRect()
   return (rE1.right >= rE2.left
    && rE1.left <= rE2.right
    && rE1.top <= rE2.bottom
    && rE1.bottom >= rE2.top)
  }

  customElements.define(
   "jugador-paloma",
   class extends Jugador {
    connectedCallback() {
     this.classList.add("sprite")
     this.classList.add("jugador")
     this.innerHTML += "🕊"
     this.style.fontSize = "60px"
     const raiz =
      document.documentElement
     const r =
      this.getBoundingClientRect()
     const left =
      (raiz.clientWidth
       - r.width) /
      2
     const top =
      (raiz.clientHeight -
       r.height) /
      2
     this.style.left = `${left}px`
     this.style.top = `${top}px`
    }

    /** @override */
    sube() {
     const top =
      this.getBoundingClientRect().
       top -
      20
     this.style.top = `${top}px`
    }

    /** @override */
    baja() {
     const top =
      this.getBoundingClientRect().
       top +
      20
     this.style.top = `${top}px`
    }

    izquierda() {
     const left =
      this.getBoundingClientRect().
       left -
      20
     this.style.left = `${left}px`
    }

    derecha() {
     const left =
      this.getBoundingClientRect().
       left +
      20
     this.style.left = `${left}px`
    }
   })

  customElements.define(
   "figura-aguila",
   class extends Figura {
    connectedCallback() {
     this.classList.add("sprite")
     this.classList.add("figura")
     this.innerHTML = "🦅"
     this.style.fontSize = "40px"
     const r =
      this.getBoundingClientRect()
     this.style.left =
      `${r.left}px`
     this.style.top = `${r.top}px`
     this.style.bottom = "auto"
     this.style.right = "auto"
    }

    /**
     * @param {HTMLElement} jugador
     *   el jugador que es
     *   perseguido.
     * @override
     */
    muevete(jugador) {
     const r =
      this.getBoundingClientRect()
     const rJ = jugador.
      getBoundingClientRect()
     const y2 = rJ.top
     const y1 = r.top
     const x2 = rJ.left
     const x1 = r.left
     const pendiente = x2 === x1 ?
      0 :
      (y2 - y1) / (x2 - x1)
     const dirección =
      x2 > x1 ? 1 : -1
     const x = x1 + dirección * 5
     const y =
      pendiente * (x - x1) + y1
     /* Evita que las figuras se
      * peguen, añadiendo un
      * movimiento aleatorio. */
     this.style.left =
      `${desvía(x)}px`
     this.style.top =
      `${desvía(y)}px`
    }
   })

  /**
   * Obtiene una desviación
   * aleatoria de 10 alrededor del
   * valor i.
   * @param {number} i valor base
   */
  function desvía(i) {
   return i + 10 -
    20 * Math.random()
  }
 </script>
</head>

<body>
 <jugador-paloma></jugador-paloma>
 <figura-aguila
   style="right: 0; top: 0;">
 </figura-aguila>
 <figura-aguila
   style="right: 0; bottom: 0;">
 </figura-aguila>
 <script>
  //@ts-check
  const juego2 = new Juego2()
  juego2.inicia()
 </script>
</body>

</html>