'Is the conversion of tiles wrong?

I'm working on an version of 15 puzzle game and for some reason, clicking the tiles works 90% of the time, but sometimes clicking a tile to move to the adjacent white (empty) tile doesn't work/do anything.

The design is to treat the grid like an array then convert to coordinates when needed. The problem is the conversion but not sure what/which part exactly exactly.

Here is the HTML, CSS, and JavaScript:

Html:

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="./style.css">
    </head>
    <body>
        <center>
            <div id="table" style="display: table;">
                <div id="row0" style="display: table-row;">
                    <div id="cell0" class="tile0" onClick="clickTile(0);"></div>
                    <div id="cell1" class="tile1" onClick="clickTile(1);"></div>
                    <div id="cell2" class="tile2" onClick="clickTile(2);"></div>
                    <div id="cell3" class="tile3" onClick="clickTile(3);"></div>
                </div>  
                <div id="row1" style="display: table-row;">
                    <div id="cell4" class="tile4" onClick="clickTile(4);"></div>
                    <div id="cell5" class="tile5" onClick="clickTile(5);"></div>
                    <div id="cell6" class="tile6" onClick="clickTile(6);"></div>
                    <div id="cell7" class="tile7" onClick="clickTile(7);"></div>
                </div>
                <div id="row2" style="display: table-row;">
                    <div id="cell8" class="tile8" onClick="clickTile(8);"></div>
                    <div id="cell9" class="tile9" onClick="clickTile(9);"></div>
                    <div id="cell10" class="tile10" onClick="clickTile(10);"></div>
                    <div id="cell11" class="tile11" onClick="clickTile(11);"></div>
                </div>
                <div id="row" style="display: table-row;">
                    <div id="cell12" class="tile12" onClick="clickTile(12);"></div>
                    <div id="cell13" class="tile13" onClick="clickTile(13);"></div>
                    <div id="cell14" class="tile14" onClick="clickTile(14);"></div>
                    <div id="cell15" class="tile15" onClick="clickTile(15);"></div>
                </div>
            </div>
            <br>
        </center>
        <script src="./script.js"></script>
    </body>
</html>

CSS:

.tile0, .tile1, .tile2, .tile3, .tile4, .tile5, .tile6, .tile7, .tile8, .tile9, .tile10, .tile11, .tile12, .tile13, .tile14, .tile15, .tile16 {
    display: table-cell;
    width: 120px;
    height: 120px;
    border: 1px solid white;
    background-color: darkcyan;
    cursor: pointer;
}

.tile0:after {background-position: 0px 0px; content:"0";}
.tile1:after {background-position: -90px 0px; content:"1";}
.tile2:after {background-position: -180px 0px; content:"2";}
.tile3:after {background-position: -270px 0px; content:"3";}
.tile4:after {background-position: 0px -90px; content:"4";}
.tile5:after {background-position: -90px -90px; content:"5";}
.tile6:after {background-position: -180px -90px; content:"6";}
.tile7:after {background-position: -270px -90px; content:"7";}
.tile8:after {background-position: 0px -180px; content:"8";}
.tile9:after {background-position: -90px -180px; content:"9";}
.tile10:after {background-position: -180px -180px; content:"10";}
.tile11:after {background-position: -270px -180px; content:"11";}
.tile12:after {background-position: 0px -270px; content:"12";}
.tile13:after {background-position: -90px -270px; content:"13";}
.tile14:after {background-position: -180px -270px; content:"14";}
.tile15 {background: white; cursor: default;}

JavaScript code:

let moves = 0;      // Tracks number of moves

function swapTiles(cell1, cell2) {
    try {
        let tempTile = document.getElementById(cell1).className;
        document.getElementById(cell1).className = document.getElementById(cell2).className;
        document.getElementById(cell2).className = tempTile;
    } catch(err){
        console.log(err)
    }

} 

function clickTile(cellID) {
    
    // Derive (X,Y) coords
    let row = ((cellID-1) % 4);
    let column = Math.ceil((cellID-1) / 4);

    // Get the tile that is in the cell
    let tile = document.getElementById("cell"+cellID).className;

    if(tile != "tile15") {
        //check if the empty tile is on the right
        if(column < 3) {
            if(document.getElementById("cell"+(cellID+1)).className=="tile15") {
                swapTiles("cell"+cellID, "cell"+(cellID+1));
                // Incriment move count
                moves = moves += 1;
                return;
            }
        }

        //check if the empty tile is on the left
        if(column > 0) {
            if(document.getElementById("cell"+(cellID-1)).className=="tile15") {
                swapTiles("cell"+cellID,"cell"+(cellID-1));
                // Incriment move count
                moves = moves += 1;
                return;
            }
        }

        //check if the empty tile is above
        if(row > 0) {
            if(document.getElementById("cell"+(cellID-4)).className=="tile15") {
                swapTiles("cell"+cellID, "cell"+(cellID-4));
                // Increment move count
                moves = moves += 1;
                return;
            }
        }

        //check if the empty tile is below
        if(row < 3) {
            if(document.getElementById("cell"+(cellID+4)).className=="tile15") {
                swapTiles("cell"+cellID, "cell"+(cellID+4));
                // Incriment move count
                moves = moves += 1;
                return;
            }
        }
    }
}

