const Board = function({ parent, squares }) { this.parent = parent; this.squares = squares; this.walls = {}; this.robots = {}; this.listeners = {}; this.initialRobotState = []; this.drawSquares(); document.addEventListener('board-reset', this.onReset.bind(this)); }; Board.prototype.drawRobots = function(robots) { this.initialRobotState = robots; this.robots = {}; while (this.parent.getElementsByClassName('content-robot').length) { const child = this.parent.getElementsByClassName('content-robot')[0]; child.parentNode.removeChild(child); } while (this.parent.getElementsByClassName('content-arrows').length) { const child = this.parent.getElementsByClassName('content-arrows')[0]; child.parentNode.removeChild(child); } robots.forEach(({ color, i, j }) => { const { x, y } = this.squares.ijToXy({ i, j }); const s = this.squares.sideLength; const id = color.replace('#', '').toUpperCase(); const ij = `${i}-${j}`; this.robots[ij] = id; const robot = document.createElement('div'); robot.id = `robot-${id}`; robot.className = 'content-robot'; robot.style.background = `radial-gradient(circle at ${s/3}px ${s/3}px, ${color} 10%, #000)`; robot.style.borderRadius = (s / 2) + 'px'; robot.style.height = s + 'px'; robot.style.width = s + 'px'; robot.style.left = x + 'px'; robot.style.top = y + 'px'; robot.dataset.i = i; robot.dataset.j = j; // Get robot from ij: document.querySelector('[data-robot=i-j]') // robot.dataset.robot = ij; const arrows = document.createElement('div'); arrows.className = 'content-arrows'; arrows.id = `arrows-${id}`; arrows.style.position = 'absolute'; arrows.style.left = (x - s) + 'px'; arrows.style.top = (y - s) + 'px'; const up = this.drawArrow({ direction: 'up', label: '▲', i, j, left: s, top: 0, parentId: id }); const down = this.drawArrow({ direction: 'down', label: '▼', i, j, left: s, top: (2 * s), parentId: id }); const left = this.drawArrow({ direction: 'left', label: '◀', i, j, left: 0, top: s, parentId: id }); const right = this.drawArrow({ direction: 'right', label: '▶', i, j, left: (2 * s), top: s, parentId: id }); arrows.appendChild(up); arrows.appendChild(down); arrows.appendChild(left); arrows.appendChild(right); this.parent.appendChild(robot); this.parent.appendChild(arrows); }); this.updateArrowVisibilities(); }; Board.prototype.drawArrow = function({ direction, label, i, j, left, top, parentId }) { const s = this.squares.sideLength; const arrow = document.createElement('div'); arrow.className = 'content-arrow'; arrow.innerHTML = label; arrow.style.left = left + 'px'; arrow.style.top = top + 'px'; arrow.style.lineHeight = s + 'px'; arrow.style.height = s + 'px'; arrow.style.width = s + 'px'; arrow.dataset.direction = direction; arrow.dataset.parent = parentId; arrow.addEventListener('click', this.onArrowClick.bind(this)); return arrow; }; Board.prototype.drawSquares = function() { for (let i = 0; i < this.squares.perSide; i++) { for (let j = 0; j < this.squares.perSide; j++) { // All squares are absolutely positioned relative to the viewport. const { x, y } = this.squares.ijToXy({ i, j }); const square = document.createElement('div'); square.className = 'content-square'; square.style.height = this.squares.sideLength + 'px'; square.style.width = this.squares.sideLength + 'px'; square.style.left = x + 'px'; square.style.top = y + 'px'; this.parent.appendChild(square); } } }; Board.prototype.drawWalls = function(edges) { this.walls = {}; edges.forEach(edge => { this.walls[edge] = true; const id = `wall-${edge}`; if (document.getElementById(id)) { return; } const [i1, j1, i2, j2] = edge.split('-'); const wall = document.createElement('div'); wall.id = id; wall.title = edge; const { x, y } = this.squares.ijToXy({ i: i1, j: j1 }); wall.style.left = x + 'px'; wall.style.top = y + 'px'; // Get wall from edge: document.querySelector('[data-wall=i1-j1-i2-j2]') wall.dataset.wall = edge; if (i1 === i2) { wall.className = 'content-wall-y'; wall.style.height = this.squares.sideLength + 'px'; } else { wall.className = 'content-wall-x'; wall.style.width = this.squares.sideLength + 'px'; } this.parent.appendChild(wall) }); this.updateArrowVisibilities(); }; Board.prototype.moveRobot = function({ id, i, j }) { const robot = document.getElementById(`robot-${id}`); const arrows = document.getElementById(`arrows-${id}`); const { x, y } = this.squares.ijToXy({ i, j }); const s = this.squares.sideLength; robot.style.left = x + 'px'; robot.style.top = y + 'px'; robot.dataset.i = i; robot.dataset.j = j; this.robots[`${i}-${j}`] = id; arrows.style.left = (x - s) + 'px'; arrows.style.top = (y - s) + 'px'; this.updateArrowVisibilities(); var event = new Event('move-increment'); document.dispatchEvent(event); } Board.prototype.updateArrowVisibilities = function() { const keys = Object.keys(this.robots); keys.forEach(key => { const id = this.robots[key]; const i = key.split('-')[0] * 1; const j = key.split('-')[1] * 1; const arrows = document.getElementById(`arrows-${id}`); const ijR = `${i + 1}-${j}`; const ijL = `${i - 1}-${j}`; const ijU = `${i}-${j - 1}`; const ijD = `${i}-${j + 1}`; const edgeR = `${i + 1}-${j}-${i + 1}-${j + 1}`; const edgeL = `${i}-${j}-${i}-${j + 1}`; const edgeU = `${i}-${j}-${i + 1}-${j}`; const edgeD = `${i}-${j + 1}-${i + 1}-${j + 1}`; arrows.querySelector("[data-direction='right']").style.display = (this.robots[ijR] || this.walls[edgeR] || i === (this.squares.perSide - 1)) ? 'none' : 'block'; arrows.querySelector("[data-direction='left']").style.display = (this.robots[ijL] || this.walls[edgeL] || i === 0) ? 'none' : 'block'; arrows.querySelector("[data-direction='up']").style.display = (this.robots[ijU] || this.walls[edgeU] || j === 0) ? 'none' : 'block'; arrows.querySelector("[data-direction='down']").style.display = (this.robots[ijD] || this.walls[edgeD] || j === (this.squares.perSide - 1)) ? 'none' : 'block'; }); } Board.prototype.onArrowClick = function(evt) { const direction = evt.currentTarget.dataset.direction; const id = evt.currentTarget.dataset.parent; const robot = document.getElementById(`robot-${id}`); const i = robot.dataset.i * 1; const j = robot.dataset.j * 1; delete this.robots[`${i}-${j}`]; const { i: i2, j: j2 } = this.findNextObstacle({ direction, i, j }); this.moveRobot({ id, i: i2, j: j2 }) }; Board.prototype.onReset = function() { this.drawRobots(this.initialRobotState); }; Board.prototype.findNextObstacle = function({ direction, i, j }) { switch (direction) { case 'right': for (let ii = i + 1; ii < this.squares.perSide; ii++) { const edge = `${ii + 1}-${j}-${ii + 1}-${j + 1}`; const ij = `${ii + 1}-${j}`; if (this.robots[ij] || this.walls[edge] || ii === (this.squares.perSide - 1)) { return { i: ii, j }; } } break; case 'left': for (let ii = i - 1; ii >= 0; ii--) { const edge = `${ii}-${j}-${ii}-${j + 1}`; const ij = `${ii - 1}-${j}`; if (this.robots[ij] || this.walls[edge] || ii === 0) { return { i: ii, j }; } } break; case 'up': for (let jj = j - 1; jj >= 0; jj--) { const edge = `${i}-${jj}-${i + 1}-${jj}`; const ij = `${i}-${jj - 1}`; if (this.robots[ij] || this.walls[edge] || jj === 0) { return { i, j: jj }; } } break; case 'down': for (let jj = j + 1; jj < this.squares.perSide; jj++) { const edge = `${i}-${jj + 1}-${i + 1}-${jj + 1}`; const ij = `${i}-${jj + 1}`; if (this.robots[ij] || this.walls[edge] || jj === (this.squares.perSide - 1)) { return { i, j: jj }; } } break; } throw Error("Could not find next obstacle, no direction found. ", direction, i, j); }