You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

278 lines
8.9 KiB

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