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>
Advertisement
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) }