Cannot read properties of undefined (reading ‘classList’)

I’m following a tutorial for making BattleShip in javascript and html but i get an error :

    Uncaught TypeError: Cannot read properties of undefined (reading 'classList')
at Script.js:82
at Array.some (<anonymous>)
at generate (Script.js:82)
at HTMLDocument.<anonymous> (Script.js:94)

This is the tutorial: https://youtu.be/U64vIhh0TyM The error happens randomly sometimes, it’s been 5 days since this error occured and I can’t understand how to fix this. I hope that someone can find a solution soon.

document.addEventListener("DOMContentLoaded", () =>{
    const userGrid = document.querySelector(".user-grid")
    const AIGrid = document.querySelector(".AI-grid")
    const displayGrid = document.querySelector(".grid-display")
    const ships = document.querySelectorAll(".ship")
    const destroyer = document.querySelector(".destroyer-container")
    const submarine = document.querySelector(".submarine-container")
    const battleship = document.querySelector(".battleship-container")
    const carrier = document.querySelector(".carrier-container")
    const startButton = document.querySelector("#start")
    const rotateButton = document.querySelector("#rotate")
    const turnDisplay = document.querySelector("#whose-go")
    const infoDisplay = document.querySelector("#info")
    let isHorizontal = true
    
    const userSquares = []
    const AISquares = []
    const widthl = 10;

    function createBoard(grid, squares, width) {
        for (let i = 0; i < width*width; i++) {
            const square = document.createElement("div")
            square.dataset.id = i
            grid.appendChild(square)
            squares.push(square)
        }
    }

    createBoard(userGrid, userSquares, widthl)
    createBoard(AIGrid, AISquares, widthl)

    const shipArray = [
        {
            name: "destroyer",
            directions: [
                [0, 1],
                [0, widthl]
            ]
        },
        {
            name: "submarine",
            directions: [
                [0, 1, 2],
                [0, widthl, widthl*2]
            ]
        },
        {
            name: "cruiser",
            directions: [
                [0, 1, 2],
                [0, widthl, widthl*2]
            ]
        },
        {
            name: "battleship",
            directions: [
                [0, 1, 2, 3],
                [0, widthl, widthl*2, widthl*3]
            ]
        },
        {
            name: "carrier",
            directions: [
                [0, 1, 2, 3, 4],
                [0, widthl, widthl*2, widthl*3, widthl*4]
            ]
        }
    ];

    function generate(ship) {
        let randomDirection = Math.abs(Math.floor(Math.random() * ship.directions.length));
        let current = ship.directions[randomDirection];
        if (current === 0) {
            direction = 1
        }
        if (current === 1) {
            direction = 10
        }
        let randomStart = Math.abs(Math.floor(Math.random() * AISquares.length - ship.directions[0].length));

        
        const isTaken = current.some(index => AISquares[randomStart + index].classList.contains("taken"))
        const isAtRightEdge = current.some(index => (randomStart + index) % widthl === widthl - 1 )
        const isAtLeftEdge = current.some(index => (randomStart + index) % widthl === 0)

        

        if (!isTaken && !isAtRightEdge && !isAtLeftEdge) {
            current.forEach(index => AISquares[randomStart + index].classList.add("taken", ship.name))
        }else generate(ship)

    }

    generate(shipArray[0])
    generate(shipArray[1])
    generate(shipArray[2])
    generate(shipArray[3])
    generate(shipArray[4])

    function rotate() {
        if (isHorizontal) {
            destroyer.classList.toggle("destroyer-container-vertical")
            isHorizontal = false
        }
    }
    rotateButton.addEventListener("click", rotate)
})
.container{
    display: flex;
}


.user-grid{
    width: 400px;
    height: 400px;
    display: flex;
    flex-wrap: wrap;
    background-color: blue;
    margin: 20px;
}

.grid div{
    width: 40px;
    height: 40px;
}

