Juego de la vida con vue, typescript y neomorfismo

Puedes ver todos los tutoriales del Juego de la vida aquí: Tutoriales Juego de la vida.

En este tutorial realizaremos un pequeño homenaje a John Horton Conway desarrollando su famoso juego de la vida con Vue, typescript e incluyendo algunos toques de neomorfismo.

Índice de contenidos

1. Introducción

El Juego de la vida es un sencillo juego con unas reglas muy simples en el cual nadie gana ni pierde.

La primera aparición del juego fue en 1970 en la revista Scientific American.

Para saber más acerca de la historia del juego puedes consultar la wikipedia.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2,7 Ghz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS Catalina 10.15.3
  • Entorno de desarrollo: Visual Studio Code
  • Framework: Vue
  • Lenguaje de programación: Typescript

3. Reglas del juego

Regla número 1: Para que una célula siga viva, esta debe tener 2 ó 3 vecinos vivos. Si tiene más muere debido a la superpoblación, si tiene menos entonces muere debido a la soledad.

Regla número 2: Si una célula muerta tiene exactamente 3 vecinas vivas revive.

4. Empezamos el juego

El primer paso a realizar es crear nuestra interfaz, utilizaremos un panel canvas, tres botones y un slider para poder elegir el tamaño de los pixeles.

<template>
  <div class="container">
    <v-container>
      <div class="buttons-container">
        <button @click="newGame()">New game</button>
        <div class="slider-container">
          <span>Pixel size</span>
          <input
            type="range"
            min="4"
            max="10"
            value="4"
            class="slider"
            id="myRange"
            v-model="pixelSize"
            @click="newGame()"
          />
          <input class="size" type="text" name="size" id="sizing" v-model="pixelSize" />
        </div>
        <button @click="play()" v-if="interval === null">Play</button>
        <button @click="pause()" v-else>Pause</button>
      </div>
      <div>
        <canvas :height="600" :width="600"></canvas>
      </div>
    </v-container>
  </div>
</template>

 

5. Estilos

Ahora le damos unos toques de neomorfismo a los botones, slider y contenedor donde estará el canvas.

<style lang="scss">
.container {
  border: 0;
  outline: 0;
  display: flex;
  justify-content: center;
  margin: 0px 350px;
  padding: 10px;
  border-radius: 50px;
  background: var(--primary-color);
  box-shadow: -5px -5px 20px var(--white-color),
    5px 5px 20px var(--secondary-color);
  box-sizing: border-box;
}

.buttons-container {
  display: flex;
  justify-content: center;
  margin-bottom: 10px;
}

button {
  border: 0;
  outline: 0;
  border-radius: 200px;
  padding: 10px;
  margin: 10px;
  width: 100px;
  font-size: 15px;
  background-color: var(--primary-color);
  font-weight: bold;
  box-shadow: -5px -5px 20px var(--white-color),
    5px 5px 20px var(--secondary-color);
  transition: all 0.2s ease-in-out;
  cursor: pointer;
  font-weight: 500;

  &:hover {
    box-shadow: inset -2px -2px 5px var(--white-color),
      inset 2px 2px 5px var(--secondary-color);
  }

  &:active {
    box-shadow: inset 1px 1px 2px var(--secondary-color),
      inset -1px -1px 2px var(--white-color);
  }
}

.slider-container {
  width: min-content;
}

.slider {
  -webkit-appearance: none;
  margin: auto;
  height: 20px;
  background: var(--primary-color);
  outline: 0;
  border-radius: 200px;
  box-shadow: -5px -5px 20px var(--white-color),
    5px 5px 20px var(--secondary-color);

  &:hover {
    box-shadow: inset -2px -2px 5px var(--white-color),
      inset 2px 2px 5px var(--secondary-color);
  }
  &::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    border-radius: 50%;
    width: 25px;
    height: 25px;
    background: var(--tertiary-color);
    cursor: pointer;
  }
  &::-moz-range-thumb {
    width: 25px;
    height: 25px;
    border-radius: 50%;
    background: var(--tertiary-color);
    cursor: pointer;
  }
}

.size {
  border: 0;
  outline: 0;
  width: 12px;
  padding: 5px;
  text-align: center;
  background-color: var(--primary-color);
  border-radius: 40px;
  box-shadow: -5px -5px 20px var(--white-color),
    5px 5px 20px var(--secondary-color);
}
</style>

 

6. Código

Primero definimos las variables que usaremos.

data() {
    const pixelSize = 4;
    const side = Math.min(window.innerWidth, window.innerHeight);
    const height = Math.min(Math.floor((side - 100) / pixelSize), 175);
    const width = Math.min(Math.floor((side - 100) / pixelSize), 175);
    const state = "0".repeat(height * width);
    return {
      counter: 0,
      color: "#10069F",
      height: height,
      velocity: 100,
      interval: null,
      pixelSize: pixelSize,
      state: state,
      width: width
    };
  }

