server-side analysis: almost there

This commit is contained in:
Thibault Duplessis 2015-05-06 19:40:49 +02:00
parent 68e2c2593d
commit c1cabd2c90
12 changed files with 134 additions and 110 deletions

View file

@ -68,18 +68,16 @@ private[api] final class RoundApi(
private def withSteps(game: Game, a: Option[(Pgn, Analysis)], initialFen: Option[String], possibleMoves: Boolean)(json: JsObject) =
json ++ Json.obj("steps" -> {
val steps = chess.Replay.boards(game.pgnMoves, initialFen, game.variant).err.zipWithIndex.map {
case (board, ply) =>
var color = chess.Color(ply % 2 == 0)
Step(
ply = ply,
move = for {
pos <- board.history.lastMove
san <- game.pgnMoves.lift(ply - 1)
} yield Step.Move(pos, san),
fen = Forsyth exportBoard board,
check = board check color,
dests = possibleMoves ?? chess.Situation(board, color).destinations)
val steps = chess.Replay.games(game.pgnMoves, initialFen, game.variant).err.map { g =>
Step(
ply = g.turns,
move = for {
pos <- g.board.history.lastMove
san <- g.pgnMoves.lastOption
} yield Step.Move(pos._1, pos._2, san),
fen = Forsyth >> g,
check = g.situation.check,
dests = possibleMoves ?? g.situation.destinations)
}
a.fold(steps) {
case (pgn, analysis) => applyAnalysis(steps, pgn, analysis, game.variant, possibleMoves)
@ -105,24 +103,21 @@ private[api] final class RoundApi(
}
private def makeVariation(fromStep: Step, info: Info, variant: Variant, possibleMoves: Boolean): List[Step] =
chess.Replay.boards(
chess.Replay.games(
info.variation take 20,
fromStep.fen.some,
variant,
info.color
).err.drop(1).zipWithIndex.map {
case (board, i) =>
val ply = info.ply + i
var color = chess.Color(ply % 2 == 0)
Step(
ply = ply,
move = for {
pos <- board.history.lastMove
san <- info.variation lift i
} yield Step.Move(pos, san),
fen = Forsyth exportBoard board,
check = board check color,
dests = possibleMoves ?? chess.Situation(board, color).destinations)
variant
).err.drop(1).map { g =>
Step(
ply = g.turns,
move = for {
pos <- g.board.history.lastMove
(orig, dest) = pos
san <- g.pgnMoves.lastOption
} yield Step.Move(orig, dest, san),
fen = Forsyth >> g,
check = g.situation.check,
dests = possibleMoves ?? g.situation.destinations)
}
private def withNote(note: String)(json: JsObject) =

@ -1 +1 @@
Subproject commit c7fd4aa7ce716c302ecc5d64f9e937653109da79
Subproject commit 46251c175f1331068b79aebee4da62abf02fde99

View file

@ -18,12 +18,14 @@ case class Step(
// who's color plays next
def color = chess.Color(ply % 2 == 0)
def json = Step.stepJsonWriter writes this
}
object Step {
case class Move(pos: (Pos, Pos), san: String) {
def uci = s"${pos._1.key}${pos._2.key}"
case class Move(orig: Pos, dest: Pos, san: String) {
def uci = s"$orig$dest"
}
implicit val stepJsonWriter: Writes[Step] = Writes { step =>

View file

@ -0,0 +1,24 @@
package lila.round
import lila.game.Step
case class AnaMove(
orig: chess.Pos,
dest: chess.Pos,
variant: chess.variant.Variant,
fen: String,
path: String,
promotion: Option[chess.PromotableRole]) {
def step: Valid[Step] =
chess.Game(variant.some, fen.some)(orig, dest, promotion) map {
case (game, move) => Step(
ply = game.turns,
move = game.pgnMoves.lastOption.map { san =>
Step.Move(move.orig, move.dest, san)
},
fen = chess.format.Forsyth >> game,
check = game.situation.check,
dests = game.situation.destinations)
}
}

View file

@ -109,8 +109,6 @@ private[round] final class Socket(
case Bye(color) => playerDo(color, _.setBye)
case Ack(uid) => withMember(uid) { _ push ackEvent }
case Broom =>
broom
if (timeBomb.boom) self ! PoisonPill

View file

@ -41,12 +41,18 @@ private[round] final class SocketHandler(
messenger.watcher(gameId, member, text, socket)
}
case ("outoftime", _) => round(Outoftime)
case ("anaMove", o) => parseAnaMove(o) foreach {
case (orig, dest, prom, moves) => {
socket ! Ack(uid)
println(s"$orig $dest $moves")
case ("anaMove", o) =>
parseAnaMove(o) foreach { anaMove =>
anaMove.step match {
case scalaz.Success(step) =>
member push lila.socket.Socket.makeMessage("step", Json.obj(
"step" -> step.json,
"path" -> anaMove.path
))
case scalaz.Failure(err) =>
member push lila.socket.Socket.makeMessage("stepFailure", err.toString)
}
}
}
}) { playerId =>
{
case ("p", o) => o int "v" foreach { v => socket ! PingVersion(uid, v) }
@ -62,12 +68,11 @@ private[round] final class SocketHandler(
case ("draw-force", _) => round(DrawForce(playerId))
case ("abort", _) => round(Abort(playerId))
case ("move", o) => parseMove(o) foreach {
case (orig, dest, prom, blur, lag) => {
socket ! Ack(uid)
case (orig, dest, prom, blur, lag) =>
member push ackEvent
round(HumanPlay(
playerId, member.ip, orig, dest, prom, blur, lag.millis, _ => socket ! Resync(uid)
))
}
}
case ("moretime", _) => round(Moretime(playerId))
case ("outoftime", _) => round(Outoftime)
@ -147,9 +152,19 @@ private[round] final class SocketHandler(
private def parseAnaMove(o: JsObject) = for {
d o obj "d"
orig d str "orig"
dest d str "dest"
moves d str "moves"
prom = d str "promotion"
} yield (orig, dest, prom, moves)
orig d str "orig" flatMap chess.Pos.posAt
dest d str "dest" flatMap chess.Pos.posAt
variant d str "variant" map chess.variant.Variant.orDefault
fen d str "fen"
path d str "path"
prom = d str "promotion" flatMap (_.headOption) flatMap chess.Role.promotable
} yield AnaMove(
orig = orig,
dest = dest,
variant = variant,
fen = fen,
path = path,
promotion = prom)
private val ackEvent = Json.obj("t" -> "ack")
}

View file

@ -76,7 +76,6 @@ case class Connected(enumerator: JsEnumerator, member: Member)
case class Bye(color: Color)
case class IsGone(color: Color)
case object AnalysisAvailable
case class Ack(uid: String)
case object GetSocketStatus
case class SocketStatus(
version: Int,

View file

@ -1999,6 +1999,9 @@ lichess.storage = {
ran: "--ranph--",
userTv: $('.user_tv').data('user-tv')
},
receive: function(t, d) {
analyse.socketReceive(t, d);
},
events: {
analysisAvailable: function() {
$.sound.dong();
@ -2052,6 +2055,7 @@ lichess.storage = {
};
cfg.path = location.hash ? location.hash.replace(/#/, '') : '';
cfg.element = element.querySelector('.analyse');
cfg.socketSend = lichess.socket.send.bind(lichess.socket);
analyse = LichessAnalyse(cfg);
cfg.jump = analyse.jump;

View file

@ -21,60 +21,33 @@ module.exports = function(steps, analysis) {
}
}
this.moveList = function(path) {
var tree = this.tree;
var moves = [];
for (var j in path) {
var p = path[j];
for (var i = 0, nb = tree.length; i < nb; i++) {
var move = tree[i];
if (p.ply == move.ply && p.variation) {
tree = move.variations[p.variation - 1];
break;
} else if (p.ply >= move.ply) moves.push(move.san);
else break;
}
}
return moves;
}.bind(this);
this.explore = function(path, san) {
this.addStep = function(step, path) {
var nextPath = treePath.withPly(path, treePath.currentPly(path) + 1);
var tree = this.tree;
var curMove = null;
nextPath.forEach(function(step) {
var curStep = null;
nextPath.forEach(function(p) {
for (i = 0, nb = tree.length; i < nb; i++) {
var move = tree[i];
if (step.ply == move.ply) {
if (step.variation) {
tree = move.variations[step.variation - 1];
var step = tree[i];
if (p.ply == step.ply) {
if (p.variation) {
tree = step.variations[p.variation - 1];
break;
} else curMove = move;
} else if (step.ply < move.ply) break;
} else curStep = step;
} else if (p.ply < step.ply) break;
}
});
if (curMove) {
curMove.variations = curMove.variations || [];
if (curMove.san == san) return nextPath;
for (var i = 0; i < curMove.variations.length; i++) {
if (curMove.variations[i][0].san == san) {
if (curStep) {
curStep.variations = curStep.variations || [];
if (curStep.san === step.san) return nextPath;
for (var i = 0; i < curStep.variations.length; i++) {
if (curStep.variations[i][0].san === step.san) {
return treePath.withVariation(nextPath, i + 1);
}
}
curMove.variations.push([{
ply: curMove.ply,
san: san,
comments: [],
variations: []
}]);
return treePath.withVariation(nextPath, curMove.variations.length);
curStep.variations.push([step]);
return treePath.withVariation(nextPath, curStep.variations.length);
}
tree.push({
ply: treePath.currentPly(nextPath),
san: san,
comments: [],
variations: []
});
tree.push(step);
return nextPath;
}.bind(this);
}

View file

@ -9,6 +9,7 @@ var autoplay = require('./autoplay');
var control = require('./control');
var promotion = require('./promotion');
var readDests = require('./util').readDests;
var socket = require('./socket');
var m = require('mithril');
module.exports = function(opts) {
@ -96,22 +97,8 @@ module.exports = function(opts) {
return role === 'knight' ? 'n' : role[0];
};
var addMove = function(orig, dest, promotionRole) {
$.sound.move();
var chess = new Chess(
this.vm.situation.fen, gameVariantChessId());
var promotionLetter = (dest[1] == 1 || dest[1] == 8) ? (promotionRole ? forsyth(promotionRole) : 'q') : null;
var move = chess.move({
from: orig,
to: dest,
promotion: promotionLetter
});
if (move) this.userJump(this.analyse.explore(this.vm.path, move.san));
else this.chessground.set(this.vm.situation);
m.redraw();
}.bind(this);
var userMove = function(orig, dest) {
$.sound.move();
if (!promotion.start(this, orig, dest, sendMove)) sendMove(orig, dest);
}.bind(this);
@ -119,12 +106,22 @@ module.exports = function(opts) {
var move = {
orig: orig,
dest: dest,
pgn: this.analyse.moveList(this.vm.path).join(' ')
variant: this.data.game.variant.key,
fen: this.vm.step.fen,
path: this.vm.pathStr
};
if (prom) move.promotion = prom;
this.socket.send('anaMove', move, {
ackable: true
});
this.socket.sendAnaMove(move);
}.bind(this);
this.addStep = function(step, path) {
this.userJump(this.analyse.addStep(step, treePath.read(path)));
m.redraw();
}.bind(this);
this.reset = function() {
this.chessground.set(this.vm.situation);
m.redraw();
}.bind(this);
this.socket = new socket(opts.socketSend, this);

View file

@ -12,6 +12,7 @@ module.exports = function(opts) {
});
return {
socketReceive: controller.socket.receive,
jump: function(ply) {
controller.jumpToMain(ply);
m.redraw();

View file

@ -2,7 +2,18 @@ module.exports = function(send, ctrl) {
this.send = send;
var anaMoveTimeout;
var handlers = {
step: function(data) {
ctrl.addStep(data.step, data.path);
clearTimeout(anaMoveTimeout);
},
stepFailure: function(data) {
console.log(data);
clearTimeout(anaMoveTimeout);
ctrl.reset();
}
};
this.receive = function(type, data) {
@ -12,4 +23,9 @@ module.exports = function(send, ctrl) {
}
return false;
}.bind(this);
this.sendAnaMove = function(move) {
this.send('anaMove', move);
anaMoveTimeout = setTimeout(this.sendAnaMove.bind(this, move), 3000);
}.bind(this);
}