.AI-grid{
    width: 400px;
    height: 400px;
    display: flex;
    flex-wrap: wrap;
    background-color:green;
    margin: 20px;
}

.grid-display{
    width: 400px;
    height: 400px;
    margin: 20px;
    background-color:yellow;
}

.destroyer-container{
    width: 80px;
    height: 40px;
    background-color: orange;
    margin: 10px;
    display: flex;
}

.destroyer-container-vertical{
    width: 40px;
    height: 80px;
    background-color: orange;
    margin: 10px;
    display: flex;
    flex-wrap: wrap;
}

.submarine-container{
    width: 120px;
    height: 40px;
    background-color: pink;
    margin: 10px;
    display: flex;
}

.submarine-container-vertical{
    width: 40px;
    height: 120px;
    background-color: pink;
    margin: 10px;
    display: flex;
    flex-wrap: wrap;
}

.cruiser-container{
    width: 120px;
    height: 40px;
    background-color: purple;
    margin: 10px;
    display: flex;
}

.cruiser-container-vertical{
    width: 40px;
    height: 120px;
    background-color: purple;
    margin: 10px;
    display: flex;
    flex-wrap: wrap;
}

.battleship-container{
    width: 160px;
    height: 40px;
    background-color: aqua;
    margin: 10px;
    display: flex;
}

.battleship-container-vertical{
    width: 40px;
    height: 160px;
    background-color: aqua;
    margin: 10px;
    display: flex;
    flex-wrap: wrap;
}

.carrier-container{
    width: 200px;
    height: 40px;
    background-color: springgreen;
    margin: 10px;
    display: flex;
}

.carrier-container-vertical{
    width: 40px;
    height: 200px;
    background-color: springgreen;
    margin: 10px;
    display: flex;
    flex-wrap: wrap;
}

.ship div{
    width: 40px;
    height: 40px;
}

.destroyer{
    background-color: orange;
}

.submarine{
    background-color: pink;
}

.cruiser{
    background-color: purple;
}

.battleship{
    background-color:aqua;
}

.carrier{
    background-color:springgreen;
}
<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="utf-8">
        <title> Battle Ships </title>
        <link rel="stylesheet" href="Style.css">
        <script src="Script.js" charset="utf-8"></script>
    </head>

    <body>

        <div class="container">
            <div class="grid user-grid"></div>
            <div class="grid AI-grid"></div>
        </div>

        <div class="hidden-info">
            <button id="start">Start Game</button>
            <button id="rotate">Rotate Your Ships</button>
            <h3 id="whose-go">Your go</h3>
            <h3 id="info"></h3>
        </div>
        
        <div class="grid-display">
            <div class="ship destroyer-container" draggable="true">
                <div class="destroyer-0"></div>
                <div class="destroyer-1"></div>
            </div>
            <div class="ship submarine-container" draggable="true">
                <div class="submarine-0"></div>
                <div class="submarine-1"></div>
                <div class="submarine-2"></div>
            </div>
            <div class="ship cruiser-container" draggable="true">
                <div class="cruiser-0"></div>
                <div class="cruiser-1"></div>
                <div class="cruiser-2"></div>
            </div>
            <div class="ship battleship-container" draggable="true">
                <div class="battleship-0"></div>
                <div class="battleship-1"></div>
                <div class="battleship-2"></div>
                <div class="battleship-3"></div>
            </div>
            <div class="ship carrier-container" draggable="true">
                <div class="carrier-0"></div>
                <div class="carrier-1"></div>
                <div class="carrier-2"></div>
                <div class="carrier-3"></div>
                <div class="carrier-4"></div>
            </div>
        </div>

    </body>

</html>

Answer

The issue is that within isTaken the number from random start + index is simply too high of a number than what AISquares can handle since it only contains 100 values/indexes, you’re potentially passing in a number > 100 and therefore it’s returning undefined and crashing. Since it’s a random number generator, it’ll sometimes not go over 100 and it works, vice versa.

function generate(ship) {
    let randomDirection = Math.abs(Math.floor(Math.random() * ship.directions.length));
    let current = ship.directions[randomDirection];
    if (current === 0) {
        direction = 1
    }
    if (current === 1) {
        direction = 10
    }
    let randomStart = Math.abs(Math.floor(Math.random() * AISquares.length - ship.directions[0].length));

    
    const isTaken = current.some(index => AISquares[randomStart + index].classList.contains("taken"))
    const isAtRightEdge = current.some(index => (randomStart + index) % widthl === widthl - 1 )
    const isAtLeftEdge = current.some(index => (randomStart + index) % widthl === 0)

    

    if (!isTaken && !isAtRightEdge && !isAtLeftEdge) {
        current.forEach(index => AISquares[randomStart + index].classList.add("taken", ship.name))
    }else generate(ship)

}

Also, I noticed some other things– direction variable is never declared anywhere, so you’re hoisting it IF it passes those conditions, but if it never passes those conditions it just never exists. However, I don’t see direction used anywhere else, so I doubt this is an issue? There’s also the issue of:

//both of the below conditions will never evaluate, so direction will never be
//hoisted, thus never exist
if (current === 0) {
    direction = 1
}
if (current === 1) {
    direction = 10
}

Reason why the above is true:

const shipArray = [
    {
        name: "destroyer",
        directions: [
            [0, 1],
            [0, widthl]
        ]
    },
    {
        name: "submarine",
        directions: [
            [0, 1, 2],
            [0, widthl, widthl*2]
        ]
    },
    {
        name: "cruiser",
        directions: [
            [0, 1, 2],
            [0, widthl, widthl*2]
        ]
    },
    {
        name: "battleship",
        directions: [
            [0, 1, 2, 3],
            [0, widthl, widthl*2, widthl*3]
        ]
    },
    {
        name: "carrier",
        directions: [
            [0, 1, 2, 3, 4],
            [0, widthl, widthl*2, widthl*3, widthl*4]
        ]
    }
];
let current = ship.directions[randomDirection];

Current evaluates to an array because ship.directions =

directions: [
                [0, 1, 2, 3, 4],
                [0, widthl, widthl*2, widthl*3, widthl*4]
            ]

And random direction is either 1 or 0, so it’s always accessing either ship.directions[0] or ship.directions[1].

Perhaps you meant to use randomDirection not current in the conditional check, since the logic checks for 0 or 1, it would make sense randomDirection should be used it since it’s the one always set to 0 or 1.

Lastly:

//this needs to be limited
let randomStart = Math.abs(Math.floor(Math.random() * AISquares.length - ship.directions[0].length));

const isTaken = current.some(index => AISquares[randomStart + index].classList.contains("taken"))

The condition of the square having taken will be random so it could be an index that’s fairly high so more often than not you’ll be adding numbers like this (67 + 66) and thereby attemping to access an index that doesn’t exist in the AISquares array and causing the script to err because classlist attempts to access a property that doesn’t exist. Maybe there’s something else I’m missing, but resolving these issues will make your program work.

edit generate() like this:

  function generate(ship) {
    let randomDirection = Math.abs(Math.floor(Math.random() * ship.directions.length));
    let current = ship.directions[randomDirection];

    if (randomDirection === 0) {
        direction = 1
    }
    if (randomDirection === 1) {
        direction = 10
    }

    let randomStart = Math.abs(Math.floor(Math.random() * AISquares.length - (ship.directions[0].length * direction)));
    
    const isTaken = current.some(index => AISquares[randomStart + index].classList.contains("taken"))
    const isAtRightEdge = current.some(index => (randomStart + index) % widthl === widthl - 1 )
    const isAtLeftEdge = current.some(index => (randomStart + index) % widthl === 0)

    if (!isTaken && !isAtRightEdge && !isAtLeftEdge) {
        current.forEach(index => AISquares[randomStart + index].classList.add("taken", ship.name))
    } else generate(ship)
 }