puzzle WIP
parent
44cdccb302
commit
778621b457
|
@ -58,18 +58,18 @@ final class Puzzle(
|
|||
}
|
||||
}
|
||||
|
||||
def show(id: Puz.Id) =
|
||||
def show(id: String) =
|
||||
Open { implicit ctx =>
|
||||
NoBot {
|
||||
OptionFuResult(env.puzzle.api.puzzle find id)(renderShow)
|
||||
OptionFuResult(env.puzzle.api.puzzle find Puz.Id(id))(renderShow)
|
||||
}
|
||||
}
|
||||
|
||||
def load(id: Puz.Id) =
|
||||
def load(id: String) =
|
||||
Open { implicit ctx =>
|
||||
NoBot {
|
||||
XhrOnly {
|
||||
OptionFuOk(env.puzzle.api.puzzle find id)(puzzleJson) map (_ as JSON)
|
||||
OptionFuOk(env.puzzle.api.puzzle find Puz.Id(id))(puzzleJson _).dmap(_ as JSON)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -90,8 +90,7 @@ final class Puzzle(
|
|||
}
|
||||
}
|
||||
|
||||
// new API
|
||||
def round2(id: Puz.Id) =
|
||||
def round2(id: String) =
|
||||
OpenBody { implicit ctx =>
|
||||
NoBot {
|
||||
???
|
||||
|
@ -133,7 +132,7 @@ final class Puzzle(
|
|||
}
|
||||
}
|
||||
|
||||
def vote(id: Puz.Id) =
|
||||
def vote(id: String) =
|
||||
AuthBody { implicit ctx => me =>
|
||||
NoBot {
|
||||
???
|
||||
|
|
|
@ -87,11 +87,7 @@ POST /training/batch controllers.Puzzle.batchSolve
|
|||
GET /training/:id controllers.Puzzle.show(id: String)
|
||||
GET /training/:id/load controllers.Puzzle.load(id: String)
|
||||
POST /training/:id/vote controllers.Puzzle.vote(id: String)
|
||||
POST /training/:id/round controllers.Puzzle.round(id: String)
|
||||
# new UI
|
||||
POST /training/:id/round2 controllers.Puzzle.round2(id: Int)
|
||||
# mobile app BC
|
||||
POST /training/:id/attempt controllers.Puzzle.round(id: Int)
|
||||
POST /training/:id/round2 controllers.Puzzle.round2(id: String)
|
||||
|
||||
# User Analysis
|
||||
GET /analysis/help controllers.UserAnalysis.help
|
||||
|
|
|
@ -59,13 +59,33 @@ final class JsonView(
|
|||
"id" -> puzzle.id,
|
||||
"rating" -> puzzle.glicko.intRating,
|
||||
"plays" -> puzzle.plays,
|
||||
"fen" -> puzzle.fen,
|
||||
"color" -> puzzle.color.name,
|
||||
"initialPly" -> puzzle.initialPly,
|
||||
"gameId" -> puzzle.gameId,
|
||||
"line" -> puzzle.line.toList.map(_.uci).mkString(" "),
|
||||
"solution" -> makeSolution(puzzle).map(defaultNodeJsonWriter.writes),
|
||||
"vote" -> puzzle.vote
|
||||
)
|
||||
|
||||
private def makeSolution(puzzle: Puzzle): Option[tree.Branch] = {
|
||||
import chess.format._
|
||||
val init = chess.Game(none, puzzle.fenAfterInitialMove.some).withTurns(puzzle.initialPly)
|
||||
val (_, branchList) = puzzle.line.tail.foldLeft[(chess.Game, List[tree.Branch])]((init, Nil)) {
|
||||
case ((prev, branches), uci) =>
|
||||
val (game, move) =
|
||||
prev(uci.orig, uci.dest, uci.promotion).fold(err => sys error s"puzzle ${puzzle.id} $err", identity)
|
||||
val branch = tree.Branch(
|
||||
id = UciCharPair(move.toUci),
|
||||
ply = game.turns,
|
||||
move = Uci.WithSan(move.toUci, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object JsonView {
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"@fnando/sparkline": "^0.3.10",
|
||||
"ceval": "2.0.0",
|
||||
"chessground": "^7.9.2",
|
||||
"chessops": "^0.7.2",
|
||||
"chessops": "^0.7.3",
|
||||
"common": "2.0.0",
|
||||
"snabbdom": "^0.7.4",
|
||||
"tree": "2.0.0"
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { build as treeBuild, ops as treeOps, path as treePath, TreeWrapper } from 'tree';
|
||||
import { ctrl as cevalCtrl, CevalCtrl } from 'ceval';
|
||||
import keyboard from './keyboard';
|
||||
import { moveTestBuild, MoveTestFn } from './moveTest';
|
||||
import mergeSolution from './solution';
|
||||
import makePromotion from './promotion';
|
||||
import computeAutoShapes from './autoShape';
|
||||
import { defined, prop, Prop } from 'common';
|
||||
import { storedProp } from 'common/storage';
|
||||
import throttle from 'common/throttle';
|
||||
import * as xhr from './xhr';
|
||||
import * as speech from './speech';
|
||||
import { Role, Move, Outcome } from 'chessops/types';
|
||||
import { parseSquare, parseUci, makeSquare, makeUci } from 'chessops/util';
|
||||
import { parseFen, makeFen } from 'chessops/fen';
|
||||
import { makeSanAndPlay } from 'chessops/san';
|
||||
import * as xhr from './xhr';
|
||||
import computeAutoShapes from './autoShape';
|
||||
import keyboard from './keyboard';
|
||||
import makePromotion from './promotion';
|
||||
import mergeSolution from './solution';
|
||||
import throttle from 'common/throttle';
|
||||
import { Api as CgApi } from 'chessground/api';
|
||||
import { build as treeBuild, ops as treeOps, path as treePath, TreeWrapper } from 'tree';
|
||||
import { Chess } from 'chessops/chess';
|
||||
import { chessgroundDests, scalachessCharPair } from 'chessops/compat';
|
||||
import { Config as CgConfig } from 'chessground/config';
|
||||
import { Api as CgApi } from 'chessground/api';
|
||||
import { Redraw, Vm, Controller, PuzzleOpts, PuzzleData, PuzzleRound, PuzzleVote, MoveTest } from './interfaces';
|
||||
import { ctrl as cevalCtrl, CevalCtrl } from 'ceval';
|
||||
import { defined, prop, Prop } from 'common';
|
||||
import { makeSanAndPlay } from 'chessops/san';
|
||||
import { moveTestBuild, MoveTestFn } from './moveTest';
|
||||
import { parseFen, makeFen } from 'chessops/fen';
|
||||
import { parseSquare, parseUci, makeSquare, makeUci } from 'chessops/util';
|
||||
import { Redraw, Vm, Controller, PuzzleOpts, PuzzleData, PuzzleRound, MoveTest } from './interfaces';
|
||||
import { Role, Move, Outcome } from 'chessops/types';
|
||||
import { storedProp } from 'common/storage';
|
||||
|
||||
export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
||||
|
||||
|
@ -53,7 +53,7 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
|||
function initiate(fromData: PuzzleData): void {
|
||||
data = fromData;
|
||||
tree = treeBuild(treeOps.reconstruct(data.game.treeParts));
|
||||
var initialPath = treePath.fromNodeList(treeOps.mainlineNodeList(tree.root));
|
||||
const initialPath = treePath.fromNodeList(treeOps.mainlineNodeList(tree.root));
|
||||
// play | try | view
|
||||
vm.mode = 'play';
|
||||
vm.loading = false;
|
||||
|
@ -64,6 +64,7 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
|||
vm.lastFeedback = 'init';
|
||||
vm.initialPath = initialPath;
|
||||
vm.initialNode = tree.nodeAtPath(initialPath);
|
||||
vm.pov = vm.initialNode.ply % 2 == 0 ? 'black' : 'white';
|
||||
|
||||
setPath(treePath.init(initialPath));
|
||||
setTimeout(function() {
|
||||
|
@ -100,16 +101,16 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
|||
const node = vm.node;
|
||||
const color: Color = node.ply % 2 === 0 ? 'white' : 'black';
|
||||
const dests = chessgroundDests(position());
|
||||
const movable = (vm.mode === 'view' || color === data.puzzle.color) ? {
|
||||
const movable = (vm.mode === 'view' || color === vm.pov) ? {
|
||||
color: dests.size > 0 ? color : undefined,
|
||||
dests
|
||||
} : {
|
||||
color: undefined,
|
||||
dests: new Map(),
|
||||
};
|
||||
color: undefined,
|
||||
dests: new Map(),
|
||||
};
|
||||
const config = {
|
||||
fen: node.fen,
|
||||
orientation: data.puzzle.color,
|
||||
orientation: vm.pov,
|
||||
turnColor: color,
|
||||
movable: movable,
|
||||
premovable: {
|
||||
|
@ -119,8 +120,8 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
|||
lastMove: uciToLastMove(node.uci)
|
||||
};
|
||||
if (node.ply >= vm.initialNode.ply) {
|
||||
if (vm.mode !== 'view' && color !== data.puzzle.color) {
|
||||
config.movable.color = data.puzzle.color;
|
||||
if (vm.mode !== 'view' && color !== vm.pov) {
|
||||
config.movable.color = vm.pov;
|
||||
config.premovable.enabled = true;
|
||||
}
|
||||
}
|
||||
|
@ -216,9 +217,6 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
|||
vm.mode = 'try';
|
||||
sendResult(false);
|
||||
}
|
||||
} else if (progress === 'retry') {
|
||||
vm.lastFeedback = 'retry';
|
||||
revertUserMove();
|
||||
} else if (progress === 'win') {
|
||||
if (vm.mode !== 'view') {
|
||||
if (vm.mode === 'play') sendResult(true);
|
||||
|
@ -382,7 +380,7 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
|||
if (!vm.canViewSolution) return;
|
||||
sendResult(false);
|
||||
vm.mode = 'view';
|
||||
mergeSolution(vm.initialNode, data.puzzle.branch, data.puzzle.color);
|
||||
mergeSolution(vm.initialNode, data.puzzle.solution, vm.pov);
|
||||
reorderChildren(vm.initialPath, true);
|
||||
|
||||
// try and play the solution next move
|
||||
|
@ -415,10 +413,8 @@ export default function(opts: PuzzleOpts, redraw: Redraw): Controller {
|
|||
if (callToVote()) thanksUntil = Date.now() + 2000;
|
||||
nbToVoteCall(5);
|
||||
vm.voted = v;
|
||||
xhr.vote(data.puzzle.id, v).then((res: PuzzleVote) => {
|
||||
data.puzzle.vote = res[1];
|
||||
redraw();
|
||||
});
|
||||
xhr.vote(data.puzzle.id, v);
|
||||
redraw();
|
||||
});
|
||||
|
||||
initiate(opts.data);
|
||||
|
|
|
@ -59,13 +59,14 @@ export interface Vm {
|
|||
nodeList: Tree.Node[];
|
||||
node: Tree.Node;
|
||||
mainline: Tree.Node[];
|
||||
pov: Color;
|
||||
mode: 'play' | 'view' | 'try';
|
||||
loading: boolean;
|
||||
round: any;
|
||||
voted?: boolean | null;
|
||||
justPlayed?: Key;
|
||||
resultSent: boolean;
|
||||
lastFeedback: 'init' | 'fail' | 'win' | 'good' | 'retry';
|
||||
lastFeedback: 'init' | 'fail' | 'win' | 'good';
|
||||
initialPath: Tree.Path;
|
||||
initialNode: Tree.Node;
|
||||
canViewSolution: boolean;
|
||||
|
@ -112,7 +113,6 @@ export interface PuzzleGame {
|
|||
rated: boolean;
|
||||
players: Array<{userId: string, name: string, color: Color}>;
|
||||
treeParts: Tree.Node[];
|
||||
clock: string;
|
||||
}
|
||||
|
||||
export interface PuzzleUser {
|
||||
|
@ -121,14 +121,10 @@ export interface PuzzleUser {
|
|||
}
|
||||
|
||||
export interface Puzzle {
|
||||
id: number;
|
||||
enabled: boolean;
|
||||
vote: number;
|
||||
color: Color;
|
||||
lines: Lines;
|
||||
branch: any;
|
||||
id: string;
|
||||
solution: Tree.Node;
|
||||
rating: number;
|
||||
attempts: number;
|
||||
plays: number;
|
||||
initialPly: number;
|
||||
}
|
||||
|
||||
|
@ -141,11 +137,6 @@ export interface PuzzleRound {
|
|||
voted?: null | true | false;
|
||||
}
|
||||
|
||||
export interface PuzzleVote {
|
||||
0: true | false; // up/down
|
||||
1: number; // new score
|
||||
}
|
||||
|
||||
export interface Promotion {
|
||||
start(orig: Key, dest: Key, callback: (orig: Key, dest: Key, prom: Role) => void): boolean;
|
||||
cancel(): void;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { PuzzleRound, PuzzleVote, PuzzleData } from './interfaces';
|
||||
import { PuzzleRound, PuzzleData } from './interfaces';
|
||||
import * as xhr from 'common/xhr';
|
||||
|
||||
export function round(puzzleId: number, win: boolean): Promise<PuzzleRound> {
|
||||
export function round(puzzleId: string, win: boolean): Promise<PuzzleRound> {
|
||||
return xhr.json(`/training/${puzzleId}/round2`, {
|
||||
method: 'POST',
|
||||
body: xhr.form({ win: win ? 1 : 0 }),
|
||||
|
@ -9,7 +9,7 @@ export function round(puzzleId: number, win: boolean): Promise<PuzzleRound> {
|
|||
});
|
||||
}
|
||||
|
||||
export function vote(puzzleId: number, v: boolean): Promise<PuzzleVote> {
|
||||
export function vote(puzzleId: string, v: boolean): Promise<void> {
|
||||
return xhr.json(`/training/${puzzleId}/vote`, {
|
||||
method: 'POST',
|
||||
body: xhr.form({ vote: v ? 1 : 0 })
|
||||
|
|
Loading…
Reference in New Issue