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.
 
 
 

327 lines
10 KiB

//===== Constructor
const Content = function({ parent, squares }) {
this.parent = parent;
this.squares = squares;
this.walls = {};
this.robots = {};
this.listeners = {};
this.initialRobotState = [];
this.playerId = null;
this.isSolving = false;
this.isSolver = false;
this.drawSquares();
document.addEventListener('L-reset', this.msgReset.bind(this));
document.addEventListener('G-connected', this.msgConnected.bind(this));
document.addEventListener('G-walls', this.msgWalls.bind(this));
document.addEventListener('G-robots', this.msgRobots.bind(this));
document.addEventListener('G-solve', this.msgSolve.bind(this));
document.addEventListener('G-move', this.msgMove.bind(this));
};
//===== UI
Content.prototype.drawRobots = function() {
this.robots = {};
this.parent.querySelectorAll('.content-robot').forEach(el => el.parentNode.removeChild(el));
this.parent.querySelectorAll('.content-arrows').forEach(el => el.parentNode.removeChild(el));
this.initialRobotState.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 * 1.25), top: (s * 0.5), parentId: id
});
const down = this.drawArrow({
direction: 'down', label: '▼', i, j, left: (s * 1.25), top: (s * 2), parentId: id
});
const left = this.drawArrow({
direction: 'left', label: '◀', i, j, left: (s * 0.5), top: (s * 1.25), parentId: id
});
const right = this.drawArrow({
direction: 'right', label: '▶', i, j, left: (s * 2), top: (s * 1.25), parentId: id
});
arrows.appendChild(up);
arrows.appendChild(down);
arrows.appendChild(left);
arrows.appendChild(right);
this.parent.appendChild(robot);
this.parent.appendChild(arrows);
});
this.updateArrowVisibilities();
};
Content.prototype.drawArrow = function({ direction, label, i, j, left, top, parentId }) {
const s = this.squares.sideLength / 2;
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;
};
Content.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);
}
}
};
Content.prototype.drawWalls = function(edges) {
this.walls = {};
this.parent.querySelectorAll('.content-wall-x').forEach(el => el.parentNode.removeChild(el));
this.parent.querySelectorAll('.content-wall-y').forEach(el => el.parentNode.removeChild(el));
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();
};
Content.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();
};
Content.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';
});
};
Content.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);
};
//===== Click handlers
Content.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 });
const detail = { id, i: i2, j: j2, emitIf: this.isSolving };
const evtMove = new CustomEvent('L-move', { detail });
document.dispatchEvent(evtMove);
};
//===== Message handlers
Content.prototype.msgRobots = function(evt) {
this.initialRobotState = evt.detail.body;
this.drawRobots()
};
Content.prototype.msgWalls = function(evt) {
this.drawWalls(evt.detail.body);
};
Content.prototype.msgMove = function(evt) {
const { id, i, j } = evt.detail.body;
this.moveRobot({ id, i, j });
};
Content.prototype.msgSolve = function(evt) {
this.isSolving = true;
this.isSolver = (this.playerId === evt.detail.id);
if (this.isSolver) {
console.error("The current player is attempting a solve.");
} else {
console.error("A different player is attempting a solve.");
//...modal.
}
};
Content.prototype.msgJoin = function(evt) {
};
Content.prototype.msgConnected = function(evt) {
this.playerId = evt.detail.body;
}
Content.prototype.msgReset = function() {
this.drawRobots();
};