round UI refactor and stuff

This commit is contained in:
Thibault Duplessis 2014-10-12 19:34:18 +02:00
parent 4be26b2b4f
commit 6bf71ce0ba
15 changed files with 207 additions and 149 deletions

View file

@ -26,7 +26,6 @@ final class Env(
nowPlaying = Env.round.nowPlaying,
dailyPuzzle = Env.puzzle.daily,
streamsOnAir = () => Env.tv.streamsOnAir,
getPools = () => Env.pool.repo.all(true),
countRounds = () => Env.round.count(true))
lazy val userInfo = mashup.UserInfo(

View file

@ -33,7 +33,7 @@ private[round] final class Drawer(
messenger.system(g, _.drawOfferSent)
Progress(g) map { g => g.updatePlayer(color, _ offerDraw g.turns) }
} inject List(Event.ReloadOwner)
case _ => fufail("[drawer] invalid yes " + pov)
case _ => fuccess(Nil)
}
def no(pov: Pov): Fu[Events] = pov match {
@ -45,7 +45,7 @@ private[round] final class Drawer(
messenger.system(g, _.drawOfferDeclined)
Progress(g) map { g => g.updatePlayer(!color, _.removeDrawOffer) }
} inject List(Event.ReloadOwner)
case _ => fufail("[drawer] invalid no " + pov)
case _ => fuccess(Nil)
}
def claim(pov: Pov): Fu[Events] =

View file

@ -64,17 +64,18 @@ final class JsonView(
"version" -> version,
"spectator" -> false,
"user" -> playerUser.map { userJsonView(_, true) },
"isOfferingRematch" -> player.isOfferingRematch.option(true),
"isOfferingDraw" -> player.isOfferingDraw.option(true),
"isProposingTakeback" -> player.isProposingTakeback.option(true)
"offeringRematch" -> player.isOfferingRematch.option(true),
"offeringDraw" -> player.isOfferingDraw.option(true),
"proposingTakeback" -> player.isProposingTakeback.option(true)
).noNull,
"opponent" -> Json.obj(
"color" -> opponent.color.name,
"ai" -> opponent.aiLevel,
"user" -> opponentUser.map { userJsonView(_, true) },
"isOfferingRematch" -> opponent.isOfferingRematch.option(true),
"isOfferingDraw" -> opponent.isOfferingDraw.option(true),
"isProposingTakeback" -> opponent.isProposingTakeback.option(true)
"offeringRematch" -> opponent.isOfferingRematch.option(true),
"offeringDraw" -> opponent.isOfferingDraw.option(true),
"proposingTakeback" -> opponent.isProposingTakeback.option(true),
"onGame" -> true
).noNull,
"url" -> Json.obj(
"socket" -> s"/$fullId/socket/v$apiVersion",
@ -144,11 +145,13 @@ final class JsonView(
"version" -> version,
"spectator" -> true,
"ai" -> player.aiLevel,
"user" -> playerUser.map { userJsonView(_, true) }),
"user" -> playerUser.map { userJsonView(_, true) },
"onGame" -> true),
"opponent" -> Json.obj(
"color" -> opponent.color.name,
"ai" -> opponent.aiLevel,
"user" -> opponentUser.map { userJsonView(_, true) }),
"user" -> opponentUser.map { userJsonView(_, true) },
"onGame" -> true),
"url" -> Json.obj(
"socket" -> s"/$gameId/${color.name}/socket",
"round" -> s"/$gameId/${color.name}"

View file

@ -34,18 +34,21 @@ private[round] final class Socket(
// when the player has been seen online for the last time
private var time: Double = nowMillis
// wheter the player closed the window (JS unload event)
private var bye: Boolean = false
// wether the player closed the window intentionally
private var bye: Int = 0
def ping {
if (isGone) notifyGone(color, false)
bye = false
if (bye > 0) {
bye = bye - 1
}
time = nowMillis
}
def setBye {
bye = true
bye = 2
}
def isGone = bye && time < (nowMillis - bye.fold(ragequitTimeout, disconnectTimeout).toMillis)
def isBye = bye > 0
def isGone = isBye && time < (nowMillis - isBye.fold(ragequitTimeout, disconnectTimeout).toMillis)
}
private val whitePlayer = new Player(White)
@ -69,7 +72,7 @@ private[round] final class Socket(
case Bye(color) => playerDo(color, _.setBye)
case Ack(uid) => withMember(uid) { _.channel push ackEvent }
case Ack(uid) => withMember(uid) { _.channel push ackEvent }
case Broom =>
broom

View file

@ -14,9 +14,7 @@ function parseFen($elem) {
fen: $this.data('fen'),
lastMove: lm ? [lm[0] + lm[1], lm[2] + lm[3]] : [],
};
console.log(config);
if (color) config.orientation = color;
console.log(config);
if (ground) ground.set(config);
else $this.data('chessground', Chessground($this[0], config));
});

