server-side analysis: almost there
This commit is contained in:
parent
68e2c2593d
commit
c1cabd2c90
|
@ -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
|
|
@ -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 =>
|
||||
|
|
24
modules/round/src/main/AnaMove.scala
Normal file
24
modules/round/src/main/AnaMove.scala
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -12,6 +12,7 @@ module.exports = function(opts) {
|
|||
});
|
||||
|
||||
return {
|
||||
socketReceive: controller.socket.receive,
|
||||
jump: function(ply) {
|
||||
controller.jumpToMain(ply);
|
||||
m.redraw();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue