Tic Tac Toe Parte 1: Svelte y Canvas 2D

Tic Tac Toe Parte 0: Comparar Svelte y React
Tic Tac Toe Parte 1: Svelte y Canvas 2D
Tic Tac Toe Parte 2: Deshacer / Rehacer sin estado
Tic Tac Toe, parte 3: Deshacer / Rehacer con almacenamiento de comandos
Tic Tac Toe Parte 4: Interactuar con el backend de Flask usando HTTP

En el artículo "Comparación: Svelte and React" intenté repetir el desarrollo del juego Tic Tac Toe. Allí completé solo la primera parte del tutorial original para React sin apoyar la historia de los movimientos. En este artículo, comenzaremos el desarrollo de este juego usando el marco Svelte con soporte para la historia de movimientos. La historia de los movimientos es en realidad un sistema de Deshacer / Rehacer. En el tutorial original sobre React, se implementa el sistema Deshacer / Rehacer con almacenamiento de estado con acceso aleatorio a cualquier estado. Al implementar el sistema Deshacer / Rehacer, generalmente se usa el patrón de Comando, y los comandos Deshacer / Rehacer se almacenan en la lista de comandos. Intentaremos implementar este enfoque más tarde; ahora ejecutaremos el sistema Deshacer / Rehacer con almacenamiento de estado.


El desarrollo utiliza la solución arquitectónica Flux que utiliza almacenamiento. Esta es una sección separada en el artículo.


Código de inicio

Código REPL


App.svelte
<script> import Board from './Board.svelte'; </script> <div class="game"> <div class="game-board"> <Board /> </div> <div class="game-info"> <div class="status">Next player: X</div> <div></div> <ol></ol> </div> </div> <style> .game { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; display: flex; flex-direction: row; } .game-info { margin-left: 20px; } .status { margin-bottom: 10px; } ol { padding-left: 30px; } </style> 

