Google Sheets Fifteen Puzzle (JavaScript)

To allow for instant image movements, this version of the fifteen puzzle for Google Sheets was created in a custom dialog box.

The advantage of doing so (compared to a puzzle that would be inserted on a sheet) is that the JavaScript code is executed client-side, which is infinitely faster.

You can copy this Google Sheets document from this page.


Playable Preview of the Puzzle

Since this is a JavaScript version, the game could also be embedded on this page:

Game Code

The Google Apps Script code:

function play() {
  const html = HtmlService.createTemplateFromFile('puzzle').evaluate()
    .setWidth(515)
    .setHeight(515);
  SpreadsheetApp.getUi().showModalDialog(html, 'Fifteen Puzzle');
}

function include(name) {
  return HtmlService.createHtmlOutputFromFile(name).getContent();
}

The HTML page with JavaScript and CSS:

<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        margin: 0;
      }
      #grid {
        display: grid;
        grid-template-columns: repeat(4, 125px);
        grid-gap: 5px;
      }
      #grid > div {
        width: 125px;
        height: 125px;
      }
    </style>
    <?!= include('images'); ?>
  </head>
  <body>
    <div id="grid">
    </div>
    <script>
    
      /*************************************************************************/
      /**  Fifteen puzzle created by Sébastien Mathier - Sheets-Pratique.com  **/
      /*************************************************************************/

      const addRow = [0, 0, 1, -1];
      const addCol = [-1, 1, 0, 0];
      
      let grid = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]];
      const control = grid.flat().join();
      
      let ok = true;
      let end = true;

      // New game
      function newGame() {
      
        end = false;

        // Shuffle images (by randomly moving adjacent images into the empty space)
        for (let n = 0; n < 9999; n++) {
          const startRow = Math.floor(Math.random() * 4);
          const startCol = Math.floor(Math.random() * 4);
      
          // If not the empty space
          if (grid[startRow][startCol] != 15) {
      
            // Test each side of the space
            for (let i = 0; i < 4; i++) {
              const row = startRow + addRow[i];
              const col = startCol + addCol[i];
      
              // If valid position
              if (row >= 0 && row <= 3 && col >= 0 && col <= 3) {
      
                // If empty space
                if (grid[row][col] === 15) {
                  [grid[row][col], grid[startRow][startCol]] = [grid[startRow][startCol], grid[row][col]];
                  break;
                }
              }
            }
          }
        }
      
        // Place images
        displayGrid();
      }
      
      // Display the grid
      function displayGrid(finished = false) {
      
        let squares = '';
        
        for (const [row, line] of grid.entries()) {
          for (const [col, number] of line.entries()) {
            squares += '<div id="p' + number + '"' + (!finished && number == 15 ? ' class="white"' : '') + ' onclick="imageClick(' + number + ')"></div>';
          }
        }
        
        document.getElementById('grid').innerHTML = squares;
      }

      // Action on clicking an image
      function imageClick(number) {
        
        if (!ok || end) {
          return;
        }
        ok = false;

        let rowClick, colClick;
          
        // Position of clicked image in the grid
        finish:
        for (const [row, line] of grid.entries()) {
          for (const [col, num] of line.entries()) {

            // If clicked image
            if (num == number) {
              rowClick = row;
              colClick = col;
              break finish;
            }
          }
        }
        
        // Test each side of the space
        for (let i = 0; i < 4; i++) {
          const row = rowClick + addRow[i];
          const col = colClick + addCol[i];
            
          // If valid position
          if (row >= 0 && row <= 3 && col >= 0 && col <= 3) {

            // If empty space
            if (grid[row][col] === 15) {
              [grid[row][col], grid[rowClick][colClick]] = [grid[rowClick][colClick], grid[row][col]];
              break;
            }
          }
        }
          
        // Check if finished
        const finished = grid.flat().join() == control;
        
        // Place images
        displayGrid(finished);

        // End
        if (finished) {
          alert('Congratulations!');
          end = true;
        }
        
        ok = true;
      }
      
      // New game on load
      window.addEventListener('load', newGame);
      
    </script>
  </body>
</html>

To avoid overloading the content of this page, images in base64 are not displayed here.