puzzle solution

puzzle-ui
Thibault Duplessis 2016-11-29 14:15:46 +01:00
parent 06faa94a33
commit 775cf9761e
12 changed files with 104 additions and 37 deletions

View File

@ -89,7 +89,7 @@ object Export extends LilaController {
OptionFuResult(Env.puzzle.api.puzzle find id) { puzzle =>
env.pngExport(
fen = chess.format.FEN(puzzle.fenAfterInitialMove | puzzle.fen),
lastMove = puzzle.initialMove.some,
lastMove = puzzle.initialMove.uci.some,
check = none,
orientation = puzzle.color.some,
logHint = s"puzzle $id"

View File

@ -35,7 +35,7 @@ private[puzzle] final class Daily(
private def makeDaily(puzzle: Puzzle): Fu[Option[DailyPuzzle]] = {
import makeTimeout.short
~puzzle.fenAfterInitialMove.map { fen =>
renderer ? RenderDaily(puzzle, fen, puzzle.initialMove) map {
renderer ? RenderDaily(puzzle, fen, puzzle.initialMove.uci) map {
case html: play.twirl.api.Html => DailyPuzzle(html, puzzle.color, puzzle.id).some
}
}

View File

@ -16,7 +16,7 @@ object Export {
"id" -> puzzle.id,
"fen" -> puzzle.fen,
"color" -> puzzle.color.name,
"move" -> puzzle.initialMove,
"move" -> puzzle.initialMove.uci,
"lines" -> lila.puzzle.Line.toJson(puzzle.lines))
})
s""""$encoded"""" -> puzzle.vote.sum

View File

@ -5,6 +5,7 @@ import play.api.libs.json._
import lila.common.PimpedJson._
import lila.game.Game
import lila.puzzle._
import lila.tree
object JsonView {
@ -27,10 +28,11 @@ object JsonView {
"attempts" -> puzzle.attempts,
"fen" -> puzzle.fen,
"color" -> puzzle.color.name,
"initialMove" -> puzzle.initialMove,
"initialMove" -> puzzle.initialMove.uci,
"initialPly" -> puzzle.initialPly,
"gameId" -> puzzle.gameId,
"lines" -> lila.puzzle.Line.toJson(puzzle.lines),
"branch" -> makeBranch(puzzle),
"enabled" -> puzzle.enabled,
"vote" -> puzzle.vote.sum
),
@ -83,4 +85,28 @@ object JsonView {
)
}).noNull
}
private def makeBranch(puzzle: Puzzle): Option[tree.Branch] = {
import chess.format._
val solution: List[Uci.Move] = Line solution puzzle.lines map { uci =>
Uci.Move(uci) err s"Invalid puzzle solution UCI $uci"
}
val init = chess.Game(none, puzzle.fenAfterInitialMove).withTurns(puzzle.initialPly)
val (_, branchList) = solution.foldLeft[(chess.Game, List[tree.Branch])]((init, Nil)) {
case ((prev, branches), uci) =>
val (game, _) = prev(uci.orig, uci.dest, uci.promotion).prefixFailuresWith(s"puzzle ${puzzle.id}").err
val branch = tree.Branch(
id = UciCharPair(uci),
ply = game.turns,
move = Uci.WithSan(uci, game.pgnMoves.last),
fen = chess.format.Forsyth >> game,
check = game.situation.check,
crazyData = none)
(game, branch :: branches)
}
branchList.foldLeft[Option[tree.Branch]](None) {
case (None, branch) => branch.some
case (Some(child), branch) => Some(branch addChild child)
}
}
}

View File

@ -46,9 +46,12 @@ object Line {
}
}
loop(lines collect {
lines.collectFirst {
case Win(move) => List(move)
} | loop(lines collect {
case Node(move, _) => List(move)
})
}
def toString(lines: Lines, level: Int = 0): String = {

View File

@ -1,18 +0,0 @@
package lila.puzzle
import chess.format.Forsyth
import java.io.{ File, OutputStream }
import scala.sys.process._
object PngExport {
private val logger = ProcessLogger(_ => (), _ => ())
def apply(execPath: String)(puzzle: Puzzle)(out: OutputStream) {
val color = puzzle.color.letter.toString
val lastMove = puzzle.initialMove
val fen = (puzzle.fenAfterInitialMove | puzzle.fen).takeWhile(' ' !=)
val exec = Process(Seq("php", "board-creator.php", fen, color, lastMove), new File(execPath))
exec #> out ! logger
}
}

View File

@ -1,6 +1,7 @@
package lila.puzzle
import chess.Color
import chess.format.Uci
import org.joda.time.DateTime
import lila.rating.Perf
@ -23,7 +24,7 @@ case class Puzzle(
def withVote(f: AggregateVote => AggregateVote) = copy(vote = f(vote))
def initialMove = history.last
def initialMove: Uci.Move = history.lastOption flatMap Uci.Move.apply err s"Bad initial move $this"
def enabled = vote.sum > -9000
@ -31,8 +32,7 @@ case class Puzzle(
import chess.format.{ Uci, Forsyth }
for {
sit1 <- Forsyth << fen
uci <- Uci.Move(initialMove)
sit2 <- sit1.move(uci.orig, uci.dest, uci.promotion).toOption map (_.situationAfter)
sit2 <- sit1.move(initialMove).toOption.map(_.situationAfter)
} yield Forsyth >> sit2
}
}

View File

@ -8,7 +8,8 @@
background: #fff;
height: 38%;
display: flex;
align-items: center;
flex-flow: column;
justify-content: center;
}
#puzzle .feedback .player {
display: flex;
@ -34,6 +35,20 @@
#puzzle .feedback .instruction strong {
font-size: 1.5em;
}
@keyframes reveal {
0% { opacity: 0; }
100% { opacity: 0.8; }
}
#puzzle .feedback .view_solution {
margin-top: 20px;
text-align: center;
opacity: 0.8;
animation: reveal 1s;
transition: opacity 0.13s;
}
#puzzle .feedback .view_solution:hover {
opacity: 1;
}
#puzzle .timeline {
display: flex;

