How to get correct neighbors of empty tile and swap it, javascript sliding puzzle

The problem is that sometimes a wrong tile moves in a diagonal direction. This is a sliding puzzle. So tiles can move only horizontally or vertically.

Same tile cannot move in a row. For example if tile moves left, the next tile move cannot be to right. This is checked before swapping.

The goal is to move tile one time when a shuffle-button is clicked. First I check the neighbors next to the empty tile. Then I pick a random neighbor and swap it. I am not sure if I am swapping correctly or if there is an error in getNeighbors-function. Any advice?

let movesEl = document.getElementById("moves");
let emptyIndEl = document.querySelector(".empty-index");
let neighborEl = document.querySelector(".neighbor");
let dirEl = document.querySelector(".direction");
let neighborsEl = document.querySelector(".neighbors");
let curEmptyIndEl = document.querySelector(".new-empty-index");
let mapEl = document.querySelector(".new-map");

class Game {
  constructor() {
    this.parentEl = document.querySelector('#app');
    this.puzzleRows = 3;
    this.puzzleCols = 4;
    this.width = 300;
    this.height = 300;
    this.cells = [];
    this.map = [0,1,2,3,4,5,6,7,8,9,10,11];
    this.shuffling = false;
    this.moves = 0;
    this.prevDir = null;
    // events
    this.onFinished = () => {};
    this.onSwap = () => {};
  }

  init() {
    this.el = this.createWrapper();
    this.parentEl.appendChild(this.el);
    this.parentEl.style.height = this.height + 'px';
    this.setMap();
  }

  createWrapper() {
    const div = document.createElement('div');
    div.classList.add('puzzle-cells');
    div.style.position = 'relative';
    div.style.margin = ' 0 auto';
    return div;
  }

  restart(){
    this.stateMoves(0)
    this.map = [0,1,2,3,4,5,6,7,8,9,10,11];
    for(let i = 0; i < this.map.length; i++){
      if(this.cells[i].index != this.map[i]){
        this.swapCells(i, this.findPosition(this.map[i]))
      }
    }
  }

  stateMoves(num){
    this.moves = num ;
    movesEl.innerText = num;
  }
  
  setMap() {
    for (let i = 0; i < this.puzzleRows * this.puzzleCols; i++) {
      this.cells.push(new Cell(this, i));
    }

    for(let i = 0; i < this.map.length; i++){
      if(this.cells[i].index != this.map[i]){
        this.swapCells(i, this.findPosition(this.map[i]))
      }
    }

    this.map = this.getMap();
  }

  shuffleTiles() {
    // Find the index of the empty tile
    const emptyIndex = this.findEmpty();
    emptyIndEl.innerText = emptyIndex;
    console.log('emptyIndex ', emptyIndex)
   
    // Get the neighbors of the empty tile 
    // contains index and directions [10, 'left']
    const neighbors = this.getNeighbors(emptyIndex);
    // Select random neighbor data
    const rand = Math.floor(Math.random() * neighbors.length);
    let neighbor = neighbors[rand];
    
    // Prevent moving same tile twice in a row 
    // Get new neighbor if directions matches
    if(this.prevDir == neighbor[1]){
      neighbors.splice(rand, 1);
      const r = Math.floor(Math.random() * neighbors.length);
      neighbor = neighbors[r];
    }
    neighborEl.innerText = neighbor[0];
    console.log('selected neigbor ind', neighbor[0])

    // Store direction
    this.statePrevDir(neighbor[1]);
    dirEl.innerText = this.prevDir;
    console.log('prevDir ', this.prevDir)
    neighborsEl.innerText = JSON.stringify(this.getNeighbors(neighbor[0]));
    console.log('current neighbors', JSON.stringify(this.getNeighbors(neighbor[0])))

    // Swap the empty tile with the selected neighbor
    this.map[emptyIndex] = this.map[neighbor[0]];
    this.map[neighbor[0]] = 11; // The empty tile now occupies the neighbor's position
    curEmptyIndEl.innerText = this.map[emptyIndex];
    console.log('swapp ', this.map[emptyIndex])
    console.log('current emptyIndex ', this.map[emptyIndex])
    // Update html 
    this.swapCells(this.map[emptyIndex], this.findPosition(this.map[neighbor[0]]))

    // Update moves html
    this.moves++;
    this.stateMoves(this.moves);
    mapEl.innerText = JSON.stringify(this.map);
    console.log('updated map', JSON.stringify(this.map))
  }

