javascripthtmlcollection

How to dynamically update an HTMLCollection using x and y coordinates


I am working on a battleship game and I am trying to update the x and y coordinates on an HTMLCollection. The way I am trying to update is not working because I am accessing it as a 2d array when it is a 1d array.

// display.js
const displayCompAttk = () => {
const result = game.computer.sendAttack(game.player)

const x = result[0]
const y = result[1]

playerCells[x][y].textContent = result[2]
}

Meaning I can only update the x or y coordinates like so playerCells[x] or playerCells[y] and that is not what I want. Is there a possible way to update the x and y coordinates of the HTMLCollection to display true or false ?

const createShip = (shipLength, name) => {
  const hits = 0
  let sunk = false

  function getLength() {
    return this.shipLength
  }

  function hit() {
    this.hits += 1
    return this.hits
  }

  function isSunk() {
    if (this.hits >= this.shipLength) {
      sunk = true
      return sunk
    }
    sunk = false
    return sunk
  }
  return {
    getLength,
    hit,
    isSunk,
    shipLength,
    name,
    hits,
  }
}

// gameboard.js

const Gameboard = () => {
  const rows = 10
  const columns = 10
  const board = []
  const missedCoord = []
  const shipArr = []
  const attackCoord = []
  const getBoard = () => [...board]

  const Water = () => {
    let isHit = false
    const hit = () => {
      isHit = true
    };
    return {
      type: "water",
      hit,
      get isHit() {
        if (isHit) {
          return "BLOOP! Miss."
        }
        return "Ship hit!"
      }
    }
  }

  for (let i = 0; i < rows; i += 1) {
    board[i] = []
    for (let j = 0; j < columns; j += 1) {
      board[i][j] = Water()
    }
  }

  // Need to reduce array amount to a single value
  const cellCount = getBoard().reduce((row, col) => row + col.length, 0)

  const validCoords = (x, y) => {
    if (x < 0) {
      return false
    }
    if (x > 9) {
      return false
    }
    if (y < 0) {
      return false
    }
    if (y > 9) {
      return false
    }
    return true
  }
  const shipIsInbounds = (x, y, ship) => {
    const shipsLength = ship.getLength()

    if (x + shipsLength > columns) {
      // console.log("Cannot place ship horizontally, out of bounds.");
      return false;
    }
    if (y + shipsLength > rows) {
      // console.log("Cannot place ship vertically, out of bounds.");
      return false;
    }
    return true
  }
  const doShipsCollide = (x, y, ship) => {
    const shipsLength = ship.getLength()
    const currentBoard = getBoard()
    for (let i = 0; i < shipsLength; i += 1) {
      if (currentBoard[x][y + i].type !== "water") {
        // console.log("Ships cannot overlap!")
        return false
      }
    }
    for (let i = 0; i < shipsLength; i += 1) {
      if (currentBoard[x + i][y].type !== "water") {
        // console.log("Ships cannot overlap!")
        return false
      }
    }
    return true
  }
  const placeHorizontal = (x, y, ship) => {
    const shipsLength = ship.getLength()
    const currentBoard = getBoard()

    // Loop through ships length
    if (validCoords(x, y) && shipIsInbounds(x, y, ship) && doShipsCollide(x, y, ship)) {
      for (let i = 0; i < shipsLength; i += 1) {
        // Change ship.name back to ship
        currentBoard[x][y + i] = ship
        shipArr.push(ship)
      }
      return true
    }
    return false
  }
  const placeVertical = (x, y, ship) => {
    const shipsLength = ship.getLength()
    const currentBoard = getBoard()

    // Loop through ships length
    if (validCoords(x, y) && shipIsInbounds(x, y, ship) && doShipsCollide(x, y, ship)) {
      for (let i = 0; i < shipsLength; i += 1) {
        // Change ship.name back to ship
        currentBoard[x + i][y] = ship
        shipArr.push(ship)
      }
      return true
    }
    return false
  }

  function canShipBeHitAgain(x, y) {
    const coords = [x, y]
    const coordStr = JSON.stringify(coords)
    const attackCoordStr = JSON.stringify(attackCoord[0])

    if (coordStr === attackCoordStr) {
      console.log("Cannot hit same spot!")
      return false
    }
    return true
  }

  function receiveAttack(x, y) {
    const water = Water()
    const currentBoard = getBoard()

    if (canShipBeHitAgain(x, y) && validCoords(x, y) && currentBoard[x][y].hit()) {
      attackCoord.push([x, y])
      console.log(`Hit at coordinates ${[x]},${[y]}`)
      return true
    }
    water.hit()
    missedCoord.push([x, y])
    return false
  }

  function allSunk() {
    shipArr.every((ship) => {
      if (!ship.isSunk()) {
        console.log("All ships are not sunk.")
        return false
      }
      return false
    })
    return true
  }
  return {
    placeVertical,
    placeHorizontal,
    getBoard,
    cellCount,
    receiveAttack,
    allSunk,
    canShipBeHitAgain,
    validCoords,
    shipIsInbounds,
    doShipsCollide,
    attackCoord
  }
}