Después creamos los métodos que nos llenarán aleatoriamente el panel de células vivas y muertas. Con esto partimos con un panel diferente en cada nuevo juego.

    newGame() {
      if (this.interval !== null) {
        this.pause();
      }
      let state = "";
      while (state.length !== this.height * this.width) {
        state += Math.floor(Math.random() * 2).toString();
      }
      this.state = state;
      this.draw();
    },
    draw() {
      const context = this.$el.querySelector("canvas").getContext("2d");
      let counter = 0;
      context.clearRect(
        0,
        0,
        this.width * this.pixelSize,
        this.height * this.pixelSize
      );
      context.fillStyle = this.color;
      while (this.state !== 0 && counter < this.width * this.height) {
        const x = counter % this.width;
        const y = Math.floor(counter / this.width);
        if (this.state[counter] === "1") {
          context.fillRect(
            x * this.pixelSize,
            y * this.pixelSize,
            this.pixelSize,
            this.pixelSize
          );
        }
        counter++;
      }
    }

Ahora necesitaremos saber si los vecinos de cada célula están vivos o muertos y así poder determinar el estado de la célula.

    neighbors(position) {
      let north = position - this.width;
      if (north < 0) {
        north = north + this.height * this.width;
      }
      let northeast = north + 1;
      if (northeast % this.width === 0) {
        northeast = northeast - this.width;
      }
      let northwest = north - 1;
      if (northwest < 0 || northwest % this.width === this.width - 1) {
        northwest = northwest + this.width;
      }
      let east = position + 1;
      if (east % this.width === 0) {
        east = east - this.width;
      }
      let west = position - 1;
      if (west < 0 || west % this.width === this.width - 1) {
        west = west + this.width;
      }
      let south = position + this.width;
      if (south > this.width * this.height) {
        south = south - this.width * this.height;
      }
      let southeast = south + 1;
      if (southeast % this.width === 0) {
        southeast = southeast - this.width;
      }
      let southwest = south - 1;
      if (southwest < 0 || southwest % this.width === this.width - 1) {
        southwest = southwest + this.width;
      }
      return [
        north,
        northeast,
        east,
        southeast,
        south,
        southwest,
        west,
        northwest
      ];
    }

Ahora necesitaremos un método que nos ayude en cada iteración para repintar el panel.

    nextIteration(state) {
      let counter = 0;
      let newState = "";
      while (counter < this.width * this.height) {
        const livingNeighbors = this.neighbors(counter).reduce(
          (alive, neighborsPosition) =>
            alive + parseInt(state[neighborsPosition]),
          0
        );
        if (
          state[counter] === "1" &&
          (livingNeighbors === 2 || livingNeighbors === 3)
        ) {
          newState += "1";
        } else if (state[counter] === "0" && livingNeighbors === 3) {
          newState += "1";
        } else {
          newState += "0";
        }
        counter++;
      }
      return newState;
    }

Y ahora haremos los métodos encargados de pausar el juego y de empezarlo.

    pause() {
      clearInterval(this.interval);
      this.interval = null;
    },
    play() {
      this.interval = setInterval(() => {
        this.draw();
        this.state = this.nextIteration(this.state);
        this.counter++;
      }, this.velocity);
    }

7. Resultado final

Como resultado final tendremos un juego de la vida con esta apariencia.

 

8. Referencias

Podéis ver todos los tutoriales sobre el juego de la vida en https://adictosaltrabajo.com/2020/04/30/el-juego-de-la-vida-de-conway/

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

He leído y acepto la política de privacidad

Información básica acerca de la protección de datos

  • Responsable: IZERTIS S.A.
  • Finalidad: Envío información de carácter administrativa, técnica, organizativa y/o comercial sobre los productos y servicios sobre los que se nos consulta.
  • Legitimación: Consentimiento del interesado
  • Destinatarios: Otras empresas del Grupo IZERTIS. Encargados del tratamiento.
  • Derechos: Acceso, rectificación, supresión, cancelación, limitación y portabilidad de los datos.
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad

Soy técnico superior en desarrollo de aplicaciones multiplataforma.

¿Quieres publicar en Adictos al trabajo?

Te puede interesar

Reviews
Destaca la importancia de la transparencia en la investigación financiada públicamente, señalando que los ciudadanos tienen derecho a conocer los resultados obtenidos. Menciona herramientas como CORDIS, que facilitan el acceso a información sobre proyectos europeos de I+D+i.
Reviews
En los últimos días, no han sido pocas las ocasiones en las que he oído o leído acerca de cómo este gran TSUNAMI de la IA, que está llegando a nuestras vidas de todas formas posibles, puede revolucionar nuestro día a día como Product Owner
Reviews
El concepto de diseño estratégico va más allá de los aspectos técnicos o estéticos de un producto o servicio. Busca solucionar necesidades, deseos y expectativas de los usuarios en relación a problemas reales, generando valor y ventaja competitiva a negocio.