  statePrevDir(dir){
    // Store direction that cannot be used for the next move.
    switch(dir){
      case 'left':
        this.prevDir = 'right';
        break;
      case 'right':
        this.prevDir = 'left';
        break;
      case 'top':
        this.prevDir = 'bottom';
        break;
      case 'bottom':
        this.prevDir = 'top';
        break;
    }
  }

  getNeighbors(emptyIndex) {
    // The grid
    // 0 1 2
    // 3 4 5
    // 6 7 8
    // 9 10 11

    const adjacentPieces = [];
    const row = Math.floor(emptyIndex / 3); // 0-3
    const col = emptyIndex % 3; // 0-2
   
    // Left neighbor
    if (col > 0) 
      adjacentPieces.push([emptyIndex - 1, 'left']); 
    // Right neighbor
    if (col < 2) 
      adjacentPieces.push([emptyIndex + 1, 'right']);
   
    // Top neighbor
    if (row > 0) 
      adjacentPieces.push([emptyIndex - 3, 'top']);
     
    // Bottom neighbor
    if (row < 3) 
      adjacentPieces.push([emptyIndex + 3, 'bottom']);
      
    return adjacentPieces;
  }

  swapCells(i, j, animate) {
    this.cells[i].setPosition(j, animate, i);
    this.cells[j].setPosition(i);
    [this.cells[i], this.cells[j]] = [this.cells[j], this.cells[i]];
  }
    
  getMap(){
    const arr = [];
      const list = [];
      for(let i = 0; i < this.cells.length; i++){
          const j = this.cells[i].index;
          let obj = {i: i, j: j}
          list.push(j);
              arr.push(obj)
      }
      return list;
  }

  findEmptyIndexFromBottom(arr){
    // Find empty index position
    let index = '';
    for(let i = this.dimmension - 1; i >= 0; i--){
      for (let j = this.dimmension - 1; j >= 0; j--){
        if(arr[i][j] == 0){
          index = this.dimmension - i;
        }
      }
    }
    return index;
  }

  findPosition(ind) {
    return this.cells.findIndex(cell => cell.index === ind);
  }

  findEmpty() {
    return this.cells.findIndex(cell => cell.isEmpty);
  }
}

// ============================
// CELL
//============================
class Cell {
  constructor(puzzle, ind) {
    this.isEmpty = false;
    this.index = ind;
    this.puzzle = puzzle;
    this.width = this.puzzle.width / this.puzzle.puzzleRows;
    this.height = this.puzzle.height / this.puzzle.puzzleCols;

    this.el = this.createTile();
    puzzle.el.appendChild(this.el);

    if (this.index === this.puzzle.puzzleRows * this.puzzle.puzzleCols - 1) {
      this.isEmpty = true;
      return;
    }
    this.tileNum(this.index);
    this.setPosition(this.index);
  }

  createTile() {
    const div = document.createElement('div');
    div.style.backgroundSize = `${Math.floor(this.puzzle.width)}px ${Math.floor(this.puzzle.height)}px`;
    div.style.position = 'absolute';
    div.classList.add('puzzle-block');
  
    div.onclick = () => {
      const currentCellIndex = this.puzzle.findPosition(this.index);
      const emptyCellIndex = this.puzzle.findEmpty();
      const {x, y} = this.getXY(currentCellIndex);
      const {x: emptyX, y: emptyY} = this.getXY(emptyCellIndex);
    
      if ((x === emptyX || y === emptyY) && (Math.abs(x - emptyX) === 1 || Math.abs(y - emptyY) === 1)) {
        if (this.puzzle.onSwap && typeof this.puzzle.onSwap === 'function') {
          this.puzzle.onSwap.call(this)
          this.puzzle.moves++
          this.puzzle.stateMoves(this.puzzle.moves);
        }

        this.puzzle.swapCells(currentCellIndex, emptyCellIndex, true);
      }
    };

    return div;
  }

