parent
303dbd939b
commit
b6910d8692
12 changed files with 679 additions and 668 deletions
@ -1,317 +0,0 @@ |
||||
//===== Constructor
|
||||
|
||||
const Content = function({ parent, squares }) { |
||||
this.parent = parent; |
||||
this.squares = squares; |
||||
|
||||
this.walls = {}; |
||||
this.robots = {}; |
||||
this.listeners = {}; |
||||
|
||||
this.playerId = null; |
||||
this.isSolving = false; |
||||
this.isSolver = false; |
||||
|
||||
this.drawSquares(); |
||||
|
||||
// Message handlers: "Local message" and "Global message"
|
||||
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(robotPositions) { |
||||
this.robots = {}; |
||||
|
||||
this.parent.querySelectorAll('.content-robot').forEach(el => el.parentNode.removeChild(el)); |
||||
this.parent.querySelectorAll('.content-arrows').forEach(el => el.parentNode.removeChild(el)); |
||||
|
||||
robotPositions.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'; |
||||
|
||||
const arrows = document.createElement('div'); |
||||
arrows.className = 'content-arrows'; |
||||
arrows.id = `arrows-${id}`; |
||||
arrows.style.position = 'absolute'; |
||||
|
||||
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) { |
||||
const robotPositions = evt.detail.body; |
||||
this.drawRobots(robotPositions);
|
||||
}; |
||||
|
||||
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(); |
||||
}; |
@ -1,257 +1,250 @@ |
||||
//===== Constructor
|
||||
// //===== Constructor
|
||||
|
||||
const Controls = function() { |
||||
this.names = {}; |
||||
this.positions = []; |
||||
this.timers = {}; |
||||
this.currentWinningGuess = Infinity; |
||||
|
||||
// this.drawGuesses();
|
||||
|
||||
// Message handlers: "Local message" and "Global message"
|
||||
document.addEventListener('L-connected', this.msgConnected.bind(this)); |
||||
document.addEventListener('L-move', this.msgMove.bind(this)); |
||||
document.addEventListener('L-reset', this.msgReset.bind(this)); |
||||
document.addEventListener('L-undo', this.msgUndo.bind(this)); |
||||
|
||||
// document.addEventListener('G-move', this.msgMove.bind(this));
|
||||
// document.addEventListener('G-win', this.msgWin.bind(this));
|
||||
document.addEventListener('G-attempt', this.msgAttempt.bind(this));
|
||||
document.addEventListener('G-guess', this.msgGuess.bind(this));
|
||||
document.addEventListener('G-players', this.msgPlayers.bind(this));
|
||||
document.addEventListener('G-robots', this.msgPlayers.bind(this));
|
||||
document.addEventListener('G-skip', this.msgSkip.bind(this)); |
||||
document.addEventListener('G-start', this.msgStart.bind(this)); |
||||
document.addEventListener('G-stop', this.msgStop.bind(this)); |
||||
// this.moves = [];
|
||||
// this.names = {};
|
||||
// this.starts = [];
|
||||
// this.timers = {};
|
||||
|
||||
// // "Local" and "Global" messages
|
||||
// document.addEventListener('L-connected', this.msgConnected.bind(this));
|
||||
// document.addEventListener('L-move', this.msgMove.bind(this));
|
||||
// document.addEventListener('L-reset', this.msgReset.bind(this));
|
||||
// document.addEventListener('L-undo', this.msgUndo.bind(this));
|
||||
|
||||
// document.addEventListener('G-attempt', this.msgAttempt.bind(this));
|
||||
// document.addEventListener('G-guess', this.msgGuess.bind(this));
|
||||
// document.addEventListener('G-players', this.msgPlayers.bind(this));
|
||||
// document.addEventListener('G-robots', this.msgRobots.bind(this));
|
||||
// document.addEventListener('G-skip', this.msgSkip.bind(this));
|
||||
// document.addEventListener('G-start', this.msgStart.bind(this));
|
||||
// document.addEventListener('G-stop', this.msgStop.bind(this));
|
||||
|
||||
|
||||
// Click handlers
|
||||
document.getElementById('controls-reset').addEventListener('click', this.onClickReset.bind(this)); |
||||
document.getElementById('controls-robots').addEventListener('click', this.onClickRobots.bind(this)); |
||||
document.getElementById('controls-skip').addEventListener('click', this.onClickSkip.bind(this)); |
||||
document.getElementById('controls-start').addEventListener('click', this.onClickStart.bind(this)); |
||||
document.getElementById('controls-stop').addEventListener('click', this.onClickStop.bind(this)); |
||||
document.getElementById('controls-undo').addEventListener('click', this.onClickUndo.bind(this)); |
||||
document.getElementById('controls-walls').addEventListener('click', this.onClickWalls.bind(this)); |
||||
// // Click handlers
|
||||
// document.getElementById('controls-reset').addEventListener('click', this.onClickReset.bind(this));
|
||||
// document.getElementById('controls-robots').addEventListener('click', this.onClickRobots.bind(this));
|
||||
// document.getElementById('controls-skip').addEventListener('click', this.onClickSkip.bind(this));
|
||||
// document.getElementById('controls-start').addEventListener('click', this.onClickStart.bind(this));
|
||||
// document.getElementById('controls-stop').addEventListener('click', this.onClickStop.bind(this));
|
||||
// document.getElementById('controls-undo').addEventListener('click', this.onClickUndo.bind(this));
|
||||
// document.getElementById('controls-walls').addEventListener('click', this.onClickWalls.bind(this));
|
||||
} |
||||
|
||||
//===== UI
|
||||
|
||||
Controls.prototype.countdownStart = function(seconds) { |
||||
clearTimeout(this.timers.countdown); |
||||
this.timers.countdown = this.countdownTick.bind(this); |
||||
|
||||
const countdown = document.getElementById('controls-countdown'); |
||||
countdown.dataset.tick = seconds; |
||||
|
||||
this.countdownTick(); |
||||
}; |
||||
|
||||
Controls.prototype.countdownTick = function() { |
||||
const countdown = document.getElementById('controls-countdown'); |
||||
const tick = countdown.dataset.tick * 1; |
||||
countdown.dataset.tick = tick - 1;
|
||||
|
||||
const s = (tick !== 1) ? 's' : ''; |
||||
countdown.innerHTML = `${tick} second${s}!`; |
||||
|
||||
if (tick === 0) { |
||||
this.countdownComplete(); |
||||
return; |
||||
} |
||||
|
||||
this.timers.countdown = setTimeout(this.countdownTick.bind(this), 1000); |
||||
}; |
||||
|
||||
Controls.prototype.countdownComplete = function() { |
||||
document.getElementById('controls-countdown').dataset.tick = 0;
|
||||
} ; |
||||
|
||||
Controls.prototype.drawGuesses = function() { |
||||
const container = document.getElementById('controls-guesses'); |
||||
container.querySelectorAll('.controls-guess').forEach(el => el.parentNode.removeChild(el)); |
||||
|
||||
for (let i = 1; i <= 30; i++) { |
||||
const guess = document.createElement('div'); |
||||
guess.className = 'controls-guess'; |
||||
guess.innerHTML = i; |
||||
guess.setAttribute('data-value', i); |
||||
guess.addEventListener('click', this.onClickGuess.bind(this)) |
||||
container.appendChild(guess); |
||||
} |
||||
}; |
||||
|
||||
Controls.prototype.showWaiting = function() { |
||||
document.getElementById('controls-start').parentNode.style.display = ''; |
||||
document.getElementById('controls-walls').parentNode.style.display = ''; |
||||
document.getElementById('controls-robots').parentNode.style.display = ''; |
||||
|
||||
document.getElementById('controls-stop').parentNode.style.display = 'none'; |
||||
// document.getElementById('controls-moves-reset').parentNode.style.display = 'none';
|
||||
document.getElementById('controls-guesses').style.display = 'none'; |
||||
document.getElementById('controls-panic').style.display = 'none'; |
||||
}; |
||||
|
||||
Controls.prototype.showGuessing = function() { |
||||
document.getElementById('controls-start').parentNode.style.display = 'none'; |
||||
document.getElementById('controls-walls').parentNode.style.display = 'none'; |
||||
document.getElementById('controls-robots').parentNode.style.display = 'none'; |
||||
document.getElementById('controls-panic').style.display = 'none'; |
||||
|
||||
document.getElementById('controls-stop').parentNode.style.display = ''; |
||||
// document.getElementById('controls-moves-reset').parentNode.style.display = '';
|
||||
document.getElementById('controls-guesses').style.display = ''; |
||||
} |
||||
// //===== UI
|
||||
|
||||
Controls.prototype.showPanic = function() { |
||||
this.showGuessing(); |
||||
document.getElementById('controls-panic').style.display = ''; |
||||
}; |
||||
// Controls.prototype.countdownStart = function(seconds) {
|
||||
// clearTimeout(this.timers.countdown);
|
||||
// this.timers.countdown = this.countdownTick.bind(this);
|
||||
|
||||
Controls.prototype.updateMoves = function() { |
||||
// Initial robot placement is first move, to allow undo, but doesn't count as a move.
|
||||
const moves = this.positions.length - 1; |
||||
// const countdown = document.getElementById('controls-countdown');
|
||||
// countdown.dataset.tick = seconds;
|
||||
|
||||
document.getElementById('controls-moves').innerHTML = moves; |
||||
document.getElementById('controls-undo').style.display = moves > 0 ? 'auto' : 'none'; |
||||
}; |
||||
// this.countdownTick();
|
||||
// };
|
||||
|
||||
//===== Message handlers
|
||||
// Controls.prototype.countdownTick = function() {
|
||||
// const countdown = document.getElementById('controls-countdown');
|
||||
// const tick = countdown.dataset.tick * 1;
|
||||
// countdown.dataset.tick = tick - 1;
|
||||
|
||||
Controls.prototype.msgAttempt = function() { |
||||
alert("Ready for winning attempt!"); |
||||
}; |
||||
// const s = (tick !== 1) ? 's' : '';
|
||||
// countdown.innerHTML = `${tick} second${s}!`;
|
||||
|
||||
Controls.prototype.msgGuess = function(evt) {
|
||||
const blurbs = [ " has a solution: ", " can do it in ", " says, maybe ", " wagers ",
|
||||
" reckons ", " is pretty sure it's ", ", confidently: ", " wants it to be ",
|
||||
" says ", " hazards ", " guesses ", " thinks it might be "]; |
||||
const blurb = blurbs[Math.floor(Math.random() * blurbs.length)]; |
||||
// if (tick === 0) {
|
||||
// this.countdownComplete();
|
||||
// return;
|
||||
// }
|
||||
|
||||
const msg = evt.detail; |
||||
const guess = msg.guess; |
||||
// this.timers.countdown = setTimeout(this.countdownTick.bind(this), 1000);
|
||||
// };
|
||||
|
||||
this.currentWinningGuess = guess; |
||||
// Controls.prototype.countdownComplete = function() {
|
||||
// document.getElementById('controls-countdown').dataset.tick = 0;
|
||||
// };
|
||||
|
||||
document.getElementById('controls-panic').querySelector('.controls-alert-urgent').innerHTML = (`${this.names[msg.id]}${blurb}${guess} moves.`); |
||||
this.showPanic(); |
||||
this.countdownStart(5); |
||||
} |
||||
// Controls.prototype.showWaiting = function() {
|
||||
// document.getElementById('controls-start').parentNode.style.display = '';
|
||||
// document.getElementById('controls-walls').parentNode.style.display = '';
|
||||
// document.getElementById('controls-robots').parentNode.style.display = '';
|
||||
|
||||
Controls.prototype.msgConnected = function() { |
||||
this.showWaiting(); |
||||
}; |
||||
|
||||
Controls.prototype.msgUndo = function(evt) { |
||||
// Remove most recent move and return to the one before that.
|
||||
this.moves.pop();
|
||||
const secondToLast = this.moves.pop(); |
||||
|
||||
const evtMove = new CustomEvent('L-move', { detail: secondToLast }); |
||||
document.dispatchEvent(evtMove); |
||||
}; |
||||
|
||||
Controls.prototype.msgMove = function(evt) { |
||||
this.moves.push(evt.detail); |
||||
this.updateMoves(); |
||||
}; |
||||
|
||||
Controls.prototype.msgPlayers = function(evt) { |
||||
const container = document.getElementById('controls-players'); |
||||
const names = evt.detail.body; |
||||
const keys = Object.keys(names); |
||||
|
||||
if (keys.length > 0) { |
||||
const nobody = document.getElementById('controls-players-nobody'); |
||||
nobody && nobody.parentNode.removeChild(nobody); |
||||
} |
||||
|
||||
keys.forEach(connectionId => { |
||||
const id = `player-${connectionId}`; |
||||
|
||||
if (document.getElementById(id)) { |
||||
return; |
||||
} |
||||
|
||||
const n = document.createElement('div'); |
||||
n.id = id; |
||||
n.innerHTML = names[connectionId]; |
||||
n.className = 'controls-player'; |
||||
container.appendChild(n) |
||||
}); |
||||
|
||||
this.names = names; |
||||
|
||||
container.querySelectorAll('.controls-player').forEach(el => { |
||||
const id = el.id.split('player-').pop(); |
||||
if (!this.names[id]) { |
||||
container.removeChild(el); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
Controls.prototype.msgReset = function() { |
||||
this.moves = []; |
||||
this.updateMoves(); |
||||
}; |
||||
|
||||
Controls.prototype.msgSkip = function() { |
||||
this.coundownComplete(); |
||||
}; |
||||
|
||||
Controls.prototype.msgStart = function() { |
||||
this.showGuessing(); |
||||
}; |
||||
|
||||
Controls.prototype.msgStop = function() { |
||||
this.showWaiting(); |
||||
} |
||||
// document.getElementById('controls-stop').parentNode.style.display = 'none';
|
||||
// // document.getElementById('controls-moves-reset').parentNode.style.display = 'none';
|
||||
// document.getElementById('controls-guesses').style.display = 'none';
|
||||
// document.getElementById('controls-panic').style.display = 'none';
|
||||
// };
|
||||
|
||||
//===== Click handlers
|
||||
// Controls.prototype.showPanic = function() {
|
||||
// this.showGuessing();
|
||||
// document.getElementById('controls-panic').style.display = '';
|
||||
// };
|
||||
|
||||
Controls.prototype.dispatch = function(evt, data) { |
||||
const e = (data ? new CustomEvent(evt, { detail: data }) : new Event(evt)); |
||||
document.dispatchEvent(e); |
||||
} |
||||
// Controls.prototype.updateMoves = function() {
|
||||
// // Initial robot placement is first move, to allow undo, but doesn't count as a move.
|
||||
// const moves = this.moves.length - this.starts.length;
|
||||
|
||||
Controls.prototype.onClickGuess = function(evt) { |
||||
const guess = evt.currentTarget.dataset.value * 1; |
||||
// document.getElementById('controls-moves').innerHTML = moves;
|
||||
// document.getElementById('controls-undo').style.display = moves > 0 ? 'block' : 'none';
|
||||
// };
|
||||
|
||||
if (!guess || guess < 1) { |
||||
return; |
||||
} |
||||
// //===== Message handlers
|
||||
|
||||
if (guess < this.currentWinningGuess) { |
||||
this.dispatch('L-guess', { moves: evt.currentTarget.dataset.value }); |
||||
} else { |
||||
alert(`That doesn't beat ${this.currentWinningGuess} - try again!`) |
||||
} |
||||
}; |
||||
// Controls.prototype.msgAttempt = function() {
|
||||
// alert("Ready for winning attempt!");
|
||||
// };
|
||||
|
||||
Controls.prototype.onClickRobots = function() { |
||||
this.dispatch('L-robots'); |
||||
}; |
||||
// Controls.prototype.msgGuess = function(evt) {
|
||||
// const blurbs = [ " has a solution: ", " can do it in ", " says, maybe ", " wagers ",
|
||||
// " reckons ", " is pretty sure it's ", ", confidently: ", " wants it to be ",
|
||||
// " says ", " hazards ", " guesses ", " thinks it might be "];
|
||||
// const blurb = blurbs[Math.floor(Math.random() * blurbs.length)];
|
||||
|
||||
Controls.prototype.onClickSkip = function() { |
||||
this.dispatch('L-skip'); |
||||
}; |
||||
// const msg = evt.detail;
|
||||
// const guess = msg.guess;
|
||||
|
||||
Controls.prototype.onClickStart = function() { |
||||
this.dispatch('L-start'); |
||||
}; |
||||
// this.currentWinningGuess = guess;
|
||||
|
||||
Controls.prototype.onClickStop = function() { |
||||
this.dispatch('L-stop'); |
||||
}; |
||||
// document.getElementById('controls-panic').querySelector('.controls-alert-urgent').innerHTML = (`${this.names[msg.id]}${blurb}${guess} moves.`);
|
||||
// this.showPanic();
|
||||
// this.countdownStart(5);
|
||||
// }
|
||||
|
||||
Controls.prototype.onClickUndo = function() { |
||||
this.dispatch('L-undo'); |
||||
}; |
||||
// Controls.prototype.msgConnected = function() {
|
||||
// this.showWaiting();
|
||||
// };
|
||||
|
||||
Controls.prototype.onClickWalls = function() { |
||||
this.dispatch('L-walls'); |
||||
}; |
||||
// Controls.prototype.msgUndo = function(evt) {
|
||||
// if (this.moves.length <= this.starts.length) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
Controls.prototype.onClickReset = function() { |
||||
this.dispatch('L-reset'); |
||||
}; |
||||
// const { id } = this.moves.pop();
|
||||
|
||||
// const indexOfPreviousMove = this.moves.reduce((acc, v, i) => (v.id === id ? i : acc), -1);
|
||||
// const previousMove = this.moves.splice(indexOfPreviousMove, 1);
|
||||
|
||||
// const evtMove = new CustomEvent('L-move', { detail: previousMove[0] });
|
||||
// document.dispatchEvent(evtMove);
|
||||
// };
|
||||
|
||||
// Controls.prototype.msgMove = function(evt) {
|
||||
// this.moves.push(evt.detail);
|
||||
// this.updateMoves();
|
||||
// };
|
||||
|
||||
// Controls.prototype.msgPlayers = function(evt) {
|
||||
// const container = document.getElementById('controls-players');
|
||||
// const names = evt.detail.body;
|
||||
// const keys = Object.keys(names);
|
||||
|
||||
// if (keys.length > 0) {
|
||||
// const nobody = document.getElementById('controls-players-nobody');
|
||||
// nobody && nobody.parentNode.removeChild(nobody);
|
||||
// }
|
||||
|
||||
// keys.forEach(connectionId => {
|
||||
// const id = `player-${connectionId}`;
|
||||
|
||||
// if (document.getElementById(id)) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const n = document.createElement('div');
|
||||
// n.id = id;
|
||||
// n.innerHTML = names[connectionId];
|
||||
// n.className = 'controls-player';
|
||||
// container.appendChild(n)
|
||||
// });
|
||||
|
||||
// this.names = names;
|
||||
|
||||
// container.querySelectorAll('.controls-player').forEach(el => {
|
||||
// const id = el.id.split('player-').pop();
|
||||
// if (!this.names[id]) {
|
||||
// container.removeChild(el);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
// Controls.prototype.msgReset = function() {
|
||||
// // Broadcast starting locations.
|
||||
// this.moves = [];
|
||||
// this.starts.forEach(move => {
|
||||
// const evtMove = new CustomEvent('L-move', { detail: move });
|
||||
// document.dispatchEvent(evtMove);
|
||||
// });
|
||||
// };
|
||||
|
||||
// Controls.prototype.msgRobots = function(evt) {
|
||||
// this.starts = [];
|
||||
// this.moves = [];
|
||||
|
||||
// evt.detail.body.forEach(({ id, i, j}) => {
|
||||
// this.starts.push({ id, i, j });
|
||||
// this.moves.push({ id, i, j });
|
||||
// });
|
||||
// };
|
||||
|
||||
// Controls.prototype.msgSkip = function() {
|
||||
// this.coundownComplete();
|
||||
// };
|
||||
|
||||
// Controls.prototype.msgStart = function() {
|
||||
// // Trim moves array to last position of each robot.
|
||||
// // Set robots array to these new initial positions.
|
||||
// const mostRecentPositions = {};
|
||||
|
||||
// this.moves.forEach(({ id, i, j }) => {
|
||||
// mostRecentPositions[id] = { id, i, j };
|
||||
// });
|
||||
|
||||
// this.starts = [];
|
||||
// Object.values(mostRecentPositions).forEach(robot => {
|
||||
// this.starts.push(robot);
|
||||
// });
|
||||
|
||||
// // Broadcast starting locations.
|
||||
// this.moves = [];
|
||||
// this.starts.forEach(move => {
|
||||
// const evtMove = new CustomEvent('L-move', { detail: move });
|
||||
// document.dispatchEvent(evtMove);
|
||||
// });
|
||||
// };
|
||||
|
||||
// Controls.prototype.msgStop = function() {
|
||||
// // this.showWaiting();
|
||||
// }
|
||||
|
||||
// //===== Click handlers
|
||||
|
||||
// Controls.prototype.dispatch = function(evt, data) {
|
||||
// const e = (data ? new CustomEvent(evt, { detail: data }) : new Event(evt));
|
||||
// document.dispatchEvent(e);
|
||||
// };
|
||||
|
||||
// Controls.prototype.onClickReset = function() {
|
||||
// this.dispatch('L-reset');
|
||||
// };
|
||||
|
||||
// Controls.prototype.onClickRobots = function() {
|
||||
// this.dispatch('L-robots');
|
||||
// };
|
||||
|
||||
// Controls.prototype.onClickSkip = function() {
|
||||
// this.dispatch('L-skip');
|
||||
// };
|
||||
|
||||
// Controls.prototype.onClickStart = function() {
|
||||
// this.dispatch('L-start');
|
||||
// };
|
||||
|
||||
// Controls.prototype.onClickStop = function() {
|
||||
// this.dispatch('L-stop');
|
||||
// };
|
||||
|
||||
// Controls.prototype.onClickUndo = function() {
|
||||
// this.dispatch('L-undo');
|
||||
// };
|
||||
|
||||
// Controls.prototype.onClickWalls = function() {
|
||||
// this.dispatch('L-walls');
|
||||
// };
|
||||
|
@ -0,0 +1,362 @@ |
||||
//===== Constructor
|
||||
|
||||
const Grid = function() { |
||||
this.colors = {}; |
||||
this.obstacles = {}; |
||||
this.robots = []; |
||||
this.walls = []; |
||||
|
||||
this.squaresPerSide = 20; |
||||
this.squareSideLength = 0; |
||||
|
||||
document.addEventListener('L-stack', this.msgStack.bind(this)); |
||||
|
||||
document.addEventListener('G-robots', this.msgRobots.bind(this)); |
||||
document.addEventListener('G-walls', this.msgWalls.bind(this)); |
||||
|
||||
window.addEventListener('resize', this.debounce(this.onResize.bind(this), 500)); |
||||
|
||||
this.onResize(); |
||||
}; |
||||
|
||||
//===== UI drawing
|
||||
|
||||
Grid.prototype.drawSquares = function() { |
||||
const grid = document.getElementById('content-grid'); |
||||
grid.querySelectorAll('.content-square').forEach(el => el.parentNode.removeChild(el)); |
||||
|
||||
const s = this.squareSideLength; |
||||
|
||||
for (let i = 0; i < this.squaresPerSide; i++) { |
||||
for (let j = 0; j < this.squaresPerSide; j++) { |
||||
|
||||
const square = document.createElement('div'); |
||||
square.className = 'content-square'; |
||||
|
||||
square.style.height = `${s}px`; |
||||
square.style.width = `${s}px`; |
||||
|
||||
square.style.left = `${i * s}px`; |
||||
square.style.top = `${j * s}px`; |
||||
|
||||
grid.appendChild(square); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
Grid.prototype.drawWalls = function() { |
||||
const grid = document.getElementById('content-grid'); |
||||
grid.querySelectorAll('.content-wall-x').forEach(el => el.parentNode.removeChild(el)); |
||||
grid.querySelectorAll('.content-wall-y').forEach(el => el.parentNode.removeChild(el)); |
||||
|
||||
const s = this.squareSideLength; |
||||
|
||||
this.walls.forEach(edge => { |
||||
const [i1, j1, i2, j2] = edge.split('-'); |
||||
|
||||
const wall = document.createElement('div'); |
||||
wall.title = edge; |
||||
|
||||
wall.style.left = `${i1 * s}px`; |
||||
wall.style.top = `${j1 * s}px`; |
||||
|
||||
if (i1 === i2) { |
||||
wall.className = 'content-wall-y'; |
||||
wall.style.height = this.squareSideLength + 'px'; |
||||
} else { |
||||
wall.className = 'content-wall-x'; |
||||
wall.style.width = this.squareSideLength + 'px'; |
||||
} |
||||
|
||||
grid.appendChild(wall) |
||||
}); |
||||
}; |
||||
|
||||
Grid.prototype.drawRobots = function() { |
||||
const grid = document.getElementById('content-grid'); |
||||
grid.querySelectorAll('.content-robot').forEach(el => el.parentNode.removeChild(el)); |
||||
|
||||
const s = this.squareSideLength; |
||||
|
||||
this.robots.forEach(({ id, i, j }) => { |
||||
const color = this.colors[id]; |
||||
|
||||
const robot = document.createElement('div'); |
||||
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 = `${i * s}px`; |
||||
robot.style.top = `${j * s}px`; |
||||
|
||||
grid.appendChild(robot); |
||||
}); |
||||
}; |
||||
|
||||
Grid.prototype.drawArrows = function() { |
||||
const grid = document.getElementById('content-grid'); |
||||
grid.querySelectorAll('.content-arrows').forEach(el => el.parentNode.removeChild(el)); |
||||
|
||||
const s = this.squareSideLength; |
||||
|
||||
this.robots.forEach(({ id, i, j }) => { |
||||
const arrows = document.createElement('div'); |
||||
arrows.id = `arrows-${id}`; |
||||
arrows.className = 'content-arrows'; |
||||
|
||||
arrows.dataset.i = i; |
||||
arrows.dataset.j = j; |
||||
arrows.dataset.id = id; |
||||
|
||||
arrows.style.left = `${i * s - s}px`; |
||||
arrows.style.top = `${j * s - s}px`; |
||||
|
||||
const up = this.drawArrow({ direction: 'up', label: '▲', left: (s * 1.25), top: (s * 0.5) }); |
||||
const down = this.drawArrow({ direction: 'down', label: '▼', left: (s * 1.25), top: (s * 2) }); |
||||
const left = this.drawArrow({ direction: 'left', label: '◀', left: (s * 0.5), top: (s * 1.25) }); |
||||
const right = this.drawArrow({ direction: 'right', label: '▶', left: (s * 2), top: (s * 1.25) }); |
||||
|
||||
arrows.appendChild(up); |
||||
arrows.appendChild(down); |
||||
arrows.appendChild(left); |
||||
arrows.appendChild(right); |
||||
|
||||
grid.appendChild(arrows); |
||||
}); |
||||
}; |
||||
|
||||
Grid.prototype.drawArrow = function({ direction, label, left, top, parentId }) { |
||||
const s = this.squareSideLength / 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; |
||||
}; |
||||
|
||||
//===== Obstacle logic
|
||||
// i and j are notations for the grid of squares.
|
||||
// x and y are notations for absolute pixels.
|
||||
//
|
||||
// "Obstacles" is a lookup table. Values irrelevant. Keys are obstacles.
|
||||
// Edge obstacle key: i1-j1-i2-j2
|
||||
// Robot obstacle key: i-j
|
||||
|
||||
Grid.prototype.updateObstacles = function() { |
||||
this.obstacles = {}; |
||||
|
||||
this.walls.forEach(edge => { this.obstacles[edge] = true; }); |
||||
|
||||
this.robots.forEach(({ i, j }) => { this.obstacles[`${i}-${j}`] = true; }); |
||||
}; |
||||
|
||||
Grid.prototype.updateArrowVisibilities = function() { |
||||
this.robots.forEach(({ id, i, j }) => { |
||||
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}`; |
||||
|
||||
const arrows = document.getElementById(`arrows-${id}`); |
||||
arrows.querySelector("[data-direction='right']").style.display = (this.obstacles[ijR] || this.obstacles[edgeR] || i === (this.squaresPerSide - 1)) ? 'none' : 'block'; |
||||
arrows.querySelector("[data-direction='left']").style.display = (this.obstacles[ijL] || this.obstacles[edgeL] || i === 0) ? 'none' : 'block'; |
||||
arrows.querySelector("[data-direction='up']").style.display = (this.obstacles[ijU] || this.obstacles[edgeU] || j === 0) ? 'none' : 'block'; |
||||
arrows.querySelector("[data-direction='down']").style.display = (this.obstacles[ijD] || this.obstacles[edgeD] || j === (this.squaresPerSide - 1)) ? 'none' : 'block'; |
||||
}); |
||||
}; |
||||
|
||||
Grid.prototype.findNextObstacle = function({ direction, i, j }) { |
||||
const sps = this.squaresPerSide; |
||||
const obstacles = this.obstacles; |
||||
|
||||
switch (direction) { |
||||
case 'right': |
||||
for (let ii = i + 1; ii < sps; ii++) { |
||||
const edge = `${ii + 1}-${j}-${ii + 1}-${j + 1}`; |
||||
const ij = `${ii + 1}-${j}`; |
||||
|
||||
if (obstacles[ij] || obstacles[edge] || ii === (sps - 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 (obstacles[ij] || obstacles[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 (obstacles[ij] || obstacles[edge] || jj === 0) { |
||||
return { i, j: jj }; |
||||
} |
||||
} |
||||
break; |
||||
|
||||
case 'down': |
||||
for (let jj = j + 1; jj < sps; jj++) { |
||||
const edge = `${i}-${jj + 1}-${i + 1}-${jj + 1}`; |
||||
const ij = `${i}-${jj + 1}`; |
||||
|
||||
if (obstacles[ij] || obstacles[edge] || jj === (sps - 1)) { |
||||
return { i, j: jj }; |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
|
||||
throw Error("Could not find next obstacle, no direction found. ", direction, i, j); |
||||
}; |
||||
|
||||
//===== DOM event handlers
|
||||
|
||||
Grid.prototype.onArrowClick = function(evt) { |
||||
const parent = evt.currentTarget.parentNode; |
||||
const direction = evt.currentTarget.dataset.direction; |
||||
|
||||
const id = parent.dataset.id; |
||||
const i1 = parent.dataset.i; |
||||
const j1 = parent.dataset.j; |
||||
|
||||
const { i, j } = this.findNextObstacle({ direction, i: i1 * 1, j: j1 * 1 }); |
||||
|
||||
const evtMove = new CustomEvent('L-arrow', { detail: { id, i, j } }); |
||||
document.dispatchEvent(evtMove); |
||||
}; |
||||
|
||||
Grid.prototype.onResize = function() { |
||||
const controlBounds = document.getElementById('controls-container').getBoundingClientRect(); |
||||
const contentBounds = document.getElementById('content-container').getBoundingClientRect(); |
||||
const grid = document.getElementById('content-grid'); |
||||
|
||||
const h = contentBounds.height - 40; |
||||
const w = contentBounds.width - controlBounds.right - 40; |
||||
const min = Math.min(h, w); |
||||
this.squaresPerSide = 20; |
||||
|
||||
// Centering
|
||||
const offsetX = (min === h) ? ((w - min) / 2) : 0; |
||||
const offsetY = (min === h) ? 0 : ((h - min) / 2); |
||||
|
||||
this.squareSideLength = Math.floor(min / this.squaresPerSide); |
||||
|
||||
// Origin (top left)
|
||||
const x0 = controlBounds.left + controlBounds.width + 40 + offsetX; |
||||
const y0 = contentBounds.top + 40 + offsetY; |
||||
|
||||
grid.style.left = `${x0}px`; |
||||
grid.style.top = `${y0}px`; |
||||
|
||||
this.drawSquares(); |
||||
this.drawWalls(); |
||||
this.drawRobots(); |
||||
this.drawArrows();
|
||||
|
||||
this.updateArrowVisibilities(); |
||||
}; |
||||
|
||||
Grid.prototype.debounce = function(fn, ms) { |
||||
let timer = null; |
||||
|
||||
return () => { |
||||
clearTimeout(timer); |
||||
timer = setTimeout(fn, ms); |
||||
} |
||||
}; |
||||
|
||||
//===== Message handlers
|
||||
|
||||
Grid.prototype.msgStack = function(evt) { |
||||
const latestPositions = evt.detail.reduce((acc, { id, i, j }) => { |
||||
acc[id] = { id, i, j }; |
||||
return acc; |
||||
}, {}); |
||||
|
||||
this.robots = Object.values(latestPositions); |
||||
|
||||
this.drawRobots(); |
||||
this.drawArrows(); |
||||
|
||||
this.updateObstacles(); |
||||
this.updateArrowVisibilities(); |
||||
}; |
||||
|
||||
Grid.prototype.msgWalls = function(evt) { |
||||
this.walls = evt.detail.body; |
||||
|
||||
this.drawWalls(); |
||||
|
||||
this.updateObstacles(); |
||||
this.updateArrowVisibilities(); |
||||
}; |
||||
|
||||
Grid.prototype.msgRobots = function(evt) { |
||||
// Do not assign position or redraw here: movements are fully managed using the stack.
|
||||
this.colors = evt.detail.body.reduce((acc, { color, id }) => { |
||||
acc[id] = color; |
||||
return acc; |
||||
}, {}); |
||||
}; |
||||
|
||||
|
||||
//============ THE TRASH BIN OF HISTORY
|
||||
// Content.prototype.drawRobot = function({ id, i, j }) {
|
||||
// const robot = document.getElementById(`robot-${id}`);
|
||||
// const arrows = document.getElementById(`arrows-${id}`);
|
||||
|
||||
// const { x, y } = this.ijToXy({ i, j });
|
||||
// const s = this.squares.sideLength;
|
||||
|
||||
// robot.style.display = 'block';
|
||||
// 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.display = 'block';
|
||||
// arrows.style.left = (x - s) + 'px';
|
||||
// arrows.style.top = (y - s) + 'px';
|
||||
// };
|
||||
|
||||
// Content.prototype.ijToXy = function({ i, j }) {
|
||||
// if ((i !== 0 && !i) || (j !== 0 && !j)) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// return {
|
||||
// x: this.squares.x0 + i * this.squares.sideLength,
|
||||
// y: this.squares.y0 + j * this.squares.sideLength
|
||||
// }
|
||||
// };
|
@ -1,52 +0,0 @@ |
||||
// This layer acts to convert squares and their edges to pixel coords and back.
|
||||
// i and j are notations for the grid of squares.
|
||||
// x and y are notations for absolute pixels.
|
||||
//
|
||||
// Grid: XY pair. 0-i on x and 0-j on y.
|
||||
// Absolute: XY pair. 0-width and 0-height, relative to viewport
|
||||
// Edge: A string. i1-j1-i1-j2.
|
||||
|
||||
const Squares = function() { |
||||
const controlBounds = document.getElementById('controls-container').getBoundingClientRect(); |
||||
const contentBounds = document.getElementById('content-container').getBoundingClientRect(); |
||||
|
||||
const h = contentBounds.height - 40; |
||||
const w = contentBounds.width - controlBounds.right - 40; |
||||
const min = Math.min(h, w); |
||||
|
||||
// Centering
|
||||
const offsetX = (min === h) ? ((w - min) / 2) : 0; |
||||
const offsetY = (min === h) ? 0 : ((h - min) / 2); |
||||
|
||||
this.perSide = 20; |
||||
this.sideLength = Math.floor(min / this.perSide); |
||||
|
||||
// Origin (top left)
|
||||
this.x0 = controlBounds.left + controlBounds.width + 40 + offsetX; |
||||
this.y0 = contentBounds.top + 40 + offsetY; |
||||
} |
||||
|
||||
Squares.prototype.ijToXy = function({ i, j }) { |
||||
if ((i !== 0 && !i) || (j !== 0 && !j)) { |
||||
return |
||||
} |
||||
|
||||
return { |
||||
x: this.x0 + i * this.sideLength, |
||||
y: this.y0 + j * this.sideLength |
||||
} |
||||
} |
||||
|
||||
Squares.prototype.xyToIj = function({ x, y }) { |
||||
const i = Math.floor((x - this.x0) / this.sideLength); |
||||
const j = Math.floor((y - this.y0) / this.sideLength); |
||||
|
||||
const min = 0; |
||||
const max = this.perSide - 1; |
||||
|
||||
// Coalesce outlying results to grid edges.
|
||||
return { |
||||
i: Math.min(Math.max(min, i), max), |
||||
j: Math.min(Math.max(min, j), max), |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
//===== Constructor
|
||||
const Stack = function() { |
||||
// This is the heart of the robot movement architecture.
|
||||
// Its elements are of the form { robotId, i, j }
|
||||
this.moves = []; |
||||
|
||||
document.addEventListener('L-arrow', this.msgArrow.bind(this)); |
||||
document.addEventListener('G-robots', this.msgRobots.bind(this)); |
||||
}; |
||||
|
||||
Stack.prototype.msgRobots = function(evt) { |
||||
this.moves = evt.detail.body.map(({ id, i, j }) => ({ id, i, j })); |
||||
|
||||
const evtStack = new CustomEvent('L-stack', { detail: this.moves }); |
||||
document.dispatchEvent(evtStack); |
||||
}; |
||||
|
||||
Stack.prototype.msgArrow = function(evt) { |
||||
this.moves.push(evt.detail); |
||||
|
||||
const evtStack = new CustomEvent('L-stack', { detail: this.moves }); |
||||
document.dispatchEvent(evtStack); |
||||
}; |
||||
|
||||
|
||||
// reset destroys stack
|
||||
// undo decrements from stack
|
||||
// store chops stack
|
||||
// moves is stack length
|
||||
// replay shares stack
|
||||
// starting spot is present in stack
|
Loading…
Reference in new issue