View file

@ -23,7 +23,7 @@ lichess.StrongSocket = function(url, version, settings) {
self.options.baseUrls = ['socket.en.lichess.org:9021'];
}
self.connect();
$(window).on('unload', function() {
window.addEventListener('unload', function() {
self.destroy();
});
};
@ -207,6 +207,7 @@ lichess.StrongSocket.prototype = {
self.ackableMessages = [];
break;
default:
console.log(m.d, m.t);
if (self.settings.receive) self.settings.receive(m.t, m.d);
var h = self.settings.events[m.t];
if ($.isFunction(h)) h(m.d || null);

File diff suppressed because one or more lines are too long

View file

@ -953,12 +953,9 @@ div.table div.username span.status:before {
vertical-align: -4px;
content: '0';
color: #ac524f;
opacity: 0.1;
}
div.table div.username.statused span.status:before {
opacity: 1;
}
div.table div.username.connected span.status:before {
div.table div.username.on-game span.status:before {
content: '3';
color: #759900;
}

View file

@ -37,7 +37,7 @@ module.exports = function(cfg, router, i18n, socketSend) {
}.bind(this);
this.userMove = function(orig, dest, meta) {
hold.register(meta.holdTime);
hold.register(this.socket, meta.holdTime);
if (!promotion.start(this, orig, dest, meta.premove)) this.sendMove(orig, dest);
$.sound.move(this.data.player.color == 'white');
}.bind(this);

View file

@ -1,11 +1,9 @@
var socket = require('./socket');
// Register move hold times and send socket alerts
var holds = [];
var nb = 10;
var register = function(hold) {
var register = function(socket, hold) {
if (!hold) return;
holds.push(hold);
if (holds.length > nb) {

View file

@ -1,13 +1,32 @@
var title = require('./title');
var blur = require('./blur');
var round = require('./round');
var status = require('./status');
function watchPageUnload(ctrl) {
var okToLeave = function() {
return lichess.hasToReload || !status.started(ctrl.data) || status.finished(ctrl.data) || !ctrl.data.clock;
};
window.addEventListener('beforeunload', function(e) {
if (!okToLeave()) {
ctrl.socket.send('bye');
var msg = 'There is a game in progress!';
(e || window.event).returnValue = msg;
return msg;
}
});
}
module.exports = function(ctrl) {
var d = ctrl.data;
title.init(ctrl);
ctrl.setTitle();
blur.init(ctrl);
if (round.playable(ctrl.data) && ctrl.data.game.turns < 2) $.sound.dong();
if (round.playable(d) && round.nbMoves(d, d.player.color) === 0) $.sound.dong();
if (round.isPlayerPlaying(d)) watchPageUnload(ctrl);
};

View file

@ -28,11 +28,11 @@ function abortable(data) {
}
function takebackable(data) {
return playable(data) && data.takebackable && !data.tournamentId && data.game.turns > 1 && !data.player.isProposingTakeback && !data.opponent.isProposingTakeback;
return playable(data) && data.takebackable && !data.tournamentId && data.game.turns > 1 && !data.player.proposingTakeback && !data.opponent.proposingTakeback;
}
function drawable(data) {
return playable(data) && data.game.turns >= 2 && !data.player.isOfferingDraw && !data.opponent.ai;
return playable(data) && data.game.turns >= 2 && !data.player.offeringDraw && !data.opponent.ai;
}
function resignable(data) {
@ -64,5 +64,6 @@ module.exports = {
moretimeable: moretimeable,
mandatory: mandatory,
getPlayer: getPlayer,
parsePossibleMoves: parsePossibleMoves
parsePossibleMoves: parsePossibleMoves,
nbMoves: nbMoves
};

View file

@ -70,9 +70,8 @@ module.exports = function(send, ctrl) {
xhr.reload(ctrl.data).then(ctrl.reload);
},
threefoldRepetition: function() {
m.startComputation();
ctrl.data.game.threefold = true;
m.endComputation();
m.redraw();
},
promotion: function(o) {
ground.promote(ctrl.chessground, o.key, o.pieceClass);
@ -81,19 +80,22 @@ module.exports = function(send, ctrl) {
if (ctrl.clock) ctrl.clock.update(o.white, o.black);
},
crowd: function(o) {
m.startComputation();
['white', 'black'].forEach(function(c) {
round.getPlayer(ctrl.data, c).statused = true;
round.getPlayer(ctrl.data, c).connected = o[c];
round.getPlayer(ctrl.data, c).onGame = o[c];
});
ctrl.data.watchers = o.watchers;
m.endComputation();
m.redraw();
},
end: function() {
ground.end(ctrl.chessground);
xhr.reload(ctrl.data).then(ctrl.reload);
$.sound.dong();
}
},
gone: function(isGone) {
if (!ctrl.data.opponent.ai) {
ctrl.data.opponent.onGame = !isGone;
m.redraw();
}
},
};
this.receive = function(type, data) {

123
ui/round/src/view/button.js Normal file
View file

@ -0,0 +1,123 @@
var chessground = require('chessground');
var round = require('../round');
var partial = chessground.util.partial;
module.exports = {
standard: function(ctrl, condition, icon, hint, socketMsg) {
return condition(ctrl.data) ? m('button', {
class: 'button hint--bottom',
'data-hint': ctrl.trans(hint),
onclick: partial(ctrl.socket.send, socketMsg, null)
}, m('span[data-icon=' + icon + ']')) : null;
},
forceResign: function(ctrl) {
if (!ctrl.data.opponent.ai && !ctrl.data.opponent.onGame && round.resignable(ctrl.data))
return m('div.force_resign_zone', [
ctrl.trans('theOtherPlayerHasLeftTheGameYouCanForceResignationOrWaitForHim'),
m('br'),
m('a.button', {
onclick: partial(ctrl.socket.send, 'resign-force'),
}, ctrl.trans('forceResignation')),
m('a.button', {
onclick: partial(ctrl.socket.send, 'draw-force'),
}, ctrl.trans('forceDraw'))
]);
},
threefoldClaimDraw: function(ctrl) {
if (ctrl.data.game.threefold) return m('div#claim_draw_zone', [
ctrl.trans('threefoldRepetition'),
m.trust('&nbsp;'),
m('a.button', {
onclick: partial(ctrl.socket.send, 'draw-claim', null)
}, ctrl.trans('claimADraw'))
]);
},
cancelDrawOffer: function(ctrl) {
if (ctrl.data.player.offeringDraw) return m('div.negotiation', [
ctrl.trans('drawOfferSent') + ' ',
m('a', {
onclick: partial(ctrl.socket.send, 'draw-no', null)
}, ctrl.trans('cancel'))
]);
},
answerOpponentDrawOffer: function(ctrl) {
if (ctrl.data.opponent.offeringDraw) return m('div.negotiation', [
ctrl.trans('yourOpponentOffersADraw'),
m('br'),
m('a.button[data-icon=E]', {
onclick: partial(ctrl.socket.send, 'draw-yes', null)
}, ctrl.trans('accept')),
m.trust('&nbsp;'),
m('a.button[data-icon=L]', {
onclick: partial(ctrl.socket.send, 'draw-no', null)
}, ctrl.trans('decline')),
]);
},
cancelTakebackProposition: function(ctrl) {
if (ctrl.data.player.proposingTakeback) return m('div.negotiation', [
ctrl.trans('takebackPropositionSent') + ' ',
m('a', {
onclick: partial(ctrl.socket.send, 'takeback-no', null)
}, ctrl.trans('cancel'))
]);
},
answerOpponentTakebackProposition: function(ctrl) {
if (ctrl.data.opponent.proposingTakeback) return m('div.negotiation', [
ctrl.trans('yourOpponentProposesATakeback'),
m('br'),
m('a.button[data-icon=E]', {
onclick: partial(ctrl.socket.send, 'takeback-yes', null)
}, ctrl.trans('accept')),
m.trust('&nbsp;'),
m('a.button[data-icon=L]', {
onclick: partial(ctrl.socket.send, 'takeback-no', null)
}, ctrl.trans('decline')),
]);
},
rematch: function(ctrl) {
return m('a.rematch.offer.button.hint--bottom', {
'data-hint': ctrl.trans('playWithTheSameOpponentAgain'),
onclick: partial(ctrl.socket.send, 'rematch-yes', null)
}, ctrl.trans('rematch'));
},
answerOpponentRematch: function(ctrl) {
if (ctrl.data.opponent.offeringRematch) return m('div.lichess_play_again_join.rematch_alert', [
ctrl.trans('yourOpponentWantsToPlayANewGameWithYou'),
m('a.glowing.button.lichess_play_again.rematch.hint--bottom', {
'data-hint': ctrl.trans('playWithTheSameOpponentAgain'),
onclick: partial(ctrl.socket.send, 'rematch-yes', null),
}, ctrl.trans('joinTheGame')),
m('a', {
onclick: partial(ctrl.socket.send, 'rematch-no', null),
}, ctrl.trans('declineInvitation'))
]);
},
cancelRematch: function(ctrl) {
if (ctrl.data.player.offeringRematch) return m('div.lichess_play_again_join.rematch_wait', [
ctrl.trans('rematchOfferSent'),
m('br'),
ctrl.trans('waitingForOpponent'),
m('br'), m('br'),
m('a.rematch_cancel', {
onclick: partial(ctrl.socket.send, 'rematch-no', null),
}, ctrl.trans('cancelRematchOffer'))
]);
},
newGame: function(ctrl) {
if (!ctrl.data.offeringRematch) return m('a.lichess_new_game.button.hint--bottom', {
'data-hint': ctrl.trans('playWithAnotherOpponent'),
href: ctrl.router.Lobby.home().url
}, ctrl.trans('newOpponent'));
},
backToTournament: function(ctrl) {
if (ctrl.data.tournament) return m('a[data-icon=G].button' + (ctrl.data.tournament.running ? '.strong' : ''), {
href: ctrl.router.Tournament.show(ctrl.data.tournament.id).url
}, ctrl.trans('backToTournament'));
},
moretime: function(ctrl) {
if (round.moretimeable(ctrl.data)) return m('a.moretime.hint--bottom-left', {
'data-hint': ctrl.trans('giveNbSeconds', ctrl.data.clock.moretime),
onclick: partial(ctrl.socket.send, 'moretime', null)
}, m('span[data-icon=O]'));
}
};

View file

@ -3,25 +3,20 @@ var chessground = require('chessground');
var round = require('../round');
var status = require('../status');
var opposite = chessground.util.opposite;
var classSet = chessground.util.classSet;
var partial = chessground.util.partial;
var renderClock = require('../clock/view');
var renderStatus = require('./status');
var button = require('./button');
function renderPlayer(ctrl, player) {
return player.ai ? m('div.username.connected.statused', [
return player.ai ? m('div.username.on-game', [
ctrl.trans('aiNameLevelAiLevel', 'Stockfish', player.ai),
m('span.status')
]) : m('div', {
class: 'username ' + player.color + ' ' + classSet({
'statused': player.statused,
'connected': player.connected,
'offline': !player.connected
})
class: 'username ' + player.color + (player.onGame ? ' on-game' : '')
},
player.user ? [
m('a', {
class: 'user_link ulpt',
class: 'user_link ulpt ' + (player.user.online ? 'online is-green' : 'offline'),
href: ctrl.router.User.show(player.user.username).url,
target: round.playable(ctrl.data) ? '_blank' : null,
'data-icon': 'r',
@ -52,63 +47,21 @@ function renderResult(ctrl) {
m('div.player', m('p', renderStatus(ctrl)));
}
function renderRematchButton(ctrl) {
return m('a.rematch.offer.button.hint--bottom', {
'data-hint': ctrl.trans('playWithTheSameOpponentAgain'),
onclick: partial(ctrl.socket.send, 'rematch-yes', null)
}, ctrl.trans('rematch'));
}
function renderTableEnd(ctrl) {
var d = ctrl.data;
return [
m('div.current_player', renderResult(ctrl)),
m('div.control.buttons', [
d.game.pool ? [
m('a.button[data-icon=,]', {
href: router.Pool.show(d.game.pool.id).url,
}, 'Return to pool'),
m('form[method=post]', {
action: router.Pool.leave(d.game.pool.id).url
}, m('button.button[type=submit]', 'Leave the pool'))
] : (d.tournament ? m('a.button' + (d.tournament.running ? '.strong' : '') + '[data-icon=G]', {
href: ctrl.router.Tournament.show(d.tournament.id).url
}, ctrl.trans('backToTournament')) : (d.opponent.ai ? renderRematchButton(ctrl) : [
m('div.separator'),
d.opponent.isOfferingRematch ? m('div.lichess_play_again_join.rematch_alert', [
ctrl.trans('yourOpponentWantsToPlayANewGameWithYou'),
m('a.glowing.button.lichess_play_again.rematch.hint--bottom', {
'data-hint': ctrl.trans('playWithTheSameOpponentAgain'),
onclick: partial(ctrl.socket.send, 'rematch-yes', null),
}, ctrl.trans('joinTheGame')),
m('a', {
onclick: partial(ctrl.socket.send, 'rematch-no', null),
}, ctrl.trans('declineInvitation'))
]) : (d.player.isOfferingRematch ? m('div.lichess_play_again_join.rematch_wait', [
ctrl.trans('rematchOfferSent'),
m('br'),
ctrl.trans('waitingForOpponent'),
m('br'), m('br'),
m('a.rematch_cancel', {
onclick: partial(ctrl.socket.send, 'rematch-no', null),
}, ctrl.trans('cancelRematchOffer'))
]) : renderRematchButton(ctrl))
])), !d.opponent.isOfferingRematch ? m('a.lichess_new_game.button.hint--bottom', {
'data-hint': ctrl.trans('playWithAnotherOpponent'),
href: ctrl.router.Lobby.home().url
}, ctrl.trans('newOpponent')) : null
button.backToTournament(ctrl) || (
d.opponent.ai ? button.rematch(ctrl) : [
m('div.separator'),
button.answerOpponentRematch(ctrl) || cancelRematch(ctrl) || button.rematch(ctrl)
]),
button.newGame(ctrl)
])
];
}
function renderButton(ctrl, condition, icon, hint, socketMsg) {
return condition(ctrl.data) ? m('button', {
class: 'button hint--bottom',
'data-hint': ctrl.trans(hint),
onclick: partial(ctrl.socket.send, socketMsg, null)
}, m('span[data-icon=' + icon + ']')) : null;
}
function renderTableWatch(ctrl) {
var d = ctrl.data;
return [
@ -140,53 +93,17 @@ function renderTablePlay(ctrl) {
])
),
m('div.control.icons', [
renderButton(ctrl, round.abortable, 'L', 'abortGame', 'abort'),
renderButton(ctrl, round.takebackable, 'i', 'proposeATakeback', 'takeback-yes'),
renderButton(ctrl, round.drawable, '2', 'offerDraw', 'draw-yes'),
renderButton(ctrl, round.resignable, 'b', 'resign', 'resign')
button.standard(ctrl, round.abortable, 'L', 'abortGame', 'abort'),
button.standard(ctrl, round.takebackable, 'i', 'proposeATakeback', 'takeback-yes'),
button.standard(ctrl, round.drawable, '2', 'offerDraw', 'draw-yes'),
button.standard(ctrl, round.resignable, 'b', 'resign', 'resign')
]),
d.game.threefold ? m('div#claim_draw_zone', [
ctrl.trans('threefoldRepetition'),
m.trust('&nbsp;'),
m('a.button', {
onclick: partial(ctrl.socket.send, 'draw-claim', null)
}, ctrl.trans('claimADraw'))
]) : (
d.player.isOfferingDraw ? m('div.negotiation', [
ctrl.trans('drawOfferSent') + ' ',
m('a', {
onclick: partial(ctrl.socket.send, 'draw-no', null)
}, ctrl.trans('cancel'))
]) : null,
d.opponent.isOfferingDraw ? m('div.negotiation', [
ctrl.trans('yourOpponentOffersADraw'),
m('br'),
m('a.button[data-icon=E]', {
onclick: partial(ctrl.socket.send, 'draw-yes', null)
}, ctrl.trans('accept')),
m.trust('&nbsp;'),
m('a.button[data-icon=L]', {
onclick: partial(ctrl.socket.send, 'draw-no', null)
}, ctrl.trans('decline')),
]) : null
),
d.player.isProposingTakeback ? m('div.negotiation', [
ctrl.trans('takebackPropositionSent') + ' ',
m('a', {
onclick: partial(ctrl.socket.send, 'takeback-no', null)
}, ctrl.trans('cancel'))
]) : null,
d.opponent.isProposingTakeback ? m('div.negotiation', [
ctrl.trans('yourOpponentProposesATakeback'),
m('br'),
m('a.button[data-icon=E]', {
onclick: partial(ctrl.socket.send, 'takeback-yes', null)
}, ctrl.trans('accept')),
m.trust('&nbsp;'),
m('a.button[data-icon=L]', {
onclick: partial(ctrl.socket.send, 'takeback-no', null)
}, ctrl.trans('decline')),
]) : null, (round.mandatory(d) && round.nbMoves(d, d.player.color) === 0) ? m('div[data-icon=j]',
button.forceResign(ctrl),
button.threefoldClaimDraw(ctrl),
button.cancelDrawOffer(ctrl),
button.answerOpponentDrawOffer(ctrl),
button.cancelTakebackProposition(ctrl),
button.answerOpponentTakebackProposition(ctrl), (round.mandatory(d) && round.nbMoves(d, d.player.color) === 0) ? m('div[data-icon=j]',
ctrl.trans('youHaveNbSecondsToMakeYourFirstMove')
) : null
];
@ -208,10 +125,7 @@ module.exports = function(ctrl) {
)
]), (ctrl.clock && !ctrl.data.blindMode) ? [
renderClock(ctrl.clock, ctrl.data.player.color, "bottom", clockRunningColor),
round.moretimeable(ctrl.data) ? m('a.moretime.hint--bottom-left', {
'data-hint': ctrl.trans('giveNbSeconds', ctrl.data.clock.moretime),
onclick: partial(ctrl.socket.send, 'moretime', null)
}, m('span[data-icon=O]')) : null
button.moretime(ctrl)
] : null
])
}