  tileNum(ind){
    const {x, y} = this.getXY(this.index);
    const left = Math.floor(this.width * x);
    const top = Math.floor(this.height * y);
    this.el.style.width = `${Math.floor(this.width - 3)}px`;
    this.el.style.height = `${Math.floor(this.height - 3)}px`;
    this.el.style.backgroundPosition = `-${left}px -${top}px`;

    // number
    const div = document.createElement("div");
    div.classList.add("num");
    div.style.width = `${Math.floor(this.width - 3)}px`;
    div.style.height = `${Math.floor(this.height - 3)}px`;
    div.innerText = ind;
    this.el.appendChild(div);
  }

  setPosition(destinationIndex, animate, currentIndex) {
    const {left, top} = this.getPositionFromIndex(destinationIndex);
    const {left: currentLeft, top: currentTop} = this.getPositionFromIndex(currentIndex);
    if (animate) {
      if (left !== currentLeft) {
        this.animate('left', currentLeft, left);
      } else if (top !== currentTop) {
        this.animate('top', currentTop, top);
      }
    } else {
      this.el.style.left = `${Math.floor(left)}px`;
      this.el.style.top = `${Math.floor(top)}px`;
    }
  }

  animate(position, currentPosition, destination) {
    const animationDuration = 50;
    const frameRate = 5;
    let step = frameRate * Math.abs((destination - currentPosition)) / animationDuration;

    let id = setInterval(() => {
      if (currentPosition < destination) {
        currentPosition = Math.min(destination, currentPosition + step);
        if (currentPosition >= destination) {
          clearInterval(id)
        }
      } else {
        currentPosition = Math.max(destination, currentPosition - step);
        if (currentPosition <= destination) {
          clearInterval(id)
        }
      }

      this.el.style[position] = currentPosition + 'px';
    }, frameRate)
  }

  getPositionFromIndex(index) {
    const {x, y} = this.getXY(index);
    return {
      left: this.width * x,
      top: this.height * y
    }
  }

  getXY(index) {
    return {
      x: index % this.puzzle.puzzleRows,
      y: Math.floor(index / this.puzzle.puzzleRows)
    }
  }
}

// Puzzle object
const game = new Game(); 
game.init();

document.querySelector('.restart').addEventListener('click', function(){
  game.restart();
})

document.querySelector('.shuffle').onclick =()=>{
  game.shuffleTiles();
}
body{
        padding: 0;
        margin: 0;
        overflow: hidden;
    }
    .container{
        padding-top: 10px;
        display: flex;
        justify-content: center;
        gap: 15px;
    }
    .app{
        width: 300px;
    }
    .num{
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: #f2f2f2;
    }
    .footer{
        display: flex;
        justify-content: center;
        flex-direction: column;
        gap: 10px;
        width: fit-content;
        margin: 0 auto;
        padding-top: 15px;
    }
    .values{
        padding-top: 15px;
        display: flex;
        flex-direction: column;
        gap: 5px;
        width: fit-content;
        margin: 0 auto;
        justify-content: center;
    }
    .values span{
        font-weight: bold;
        text-transform: uppercase;
    }
    .values div{
        display: flex;
        flex-direction: column;
        gap: 3px;
    }
<div class="container">
    <div id="app" class="app"></div>

</div>
<div class="footer">
    <button class="shuffle">shuffle</button>
    <button class="restart">restart</button>
    <span id="moves"></span>
</div>
<div class="values">
    <div>Empty index: <span class="empty-index"></span></div>
    <div>Selected neighbor: <span class="neighbor"></span></div>
    <div>Prevented direction: <span class="direction"></span></div>
    <div>Current neighbors: <span class="neighbors"></span></div>
    <div>Empty index after swap: <span class="new-empty-index"></span></div>
    <div>Map after swap: <span class="new-map"></span></div>
   </div>