DEV Community

Cover image for Coding a Sliding Puzzle game in Javascript
artydev
artydev

Posted on • Updated on

Coding a Sliding Puzzle game in Javascript

See the source code below.
You can play with it here : SlidingPuzzle;

The code is not yet documented. If you are interested, I will be glad to answer your questions...

<html lang="de">
  <head>
  <meta charset="utf-8">
    <title>title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://efpage.de/DML/DML_homepage/lib/DML-min.js"></script>
  </head>
  <body> 

  <script> 
  function sleep (ms) {
    return new Promise((r,err) => {
      setTimeout(r,ms)
    })
  }

  class Board {

    cssCell = `
      background:blueviolet;height:100px; width:100px;
      margin:2px;cursor:pointer;flex: 0 0 100px;
      display:flex;align-items: center;
      justify-content: center;color: orange; 
    `

    cssBoard = `
      padding:5px;display:flex;
      flex-wrap:wrap;background:orange;
      width:416px;margin:0 auto;
    `


    checkValidBoard () { 
      function checkPos (c) {
        return c.dataset.pos == c.dataset.curpos
      }
      return this.cells.every(checkPos);
    }

    findDirectionToMove(cell) {

      const [getCol,getRow] = [(pos) => pos%4,(pos) => Math.trunc(pos/4)]

      const cellpos = Number(cell.dataset.curpos);
      const emptyCellpos  = Number(this.emptyCell.dataset.curpos); 

      const rowcolEmpty = [getRow(emptyCellpos), getCol(emptyCellpos)];
      const rowcolCell =  [getRow(cellpos), getCol(cellpos)];

      const sameColOrRow =  
      (rowcolEmpty[0] == rowcolCell[0] ) || (rowcolEmpty[1] == rowcolCell[1]);

      if (!sameColOrRow) return;

      const directions = {
        [ cellpos - 1] : "left",
        [ cellpos + 1] : "right",
        [ cellpos - 4] : "up",
        [ cellpos + 4] : "down" 
      }
      let posdir = Object.keys(directions).find(pos => pos == emptyCellpos);
      return (directions[posdir]);        
    } 

    findOpositeDirection(direction) { 
      if (direction == "up") { return "down" }
      if (direction == "down") { return "up" }
      if (direction == "left") { return "right" }
      if (direction = "right") { return "left" }
    }

    moveCell (cell) {
      const direction  = this.findDirectionToMove(cell);
      if (!direction) return;

      const amount = 104;
      const duration = 200;
      const coef = {
        'left' : -1,
        'right' : 1,
        'down' : 1,
        'up' : -1
      }

      const oppositeDirection =  this.findOpositeDirection(direction);

      const cssTranslate = (cell, direction) => {
        if (direction == 'left' || direction == 'right') { 
          cell.dataset.dx = Number(cell.dataset.dx) + amount * coef[direction]
        }
        if (direction == 'up' || direction == 'down') {  
          cell.dataset.dy = Number(cell.dataset.dy) + amount * coef[direction]     
        }  
        return `translate(${cell.dataset.dx}px, ${cell.dataset.dy}px )` 
      }

      cell.animate( 
        {transform: cssTranslate(cell, direction)}, 
        {duration: duration, fill: "forwards"});    

      this.emptyCell.animate(
       {transform:  cssTranslate(this.emptyCell, oppositeDirection)}, 
       {duration: duration, fill: "forwards"} 
      );

      let temp = this.emptyCell.dataset.curpos;
      this.emptyCell.dataset.curpos = cell.dataset.curpos;
      cell.dataset.curpos = temp;

      if (cell.dataset.pos !=  cell.dataset.curpos) {
        cell.style.background = "deeppink";
        cell.style.color = "white";
      }
      else { 
         cell.style.background = "blueviolet";
      }
    }

    createCells () {
      // fills cells array with cell element
      this.cells = [...Array(16)].map((_, i) => { 
        let options = {
          style : this.cssCell,
          ["data-pos"] : i, ["data-curpos"] : i,
          ["data-dx"] : 0, ["data-dy"] : 0
        }
        let content =  h1(`${i}`);
        let cell = create("div", options, content);
        cell.onclick =  () => this.moveCell(cell);
        return cell;
      });

      // empty cell customisation 
      this.emptyCell = this.cells[15];
      this.emptyCell.style.background = "rgba(255,0,0,0.0)";
      this.emptyCell.style.zIndex = 0;
      this.emptyCell.style.color = "transparent";
    }

    displayBoard () {
      selectBase(div(""));
        print("<h1 style='text-align:center'>Sliding Puzzle</h1>");
      unselectBase();
      selectBase(div("", {style: this.cssBoard}));
        this.cells.map(appendBase);
      unselectBase();
      selectBase(div("", "text-align:center;margin-top:20px"));
        button("Shuffle") .onclick = () => this.shuffle();
      unselectBase();
    }

    moveCellByCurpos(cellpos) {
      let cell = this.cells.find(cell => cell.dataset.curpos == cellpos);
      this.moveCell(cell);
    }

    async shuffle () {
      for (let i = 0; i < 200; i++) {
        const emptyPos = Number(this.emptyCell.dataset.curpos); 
        const validPos = [
            emptyPos - 1, emptyPos + 1,
            emptyPos - 4, emptyPos + 4
          ].filter(pos => {return (pos >= 0) && (pos <= 15);
        }) 
        const randomPos= validPos[Math.floor(Math.random()*validPos.length)];
        await sleep(90);
        this.moveCellByCurpos(randomPos);  
      }
    }

    start () {  
      this.createCells ();  
      this.displayBoard(); 
    }
  }

  function main() {
    let b = new Board();
    b.start()
  }

  main();
  </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Discussion (0)