View File

@ -11,6 +11,7 @@ var opposite = chessground.util.opposite;
var groundBuild = require('./ground');
var socketBuild = require('./socket');
var moveTestBuild = require('./moveTest');
var mergeSolution = require('./solution');
var throttle = require('common').throttle;
var xhr = require('./xhr');
var sound = require('./sound');
@ -18,9 +19,11 @@ var sound = require('./sound');
module.exports = function(opts, i18n) {
var vm = {
mode: 'play', // play | try | view
loading: false,
justPlayed: null,
initialPath: null
initialPath: null,
canViewSolution: false
};
var data = opts.data;
@ -42,6 +45,12 @@ module.exports = function(opts, i18n) {
m.redraw();
}, 500);
setTimeout(function() {
vm.canViewSolution = true;
m.redraw();
}, 500);
// }, 5000);
var showGround = function() {
var node = vm.node;
var color = node.ply % 2 === 0 ? 'white' : 'black';
@ -188,6 +197,12 @@ module.exports = function(opts, i18n) {
jump(path);
};
var viewSolution = function() {
vm.mode = 'view';
mergeSolution(tree, vm.initialPath, data.puzzle.branch);
m.redraw();
};
var socket = socketBuild({
send: opts.socketSend,
addNode: addNode,
@ -215,6 +230,7 @@ module.exports = function(opts, i18n) {
tree: tree,
ground: ground,
userJump: userJump,
viewSolution: viewSolution,
trans: lichess.trans(opts.i18n),
socketReceive: socket.receive
};

View File

@ -1,18 +1,33 @@
var m = require('mithril');
var bindOnce = require('common').bindOnce;
function yourTurn(ctrl) {
function play(ctrl) {
var turnColor = ctrl.ground.data.turnColor;
var puzzleColor = ctrl.data.puzzle.color;
return m('div.your_turn.player.' + turnColor, [
m('div.no-square', m('piece.king.' + turnColor)),
m('div.instruction', [
m('strong', ctrl.trans.noarg(turnColor == puzzleColor ? 'yourTurn' : 'waiting')),
m('em', ctrl.trans.noarg(puzzleColor == 'white' ? 'findTheBestMoveForWhite' : 'findTheBestMoveForBlack'))
])
return m('div.feedback.play', [
m('div.player.' + turnColor, [
m('div.no-square', m('piece.king.' + turnColor)),
m('div.instruction', [
m('strong', ctrl.trans.noarg(turnColor == puzzleColor ? 'yourTurn' : 'waiting')),
m('em', ctrl.trans.noarg(puzzleColor == 'white' ? 'findTheBestMoveForWhite' : 'findTheBestMoveForBlack'))
])
]),
ctrl.vm.canViewSolution ? m('div.view_solution',
m('a.button', {
config: bindOnce('click', ctrl.viewSolution)
}, 'View the solution')
) : null
]);
}
function view(ctrl) {
return m('div.feedback.view', [
'view'
]);
}
module.exports = function(ctrl) {
return m('div.feedback', yourTurn(ctrl));
if (ctrl.vm.mode === 'play') return play(ctrl);
if (ctrl.vm.mode === 'view') return view(ctrl);
};

View File

@ -5,7 +5,7 @@ function makeConfig(data, config, onMove) {
fen: config.fen,
check: config.check,
lastMove: config.lastMove,
orientation: data.orientation,
orientation: data.puzzle.color,
coordinates: data.pref.coords !== 0,
movable: {
free: false,

View File

@ -0,0 +1,10 @@
var treeOps = require('tree').ops;
module.exports = function(tree, initialPath, solution) {
var initialNode = tree.nodeAtPath(initialPath);
var solutionNode = treeOps.childById(initialNode, solution.id);
if (solutionNode) treeOps.merge(solutionNode, solution);
else initialNode.children.push(solution);
};