puzzle solution
This commit is contained in:
parent
06faa94a33
commit
775cf9761e
|
@ -89,7 +89,7 @@ object Export extends LilaController {
|
||||||
OptionFuResult(Env.puzzle.api.puzzle find id) { puzzle =>
|
OptionFuResult(Env.puzzle.api.puzzle find id) { puzzle =>
|
||||||
env.pngExport(
|
env.pngExport(
|
||||||
fen = chess.format.FEN(puzzle.fenAfterInitialMove | puzzle.fen),
|
fen = chess.format.FEN(puzzle.fenAfterInitialMove | puzzle.fen),
|
||||||
lastMove = puzzle.initialMove.some,
|
lastMove = puzzle.initialMove.uci.some,
|
||||||
check = none,
|
check = none,
|
||||||
orientation = puzzle.color.some,
|
orientation = puzzle.color.some,
|
||||||
logHint = s"puzzle $id"
|
logHint = s"puzzle $id"
|
||||||
|
|
|
@ -35,7 +35,7 @@ private[puzzle] final class Daily(
|
||||||
private def makeDaily(puzzle: Puzzle): Fu[Option[DailyPuzzle]] = {
|
private def makeDaily(puzzle: Puzzle): Fu[Option[DailyPuzzle]] = {
|
||||||
import makeTimeout.short
|
import makeTimeout.short
|
||||||
~puzzle.fenAfterInitialMove.map { fen =>
|
~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
|
case html: play.twirl.api.Html => DailyPuzzle(html, puzzle.color, puzzle.id).some
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ object Export {
|
||||||
"id" -> puzzle.id,
|
"id" -> puzzle.id,
|
||||||
"fen" -> puzzle.fen,
|
"fen" -> puzzle.fen,
|
||||||
"color" -> puzzle.color.name,
|
"color" -> puzzle.color.name,
|
||||||
"move" -> puzzle.initialMove,
|
"move" -> puzzle.initialMove.uci,
|
||||||
"lines" -> lila.puzzle.Line.toJson(puzzle.lines))
|
"lines" -> lila.puzzle.Line.toJson(puzzle.lines))
|
||||||
})
|
})
|
||||||
s""""$encoded"""" -> puzzle.vote.sum
|
s""""$encoded"""" -> puzzle.vote.sum
|
||||||
|
|
|
@ -5,6 +5,7 @@ import play.api.libs.json._
|
||||||
import lila.common.PimpedJson._
|
import lila.common.PimpedJson._
|
||||||
import lila.game.Game
|
import lila.game.Game
|
||||||
import lila.puzzle._
|
import lila.puzzle._
|
||||||
|
import lila.tree
|
||||||
|
|
||||||
object JsonView {
|
object JsonView {
|
||||||
|
|
||||||
|
@ -27,10 +28,11 @@ object JsonView {
|
||||||
"attempts" -> puzzle.attempts,
|
"attempts" -> puzzle.attempts,
|
||||||
"fen" -> puzzle.fen,
|
"fen" -> puzzle.fen,
|
||||||
"color" -> puzzle.color.name,
|
"color" -> puzzle.color.name,
|
||||||
"initialMove" -> puzzle.initialMove,
|
"initialMove" -> puzzle.initialMove.uci,
|
||||||
"initialPly" -> puzzle.initialPly,
|
"initialPly" -> puzzle.initialPly,
|
||||||
"gameId" -> puzzle.gameId,
|
"gameId" -> puzzle.gameId,
|
||||||
"lines" -> lila.puzzle.Line.toJson(puzzle.lines),
|
"lines" -> lila.puzzle.Line.toJson(puzzle.lines),
|
||||||
|
"branch" -> makeBranch(puzzle),
|
||||||
"enabled" -> puzzle.enabled,
|
"enabled" -> puzzle.enabled,
|
||||||
"vote" -> puzzle.vote.sum
|
"vote" -> puzzle.vote.sum
|
||||||
),
|
),
|
||||||
|
@ -83,4 +85,28 @@ object JsonView {
|
||||||
)
|
)
|
||||||
}).noNull
|
}).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)
|
case Node(move, _) => List(move)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def toString(lines: Lines, level: Int = 0): String = {
|
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
|
package lila.puzzle
|
||||||
|
|
||||||
import chess.Color
|
import chess.Color
|
||||||
|
import chess.format.Uci
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
import lila.rating.Perf
|
import lila.rating.Perf
|
||||||
|
@ -23,7 +24,7 @@ case class Puzzle(
|
||||||
|
|
||||||
def withVote(f: AggregateVote => AggregateVote) = copy(vote = f(vote))
|
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
|
def enabled = vote.sum > -9000
|
||||||
|
|
||||||
|
@ -31,8 +32,7 @@ case class Puzzle(
|
||||||
import chess.format.{ Uci, Forsyth }
|
import chess.format.{ Uci, Forsyth }
|
||||||
for {
|
for {
|
||||||
sit1 <- Forsyth << fen
|
sit1 <- Forsyth << fen
|
||||||
uci <- Uci.Move(initialMove)
|
sit2 <- sit1.move(initialMove).toOption.map(_.situationAfter)
|
||||||
sit2 <- sit1.move(uci.orig, uci.dest, uci.promotion).toOption map (_.situationAfter)
|
|
||||||
} yield Forsyth >> sit2
|
} yield Forsyth >> sit2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
background: #fff;
|
background: #fff;
|
||||||
height: 38%;
|
height: 38%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-flow: column;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
#puzzle .feedback .player {
|
#puzzle .feedback .player {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -34,6 +35,20 @@
|
||||||
#puzzle .feedback .instruction strong {
|
#puzzle .feedback .instruction strong {
|
||||||
font-size: 1.5em;
|
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 {
|
#puzzle .timeline {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -11,6 +11,7 @@ var opposite = chessground.util.opposite;
|
||||||
var groundBuild = require('./ground');
|
var groundBuild = require('./ground');
|
||||||
var socketBuild = require('./socket');
|
var socketBuild = require('./socket');
|
||||||
var moveTestBuild = require('./moveTest');
|
var moveTestBuild = require('./moveTest');
|
||||||
|
var mergeSolution = require('./solution');
|
||||||
var throttle = require('common').throttle;
|
var throttle = require('common').throttle;
|
||||||
var xhr = require('./xhr');
|
var xhr = require('./xhr');
|
||||||
var sound = require('./sound');
|
var sound = require('./sound');
|
||||||
|
@ -18,9 +19,11 @@ var sound = require('./sound');
|
||||||
module.exports = function(opts, i18n) {
|
module.exports = function(opts, i18n) {
|
||||||
|
|
||||||
var vm = {
|
var vm = {
|
||||||
|
mode: 'play', // play | try | view
|
||||||
loading: false,
|
loading: false,
|
||||||
justPlayed: null,
|
justPlayed: null,
|
||||||
initialPath: null
|
initialPath: null,
|
||||||
|
canViewSolution: false
|
||||||
};
|
};
|
||||||
|
|
||||||
var data = opts.data;
|
var data = opts.data;
|
||||||
|
@ -42,6 +45,12 @@ module.exports = function(opts, i18n) {
|
||||||
m.redraw();
|
m.redraw();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
vm.canViewSolution = true;
|
||||||
|
m.redraw();
|
||||||
|
}, 500);
|
||||||
|
// }, 5000);
|
||||||
|
|
||||||
var showGround = function() {
|
var showGround = function() {
|
||||||
var node = vm.node;
|
var node = vm.node;
|
||||||
var color = node.ply % 2 === 0 ? 'white' : 'black';
|
var color = node.ply % 2 === 0 ? 'white' : 'black';
|
||||||
|
@ -188,6 +197,12 @@ module.exports = function(opts, i18n) {
|
||||||
jump(path);
|
jump(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var viewSolution = function() {
|
||||||
|
vm.mode = 'view';
|
||||||
|
mergeSolution(tree, vm.initialPath, data.puzzle.branch);
|
||||||
|
m.redraw();
|
||||||
|
};
|
||||||
|
|
||||||
var socket = socketBuild({
|
var socket = socketBuild({
|
||||||
send: opts.socketSend,
|
send: opts.socketSend,
|
||||||
addNode: addNode,
|
addNode: addNode,
|
||||||
|
@ -215,6 +230,7 @@ module.exports = function(opts, i18n) {
|
||||||
tree: tree,
|
tree: tree,
|
||||||
ground: ground,
|
ground: ground,
|
||||||
userJump: userJump,
|
userJump: userJump,
|
||||||
|
viewSolution: viewSolution,
|
||||||
trans: lichess.trans(opts.i18n),
|
trans: lichess.trans(opts.i18n),
|
||||||
socketReceive: socket.receive
|
socketReceive: socket.receive
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,33 @@
|
||||||
var m = require('mithril');
|
var m = require('mithril');
|
||||||
|
var bindOnce = require('common').bindOnce;
|
||||||
|
|
||||||
function yourTurn(ctrl) {
|
function play(ctrl) {
|
||||||
var turnColor = ctrl.ground.data.turnColor;
|
var turnColor = ctrl.ground.data.turnColor;
|
||||||
var puzzleColor = ctrl.data.puzzle.color;
|
var puzzleColor = ctrl.data.puzzle.color;
|
||||||
return m('div.your_turn.player.' + turnColor, [
|
return m('div.feedback.play', [
|
||||||
|
m('div.player.' + turnColor, [
|
||||||
m('div.no-square', m('piece.king.' + turnColor)),
|
m('div.no-square', m('piece.king.' + turnColor)),
|
||||||
m('div.instruction', [
|
m('div.instruction', [
|
||||||
m('strong', ctrl.trans.noarg(turnColor == puzzleColor ? 'yourTurn' : 'waiting')),
|
m('strong', ctrl.trans.noarg(turnColor == puzzleColor ? 'yourTurn' : 'waiting')),
|
||||||
m('em', ctrl.trans.noarg(puzzleColor == 'white' ? 'findTheBestMoveForWhite' : 'findTheBestMoveForBlack'))
|
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) {
|
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,
|
fen: config.fen,
|
||||||
check: config.check,
|
check: config.check,
|
||||||
lastMove: config.lastMove,
|
lastMove: config.lastMove,
|
||||||
orientation: data.orientation,
|
orientation: data.puzzle.color,
|
||||||
coordinates: data.pref.coords !== 0,
|
coordinates: data.pref.coords !== 0,
|
||||||
movable: {
|
movable: {
|
||||||
free: false,
|
free: false,
|
||||||
|
|
10
ui/puzzle/src/solution.js
Normal file
10
ui/puzzle/src/solution.js
Normal 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);
|
||||||
|
};
|
Loading…
Reference in a new issue