Board.svelte
 <script> import { onMount } from 'svelte'; export let width = 3; export let height = 3; export let cellWidth = 34; export let cellHeight = 34; export let colorStroke = "#999"; let boardWidth = 1 + (width * cellWidth); let boardHeight = 1 + (height * cellHeight); let canvas; onMount(() => { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, boardWidth, boardHeight); ctx.beginPath(); // vertical lines for (let x = 0; x <= boardWidth; x += cellWidth) { ctx.moveTo(0.5 + x, 0); ctx.lineTo(0.5 + x, boardHeight); } // horizontal lines for (let y = 0; y <= boardHeight; y += cellHeight) { ctx.moveTo(0, 0.5 + y); ctx.lineTo(boardWidth, 0.5 + y); } // draw the board ctx.strokeStyle = colorStroke; ctx.stroke(); ctx.closePath(); }); </script> <canvas bind:this={canvas} width={boardWidth} height={boardHeight} ></canvas> 

El código de inicio muestra una cuadrícula vacía. Se muestra con el elemento de lienzo HTML5. Para obtener más información sobre el uso de este elemento, consulte el artículo anterior Desarrollo de Breakout en Svelte . Cómo dibujar una cuadrícula espiada aquí . El componente de tablero se puede reutilizar en otros juegos. Al cambiar las variables ancho y alto , puede cambiar el tamaño de la cuadrícula; al cambiar los valores de las variables cellWidth y cellHeight, puede cambiar el tamaño de la celda.


Rellenar celdas con ceros

Código REPL
En la función onMount () agregó la salida de ceros en las celdas después de la salida de la cuadrícula. Y algunos números mágicos relacionados con los valores de posicionamiento en las celdas.


 ctx.beginPath(); ctx.font = "bold 22px Century Gothic"; let d = 8; for (let i = 0; i < height; i+=1) { for (let j = 0; j < width; j+=1) { ctx.fillText("O", j * cellWidth + d + 1, (i + 1) * cellHeight - d); } } ctx.closePath(); 

Agregar almacenamiento de estado

Código REPL
En esta sección, verificamos los cambios de estado utilizando el repositorio de usuarios . Se agregó almacenamiento de estado en un archivo stores.js separado, este almacenamiento se importa a ambos componentes: aplicación y placa. Los métodos state1 y state2 que cambian el estado del juego se definen en esta tienda.


 import { writable } from 'svelte/store'; function createState() { const { subscribe, set, update } = writable(Array(9).fill('O')); return { subscribe, state1: () => set(Array(9).fill('1')), state2: () => set(Array(9).fill('2')), }; } export const state = createState(); 

Se agregaron dos botones Estado 1 y Estado 2 al componente de la aplicación. Al hacer clic en los botones, llamamos a los métodos correspondientes en el repositorio.


 <button on:click={state.state1}>State 1</button> <button on:click={state.state2}>State 2</button> 

En el componente Board, cambié la línea de salida de ceros a la salida de datos de su almacenamiento de estado . Aquí usamos la suscripción automática al almacenamiento .


 ctx.fillText($state[k], j * cellWidth + d + 1, (i + 1) * cellHeight - d); 

En esta etapa, el campo del juego está lleno de ceros de forma predeterminada, haga clic en el botón Estado 1 - el campo está lleno de unidades, haga clic en el botón Estado 2 - el campo está lleno de deuces.


Llenar una celda con un clic del mouse

Código REPL
En la tienda de estado , agregué el método setCell () , que llena la celda seleccionada con una cruz.


 setCell: (i) => update(a => {a[i] = 'X'; return a;}), 

Se agregó un controlador de eventos de clic del mouse al lienzo, aquí determinamos el índice de la celda y llamamos al método setCell () del almacenamiento de estado .


 function handleClick(event) { let x = Math.trunc((event.offsetX + 0.5) / cellWidth); let y = Math.trunc((event.offsetY + 0.5) / cellHeight); let i = y * width + x; state.setCell(i); } 

Por defecto, el campo de juego está lleno de ceros, hacemos clic en cualquier celda, el cero se reemplaza con una cruz.


Historia de movimientos

Código REPL
Permítame recordarle que actualmente estamos ejecutando el sistema Deshacer / Rehacer con almacenamiento de estado con acceso aleatorio.


 import { writable } from 'svelte/store'; class History { constructor() { this.history = new Array; this.current = -1; } currentState() { return this.history[this.current]; } push(state) { // TODO: remove all redo states this.current++; this.history.push(state); } } function createHistory() { const { subscribe, set, update } = writable(new History); return { subscribe, push: (state) => update(h => { h.push(state); return h; }), }; } export const history = createHistory(); 

La tienda estatal se ha eliminado, la tienda de historial se ha agregado para almacenar el historial de movimientos. Lo describimos usando la clase Historia . Para almacenar los estados, use la matriz de historial . A veces, al implementar el sistema Deshacer / Rehacer, se utilizan dos pilas LIFO: deshacer-apilar y rehacer-apilar. Utilizamos una única matriz de historial para almacenar estados en el historial . La propiedad actual se usa para determinar el estado actual del juego. Todos los estados en la historia desde el comienzo de la matriz hasta el estado con el índice actual , por así decirlo, están en la lista Deshacer, y todos los demás están en la lista Rehacer. Al disminuir o aumentar la propiedad actual , en otras palabras, al ejecutar los comandos Deshacer o Rehacer, seleccionamos el estado más cercano al comienzo o al final del juego. Los métodos de deshacer y rehacer aún no están implementados, se agregarán más adelante. Los métodos CurrentState () y push () se han agregado a la clase History . El método currentState () devuelve el estado actual del juego, usando el método push () agregamos un nuevo estado al historial de movimientos.


En el componente Aplicación , eliminamos los botones Estado 1 y Estado 2 . Y agregó un botón pulsador:


 <button on:click={() => history.push(Array(9).fill($history.current + 1))}>Push</button> 

Al hacer clic en este botón, se agrega un nuevo estado al historial de movimientos, la matriz del historial simplemente se llena con el valor del índice de estado actual actual .


En el componente Tablero , muestra el estado actual del historial de movimientos. Aquí se explica cómo usar la suscripción automática al almacenamiento :


 ctx.fillText($history.currentState()[k], j * cellWidth + d + 1, (i + 1) * cellHeight - d); 

En el método push de la tienda de historial , puede agregar resultados a la consola del navegador y ver cómo cambia después de hacer clic en el botón Push .


 h.push(state); console.log(h); return h; 

Al hacer clic en una celda

Código REPL
En el componente de la aplicación , se eliminó el botón Push .


El método clickCell se ha definido en el almacén de historial . Aquí creamos una copia completa del estado del juego, cambiamos el estado de la celda seleccionada y agregamos un nuevo estado al historial de movimientos:


 clickCell: (i) => update(h => { // create a copy of the current state const state = h.currentState().slice(); // change the value of the selected cell to X state[i] = 'X'; // add the new state to the history h.push(state); console.log(h.history); return h; }), 

En el componente Board , la llamada al método de almacenamiento callClick () se agregó a la función handleClick () :


 history.clickCell(i); 

En la consola del navegador aquí también podemos ver cómo cambia el estado del historial de movimientos después de cada clic del mouse.


En los siguientes artículos, terminaremos el juego hasta el final, con una interfaz para cancelar / devolver pasos y acceso aleatorio a cualquier paso del juego. Considere una implementación del sistema Deshacer / Rehacer utilizando el patrón de diseño de Comando. Considere la interacción con el backend, el jugador competirá con el agente intelectual en el backend.


Arquitectura de flujo

En el desarrollo de este juego, se observa el uso de la solución arquitectónica Flux . Las acciones se implementan como métodos en la definición del almacén de historial en el archivo stores.js . Hay un repositorio de historia, que se describe en forma de la clase Historia . Las vistas se implementan como componentes de la aplicación y la placa . Para mí, todo esto es lo mismo que la arquitectura MVC , vista lateral. Acciones - controlador, almacenamiento - modelo, vista - vista. Las descripciones de ambas arquitecturas prácticamente coinciden.


Repositorio de GitHub

https://github.com/nomhoi/tic-tac-toe-part1


Instalar el juego en la computadora local:


 git clone https://github.com/nomhoi/tic-tac-toe-part1.git cd tic-tac-toe-part1 npm install npm run dev 

Lanzamos el juego en un navegador en la dirección: http: // localhost: 5000 / .

Source: https://habr.com/ru/post/458752/


All Articles