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.
248 lines
8.4 KiB
248 lines
8.4 KiB
//===== Constructor
|
|
const Controls = function() {
|
|
this.names = {};
|
|
this.playerId = null;
|
|
this.countdownTimer = null;
|
|
|
|
this.currentMoveCount = 0;
|
|
this.winningMoveCount = Infinity;
|
|
|
|
// "Local" and "Global" messages
|
|
document.addEventListener('L-conn-error', this.msgConnectionError.bind(this));
|
|
document.addEventListener('L-complete', this.msgComplete.bind(this));
|
|
document.addEventListener('L-join', this.msgJoin.bind(this));
|
|
document.addEventListener('L-newround', this.msgNewRound.bind(this));
|
|
document.addEventListener('L-replay-complete', this.msgReplayComplete.bind(this));
|
|
document.addEventListener('L-skip', this.msgSkip.bind(this));
|
|
document.addEventListener('L-stack', this.msgStack.bind(this));
|
|
|
|
document.addEventListener('G-connected', this.msgConnected.bind(this));
|
|
document.addEventListener('G-countdown', this.msgCountdown.bind(this));
|
|
document.addEventListener('G-full', this.msgFull.bind(this));
|
|
document.addEventListener('G-players', this.msgPlayers.bind(this));
|
|
document.addEventListener('G-state', this.msgState.bind(this));
|
|
document.addEventListener('G-win', this.msgWin.bind(this));
|
|
|
|
// Click handlers
|
|
document.getElementById('controls-moves-reset').addEventListener('click', this.onClickMovesReset.bind(this));
|
|
document.getElementById('controls-moves-undo').addEventListener('click', this.onClickMovesUndo.bind(this));
|
|
document.getElementById('controls-moves-solve').addEventListener('click', this.onClickMovesSolve.bind(this));
|
|
|
|
document.getElementById('controls-options-objective').addEventListener('click', this.onClickOptionsObjective.bind(this));
|
|
document.getElementById('controls-options-robots').addEventListener('click', this.onClickOptionsRobots.bind(this));
|
|
document.getElementById('controls-options-walls').addEventListener('click', this.onClickOptionsWalls.bind(this));
|
|
|
|
document.getElementById('controls-countdown-skip').addEventListener('click', this.onClickCountdownSkip.bind(this));
|
|
document.getElementById('controls-win-next').addEventListener('click', this.onClickWinNext.bind(this));
|
|
|
|
this.setState('CONNECTING');
|
|
}
|
|
|
|
//===== Countdown
|
|
|
|
Controls.prototype.countdownStart = function(seconds) {
|
|
clearTimeout(this.countdownTimer);
|
|
this.countdownTimer = this.countdownTick.bind(this);
|
|
|
|
const countdown = document.getElementById('controls-countdown-value');
|
|
countdown.dataset.tick = seconds * 1;
|
|
|
|
this.countdownTick();
|
|
};
|
|
|
|
Controls.prototype.countdownTick = function() {
|
|
const countdown = document.getElementById('controls-countdown-value');
|
|
const tick = countdown.dataset.tick * 1;
|
|
countdown.dataset.tick = tick - 1;
|
|
|
|
countdown.innerHTML = tick;
|
|
|
|
if (tick === 0) {
|
|
this.countdownComplete();
|
|
return;
|
|
}
|
|
|
|
this.countdownTimer = setTimeout(this.countdownTick.bind(this), 1000);
|
|
};
|
|
|
|
Controls.prototype.countdownComplete = function() {
|
|
clearTimeout(this.countdownTimer);
|
|
document.getElementById('controls-countdown-value').innerHTML = 0;
|
|
};
|
|
|
|
//===== UI
|
|
Controls.prototype.setState = function(state) {
|
|
const blocks = [
|
|
'controls-connection',
|
|
'controls-countdown',
|
|
'controls-moves',
|
|
'controls-options',
|
|
'controls-players',
|
|
'controls-win',
|
|
];
|
|
|
|
blocks.forEach(id => document.getElementById(id).style.display = 'none');
|
|
|
|
// IDs to show for each state.
|
|
const STATE = {
|
|
CONNECTING: ['controls-connection'],
|
|
PLAY: ['controls-players', 'controls-moves', 'controls-options'],
|
|
COUNTDOWN: ['controls-players', 'controls-moves', 'controls-countdown'],
|
|
WIN: ['controls-players', 'controls-win']
|
|
};
|
|
|
|
(STATE[state] || []).forEach(id => document.getElementById(id).style.display = 'block');
|
|
};
|
|
|
|
Controls.prototype.drawPlayers = function() {
|
|
const container = document.getElementById('controls-players');
|
|
container.querySelectorAll('.controls-player').forEach(el => {
|
|
container.removeChild(el);
|
|
});
|
|
|
|
const keys = Object.keys(this.names);
|
|
|
|
keys.forEach(connectionId => {
|
|
const n = document.createElement('div');
|
|
n.innerHTML = this.playerId === connectionId ? `${this.names[connectionId]} (you)` : this.names[connectionId];
|
|
n.className = 'controls-player';
|
|
container.appendChild(n)
|
|
});
|
|
|
|
const msg = container.querySelector('.controls-message');
|
|
msg.innerHTML = (keys.length > 0 ? '' : 'Nobody is in the game yet.');
|
|
msg.style.display = (keys.length > 0 ? 'none' : 'block');
|
|
};
|
|
|
|
//===== Message handlers
|
|
|
|
Controls.prototype.msgJoin = function() {
|
|
this.setState('CONNECTING');
|
|
document.querySelector('#controls-connection .controls-message').innerHTML = 'Connecting...';
|
|
};
|
|
|
|
Controls.prototype.msgComplete = function(evt) {
|
|
if (this.currentMoveCount < this.winningMoveCount) {
|
|
document.getElementById('controls-moves-solve').style.display = (evt.detail.complete ? 'block' : 'none');
|
|
}
|
|
};
|
|
|
|
Controls.prototype.msgConnected = function(evt) {
|
|
this.playerId = evt.detail.body;
|
|
this.drawPlayers();
|
|
};
|
|
|
|
Controls.prototype.msgConnectionError = function(evt) {
|
|
this.setState('CONNECTING');
|
|
|
|
const msg = (evt.detail === 'noname'
|
|
? "Didn't get your name - refresh to try again."
|
|
: "Can't reach the server.");
|
|
|
|
document.querySelector('#controls-connection .controls-message').innerHTML = msg;
|
|
};
|
|
|
|
Controls.prototype.msgFull = function() {
|
|
this.setState('CONNECTING');
|
|
document.querySelector('#controls-connection .controls-message').innerHTML = "Sorry, the game is full right now. Please try again later.";
|
|
};
|
|
|
|
Controls.prototype.msgCountdown = function(evt) {
|
|
this.winningMoveCount = evt.detail.body.moveCount;
|
|
|
|
document.getElementById('controls-countdown-moves').innerHTML = evt.detail.body.moveCount;
|
|
document.getElementById('controls-countdown-player').innerHTML = `${this.names[evt.detail.body.id]} has a solution!`;
|
|
|
|
const { duration, timestamp } = evt.detail.body;
|
|
|
|
const now = new Date().getTime();
|
|
const diff = Math.ceil((now - timestamp) / 1000);
|
|
const remaining = duration - diff;
|
|
|
|
this.countdownStart(remaining);
|
|
};
|
|
|
|
Controls.prototype.msgSkip = function() {
|
|
this.countdownComplete();
|
|
};
|
|
|
|
Controls.prototype.msgStack = function(evt) {
|
|
const robots = evt.detail.reduce((acc, { id }) => acc.has(id) ? acc : acc.add(id), new Set());
|
|
this.currentMoveCount = evt.detail.length - robots.size;
|
|
|
|
document.getElementById('controls-moves-solve').style.display = 'none';
|
|
|
|
document.getElementById('controls-moves-count').innerHTML = this.currentMoveCount;
|
|
|
|
document.getElementById('controls-moves-reset').style.display = this.currentMoveCount > 0 ? 'block' : 'none';
|
|
document.getElementById('controls-moves-undo').style.display = this.currentMoveCount > 0 ? 'block' : 'none';
|
|
};
|
|
|
|
Controls.prototype.msgPlayers = function(evt) {
|
|
this.names = evt.detail.body;
|
|
this.drawPlayers();
|
|
};
|
|
|
|
Controls.prototype.msgState = function(evt) {
|
|
this.setState(evt.detail.body);
|
|
};
|
|
|
|
Controls.prototype.msgWin = function(evt) {
|
|
document.getElementById('controls-win-message').innerHTML = `Congratulations ${this.names[evt.detail.player_id]} !`;
|
|
document.getElementById('controls-win-count').innerHTML = evt.detail.body.moveCount;
|
|
};
|
|
|
|
Controls.prototype.msgNewRound = function() {
|
|
this.winningMoveCount = Infinity;
|
|
document.getElementById('controls-win-next').style.display = 'none';
|
|
// SHOW ARROWS IN GRID
|
|
};
|
|
|
|
Controls.prototype.msgReplayComplete = function() {
|
|
document.getElementById('controls-win-next').style.display = 'block';
|
|
};
|
|
|
|
//===== Click handlers
|
|
|
|
Controls.prototype.dispatch = function(evt, data) {
|
|
const e = (data ? new CustomEvent(evt, { detail: data }) : new Event(evt));
|
|
document.dispatchEvent(e);
|
|
};
|
|
|
|
// Options block
|
|
|
|
Controls.prototype.onClickOptionsObjective = function() {
|
|
this.dispatch('L-objective');
|
|
};
|
|
|
|
Controls.prototype.onClickOptionsRobots = function() {
|
|
this.dispatch('L-robots');
|
|
};
|
|
|
|
Controls.prototype.onClickOptionsWalls = function() {
|
|
this.dispatch('L-walls');
|
|
};
|
|
|
|
// Moves block
|
|
|
|
Controls.prototype.onClickMovesReset = function() {
|
|
this.dispatch('L-reset');
|
|
};
|
|
|
|
Controls.prototype.onClickMovesSolve = function() {
|
|
this.dispatch('L-submit');
|
|
};
|
|
|
|
Controls.prototype.onClickMovesUndo = function() {
|
|
this.dispatch('L-undo');
|
|
};
|
|
|
|
// Countdown/win block
|
|
|
|
Controls.prototype.onClickCountdownSkip = function() {
|
|
this.dispatch('L-skip');
|
|
};
|
|
|
|
Controls.prototype.onClickWinNext = function() {
|
|
this.dispatch('L-newround');
|
|
}; |