Source: drawing.js

// Functions related to drawing level on the screen

TILE_SIZE = 40
function px(n) { return String(n) + "px" }


/**
 * Represents a level that is displayed to the user
 */
class BasicLevelDisplay {

  constructor(element, player, targets, boxes) {
    this.element = element
    this.player = player
    this.targets = targets
    this.boxes = boxes
  }

  move_player_to(position) {
    this.move_element_to(this.player, position)
  }

  move_box_to(box_index, position) {
    this.move_element_to(this.boxes[box_index], position)
    play_move_box_sound()
  }

  set_target_has_box(target_index, has_box) {
    set_target_has_box(this.targets[target_index], has_box)
  }

  set_box_is_satisfied(box_index, is_satisfied) {
    set_box_is_satisfied(this.boxes[box_index], is_satisfied)
    if (is_satisfied)
      play_box_satisfied_sound()
  }

  move_element_to(element, position) {
    let [x, y] = position
    x *= TILE_SIZE
    y *= TILE_SIZE
    element.style.left = px(x)
    element.style.top = px(y)
  }
}


/**
 * Draws level using HTML 
 * @param {Level} level 
 * @param {function} on_tile_click 
 */
function draw_level(level, on_tile_click) {
  let outer_element = document.createElement("div")
  let element = document.createElement("div")
  outer_element.appendChild(element)

  let scale = 1
  if (level.width >= 10 || level.height >= 10) {
    scale = 1 + (10 - Math.max(level.width, level.height)) / 40
    element.style.transform = `scale(${Math.round(100 * scale)}%)`
  }


  element.classList.add("level")
  outer_element.classList.add("level-outer")
  if (level.width == 0 || level.height == 0)
    return new BasicLevelDisplay(outer_element)

  let tiles = new Array(level.width)
  element.style.width = px((level.width * TILE_SIZE) * scale)
  element.style.height = px((level.height * TILE_SIZE + TILE_SIZE / 2) * scale)

  for (let x = 0; x < level.width; x++) {
    tiles[x] = new Array(level.height)
    for (let y = 0; y < level.height; y++) {
      let tile = new_empty_tile_at(x, y)
      tile.classList.add("tile-floor")
      if (on_tile_click != undefined)
        tile.onclick = _ => on_tile_click([x, y])
      tiles[x][y] = tile
      element.appendChild(tile)
    }
  }

  for (let [x, y] of level.walls) {
    tiles[x][y].classList.add('tile-wall')
  }

  let targets = []
  for (let [x, y] of level.targets) {
    tiles[x][y].classList.remove("tile-floor")
    tiles[x][y].classList.add("tile-target")
    targets.push(tiles[x][y])
  }

  let boxes = []
  for (let [x, y] of level.boxes) {
    let box = new_box_at(x, y)
    if (is_box_satisfied(level, [x, y])) {
      set_target_has_box(tiles[x][y], true)
      set_box_is_satisfied(box, true)
    }
    element.appendChild(box)
    boxes.push(box)
  }

  let player = undefined
  if (level.player != undefined) {
    player = new_player_at(level.player[0], level.player[1])
    element.appendChild(player)
  }

  return new BasicLevelDisplay(outer_element, player, targets, boxes)
}

function new_empty_tile_at(x, y) {
  let tile = document.createElement("div")
  tile.classList.add("tile")
  size_and_position_element(tile, x, y)
  tile.style.height = px(1.5 * TILE_SIZE)
  return tile
}

function new_box_at(x, y) {
  let box = document.createElement("div")
  box.classList.add("box")
  size_and_position_element(box, x, y)
  return box
}

function new_player_at(x, y) {
  let player = document.createElement("div")
  player.classList.add("player")
  size_and_position_element(player, x, y)
  return player
}

function size_and_position_element(element, x, y) {
  element.style.width = px(TILE_SIZE)
  element.style.height = px(TILE_SIZE)
  move_element_to(element, [x, y])
}

function move_element_to(element, position) {
  let [x, y] = position
  x *= TILE_SIZE
  y *= TILE_SIZE
  element.style.left = px(x)
  element.style.top = px(y)
}

function set_target_has_box(target, has_box) {
  if (has_box)
    target.classList.add("has-box")
  else
    target.classList.remove("has-box")
}

function set_box_is_satisfied(box, is_satisfied) {
  if (is_satisfied)
    box.classList.add("is-satisfied")
  else
    box.classList.remove("is-satisfied")
}