Is it possible because of let? It can't be though since it's scoped to the function, and it's working for the most part anyway.



Solution 1:[1]

The way you calculate the values for row and column is wrong:

  • You should not subtract 1, as indeed the least cellID value is 0 in your HTML, not 1.
  • You need to floor, not ceil
  • row and column should be reversed

It should be:

let column = cellID % 4;
let row = Math.floor(cellID / 4);

Solution 2:[2]

Your row and column formulas are wrong. See below for corrected code.

let moves = 0;      // Tracks number of moves

function swapTiles(cell1, cell2) {
    try {
        let tempTile = document.getElementById(cell1).className;
        document.getElementById(cell1).className = document.getElementById(cell2).className;
        document.getElementById(cell2).className = tempTile;
    } catch(err){
        console.log(err)
    }

} 

function clickTile(cellID) {
    
    // Derive (X,Y) coords
    let row = Math.floor((cellID) / 4);
    let column = (cellID) % 4;
    console.log(row, column)

    // Get the tile that is in the cell
    let tile = document.getElementById("cell"+cellID).className;

    if(tile != "tile15") {
        //check if the empty tile is on the right
        if(column < 3) {
            if(document.getElementById("cell"+(cellID+1)).className=="tile15") {
                swapTiles("cell"+cellID, "cell"+(cellID+1));
                // Incriment move count
                moves = moves += 1;
                return;
            }
        }

        //check if the empty tile is on the left
        if(column > 0) {
            if(document.getElementById("cell"+(cellID-1)).className=="tile15") {
                swapTiles("cell"+cellID,"cell"+(cellID-1));
                // Incriment move count
                moves = moves += 1;
                return;
            }
        }

        //check if the empty tile is above
        if(row > 0) {
            if(document.getElementById("cell"+(cellID-4)).className=="tile15") {
                swapTiles("cell"+cellID, "cell"+(cellID-4));
                // Increment move count
                moves = moves += 1;
                return;
            }
        }

        //check if the empty tile is below
        if(row < 3) {
            if(document.getElementById("cell"+(cellID+4)).className=="tile15") {
                swapTiles("cell"+cellID, "cell"+(cellID+4));
                // Incriment move count
                moves = moves += 1;
                return;
            }
        }
    }
}
.tile0, .tile1, .tile2, .tile3, .tile4, .tile5, .tile6, .tile7, .tile8, .tile9, .tile10, .tile11, .tile12, .tile13, .tile14, .tile15, .tile16 {
    display: table-cell;
    width: 120px;
    height: 120px;
    border: 1px solid white;
    background-color: darkcyan;
    cursor: pointer;
}

.tile0:after {background-position: 0px 0px; content:"0";}
.tile1:after {background-position: -90px 0px; content:"1";}
.tile2:after {background-position: -180px 0px; content:"2";}
.tile3:after {background-position: -270px 0px; content:"3";}
.tile4:after {background-position: 0px -90px; content:"4";}
.tile5:after {background-position: -90px -90px; content:"5";}
.tile6:after {background-position: -180px -90px; content:"6";}
.tile7:after {background-position: -270px -90px; content:"7";}
.tile8:after {background-position: 0px -180px; content:"8";}
.tile9:after {background-position: -90px -180px; content:"9";}
.tile10:after {background-position: -180px -180px; content:"10";}
.tile11:after {background-position: -270px -180px; content:"11";}
.tile12:after {background-position: 0px -270px; content:"12";}
.tile13:after {background-position: -90px -270px; content:"13";}
.tile14:after {background-position: -180px -270px; content:"14";}
.tile15 {background: white; cursor: default;}
<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="./style.css">
    </head>
    <body>
        <center>
            <div id="table" style="display: table;">
                <div id="row0" style="display: table-row;">
                    <div id="cell0" class="tile0" onClick="clickTile(0);"></div>
                    <div id="cell1" class="tile1" onClick="clickTile(1);"></div>
                    <div id="cell2" class="tile2" onClick="clickTile(2);"></div>
                    <div id="cell3" class="tile3" onClick="clickTile(3);"></div>
                </div>  
                <div id="row1" style="display: table-row;">
                    <div id="cell4" class="tile4" onClick="clickTile(4);"></div>
                    <div id="cell5" class="tile5" onClick="clickTile(5);"></div>
                    <div id="cell6" class="tile6" onClick="clickTile(6);"></div>
                    <div id="cell7" class="tile7" onClick="clickTile(7);"></div>
                </div>
                <div id="row2" style="display: table-row;">
                    <div id="cell8" class="tile8" onClick="clickTile(8);"></div>
                    <div id="cell9" class="tile9" onClick="clickTile(9);"></div>
                    <div id="cell10" class="tile10" onClick="clickTile(10);"></div>
                    <div id="cell11" class="tile11" onClick="clickTile(11);"></div>
                </div>
                <div id="row" style="display: table-row;">
                    <div id="cell12" class="tile12" onClick="clickTile(12);"></div>
                    <div id="cell13" class="tile13" onClick="clickTile(13);"></div>
                    <div id="cell14" class="tile14" onClick="clickTile(14);"></div>
                    <div id="cell15" class="tile15" onClick="clickTile(15);"></div>
                </div>
            </div>
            <br>
        </center>
        <script src="./script.js"></script>
    </body>
</html>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 trincot
Solution 2 Samson