Checking function for sliding puzzle javascript

1.8k views Asked by At

I created a sliding puzzle with different formats like: 3x3, 3x4, 4x3 and 4x4. When you run my code you can see on the right side a selection box where you can choose the 4 formats. The slidingpuzzle is almost done. But I need a function which checks after every move if the puzzle is solved and if that is the case it should give out a line like "Congrantulations you solved it!" or "You won!". Any idea how to make that work?

In the javascript code you can see the first function loadFunc() is to replace every piece with the blank one and the functions after that are to select a format and change the format into it. The function Shiftpuzzlepieces makes it so that you can move each piece into the blank space. Function shuffle randomizes every pieces position. If you have any more question or understanding issues just feel free to ask in the comments. Many thanks in advance.

Since I don't have enough reputation I will post a link to the images here: https://i.stack.imgur.com/9fIyk.jpg . These images are just placeholders right now.

Here is the jsfiddle:

    http://jsfiddle.net/Cuttingtheaces/vkyxgwo6/19/
2

There are 2 answers

0
sainaen On BEST ANSWER

As always, there is a "hacky", easy way to do this, and then there is more elegant but one that requires significant changes to your code.

Hacky way

To accomplish this as fast and dirty as possible, I would go with parsing id-s of pieces to check if they are in correct order, because they have this handy pattern "position" + it's expected index or "blank":

function isFinished() {
    var puzzleEl = document.getElementById('slidingpuzzleContainer').children[0];
    // convert a live list of child elements into regular array
    var pieces = [].slice.call(puzzleEl.children);

    return pieces
        .map(function (piece) {
            return piece.id.substr(8); // strip "position" prefix
        })
        .every(function (id, index, arr) {
            if (arr.length - 1 == index) {
                // last peace, check if it's blank
                return id == "blank";
            }
            // check that every piece has an index that matches its expected position
            return index == parseInt(id);
        });
}

Now we need to check it somewhere, and naturally the best place would be after each move, so shiftPuzzlepieces() should be updated to call isFinished() function, and show the finishing message if it returns true:

function shiftPuzzlepieces(el) {
    // ...
    if (isFinished()) {
        alert("You won!");
    }
}

And voilĂ : live version.

How would I implement this game

For me, the proper way of implementing this would be to track current positions of pieces in some data structure and check it in similar way, but without traversing DOM or checking node's id-s. Also, it would allow to implement something like React.js application: onclick handler would mutate current game's state and then just render it into the DOM.

Here how I would implement the game:

/**
 * Provides an initial state of the game
 * with default size 4x4
 */
function initialState() {
  return {
    x: 4,
    y: 4,
    started: false,
    finished: false
  };
}

/**
 * Inits a game
 */
function initGame() {
  var gameContainer = document.querySelector("#slidingpuzzleContainer");
  var gameState = initialState();

  initFormatControl(gameContainer, gameState);
  initGameControls(gameContainer, gameState);

  // kick-off rendering
  render(gameContainer, gameState);
}

/**
 * Handles clicks on the container element
 */
function initGameControls(gameContainer, gameState) {
  gameContainer.addEventListener("click", function hanldeClick(event) {
    if (!gameState.started || gameState.finished) {
      // game didn't started yet or already finished, ignore clicks
      return;
    }
    if (event.target.className.indexOf("piece") == -1) {
      // click somewhere not on the piece (like, margins between them)
      return;
    }

    // try to move piece somewhere
    movePiece(gameState, parseInt(event.target.dataset.index));

    // check if we're done here
    checkFinish(gameState);

    // render the state of game
    render(gameContainer, gameState);

    event.stopPropagation();
    return false;
  });
}

/**
 * Checks whether game is finished
 */
function checkFinish(gameState) {
  gameState.finished = gameState.pieces.every(function(id, index, arr) {
    if (arr.length - 1 == index) {
      // last peace, check if it's blank
      return id == "blank";
    }
    // check that every piece has an index that matches its expected position
    return index == id;
  });
}

/**
 * Moves target piece around if there's blank somewhere near it
 */
function movePiece(gameState, targetIndex) {
  if (isBlank(targetIndex)) {
    // ignore clicks on the "blank" piece
    return;
  }

  var blankPiece = findBlankAround();
  if (blankPiece == null) {
    // nowhere to go :(
    return;
  }

  swap(targetIndex, blankPiece);


  function findBlankAround() {
    var up = targetIndex - gameState.x;
    if (targetIndex >= gameState.x && isBlank(up)) {
      return up;
    }
    var down = targetIndex + gameState.x;
    if (targetIndex < ((gameState.y - 1) * gameState.x) && isBlank(down)) {
      return down;
    }
    var left = targetIndex - 1;
    if ((targetIndex % gameState.x) > 0 && isBlank(left)) {
      return left;
    }
    var right = targetIndex + 1;
    if ((targetIndex % gameState.x) < (gameState.x - 1) && isBlank(right)) {
      return right;
    }
  }

  function isBlank(index) {
    return gameState.pieces[index] == "blank";
  }

  function swap(i1, i2) {
    var t = gameState.pieces[i1];
    gameState.pieces[i1] = gameState.pieces[i2];
    gameState.pieces[i2] = t;
  }
}

/**
 * Handles form for selecting and starting the game
 */
