puzzle solution
parent
06faa94a33
commit
775cf9761e
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
};
|
Loading…
Reference in New Issue