// player.js

const createComputer = () => {
  const computerGameboard = Gameboard()
  // console.log(computerGameboard.attackCoord)
  const arrayOfCoords = []
  const compAttkCoords = []
  let counter = 0

  const shuffleArray = (array) => {
    for (let i = array.length - 1; i > 0; i -= 1) {
      const j = Math.floor(Math.random() * (i + 1));
      [array[i], array[j]] = [array[j], array[i]]
    }
  }

  for (let i = 0; i < 10; i += 1) {
    for (let j = 0; j < 10; j += 1) {
      arrayOfCoords.push([i, j]);
    }
  }

  shuffleArray(arrayOfCoords)

  const placeShipHorizontal = (ship) => {
    let x;
    let y;
    do {
      x = Math.floor((Math.random() * 9));
      y = Math.floor((Math.random() * 9));
    }
    while (computerGameboard.placeHorizontal(x, y, ship) === false);
    computerGameboard.placeHorizontal(x, y, ship)
  }

  const placeShipVertical = (ship) => {
    let x
    let y
    do {
      x = Math.floor((Math.random() * 9))
      y = Math.floor((Math.random() * 9))
    }
    while (computerGameboard.placeVertical(x, y, ship) === false)
    computerGameboard.placeVertical(x, y, ship)
  }

  const setEnemyBoard = (player) => player.getPlayerBoard

  const sendAttack = (player) => {
    const randomXCoords = arrayOfCoords[counter][0]
    const randomYCoords = arrayOfCoords[counter][1]

    compAttkCoords.push([randomXCoords, randomYCoords])

    const response = player.playerGameboard.receiveAttack(randomXCoords, randomYCoords)
    counter += 1;

    return [randomXCoords, randomYCoords, response]
  }

  return {
    computerGameboard,
    setEnemyBoard,
    placeShipHorizontal,
    placeShipVertical,
    sendAttack,
    compAttkCoords
  }
}

const createPlayer = (name) => {
  const getName = () => name
  const playerGameboard = Gameboard()
  // console.log(playerGameboard.attackCoord)

  const placeShipHorizontal = (x, y, ship) => playerGameboard.placeHorizontal(x, y, ship)

  const placeShipVertical = (x, y, ship) => playerGameboard.placeVertical(x, y, ship)

  const setEnemyBoard = (comp) => comp.getCompBoard

  const sendAttack = (x, y, comp) => comp.computerGameboard.receiveAttack(x, y)

  return {
    getName,
    placeShipHorizontal,
    placeShipVertical,
    setEnemyBoard,
    sendAttack,
    playerGameboard
  }
}

// game.js 

const createGame = () => {
  const player = createPlayer("Player")
  const computer = createComputer()

  const carrier = createShip(5, "Carrier")
  const battleship = createShip(4, "Battleship")
  const destroyer = createShip(3, "Destroyer")
  const submarine = createShip(3, "Submarine")
  const patrolBoat = createShip(2, "Patrol Boat")

  return {
    player,
    computer,
    carrier,
    battleship,
    destroyer,
    submarine,
    patrolBoat
  }
}

