Create a Sliding Puzzle with your favorite tools.
Tags: dml
Published: 2024-04-01 18:00:42

Sometimes ago I created a sliding puzzle using [DML](https://efpage.de/DML/DML_homepage/), which is in fact mainly Javascript.

I was curious to see other implementations.

For recall here is my version : [Sliding](https://flems.io/#0=N4IgzgpgNhDGAuEAmIBcIB0ALeBbKIANCLAPYB2YpMaJ1UAhgA6QrFiwBO9aA2gAyEAHPwwj+EgMz8ArAEYA7DIVC5AXWIAzAJYwwfUOQa4ItbHgLEy5RDdoAeHPgAEjcgHMAvAB0QSCL4AfN7kzs6OEAxIwaHhJvAMzrBYDJyQ8D4gAK7wmgC0QkEhYWH28NrwMIHllRD2APQ1VcUl9vGJRiaZAG7aEADuTKSc8L5JFLYZvv3aSPBYnv69sBB5M3NYhM7a5BXaDFB5HAcQnnIY-EWxpRyc2kzwzmCcsJk48Cyo9fUQmkwM7ggGH89QAIgBZAAyYKhAH0sKQTP9AfUoNoAEYwyF5XA7DAAKzAQQat3u8BipXqWEi0Ra9nRpCQAE9As4QnTSQ9WS1NFlyAhtBQnjAIExnAAKXBgACUzmALTCnAg8CynFC5AGzgACtxcZBxeLOIQIJxOLLPKz5dcSukACraEykHKGwhS6UK5wAX3dsU9LRasEYYDAzgAQqRUkg5R6PbBgwBhaBQZyeZwAAw9YXRDFgAGt3Nw+UhUOioFkIL1qMqANzU7TuHCoOQSJgAD2rznW8ybLfbmecuFS7h2qAATG3q7BVVROKghjtEJxq5oYK3UM5+BvnM3+BP+0htGAmIwmagVxB2wd6+Q8hUIFL1ysbCbq-38VkwOVNEy8tZJo+IGfJcyCgYZ12GBgPAgDsPQza1Y2DcNIxTdN+3+JADw8VAZAnA8jxPM9V1fa0wnPVs1k4ZhUH6SimGrbM8wLJ1yGLCCoOIkoSi7LBUAAFjkAA2CdB04YdyFQTcGByUgOJKODONjak8wANSvJAkM4KNxVlYA2RI5xeX5cohWSOBcy1UgQ3FWAdP7RVlVVUJYGBBgEnSDAhhDTxU2cpBXIYdyp04Tz+z9fSlRVNVnHmQ8MBWKAoDADAKxNJlrKU8zLOlWSwjChTrR0FjQW0JUBQoW1SHBUhuggayk1s-T+2sT9nF4QF4HjahCHagAlUh+jUFDeHFTzzVZTyAFJeMIEaspTVlwVcrAMHgTg+VgWawHqXjpTUOz8s4sJmseeKoE8lCADkslwdETTqhKXLc5U4tVUacpKY7nHvB4mUTBLzsu67bs4cUYqS774F+pNHoC56grevTDsRw7Pu4foQIAUVwH6ht6-rxQhqH-qyrZ2s6qACexyG-rOrK1Heo6KBatGQJplDWrx-p7tpmVSeVcnudG+n+yapnHjAYwIHJgB5Tg+v6dn+0NfrMappkBEG7znBZ6gaY15xZQAH0NiUdagLGft4dQU1TM29fUbKRf07RNAlABCCWTBluX+tlCLHIZ5HOM+g9SuMygUKtJGwl4JIk3OvJt0G9dfBgTRRiIfbY9O86AGok+cFOQDuBsM8ILO4+JkNE945O2WyJhfHL-SY8rnnnHz2vC-rpB+vIMZQv7GBHk80OUOl9F8TgeAMFzCAmTAcVQ+nwVKGlDBCqQTb5ucc6tcJmm3v7f2oqXkqV6Z3hR5KtRsujoOvQfj1N+lzy72KsPV7Pz+KB0h+whdhKZeZVQha18FkRuIA-4n1CL4Xu-R+4gC9P2QB38L6gNTHAvuvhoEOSiuAyByDnauzQSAm29c04Z1wZFWBxd6w4DGHlQ6qDgHhxQr4EuDCoFym1ng2hlDGEeiYWED0uBqpSyTBKU6DUkYh3PmQlCYMN47CQB-dBFUqo1W5o7Yh7tWGrz9nwwOotKCPGMMxR4qZmy8UDiHVUrlV4oVHBIWxYtxi-EjvtAA5JQrx3c8hyGbvfLxnD4B+PXIE7x8DyDhO3EE6OXiIGxICYPRq+lPqkCYG-RAaiyGpmilgWKL9skQFyeHUh4cdGHRMS1OMYBbSUUoIwRAKFuZbH0b-HeUdo4sPkWwrWPjfhhOcMbZwHSMHOBCfQsJf99pHWhn5J6M8kCtkBjdO6p0YbuRWbKfO5i+SPAAFTuM0LwcZe0W5EPvr0n+EzElMD8aM8Z5CvHRK8X-f+KMFn+W2UyNZwNuZbOesyXZzh9k2GcMcsgvwzl9NXoNA60dPQiMuTA9Mq1IJgGabVAAJMATZizYbLNbJ6NsWw8UEp+cCpkpLVnSjTJ84RJQmrQ0gg6VytVPlhGABiygmhhi4EfMGBpmLsVtLGXC3+notj7WAEgex4d1zysouHLYOgEpF35ZwfokYiQgG9B2Zl-d9JKIPqy3Yg5EDijsjyxpYAtWCqOsKu1YrTVqxplsTJJSykGOlVyuVCrV5KsDRQNVugoCauGDqzSerkX9h0UPZU0VvqKMKeDd13ylkvWCpZQObqfo0yBTPeGlkUKUqzSWsAtjM1EuzXvZN2NjG6MBYS9y503b5PLbWytMjo6bM-EyGAGAGL5kLCxdhfgICiiYDsXMvhA7zIegOodIFhgTv6IUxA87UlI2gJAHhcz+2QyHSOpiRYJ2lnLJWYe279JMqZbGJUHKaZWV7Zxb4Blw0hlOiGVIlE-kzHmG3L6MATA2H7Eon9Q0MAwYAIKmgYGlQSu0MCDiYAaWEWxtBjQPZc4ezhMnhy8tGS5YRl0QG7pBhMSZ4n314HA-yeQQogDrtoLY9G-CMcrb4VjtHo4ccJXkFZPHu6CFagxhIQmmQifXJcS5TLOL4b-IBSxYQsByHFGmPF2hPRpiqdHJTkifJPqtXA7Q3Qm4EYeKvMAWxlM2H07I6GFBAzaDzOzbSO8lFiJqjTbRC7eE0LboHA1mZ+wfsJsBqcn5ESHgcUKCDabkoZoSqm2KP6rYyGFiapLZql3HqBKesdUZMHF3cNmcUo4ZAyEIIIOrFx3QgDzbllLUAMDkYwAALwAJIsQvChfgzXYp5bax11dnAJ28qPKkFTt7OIPutHhY8iGNJaTfTaaA09QywzPt0a1IAcGOcOkwO4Nh9uODkE8ArngvGIFbPAPIV53DiSfIuLxgQADKaJMLuG1FkTrnWYANHU8EKBgc+SQBgAgbb+ojtkc29DnbB49u+Es8Acj64qNgFW96OHJRINJiSmh8UzAmCAXUrDPHEOEfwBh7VPHkOttI-M-tyzvg7sPaey9lTL4RJiTyPATJY5dytkOwF9EORBfkH2x9rAWRNDnhwc4DALm0TudTJ5i0BTYpgDlwrmA2lweUBp3Tw3QiYzWh8xIhKoZfqvUsoLOa3TFNJtOmlpKP7lEsW5jvLtgV7deR8vHLKQ2ifiL89I96C3OIBSZPyJ4evzwSnW6RNd4p8PaAGx2TP9hnDOMG9sXOucU8fTcYTCyxGrrrJBvm6mNb-c5plDBS5n1uhqQr0NOZJRy+lsToEr6asO-50iaR7vg-e-OGmgPn6Q-J9d7UMoqAi5t5a+AGi7egRUz8FlAAMh3xKc69grEyDx7lWU+1UaQV7rgCvqY2-fYr7wRa8wN6gWGOKZ-y1GnX+0oc+-swK8MAYAPB5hdoAsGAdUKhhQp10NmxT9tcw9fMkxbd4wA9DQr9EQK875GVzd4JrRPxUhHhPNdJPlIMTNrdEpk9DVEtYolsTxVtDcH4mEmEWh8N3BJYUINQFYGDz9Yh2CTB2sEgRhtIWh+CgRdd5dzwRDYgSQuAyQKRwh6gGRmQYhgcLBAgiBwAacbNaB+AJIAkRBUAFB9VCBDBJYzBCRND7N4BaATCzCTAzA6krCJgVNaBlC-luk+cdhRNZJ0IfsfCWgpsHV1xjgDdRAhAT8QgwoQh1MSMojNDGcQF9B0BJAxxJB9UNAQBOhTB0AsVZgIAABxDg0ceocgfVIAA)

```html
<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(10);
        this.moveCellByCurpos(randomPos);  
      }
    }
    
    start () {  
      this.createCells ();  
      this.displayBoard(); 
    }
  }
  
  let game = new Board() 
  game.start()
  game.shuffle()
  </script>
  </body>
</html>
```