function initFormatControl(gameContainer, state) {
  var formatContainer = document.querySelector("#formatContainer");
  var formatSelect = formatContainer.querySelector("select");
  var formatApply = formatContainer.querySelector("button");

  formatSelect.addEventListener("change", function(event) {
    formatApply.disabled = false;
  });

  formatContainer.addEventListener("submit", function(event) {
    var rawValue = event.target.format.value;
    var value = rawValue.split("x");

    // update state
    state.x = parseInt(value[0], 10);
    state.y = parseInt(value[1], 10);
    state.started = true;
    state.pieces = generatePuzzle(state.x * state.y);

    // render game
    render(gameContainer, state);

    event.preventDefault();
    return false;
  });
}

/**
 * Renders game's state into container element
 */
function render(container, state) {
  var numberOfPieces = state.x * state.y;
  updateClass(container, state.x, state.y);
  clear(container);

  var containerHTML = "";
  if (!state.started) {
    for (var i = 0; i < numberOfPieces; i++) {
      containerHTML += renderPiece("", i) + "\n";
    }
  } else if (state.finished) {
    containerHTML = "<div class='congratulation'><h2 >You won!</h2><p>Press 'Play!' to start again.</p></div>";
  } else {
    containerHTML = state.pieces.map(renderPiece).join("\n");
  }

  container.innerHTML = containerHTML;

  function renderPiece(id, index) {
    return "<div class='piece' data-index='" + index + "'>" + id + "</div>";
  }

  function updateClass(container, x, y) {
    container.className = "slidingpuzzleContainer" + x + "x" + y;
  }

  function clear(container) {
    container.innerHTML = "";
  }
}

/**
 * Generates a shuffled array of id-s ready to be rendered
 */
function generatePuzzle(n) {
  var pieces = ["blank"];
  for (var i = 0; i < n - 1; i++) {
    pieces.push(i);
  }

  return shuffleArray(pieces);

  function shuffleArray(array) {
    for (var i = array.length - 1; i > 0; i--) {
      var j = Math.floor(Math.random() * (i + 1));
      var temp = array[i];
      array[i] = array[j];
      array[j] = temp;
    }
    return array;
  }
}
body {
  font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Helvetica, Arial, sans-serif;
  font-size: 12px;
  color: #000;
}
#formatContainer {
  position: absolute;
  top: 50px;
  left: 500px;
}
#formatContainer label {
  display: inline-block;
  max-width: 100%;
  margin-bottom: 5px;
}
#formatContainer select {
  display: block;
  width: 100%;
  margin-top: 10px;
  margin-bottom: 10px;
}
#formatContainer button {
  display: inline-block;
  width: 100%;
}
.piece {
  width: 96px;
  height: 96px;
  margin: 1px;
  float: left;
  border: 1px solid black;
}
.slidingpuzzleContainer3x3,
.slidingpuzzleContainer3x4,
.slidingpuzzleContainer4x3,
.slidingpuzzleContainer4x4 {
  position: absolute;
  top: 50px;
  left: 50px;
  border: 10px solid black;
}
.slidingpuzzleContainer3x3 {
  width: 300px;
  height: 300px;
}
.slidingpuzzleContainer3x4 {
  width: 300px;
  height: 400px;
}
.slidingpuzzleContainer4x3 {
  width: 400px;
  height: 300px;
}
.slidingpuzzleContainer4x4 {
  width: 400px;
  height: 400px;
}
.congratulation {
  margin: 10px;
}
}
<body onload="initGame();">
  <div id="slidingpuzzleContainer"></div>
  <form id="formatContainer">
    <label for="format">select format:</label>
    <select name="format" id="format" size="1">
      <option value="" selected="true" disabled="true"></option>
      <option value="3x3">Format 3 x 3</option>
      <option value="3x4">Format 3 x 4</option>
      <option value="4x3">Format 4 x 3</option>
      <option value="4x4">Format 4 x 4</option>
    </select>
    <button type="submit" disabled="true">Play!</button>
  </form>
</body>

Here we have the initGame() function that starts everything. When called it will create an initial state of the game (we have default size and state properties to care about there), add listeners on the controls and call render() function with the current state.

initGameControls() sets up a listener for clicks on the field that will 1) call movePiece() which will try to move clicked piece on the blank spot if the former is somewhere around, 2) check if after move game is finished with checkFinish(), 3) call render() with updated state.

Now render() is a pretty simple function: it just gets the state and updates the DOM on the page accordingly.

Utility function initFormatControl() handles clicks and updates on the form for field size selection, and when the 'Play!' button is pressed will generate initial order of the pieces on the field and call render() with new state.

The main benefit of this approach is that almost all functions are decoupled from one another: you can tweak logic for finding blank space around target piece, to allow, for example, to swap pieces with adjacent ids, and even then functions for rendering, initialization and click handling will stay the same.

3
Vel On
    $(document).on('click','.puzzlepiece', function(){
        var count = 0;
         var imgarray = []; 
        var test =[0,1,2,3,4,5,6,7,8,'blank']
        $('#slidingpuzzleContainer img').each(function(i){
          var imgalt =  $(this).attr('alt');             
            imgarray[i] = imgalt;            
            count++;

        });
        var is_same = (imgarray.length == test.length) &&   imgarray.every(function(element, index) {
        return element === array2[index]; 
    });

        console.log(is_same);    ///it will true if two array is same           
    });

try this... this is for only 3*3.. you pass the parameter and makethe array value as dynamically..