// display.js

const playerContainer = document.querySelector(".player-container")
const computerContainer = document.querySelector(".computer-container")
const compCells = document.getElementsByClassName("c-board-cell")
const playerCells = document.getElementsByClassName("p-board-cell")

const game = createGame()

const createPlayerDisplay = () => {
  const board = game.player.playerGameboard.getBoard()

  for (let i = 0; i < board.length; i += 1) {
    board[i] = []
    for (let j = 0; j < board.length; j += 1) {
      const cell = document.createElement("td")
      cell.classList.add("p-board-cell")
      cell.setAttribute("x", i)
      cell.setAttribute("y", j)
      board[i][j] = cell
    }
  }

  for (let i = 0; i < 10; i += 1) {
    const row = document.createElement("tr")
    for (let j = 0; j < 10; j += 1) {
      row.append(board[i][j])
    }
    row.classList.add("p-board-row")
    playerContainer.append(row)
  }
}

createPlayerDisplay()

const createCompDisplay = () => {
  const board = game.computer.computerGameboard.getBoard()

  for (let i = 0; i < board.length; i += 1) {
    board[i] = []
    for (let j = 0; j < board.length; j += 1) {
      const cell = document.createElement("td")
      cell.classList.add("c-board-cell")
      cell.setAttribute("x", i)
      cell.setAttribute("y", j)
      board[i][j] = cell
    }
  }

  for (let i = 0; i < 10; i += 1) {
    const row = document.createElement("tr")
    for (let j = 0; j < 10; j += 1) {
      row.append(board[i][j])
    }
    row.classList.add("c-board-row")
    computerContainer.append(row)
  }
}

createCompDisplay()


const displayPlayerAttk = () => {
  for (let i = 0; i < compCells.length; i += 1) {
    compCells[i].addEventListener("click", (e) => {
      if (compCells[i].textContent.includes("true" || "false")) {
        return false
      }
      const xPos = e.currentTarget.getAttribute("x")
      const yPos = e.currentTarget.getAttribute("y")
      compCells[i].textContent = game.player.sendAttack(Number(xPos), Number(yPos), game.computer)
      return true
    })
  }
}

displayPlayerAttk()

const displayCompAttk = () => {
  const result = game.computer.sendAttack(game.player)

  const x = result[0]
  const y = result[1]

  playerCells[x][y].textContent = result[2]
}


game.player.sendAttack(1, 2, game.computer)

game.computer.placeShipHorizontal(game.carrier)
game.computer.placeShipVertical(game.battleship)
game.player.placeShipHorizontal(1, 2, game.carrier)
game.player.placeShipVertical(6, 6, game.battleship)

displayCompAttk()
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  text-decoration: none;
  list-style: none;
}

td {
  border: black solid 1px;
  width: 25px;
  height: 25px;
}

tr {
  border: black solid 1px;
}
<body>
  <header>
    <h1 class="title">Battleship</h1>
  </header>
  <main>
    <div class="main-container">
      <h1 class="computer-header">Player</h1>
      <table class="player-container"></table>
      <h1 class="computer-header">Computer</h1>
      <table class="computer-container"></table>
      <div class="ship-container">
      </div>
    </div>
  </main>
</body>


Solution

  • Frame Challenge

    A confusing part of code is overwriting the variable board array content in the create display functions. board initially contains a cloned 2D array of Water objects (to get its dimensions) but entries are overwritten with td elements used to create a board table in HTML.

    In createPlayerDisplay the 2D array of board elements is not saved or returned, but if made a property of player, say player.grid, could be used to set HTML table cells using x and y coordinates.

    Possible patch

    1. Insert game.player.grid = board; at the end of the createPlayerDisplay function body

    2. replace the last line of displayCompAttk, playerCells[x][y].textContent = result[2], with

       game.player.grid[x][y].textContent = result[2]
      

    Note I did not attempt to finish or debug the game further. With the patch the game ran without runtime errors and clicks on the "Computer" display board updated cells with "true" or "false".

    Conversion formula

    To update the linear playerCells array as asked in the title however, you could use the conversion formula

    index = y * rowLength + x