PRIMER HTML5 CANVAS juego Puzzle 15 -- javascript campo con beginner campo con html5 campo con canvas campo con sliding-tile-puzzle camp codereview Relacionados El problema

First HTML5 Canvas Game Puzzle 15


6
vote

problema

Español

Este es mi primer juego hecho usando lienzo, actualmente funciona bien, pero sé que mi javascript podría ser mejor ya que todavía estoy aprendiendo. ¿Qué mejoras puedo hacer en la estructura de mi javascript?

  document.addEventListener("DOMContentLoaded", function() {    var c = document.getElementById("board");   var ctx = c.getContext("2d");   c.width = 400;   c.height = 400;    var map = [     [4,9,12,8],     [6,15,3,11],     [7,0,1,10],     [13,14,2,5]   ];    var winMap = [     [1,2,3,4],     [5,6,7,8],     [9,10,11,12],     [13,14,15,0]   ];    var tileMap = [];    var tile = {   width : 100,   height : 100   };    var pos = {     x : 0,     y : 0,     textx : 45,     texty : 55   };    var drawTile = function(){     ctx.fillStyle = '#EB5E55';     ctx.shadowColor = '#000';     ctx.shadowBlur = 4;     ctx.shadowOffsetX = 0;     ctx.shadowOffsetY = 2;     ctx.fillRect(pos.x + 5,pos.y + 5,tile.width-10,tile.height-10);     ctx.shadowColor = "transparent";     ctx.fillStyle = '#FFFFFF';     ctx.font = "20px Arial";     //adjust center for larger numbers     if(map[i][j] >= 10){       ctx.fillText(map[i][j], pos.textx -2 , pos.texty);     }else{       ctx.fillText(map[i][j], pos.textx + 2 , pos.texty);     }   };    var buildBoard = function(){     for(i = 0; i < map.length; i++){        tileMap[i] = [];        for(j = 0; j < map[i].length; j++){         var currentTile = {             tileName : map[i][j],             x : pos.x,             y : pos.y,             width : 100,             height : 100,             tileIndex : j           };          if(map[i][j] !== 0){           //create our numbered box           drawTile();           //push box id and cords to tilemap array           tileMap[i].push(currentTile);          }else{           //create our zero box           tileMap[i].push(currentTile);         }         pos.textx += 100;         pos.x += 100;       }       pos.x = 0;       pos.textx = 43;       pos.texty += 100;        pos.y += 100;     }   };    //get mouse position   function getPosition(event){      var x = event.x;     var y = event.y;     x -= c.offsetLeft;     y -= c.offsetTop;      //Check to see which box we are in     for(var i = 0; i < tileMap.length; i++){       for(var j = 0; j < tileMap[i].length; j++){         if(y > tileMap[i][j].y && y < tileMap[i][j].y + tileMap[i][j].height &&           x > tileMap[i][j].x && x < tileMap[i][j].x + tileMap[i][j].width){               checkMove(tileMap[i][j].tileName, tileMap[i][j].tileIndex);            }       }     }   }     // detect if move is possible   var checkMove = function(item, itemIndex){     //check column for zero and clicked box     var checkColumn = function(){       var zeroIndex = null;       //check for zero       for(var x = 0; x < map.length; x++){           zeroIndex = map[x].indexOf(0);         if(zeroIndex > -1){           zeroIndex = zeroIndex;           break;         }       }       if(zeroIndex === itemIndex){         //create a new array with column values         var tempArr = [];         for(var i = 0; i < map.length; i++){           tempArr.push(map[i][zeroIndex]);         }         //keep track of our clicked item and zero         var zero = tempArr.indexOf(0);         var selectedItem = tempArr.indexOf(item);          //sort our tempArray         if (selectedItem >= tempArr.length) {           var k = selectedItem - tempArr.length;           while ((k--) + 1) {             map[i].push(undefined);           }         }         tempArr.splice(selectedItem, 0, tempArr.splice(zero, 1)[0]);          //update our map with the correct values for the column         for(var l = 0; l < map.length; l++){           map[l][zeroIndex] = tempArr[l];         }       }     };       //check row for zero and clicked box     var checkRow = function(){       for(var i = 0; i< map.length; i++){         var itemIndex = map[i].indexOf(item);         var zeroIndex = map[i].indexOf(0);         //if zero and clicked box are present in same row         if(itemIndex > -1 && zeroIndex > -1){           //reorder row           if (itemIndex >= map[i].length) {             var k = itemIndex - map[i].length;             while ((k--) + 1) {               map[i].push(undefined);             }           }           map[i].splice(itemIndex, 0, map[i].splice(zeroIndex, 1)[0]);         }       }     };      checkColumn();     checkRow();      clear();   };    var clear = function(){     ctx.clearRect(0, 0, 400, 400);     pos = {       x : 0,       y : 0,       textx : 46,       texty : 55     };     buildBoard();     checkWin();   };    var checkWin = function(){     var allMatch = true;     for(var i = 0; i < winMap.length; i++){       for(var j = 0; j < winMap[i].length; j++){         if(map[i][j] !== winMap[i][j]){           allMatch = false;         }       }         }     if(allMatch){       var winMessage = document.querySelector('.win');       winMessage.classList.remove('hide');       winMessage.classList.add('fall');     }   }    buildBoard();   c.addEventListener("mousedown", getPosition, false); });  
  <canvas id="board"></canvas>  

Ver el codepen

Original en ingles

This is my first game made using canvas, currently it works well but I know my JavaScript could be better as I am still learning. What improvements can I make on the structure of my JavaScript?

document.addEventListener("DOMContentLoaded", function() {    var c = document.getElementById("board");   var ctx = c.getContext("2d");   c.width = 400;   c.height = 400;    var map = [     [4,9,12,8],     [6,15,3,11],     [7,0,1,10],     [13,14,2,5]   ];    var winMap = [     [1,2,3,4],     [5,6,7,8],     [9,10,11,12],     [13,14,15,0]   ];    var tileMap = [];    var tile = {   width : 100,   height : 100   };    var pos = {     x : 0,     y : 0,     textx : 45,     texty : 55   };    var drawTile = function(){     ctx.fillStyle = '#EB5E55';     ctx.shadowColor = '#000';     ctx.shadowBlur = 4;     ctx.shadowOffsetX = 0;     ctx.shadowOffsetY = 2;     ctx.fillRect(pos.x + 5,pos.y + 5,tile.width-10,tile.height-10);     ctx.shadowColor = "transparent";     ctx.fillStyle = '#FFFFFF';     ctx.font = "20px Arial";     //adjust center for larger numbers     if(map[i][j] >= 10){       ctx.fillText(map[i][j], pos.textx -2 , pos.texty);     }else{       ctx.fillText(map[i][j], pos.textx + 2 , pos.texty);     }   };    var buildBoard = function(){     for(i = 0; i < map.length; i++){        tileMap[i] = [];        for(j = 0; j < map[i].length; j++){         var currentTile = {             tileName : map[i][j],             x : pos.x,             y : pos.y,             width : 100,             height : 100,             tileIndex : j           };          if(map[i][j] !== 0){           //create our numbered box           drawTile();           //push box id and cords to tilemap array           tileMap[i].push(currentTile);          }else{           //create our zero box           tileMap[i].push(currentTile);         }         pos.textx += 100;         pos.x += 100;       }       pos.x = 0;       pos.textx = 43;       pos.texty += 100;        pos.y += 100;     }   };    //get mouse position   function getPosition(event){      var x = event.x;     var y = event.y;     x -= c.offsetLeft;     y -= c.offsetTop;      //Check to see which box we are in     for(var i = 0; i < tileMap.length; i++){       for(var j = 0; j < tileMap[i].length; j++){         if(y > tileMap[i][j].y && y < tileMap[i][j].y + tileMap[i][j].height &&           x > tileMap[i][j].x && x < tileMap[i][j].x + tileMap[i][j].width){               checkMove(tileMap[i][j].tileName, tileMap[i][j].tileIndex);            }       }     }   }     // detect if move is possible   var checkMove = function(item, itemIndex){     //check column for zero and clicked box     var checkColumn = function(){       var zeroIndex = null;       //check for zero       for(var x = 0; x < map.length; x++){           zeroIndex = map[x].indexOf(0);         if(zeroIndex > -1){           zeroIndex = zeroIndex;           break;         }       }       if(zeroIndex === itemIndex){         //create a new array with column values         var tempArr = [];         for(var i = 0; i < map.length; i++){           tempArr.push(map[i][zeroIndex]);         }         //keep track of our clicked item and zero         var zero = tempArr.indexOf(0);         var selectedItem = tempArr.indexOf(item);          //sort our tempArray         if (selectedItem >= tempArr.length) {           var k = selectedItem - tempArr.length;           while ((k--) + 1) {             map[i].push(undefined);           }         }         tempArr.splice(selectedItem, 0, tempArr.splice(zero, 1)[0]);          //update our map with the correct values for the column         for(var l = 0; l < map.length; l++){           map[l][zeroIndex] = tempArr[l];         }       }     };       //check row for zero and clicked box     var checkRow = function(){       for(var i = 0; i< map.length; i++){         var itemIndex = map[i].indexOf(item);         var zeroIndex = map[i].indexOf(0);         //if zero and clicked box are present in same row         if(itemIndex > -1 && zeroIndex > -1){           //reorder row           if (itemIndex >= map[i].length) {             var k = itemIndex - map[i].length;             while ((k--) + 1) {               map[i].push(undefined);             }           }           map[i].splice(itemIndex, 0, map[i].splice(zeroIndex, 1)[0]);         }       }     };      checkColumn();     checkRow();      clear();   };    var clear = function(){     ctx.clearRect(0, 0, 400, 400);     pos = {       x : 0,       y : 0,       textx : 46,       texty : 55     };     buildBoard();     checkWin();   };    var checkWin = function(){     var allMatch = true;     for(var i = 0; i < winMap.length; i++){       for(var j = 0; j < winMap[i].length; j++){         if(map[i][j] !== winMap[i][j]){           allMatch = false;         }       }         }     if(allMatch){       var winMessage = document.querySelector('.win');       winMessage.classList.remove('hide');       winMessage.classList.add('fall');     }   }    buildBoard();   c.addEventListener("mousedown", getPosition, false); });
<canvas id="board"></canvas>

View The CodePen

              

Lista de respuestas

2
 
vote
vote
La mejor respuesta
 

¡Tu rompecabezas se ve bien! Aquí hay algunas recomendaciones con respecto a su código:

Normas

Su getPosition Accede a los atributos no estándar y no funciona por ejemplo. en Firefox. Siga este consejo y escribe

  let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top;   

alcance

Eliminar referencias a globales como c map3 desde dentro de sus funciones y pasa a través de los parámetros.

Mueva todo el código que no confíe directamente en el evento 9988776655544334 de ese enorme oyente de eventos.

estado

Separe el estado del juego de sus propiedades de UI. P.ej. El ancho y la altura del azulejo son atributos de su representación visual en la pantalla y no deben ser parte del modelo de su junta.

También separe la lógica que maneja las actualizaciones del estado del juego de la lógica que maneja las actualizaciones de UI. P.ej. Introduce un modelo de junta y funciones que operan en ese modelo, como push , 9988776655544336 etc. y escuche los cambios estatales para las actualizaciones de la UI utilizando el patrón de diseño del observador.

La representación de su junta es redundante y más complicada de lo necesario. Simplemente almacenar números en columnas y filas específicas en una matriz bidimensional, similar a su map y 9988776655544338 deben ser suficientes.

Considere aplanar su matriz bidimensional en una gran matriz unidimensional por filas o columnas de concatenación. Esto simplifica la creación de matriz, copiando y comparando y mejora el rendimiento. P.ej. su checkWin lógica podría ser reescrita como

  let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 0  

También considere rastrear explícitamente y almacenar las coordenadas de la baldosa vacía. Esto simplifica y generaliza su código un poco.

Un estado de la placa ejemplar consistiría entonces en un tablero con ancho, altura, azulejos y coordenadas X e Y de la baldosa vacía, por ejemplo:

let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 1

Es ventajoso hacer que su estado de la Junta esté inmutable. Los cambios en el estado presionan las baldosas y luego requieren anulación de la antigua matriz de azulejos con la actualizada en lugar de mutilarla directamente. Para mover o simplemente empujar un azulejo en la posición X, Y, puede usar la siguiente función eficiente:

  let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 2  

naming

Elegir nombres descriptivos. P.ej. CAMBIAR let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 3 EN let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 4 . Preocupaciones separadas y claramente. P.ej. Su función let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 5 Simplemente debe verificar si se resuelve el rompecabezas, no realice tareas adicionales, como mostrar un mensaje. Lo mismo ocurre con let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 6 .

opcional

El let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 7 argumento en let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 8 por defecto, puede dejarlo fuera.

Interfaz de usuario

Su interfaz de usuario se puede simplificar reemplazando el c0 con elementos DOM de estilo para cada azulejo individual. Su navegador es bastante bueno en el manejo de eventos y la asignación de coordenadas del mouse, así que aproveche eso.

usando react.js - Una biblioteca establecida que maneja y minimiza las actualizaciones de DOM cuando (UI) CAMBIOS DE ESTADO: obtenemos el siguiente código:

  c1  
  c2  
  c3  
 

Your puzzle looks nice! Here are a few recommendations regarding your code:

Standards

Your getPosition function accesses non-standard attributes and doesn't work e.g. in Firefox. Follow this advice and write

let rect = canvas.getBoundingClientRect(); let x = event.clientX - rect.left; let y = event.clientY - rect.top; 

Scope

Remove references to globals such as c or map from within your functions and pass them via parameters.

Move all code that doesn't directly rely on the DOMContentLoaded event out of that one huge event listener.

State

Separate the game state from your UI properties. E.g. the tile's width and height are attributes of their visual representation on the screen and should not be part of your board model.

Also separate the logic that handles game state updates from the logic that handles UI updates. E.g. introduce a board model and functions operating on that model such as push, isSolved etc. and listen to state changes for UI updates by using the observer design pattern.

Your board representation is redundant and more complicated than necessary. Simply storing numbers at specific columns and rows in a two-dimensional array - similar to your map and winMap arrays - should be sufficient.

Consider flattening your two-dimensional array into a large one-dimensional array by concatenating rows or columns. This simplifies array creation, copying and comparing and improves performance. E.g. your checkWin logic could then be rewritten as

map.length == winMap.length && map.every((tile, i) => tile == winMap[i]) 

Also consider explicitly tracking and storing the coordinates of the empty tile. This simplifies and generalizes your code somewhat.

An exemplary board state would then consist of a board with width, height, tiles and the x and y coordinates of the empty tile, e.g.:

const board = {width: 2, height: 2, tiles: [3, 1, 0, 2], x: 0, y: 1}

It is advantageous to make your board state immutable. State changes by pushing tiles then require overriding the old array of tiles with the updated one instead of directly mutating it. For moving or rather pushing a tile at position x, y, you could then use the following efficient function:

function pushTile(board, x, y) {   if ((x == board.x) == (y == board.y)) return board;    let tiles = board.tiles.slice();   let start = board.x + board.y * board.width;   let stop  = x + y * board.width;   let step  = y == board.y ? 1 : board.width;    if (start > stop) step = -step;    for (let i = start; i != stop; i += step) {     [tiles[i + step], tiles[i]] = [tiles[i], tiles[i + step]];   }   return Object.assign({}, board, {tiles: tiles, x: x, y: y}); } 

Naming

Choose descriptive names. E.g. change c into canvas. Separate and clearly name concerns. E.g. your function checkWin should just check whether the puzzle is solved, not perform additional tasks such as displaying a message. The same holds for checkMove.

Optional

The useCapture argument in addEventListener("mousedown", click, false) is false per default, you can leave it out.

User Interface

Your user interface can be simplified by replacing the <canvas> with styled DOM elements for each individual tile. Your browser is pretty good at event handling and mapping mouse coordinates, so take advantage of that.

Using react.js - an established library which handles and minimizes DOM updates when (UI) state changes - we get the following code:

// Compute new board with tiles at x, y pushed towards the empty space: function pushTile(board, x, y) {   if ((x == board.x) == (y == board.y)) return board;      let tiles = board.tiles.slice();   let start = board.x + board.y * board.width;   let stop  = x + y * board.width;   let step  = y == board.y ? 1 : board.width;    if (start > stop) step = -step;    for (let i = start; i != stop; i += step) {     [tiles[i + step], tiles[i]] = [tiles[i], tiles[i + step]];   }   return Object.assign({}, board, {tiles: tiles, x: x, y: y}); }  // Compare board tiles with a given solution: function isSolved(board, solution) {   return board.tiles.every((tile, i) => tile == solution[i]); }  // Retrieve value of tile at x, y: function tileAt(board, x, y) {   return board.tiles[x + y * board.width]; }  // React component displaying a single tile: function Tile(props) {   const className = props.isSpace ? "tile tile-space" : "tile";   return (     <button className={className} onMouseDown={props.onMouseDown}>       {props.value}     </button>   ); }  // React component displaying a grid of tiles:  function Tiles(props) {   let rows = [];   for (let y = 0; y < props.board.height; y++) {     let row = [];     for (let x = 0; x < props.board.width; x++) {       row.push(         <Tile           value={tileAt(props.board, x, y)}           isSpace={props.board.x == x && props.board.y == y}            onMouseDown={() => props.onMouseDown(x, y)}         />       );     }     rows.push(<div className="tiles-row">{row}</div>);   }   return (<div className="tiles">{rows}</div>); }  // React component displaying a complete puzzle: class Puzzle extends React.Component {   constructor(props) {     super(props);          const board = {       tiles: props.tiles,       width: props.width,       height: props.height,       x: props.x,       y: props.y     };     this.solution = props.solution;     this.state = {board: board, solved: isSolved(board, this.solution)}   }   handleMouseDown(x, y) {     let board = pushTile(this.state.board, x, y);     this.setState({board: board, solved: isSolved(board, this.solution)});   }   render() {     return (       <div className="puzzle">         <Tiles           board={this.state.board}           onMouseDown={(x, y) => this.handleMouseDown(x, y)}         />         {this.state.solved && <span className="puzzle-solved">You won!</span>}       </div>     );   } }  // Initialise and render the puzzle: ReactDOM.render(   <Puzzle     tiles={[       4,  9,  12, 8,       6,  15, 3,  11,       7,  0,  1,  10,       13, 14, 2,  5     ]}     solution={[       1,  2,  3,  4,       5,  6,  7,  8,       9,  10, 11, 12,       13, 14, 15, 0     ]}     width={4} height={4}     x={1} y={2}   />,   document.getElementById("root") );
.puzzle {   position: relative;   display: inline-block;   text-align: center;   background-color: #3A3335;   border: 2px solid #3A3335;   box-shadow: 0 10px 30px -10px #000; }  .puzzle-solved {   position: absolute;   width: 100%;   left: 50%;   top: 50%;   transform: translate(-50%,-50%) rotate(-15deg);   font-size: 50px;   color: #FDF0D5;   text-shadow: 3px 3px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; }  .tile {   margin: 5px;   width: 90px;   height: 90px;   font-size: 20px;   color: #FDF0D5;   background-color: #EB5E55;   box-shadow: 0px 2px 4px 0px #000;   border: none; }  .tile-space {   visibility: hidden; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>  <div id="root"></div>
 
 
2
 
vote

No tengo suficiente reputación para comentar, así que aquí hay una respuesta ..

Dada solo fila 0, la columna 3 está vacía ..

Cuando hago clic en 0,0, todo el bloque en la fila 0 se moverá a la derecha y la placa terminará con 0,0 que está vacía.

.. Sin embargo ...

Cuando hago clic en 3,3 solo un bloque se moverá hacia arriba y la placa terminará con 1,3 estar vacía ...

¡Es posible que desee comprobar esto, ¡parece un error menor para mí!

 

I do not have enough reputation to comment so here is an answer..

Given only row 0, column 3 is empty..

When I click on 0,0 all the block on row 0 will move to the right and the board will end up with 0,0 being empty.

..however..

When I click on 3,3 only one block will move up and the board will end up with 1,3 being empty..

You may want to check this, seems like a minor bug to me!

 
 
   
   

Relacionados problema

6  15 Puzzle juego JavaScript  ( 15 puzzle game javascript ) 
He creado un programa que le permite jugar al juego de quince rompecabezas. Tienes que poner los bloques en orden ascendente moviéndolos al espacio abierto. M...

5  Mi solucionador de 15 rompecabezas es demasiado lento  ( My 15 puzzle solver is too slow ) 
Toma mi código más de 700 segundos para resolver un rompecabezas simple: 2 7 11 5 13 0 9 4 14 1 8 6 10 3 12 15 MI FUNCIÓN findSolution(puzzle...

3  N-Puzzle en Clojescript  ( N puzzle in clojurescript ) 
Estoy en el proceso de crear un solucionador de N-Puzzle en Clojurescript. Tengo el modelo básico en funcionamiento y puedo mover las baldosas en la pantalla....

1  Resolviendo el algoritmo de 15 rompecabezas utilizando IDA * o A *  ( Solving 15 puzzle algorithm using ida or a ) 
Actualmente estoy usando el álgoritmo BESTFIRST para resolver el rompecabezas 15. Funciona bien cuando tengo insumos simples y tarda aproximadamente 1-20 segu...

4  Rompecabezas de bloques deslizantes, A *  ( Sliding block puzzle a ) 
Soy un programador para principiantes y espero que alguien esté dispuesto a echar un vistazo a mi código y decirme cómo mejorar mi enfoque de estilo y desarro...

3  EJERCICIO SIMPLE QUE REPRESENTA UNA STIPMIENTO  ( Simple exercice representing a slidepuzzle ) 
He estado ayudando a un estudiante con un ejercicio, lo escribió rápidamente y pensé que sería la oportunidad ideal para mejorar mi propio estilo y enfoque de...

6  Una función que mueve un azulejo en el juego de quince  ( A function that moves a tile in the game of fifteen ) 
He creado una función que moverá un azulejo que un usuario especifica dentro de la placa de juego. El tablero de juego puede ser de 3 * 3 de hasta 9 * 9 y e...

4  Manhattan Distancia + Función de puntuación de conflictos lineales para soldado de rompecabezas de azulejos deslizantes  ( Manhattan distance linear conflicts scoring function for sliding tile puzzle s ) 
He escrito otro solucionador de N-Puzzle. Utilicé el algoritmo A * para buscar con la heurística de la distancia de Manhattan. Funcionó muy bien para el 9-rom...

5  Búsqueda de espacio estatal para rompecabezas de azulejos deslizantes  ( State space search for sliding tile puzzle ) 
Estoy trabajando en Búsqueda de espacio estatal (8 rompecabezas) en Python y cuando ejecuto mi programa con python3 -m profile , descubro que la mayor part...

5  Comparando los solversadores de rompecabezas en Java  ( Comparing puzzle solvers in java ) 
Tengo este programa que resuelve un $ (n ^ 2 - 1) $ - rompecabezas para el general $ n $. Tengo tres solucionadores: BidirectionalBFSPathFinder AS...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos