Jquery UI Dragabble is not enabled

Viewed 75

class Cell {
    
  constructor(game, index) {
    this.isEmpty = false;
    this.game = game;
    this.index = index;
    this.height = (game.height / game.dimension);
    this.width = (game.width / game.dimension);  
    this.id = "cell-" + index;
    this.cell = this.createCell();
    $("#content").append(this.cell);
    if (this.index === this.game.dimension * this.game.dimension - 1) {
      this.isEmpty = true;
      return;
    }
    this.setImage();
    this.setPosition(this.index)
  }
    
  createCell() {
    const cell = document.createElement("div");    
    $(cell).css({
      "width": this.height + "px",
      "height": this.width + "px",
      "position": "absolute",
      "border": "1px solid #fff"
    })
    $(cell).attr("id", this.id);
    //On click Move to the Empty Space
    $(cell).on("click", () => {
      if (this.game.gameStarted) {
        let validCells = this.game.checkValidCells();
        let compareCells = [];
        if (validCells.right) {
          compareCells.push(this.game.cells[validCells.right].cell);
        }
        if (validCells.left || validCells.left === 0) {
          compareCells.push(this.game.cells[validCells.left].cell);
        }
        if (validCells.top || validCells.top === 0) {
          compareCells.push(this.game.cells[validCells.top].cell);
        }
        if (validCells.bottom) {
          compareCells.push(this.game.cells[validCells.bottom].cell);
        }
        let i = this.game.cells.findIndex(item => item.cell === cell);
        let j = this.game.findEmptyCell();
        if (compareCells.indexOf(cell) != -1) {
          [this.game.cells[i], this.game.cells[j]] = [this.game.cells[j], this.game.cells[i]];  
          this.game.cells[i].setPosition(i);
          this.game.cells[j].setPosition(j);
          if (this.game.checkWin()) {
             alert("you won the game!!");
             this.game.numberOfMoves = 0; 
             this.game.gameStarted = false;
          }
          this.game.numberOfMoves++;
          $("#moves").html("Moves: " + this.game.numberOfMoves);
        }
        this.game.dragTheTile();
      }
    })
    return cell;
  }
    
  setImage() {
    const left = this.width * (this.index % this.game.dimension);
    const top = this.height * Math.floor(this.index / this.game.dimension);
    const bgPosition = -left + "px" + " " + -top + "px";
    const bgSize = this.game.width + "px " + this.game.height + "px" 
    $(this.cell).css({
      "background": 'url(' + this.game.imageSrc + ')',
      "background-position" : bgPosition,
      "background-size": bgSize
    })      
  }
    
  setPosition(index) {
    const {left, top} = this.getPosition(index);
    $(this.cell).css({
      "left": left + "px",
      "top": top + "px"
    })
  }
  
  makeDraggable(index) {
    let emptyCellIndex = this.game.findEmptyCell();
    $(this.cell).draggable({
      containment: "parent",
      snap: this.game.cells[emptyCellIndex],
      cursor: "move",
      snapMode: "inner",
      snapTolerance: 20,
      helper: "clone",
      opacity: 0.5
    })
  }
    
  makeDroppable(index) {
    $(this.cell).droppable({
      drop: (event, ui) => {
        let draggedCell;
        draggedCell = ui.draggable;
        let i = this.game.cells.findIndex(item => item.cell === draggedCell[0]);
        let j = this.game.findEmptyCell();
        [this.game.cells[i], this.game.cells[j]] = [this.game.cells[j], this.game.cells[i]];  
        this.game.cells[i].setPosition(i);
        this.game.cells[j].setPosition(j);
        this.game.clearDrag();
        this.game.numberOfMoves++;
        $("#moves").html("Moves: " + this.game.numberOfMoves);
        if (this.game.checkWin()) {
          alert("you won the game!!");
          this.game.numberOfMoves = 0;
          this.game.gameStarted = false;
        } else {
          this.game.dragTheTile();
        }
      }
    })
  }
 
  getPosition(index) {
    return {
      left: this.width * (index % this.game.dimension),
      top: this.height * Math.floor(index / this.game.dimension)
    }
  }
    
}
class GameBoard {
  constructor(dimension){
    this.dimension = dimension;
    this.imageSrc = 'https://i.ibb.co/1XfXq6S/image.jpg'
    this.cells = [];
    let length = Math.min(window.innerHeight, window.innerWidth);
    this.width = length - 100;
    this.height = length - 100;
    this.setup();
    this.gameStarted = false;
    this.numberOfMoves = 0;
  }
 
  setup() {
    for(let i = 0;i < this.dimension * this.dimension; i++) {
      this.cells.push(new Cell(this, i));
    }
  }

  shuffle() {
    for (let i = this.cells.length -1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [this.cells[i], this.cells[j]] = [this.cells[j], this.cells[i]];  
      this.cells[i].setPosition(i);
      this.cells[j].setPosition(j);
    }
  }
    
  findEmptyCell() {
    return this.cells.findIndex(cell => cell.isEmpty)
  }
    
  checkValidCells() {
    const emptyCell = this.findEmptyCell(),
    topCell = emptyCell - this.dimension,
    leftCell = emptyCell - 1,
    rightCell = emptyCell + 1,
    bottomCell = emptyCell + this.dimension;
    const mod = emptyCell % this.dimension;
    let left, right, top, bottom;
    if (mod != 0) {
      left = leftCell;
    } 
    if (mod != this.dimension -1) {
      right = rightCell;
    }
    if (emptyCell >= this.dimension) {
      top = topCell;
    }
    if (emptyCell < this.dimension * (this.dimension - 1)) {
      bottom = bottomCell
    }
    return {right: right, left: left, top: top, bottom: bottom};
  }

  findPosition(index) {
    return this.cells.findIndex(cell => cell.index === index);
  }
    
  checkWin() {
    for (let i = 0; i < this.cells.length; i++) {
      if (i != this.cells[i].index) {
        return false;
      }
    }
    return true;
  }

  clearDrag() {
    this.cells.forEach(cell => {  
      if($(cell.cell).data('ui-draggable')) $(cell.cell).draggable("destroy");
    })
  }

  dragTheTile() {
    this.clearDrag();
    const validCells = this.checkValidCells();
    let availableCells = [];
    if (validCells.right) {
      availableCells.push(this.cells[this.findPosition(validCells.right)].index);
    }
    if (validCells.left || validCells.left === 0) {
      availableCells.push(this.cells[this.findPosition(validCells.left)].index);
    }
    if (validCells.top || validCells.top === 0) {
      availableCells.push(this.cells[this.findPosition(validCells.top)].index);  
    }
    if (validCells.bottom) {
      availableCells.push(this.cells[this.findPosition(validCells.bottom)].index);   
    }
    let emptyCellIndex = this.findEmptyCell();
    availableCells.forEach(cell => {
      this.cells[cell].makeDraggable(cell);
      this.cells[emptyCellIndex].makeDroppable(cell);
    })
  }

  solve() {
    let i;
    for (i = 0; i < this.cells.length; i++) {
      let j = this.cells[i].index;
      if (i != j) {
        [this.cells[i], this.cells[j]] = [this.cells[j], this.cells[i]];  
        this.cells[i].setPosition(i);
        this.cells[j].setPosition(j);
        i--;
      }
      if (i === this.cells.length - 1) {
        [this.cells[i], this.cells[i - 1]] = [this.cells[i - 1], this.cells[i]];  
        this.cells[i].setPosition(i);
        this.cells[i - 1].setPosition(i - 1);
      }
    }
  }
    
}
$(document).ready(() => {
  let gb;
  $("#startGame").on("click", () => {
    gb.gameStarted = true;
    gb.shuffle();
    gb.numberOfMoves = 0;
    $("#moves").html("Moves: " + gb.numberOfMoves);
    gb.clearDrag();
    gb.dragTheTile();
  })
  $("#solve").on("click", () => {
    if (gb.gameStarted) {
      gb.solve();
      gb.clearDrag();
      gb.dragTheTile();
    }
  })
  $("#generate-puzzle").on("click", () => {
    let number = parseInt($("#dimension").val());
    if(number >= 3 && Number.isInteger(number)) {
      gb = new GameBoard(number);
      $("#content").css("display", "block");
      $("#solve").css("display", "block");
      $("#startGame").css("display", "block");
      $("#enter-dimension").css("display", "none");
      $("#content").css({
        width: gb.width + "px",
        height : gb.height + "px"
      })
    } else {
      $("#alert").css("display", "block");
    }
  })
})
body {
    background-color: #3d3d3d;
}

#content {
  width: 400px;
  height : 400px;
  background-color: #fff;
  margin:  auto;
  position: relative;
  display: none;
}
#startGame {
  margin: auto;
  display: none;
}
#solve {
  margin: auto;
  display: none;
}
#alert {
  display: none;
}
#moves {
  margin: auto;
  padding:  5px;
  text-align: center;
  color: #FFFF00;

}
#enter-dimension {
  margin: auto; 
}
#label-dimension {
  color: #ddd;
}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <!-- JQuery -->
    <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>

     <!-- Bootstrap -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" integrity="sha512-uto9mlQzrs59VwILcLiRYeLKPPbS/bT71da/OEBYEwcdNUk8jYIy+D176RYoop1Da+f9mvkYrmj5MCLZWEtQuA==" crossorigin="anonymous"></script>

  <title>Image Puzzle</title>
      
</head>
    
<body>
  
  <div class="container col-md-6">
    <div id="moves">
    Moves : 0
    </div>
    <div class="d-flex justify-content-center row">
      <div class="col-md-6">
        <div class="mt-5" id="enter-dimension">
        <form>
        <div class="form-group">
          <label id="label-dimension" for="dimension">Enter the Dimension of the puzzle</label>
          <input type="number" class="form-control" id="dimension">
        </div>
        <div class="alert alert-danger" id="alert" role="alert">
         Enter a Valid Positive Integer Greater than or Equal to 3
        </div>
        <button type="button" class="btn btn-primary" id="generate-puzzle">Generate the Puzzle</button>
        </form>
        </div>
      </div>
    </div>
    <div class="row">
         <!--The GRid Layout and the tiles -->
      <div class="mt-3"  id="content">
      </div>
    </div>
    <!--Button to Start the Game -->
    <div class="row buttons">
      <button type="button" class="btn btn-info mt-2" id="startGame">Start Game</button>
      <button type="button" class="btn btn-success mt-2" id="solve">Solve This!</button>
    </div>   
  </div>
      
</body>
    
</html>

The puzzle works perfectly but with one issue. If I drag-drop a cell to an empty cell I am unable to drag and drop that tile again to its previous position which is the current empty cell. The way I see it there is a problem somewhere in makeDraggable() and dragTheTile() functions. When I move a cell with a mouse click I can drag and drop that same cell to its previous position (which is the current empty cell) or If I drag and drop a cell to an empty space I can move it to its previous position (which is the current empty cell) with mouse click. But after dragging the same cell cannot be dragged again. looks like draggable() is disabled for that particular cell. Once a cell is dragged, The next move it should be a draggable cell (since it is a neighbour cell to the empty cell) When I make it draggable the "ui-draggable" class is added to it but "ui-draggable-handle" class is missing.

1 Answers

The problem was with the function where I cleared the draggable property:

clearDrag() {
    this.cells.forEach(cell => {  
      if($(cell.cell).data('ui-draggable')) $(cell.cell).draggable("destroy");
    })
  }

The "destroy" method gives some conflicts. The alternative method I found was to first initialize all the cells with draggable property but disable it in the beginning and enabling it again when I need. the property used here was "disabled: true" and "disabled: false".

Instead of the dragTheTile() function I wrote a new function called dragTile() as follows:

 dragTile() {
    this.cells.forEach(cell => {
      cell.makeDraggable();
    })
    const validCells = this.checkValidCells();
    let availableCells = [];
    if (validCells.right) {
      availableCells.push(this.cells[this.findPosition(validCells.right)].index);
    }
    if (validCells.left || validCells.left === 0) {
      availableCells.push(this.cells[this.findPosition(validCells.left)].index);
    }
    if (validCells.top || validCells.top === 0) {
      availableCells.push(this.cells[this.findPosition(validCells.top)].index);  
    }
    if (validCells.bottom) {
      availableCells.push(this.cells[this.findPosition(validCells.bottom)].index);   
    }
    let emptyCellIndex = this.findEmptyCell();
    availableCells.forEach(cell => {
      $(this.cells[cell].cell).draggable({disabled: false});
    })
    this.cells[emptyCellIndex].makeDroppable(availableCells);
  }


This solved the problem very easily.

Related