A Very Simple Game in plain JS
Tags: dml, mvu, javascript
Published: 2023-04-10 16:20:57

You can play with it here [SlidingGame](https://flems.io/#0=N4IgzgpgNhDGAuEAmIBcIB0ALeBbKIANCAGYCWMYaA2qAHYCGuEamO+RIsA9nYn6wA87KAAIoDOgHMAvAB0QSCAoB8cuqNHCIDJGo2atzeA1GwsDAE6R48kAFd4JALQAOBaID0+w1vhl4GBV-QIhBTxCg9V9BY1NGZjsANzIIAHcAB25LeA8ePgg+OzSyJHgsGSUU2AhnErKsQlEyOgCyBihnMFgOiBkARgwABg9vaMNBbssyDPhRMEtYOxx4DLBUT08IEgyGKQgMJU8AEQBZABkTi4B9LG5mXf3PKDIAIyvz51wWjAArMFU4SmM3gPi0niwOj040Er24SAAnmDNJNYNNZsjDEkrKIkNwkhBRDJRHR0qIAJK4PYQAAUAEoANzjXx4gkYBawInM3yiBQrNYbTwZV5gDDwEq4KQYHi4IWWbjkGDXMhU-ZgTyufpDABMADZ+gAWACcrl1AGYAKwG7VmgDsBotnltACl4EMAFr2ADyAEdrgahkMAB4BoZ-DJSBRMgy+Ej2OgIMi8eYwCAZGm4MB00TAbm+SwQeD2SwaUlpUQABXl30gNJpliaEEslmzMhUObzPJsABUVRBuI5601M4zO5oAL6jmOGcfqTuwCRgMCiABC3CsSA7098sCXAGFoGJiQADTuvBiwADWUnl8aQqCQVkv2Uk+2jPNEkLIUhwqC1QwyIMGVEepyj-QNAPfHkqUsKQWlQACgNgYswGyVAshaRBLAZEgYCDVBRCGQjRH-SDOyQMgwAyCQEVQXCICAjpvzoZwAggTMCJqApsM7X57DAfwSARZx8n4eBOMKLCGR4KA0NEF9pAgYDO12JAKOkAj+jI6dj2jMczCXNcNyJURT23URVPUqRwMgiiqJoui8Kg2M8LqSwGAyVA0ncjIGXPK8bwHOh70fSxn3cxTnMMUCsFQANXEgmC4LoBDRAYRxuCizQ4UsJRLGcdyKP4giLW03xdLnczNCwSj4GyBEsmXYlqAAXSyww724JrRFa9rNALPFut6-TOpaKQTJIDpID60QBu4MaJqmpTKo-TRzDgS8ADUmKQIzcvpLdVsMOME38ZN1qvCsuppWBs1zKqeQLIsSzMQ4GBMGwMEaoliVgN6PsLaVi0amaJ30-rC2LDRyko6VD1FCACUsBEbshS6uqnI7ZzofTyGC44yALRNeG7bhTnxWkaigKA7vBsxeAEnr9ngPduCgJpmYAJW4NIWpM6g6c0GlGtbdtvoAUlEA1CEF0RhYxol21Od6sDFSx41geXl08KW6TptqVqO+m6EZqmoG+4kADl7FwV4mxuw9-oYT7kMsEXQeNxn2NmBED2pi3RGt237ZhhHcB9v2oCdl3gYxvSHrWhm5nlNIZIAUXD+AEX5rmeZpb2s8jkWOcLVmoHzzPfcPEWDYTz3k55mTI5zwtubSB3-YxkuWbZjvzYx2u6fyRmwCYCAy69Sw265Ov+sbtmM596ghj5mRiRTpvD2XvmAB8d9m+eoEXrPqH6Vf18PyPT8HuuyBIOWAEJR+YCep557Mnqh+Ojfr3FCbgM6JsTL3R-poagZtvrOBIi1AiCgYAkFyEQWW4Dq5dVEAAamgbAkA0wfyIJlrPHqEC0FQINDA3kDgMgKAIaAohqDlyYLIdgvEaQcYgDpuOD2MA5iNQopYEyXpXi-AARgS8EAERgBpHwgBSYTZ0gwHjJANJZZa1FhZNBa9RAFyrp3AEddMZG0-i9aRxMTbUF4YTWuWNDYfkUV6RqbECZE0AVI-+pjaa33vq45xsifoUPsFQkAHjaFGI0AoFhbCPbYx-nfOWJjAF+PCTzNhwTQGhP8YEqJdNYneJkcmTRcDtiINST-dJChcE4CjBw7JXjcmmJMuU78lSgklMMZDF6hSEFVLrtE1a2N9K4AppHPurSPzDzmPE3xxJQ4KJaEgJxeS6Ck3JgSEZHsckP0mbwD+7S6DfyNuMtKgz4xzGJFqbUHtDlIGLO9KZohtSBkuUnem2xgGy3geJUQzh+g0LSU0z5PzZYRM0r8n+ASCLfI4fso6hzuAZAcYgBZ9Tpk1VFHYhFEAkUuK2XQKcQ9nm7jAN2CKYAJCIBMn3JoOK1EgNATknFiSQAfI8HvP+Pj8nEkaXghQoyDmO0fADeAhwgwz1ob4IOdtLB92joDJAQZsyYKYEFOYAAqF5JBqA4qsaA3pMSvEMoKZQll+8DWcsUMknlh0xVmxlUKxEoqxWaAlfbG1ArnayoRAqo5yrRBqp4NsTVbjAHap-rqtpz0NDHngCSsltIAAkwBXXvXdXaoM45AJNATUmwVhwETppFXSCqPToWrRtZIFU71aSy2AKIaNkgwAkGyLgTiS5iX1tjZStlizszjlBUbGt1z3KAIIoO25vAmiKigNgxtlg0gbgBCAUQYaPx4rrjM7RkcMDlqpIgZRhCa11pNjO5tBkiUxsrTSddldI5NDhRirFsie19qOgOm5w7cRvtkROigU6KEzrnblBdS66arp-tw2t3sTJXojvy5NMc3ZdQ9tBwusGc2uwDtmlNQMENgEuahrD6GNEQfDiWj8MzUV1WRo1L6-EsA0mdVKzD8GRagaNjkmZo1pB+MmlASA2YOPBSI9QDAInyO1Xqo1ENq0BN4gWsSHj00bGrXY6ijAc05PyaWvx1Tc0hoibFKpzqYApNkZ08geaXHNO8eWg9fpD00Q6EQJHSRvLfCbFEJO5cZtlxWHctnEo5QzCHi0TAZgfA6YzO8-zfTABBZsDAUb9F1HSFqGAqTphpNcJoZAaXvMLKIbOxJlblDVhrGkZAvC6w9pocDIriQVclgaar4h8twsAd1WltCBIIhgARSL+5DzPqNgLRQybnCNQUOQsgQ2jojbdSJWOC6pszdWnNsbcrJsESGCtj8a2TDOERJtwistOF5bmGbEyDmL3hLIEkah8lZiyKzM1yAMAEArndWs2W2J+E9DoNibqf2AfamODSGtMVsGkSDPdr8eDIcQWh4uyczWgfO2lPAIMhx3JpEpNSPdjrWQQB2zyf8vrRBBmJ74UnaqESU8MP+OnmgGey00NtlnhFGckUDJz-8ssDFgpNtAABH3az875dTDAvAFxkCvBS3LhDNAzMGQSYZZsxdY2a+ks2UTWNHXc9ooL1MzD8TqjWMdOM12qY3cFlFsNvOnwtCZnkyGdFR26zASXuxYABEKxQsM2oLTdJ-i7zd7uDgAC9yTBQYiZIYM07NHTstRBLe0lGucMK94Xn2KJJGUewlpHsMjTD4Hn4Q-R5hZxgDIAA5IgIM8BnBMSkClLiWFq8qAAMovCspWew4fw8wHCFgfoaggke3jJn97n3dfSdU3CYyxJJ-wBF7SHPef7s1rD311ThLU9LrpOr53O-4ZpY8jSDyGRCi7XdYf3wE+hdT9F6RrsD-l-Z9u+vogFC68N6by3ySTYDISULQzgdUnkDygEPKt+hgrwjgdUdAeeHeWA9gJA9EPKkuCYLwsuxIB0bYtaqmYAyBqBMA9IHssB8A8BeenMEANg6BUuWBl4cuisVqwec+64uUGALQpIlgAAEt2BcA0vni9q-ivpemwRuNAb4JFgWJWs5qQbLJFifulufvClfivpIR1ILm9m-k-lCnTOQZQQoJ1HQZgTLowTgfLrQiprDEZhgDANIIFu2EMOnqZjYYJnJrWurDZo6uBouKsERjJl1OyNRDLrSN8nSNvM1orqpsruPIeCuL7ItjSH4e7CdnoXXAYbwHnnNCYdLtgXLJYXSl4jMrpnYYUFII4YRC4UfrDOppZp4fYN4WKr4c7P4d1CUeZqKPZKETSOEZEezkrkMvEYkThska0akYQsujyKdnXPftoaIdAQnqtLEZHAkXuEkcQlmCwatOBhdrbqKN5rMsFHWGrswUxoDIRk1L9PQhoYMSroeF9rZkpr4M7AiAmPMEQfRAdJ1qtDOnLOBhVsSHHs0FoFzsCWQOgugtUYnCbHMNoldN1AxmIbDNbhLm6sxnHLLIctiN3gifzOzvCSQiRJzoSQwsSQSZXHiaQiSZSWgowrLKloqFhHWCLMwT8aUrsuosuCoECaIAAGR8lclaBnIWjNbI5YnPIRR4i4B4nyAK6iA4mlAInUDFaqy4TcDZA0iqlqaSDSkHRqqKlIAIllEOFYApbNYMBzoBApgQBpg0hagaHRGwwrHDHrGjFSn3AIkaFTFgwW5HQCRWDwDfERY74yFObwzyGW6wxJ40Sp6RnWLmRLGxgawJJUgtDBkPSE4YGyS6BMF4Hsk8glCCZpAYCvAmRlirjsFp5kHsgmA5Dxl9LtRJmGBpkIFi5AhogghgjhBwiIj6BD54BQD6CcBL5PasAB66gIQgC9r0BjysB-BUDECiSSSsDTmECznMDzmEqcDLkCDoDTktTjhAA):



```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>
      var dove = new Image();
      dove.src =
        "https://pbs.twimg.com/profile_images/810261498635423745/7Jt0ZuOq_400x400.jpg";
      function sleep(ms) {
        return new Promise((r, err) => {
          setTimeout(r, ms);
        });
      }

      class Board {
        cssCell = `
      background:darkorange;
      height:100px; width:100px;
      margin:0px;cursor:pointer;flex: 0 0 100px;
      display:flex;align-items: center;
      justify-content: center;color: orange; 
      padding: 1px;
    `;

        cssBoard = `
      padding:10px;display:flex;
      flex-wrap:wrap;background:darkorange;
      width:408px;margin:0 auto;
      border-radius: 5px;
    `;

        historypos = [];
        undos = [];
        redos = [];
        undoing = false;
        redoing = false;

        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 = 102;
          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;

          this.historypos.push(Number(cell.dataset.curpos));

          if (this.undoing == false) this.undos = [...this.historypos];
          this.undoing = false;

          if (this.redoing == false) this.redos = [...this.undos];
          this.redoing = false;
        }

        createCells() {
          // fills cells array with cell element
          this.cells = [...Array(16)].map((_, i) => {
            let y = Math.trunc(i / 4);
            let x = i % 4;
            let options = {
              style: this.cssCell,
              ["data-pos"]: i,
              ["data-curpos"]: i,
              ["data-dx"]: 0,
              ["data-dy"]: 0
            };
            let cell = create("div", options);
            selectBase(cell);
            var canvas = canvas2D({ width: "100px", height: "100px" });
            canvas.ctx.drawImage(
              dove,
              100 * x,
              100 * y,
              100,
              100,
              0,
              0,
              100,
              100
            );
            unselectBase();
            cell.onclick = () => {
              this.moveCell(cell);
            };
            return cell;
          });

          // empty cell customisation
          this.emptyCell = this.cells[15];
          this.emptyCell.style.opacity = "0.25";
          this.emptyCell.style.zIndex = 0;
        }

        displayBoard() {
          selectBase(div(""));
          print("<h1 style='text-align:center'>Sliding Puzzle</h1>");
          unselectBase();

          this.board = selectBase(div("", { style: this.cssBoard }));
          this.cells.map(appendBase);
          unselectBase();

          selectBase(div("", "text-align:center;margin-top:20px"));
          button("Shuffle").onclick = () => this.shuffle();
          button("Reset").onclick = () => {
            this.board.innerHTML = "";
            selectBase(this.board);
            this.createCells();
            this.cells.map(appendBase);
            unselectBase();
          };
          button("undo").onclick = () => {
            if (this.undos.length > 0) {
              this.undoing = true;
              let lastpos = this.undos.splice(-1)[0];
              this.moveCellByCurpos(lastpos);
            }
          };
          button("redo").onclick = () => {
            if (this.redos.length > 0) {
              this.redoing = true;
              let lastpos = this.redos.splice(-1)[0];
              this.moveCellByCurpos(lastpos);
            }
          };
          unselectBase();
        }

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

        async shuffle() {
          for (let i = 0; i < 100; 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();
        }
      }

      function main() {
        dove.onload = () => {
          window.b = new Board();
          b.start();
        };
      }

      main();
    </script>
  </body>
</html>

```