complete (?) implementation of study forced variations. whew.
parent
583d260e5e
commit
f0e31a48cf
|
@ -57,6 +57,9 @@ case class Chapter(
|
|||
def setClock(clock: Option[Centis], path: Path): Option[Chapter] =
|
||||
updateRoot(_.setClockAt(clock, path))
|
||||
|
||||
def forceVariation(force: Boolean, path: Path): Option[Chapter] =
|
||||
updateRoot(_.forceVariationAt(force, path))
|
||||
|
||||
def opening: Option[FullOpening] =
|
||||
if (!Variant.openingSensibleVariants(setup.variant)) none
|
||||
else FullOpeningDB searchInFens root.mainline.map(_.fen)
|
||||
|
|
|
@ -94,6 +94,9 @@ final class ChapterRepo(coll: Coll) {
|
|||
def setClock(chapter: Chapter, path: Path, clock: Option[chess.Centis]): Funit =
|
||||
setNodeValue(chapter, path, "l", clock)
|
||||
|
||||
def forceVariation(chapter: Chapter, path: Path, force: Boolean): Funit =
|
||||
setNodeValue(chapter, path, "fv", force option true)
|
||||
|
||||
def setScore(chapter: Chapter, path: Path, score: Option[lila.tree.Eval.Score]): Funit =
|
||||
setNodeValue(chapter, path, "e", score)
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ case class Node(
|
|||
def addChild(child: Node) = copy(children = children addNode child)
|
||||
|
||||
def withClock(centis: Option[Centis]) = copy(clock = centis)
|
||||
def withForceVariation(force: Boolean) = copy(forceVariation = force)
|
||||
|
||||
def isCommented = comments.value.nonEmpty
|
||||
|
||||
|
@ -113,6 +114,14 @@ object Node {
|
|||
case (head, tail) => get(head) flatMap (_.children nodeAt tail)
|
||||
}
|
||||
|
||||
def nodesOn(path: Path): List[(Node, Path)] = path.split ?? {
|
||||
case (head, tail) => get(head) ?? { first =>
|
||||
(first, Path(List(head))) :: first.children.nodesOn(tail).map {
|
||||
case (n, p) => (n, p prepend head)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def addNodeAt(node: Node, path: Path): Option[Children] = path.split match {
|
||||
case None => addNode(node).some
|
||||
case Some((head, tail)) => updateChildren(head, _.addNodeAt(node, tail))
|
||||
|
@ -256,6 +265,10 @@ object Node {
|
|||
if (path.isEmpty) copy(clock = clock).some
|
||||
else updateChildrenAt(path, _ withClock clock)
|
||||
|
||||
def forceVariationAt(force: Boolean, path: Path): Option[Root] =
|
||||
if (path.isEmpty) copy(clock = clock).some
|
||||
else updateChildrenAt(path, _ withForceVariation force)
|
||||
|
||||
private def updateChildrenAt(path: Path, f: Node => Node): Option[Root] =
|
||||
withChildren(_.updateAt(path, f))
|
||||
|
||||
|
|
|
@ -17,6 +17,14 @@ case class Path(ids: List[UciCharPair]) extends AnyVal {
|
|||
def +(node: Node): Path = Path(ids :+ node.id)
|
||||
def +(more: Path): Path = Path(ids ::: more.ids)
|
||||
|
||||
def prepend(id: UciCharPair) = Path(id :: ids)
|
||||
|
||||
def intersect(other: Path): Path = Path {
|
||||
ids zip other.ids takeWhile {
|
||||
case (a, b) => a == b
|
||||
} map (_._1)
|
||||
}
|
||||
|
||||
override def toString = ids.mkString
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ case class Position(chapter: Chapter, path: Path) {
|
|||
def ref = Position.Ref(chapter.id, path)
|
||||
|
||||
def node: Option[RootOrNode] = chapter.root nodeAt path
|
||||
|
||||
override def toString = ref.toString
|
||||
}
|
||||
|
||||
case object Position {
|
||||
|
|
|
@ -165,6 +165,12 @@ private final class Socket(
|
|||
"w" -> who(uid)
|
||||
), noMessadata)
|
||||
|
||||
case ForceVariation(pos, force, uid) => notifyVersion("forceVariation", Json.obj(
|
||||
"p" -> pos,
|
||||
"force" -> force,
|
||||
"w" -> who(uid)
|
||||
), noMessadata)
|
||||
|
||||
case SetConceal(pos, ply) => notifyVersion("conceal", Json.obj(
|
||||
"p" -> pos,
|
||||
"ply" -> ply.map(_.value)
|
||||
|
@ -322,6 +328,7 @@ object Socket {
|
|||
case class DeleteComment(position: Position.Ref, commentId: Comment.Id, uid: Uid)
|
||||
case class SetGlyphs(position: Position.Ref, glyphs: Glyphs, uid: Uid)
|
||||
case class SetClock(position: Position.Ref, clock: Option[Centis], uid: Uid)
|
||||
case class ForceVariation(position: Position.Ref, force: Boolean, uid: Uid)
|
||||
case class ReloadChapters(chapters: List[Chapter.Metadata])
|
||||
case object ReloadAll
|
||||
case class ChangeChapter(uid: Uid, position: Position.Ref)
|
||||
|
|
|
@ -104,6 +104,14 @@ final class SocketHandler(
|
|||
} api.promote(userId, studyId, position.ref, toMainline, uid)
|
||||
}
|
||||
}
|
||||
case ("forceVariation", o) => AnaRateLimit(uid, member) {
|
||||
reading[AtPosition](o) { position =>
|
||||
for {
|
||||
force <- (o \ "d" \ "force").asOpt[Boolean]
|
||||
userId <- member.userId
|
||||
} api.forceVariation(userId, studyId, position.ref, force, uid)
|
||||
}
|
||||
}
|
||||
case ("setRole", o) => AnaRateLimit(uid, member) {
|
||||
reading[SetRole](o) { d =>
|
||||
member.userId foreach { userId =>
|
||||
|
|
|
@ -269,7 +269,13 @@ final class StudyApi(
|
|||
} match {
|
||||
case Some(newChapter) =>
|
||||
chapterRepo.update(newChapter) >>-
|
||||
sendTo(study, Socket.Promote(position, toMainline, uid))
|
||||
sendTo(study, Socket.Promote(position, toMainline, uid)) >>
|
||||
newChapter.root.children.nodesOn {
|
||||
newChapter.root.mainlinePath.intersect(position.path)
|
||||
}.collect {
|
||||
case (node, path) if node.forceVariation =>
|
||||
doForceVariation(Study.WithChapter(study, newChapter), path, false, Uid(""))
|
||||
}.sequenceFu.void
|
||||
case None =>
|
||||
fufail(s"Invalid promoteToMainline $studyId $position") >>-
|
||||
reloadUidBecauseOf(study, uid, chapter.id)
|
||||
|
@ -277,6 +283,23 @@ final class StudyApi(
|
|||
}
|
||||
}
|
||||
|
||||
def forceVariation(userId: User.ID, studyId: Study.Id, position: Position.Ref, force: Boolean, uid: Uid): Funit =
|
||||
sequenceStudyWithChapter(studyId, position.chapterId) { sc =>
|
||||
Contribute(userId, sc.study) {
|
||||
doForceVariation(sc, position.path, force, uid)
|
||||
}
|
||||
}
|
||||
|
||||
private def doForceVariation(sc: Study.WithChapter, path: Path, force: Boolean, uid: Uid): Funit =
|
||||
sc.chapter.forceVariation(force, path) match {
|
||||
case Some(newChapter) =>
|
||||
chapterRepo.forceVariation(newChapter, path, force) >>-
|
||||
sendTo(sc.study, Socket.ForceVariation(Position(newChapter, path).ref, force, uid))
|
||||
case None =>
|
||||
fufail(s"Invalid forceVariation ${Position(sc.chapter, path)} $force") >>-
|
||||
reloadUidBecauseOf(sc.study, uid, sc.chapter.id)
|
||||
}
|
||||
|
||||
def setRole(byUserId: User.ID, studyId: Study.Id, userId: User.ID, roleStr: String) = sequenceStudy(studyId) { study =>
|
||||
(study isOwner byUserId) ?? {
|
||||
val role = StudyMember.Role.byId.getOrElse(roleStr, StudyMember.Role.Read)
|
||||
|
@ -453,19 +476,6 @@ final class StudyApi(
|
|||
}
|
||||
}
|
||||
|
||||
def forceVariation(studyId: Study.Id, position: Position.Ref, force: Boolean, uid: Uid): Funit =
|
||||
sequenceStudyWithChapter(studyId, position.chapterId) { sc =>
|
||||
sc.chapter.forceVariation(force, position.path) match {
|
||||
case Some(newChapter) =>
|
||||
studyRepo.updateNow(sc.study)
|
||||
chapterRepo.forceVariation(newChapter, position.path, force) >>-
|
||||
sendTo(sc.study, Socket.SetClock(position, clock, uid))
|
||||
case None =>
|
||||
fufail(s"Invalid setClock $position $clock") >>-
|
||||
reloadUidBecauseOf(sc.study, uid, position.chapterId)
|
||||
}
|
||||
}
|
||||
|
||||
def explorerGame(userId: User.ID, studyId: Study.Id, data: actorApi.ExplorerGame, uid: Uid) = sequenceStudyWithChapter(studyId, data.position.chapterId) {
|
||||
case Study.WithChapter(study, chapter) => Contribute(userId, study) {
|
||||
if (data.insert) explorerGameHandler.insert(userId, study, Position(chapter, data.position.path), data.gameId) flatMap {
|
||||
|
|
|
@ -36,7 +36,8 @@ object TreeBuilder {
|
|||
crazyData = node.crazyData,
|
||||
eval = node.score.map(_.eval),
|
||||
children = toBranches(node.children),
|
||||
opening = FullOpeningDB findByFen node.fen.value
|
||||
opening = FullOpeningDB findByFen node.fen.value,
|
||||
forceVariation = node.forceVariation
|
||||
)
|
||||
|
||||
def makeRoot(root: Node.Root) =
|
||||
|
|
|
@ -217,6 +217,7 @@ declare namespace Tree {
|
|||
glyphs?: Glyph[];
|
||||
clock?: Clock;
|
||||
parentClock?: Clock;
|
||||
forceVariation: boolean;
|
||||
shapes?: Shape[];
|
||||
comp?: boolean;
|
||||
san?: string;
|
||||
|
|
|
@ -520,6 +520,12 @@ export default class AnalyseCtrl {
|
|||
if (this.study) this.study.promote(path, toMainline);
|
||||
}
|
||||
|
||||
forceVariation(path: Tree.Path, force: boolean): void {
|
||||
this.tree.forceVariationAt(path, force);
|
||||
this.jump(path);
|
||||
if (this.study) this.study.forceVariation(path, force);
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.showGround();
|
||||
this.redraw();
|
||||
|
|
|
@ -34,6 +34,7 @@ export interface StudyCtrl {
|
|||
setPath(path: Tree.Path, node: Tree.Node, playedMyself: boolean): void;
|
||||
deleteNode(path: Tree.Path): void;
|
||||
promote(path: Tree.Path, toMainline: boolean): void;
|
||||
forceVariation(path: Tree.Path, force: boolean): void;
|
||||
setChapter(id: string, force?: boolean): void;
|
||||
toggleSticky(): void;
|
||||
toggleWrite(): void;
|
||||
|
|
|
@ -429,6 +429,14 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
ctrl.tree.setClockAt(d.c, position.path);
|
||||
redraw();
|
||||
},
|
||||
forceVariation(d) {
|
||||
const position = d.p,
|
||||
who = d.w;
|
||||
setMemberActive(who);
|
||||
if (wrongChapter(d)) return;
|
||||
ctrl.tree.forceVariationAt(position.path, d.force);
|
||||
redraw();
|
||||
},
|
||||
conceal(d) {
|
||||
if (wrongChapter(d)) return;
|
||||
data.chapter.conceal = d.ply;
|
||||
|
@ -506,6 +514,12 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
|
|||
path
|
||||
}));
|
||||
},
|
||||
forceVariation(path, force) {
|
||||
makeChange("forceVariation", addChapterId({
|
||||
force,
|
||||
path
|
||||
}));
|
||||
},
|
||||
setChapter(id, force) {
|
||||
if (id === vm.chapterId && !force) return;
|
||||
if (!vm.mode.sticky || !makeChange("setChapter", id)) {
|
||||
|
|
|
@ -36,39 +36,41 @@ function renderChildrenOf(ctx: Ctx, node: Tree.Node, opts: Opts): MaybeVNodes |
|
|||
if (opts.isMainline) {
|
||||
const isWhite = main.ply % 2 === 1,
|
||||
commentTags = renderMainlineCommentsOf(ctx, main, conceal, true).filter(nonEmpty);
|
||||
if (!cs[1] && empty(commentTags)) return ((isWhite ? [moveView.renderIndex(main.ply, false)] : []) as MaybeVNodes).concat(
|
||||
if (!cs[1] && empty(commentTags) && !main.forceVariation) return ((isWhite ? [moveView.renderIndex(main.ply, false)] : []) as MaybeVNodes).concat(
|
||||
renderMoveAndChildrenOf(ctx, main, {
|
||||
parentPath: opts.parentPath,
|
||||
isMainline: true,
|
||||
conceal
|
||||
}) || []
|
||||
);
|
||||
const mainChildren = renderChildrenOf(ctx, main, {
|
||||
const mainChildren = main.forceVariation ? undefined : renderChildrenOf(ctx, main, {
|
||||
parentPath: opts.parentPath + main.id,
|
||||
isMainline: true,
|
||||
conceal
|
||||
});
|
||||
const passOpts = {
|
||||
parentPath: opts.parentPath,
|
||||
isMainline: true,
|
||||
isMainline: !main.forceVariation,
|
||||
conceal
|
||||
};
|
||||
return (isWhite ? [moveView.renderIndex(main.ply, false)] : [] as MaybeVNodes).concat([
|
||||
renderMoveOf(ctx, main, passOpts),
|
||||
isWhite ? emptyMove(passOpts.conceal) : null,
|
||||
h('interrupt', commentTags.concat(
|
||||
renderLines(ctx, cs.slice(1), {
|
||||
parentPath: opts.parentPath,
|
||||
isMainline: true,
|
||||
conceal,
|
||||
noConceal: !conceal
|
||||
})
|
||||
))
|
||||
] as MaybeVNodes).concat(
|
||||
isWhite && mainChildren ? [
|
||||
moveView.renderIndex(main.ply, false),
|
||||
emptyMove(passOpts.conceal)
|
||||
] : []).concat(mainChildren || []);
|
||||
return (isWhite ? [moveView.renderIndex(main.ply, false)] : [] as MaybeVNodes).concat(
|
||||
main.forceVariation ? [] : [
|
||||
renderMoveOf(ctx, main, passOpts),
|
||||
isWhite ? emptyMove(passOpts.conceal) : null
|
||||
]).concat([
|
||||
h('interrupt', commentTags.concat(
|
||||
renderLines(ctx, main.forceVariation ? cs : cs.slice(1), {
|
||||
parentPath: opts.parentPath,
|
||||
isMainline: passOpts.isMainline,
|
||||
conceal,
|
||||
noConceal: !conceal
|
||||
})
|
||||
))
|
||||
] as MaybeVNodes).concat(
|
||||
isWhite && mainChildren ? [
|
||||
moveView.renderIndex(main.ply, false),
|
||||
emptyMove(passOpts.conceal)
|
||||
] : []).concat(mainChildren || []);
|
||||
}
|
||||
if (!cs[1]) return renderMoveAndChildrenOf(ctx, main, opts);
|
||||
return renderInlined(ctx, cs, opts) || [renderLines(ctx, cs, opts)];
|
||||
|
|
|
@ -18,8 +18,7 @@ interface Coords {
|
|||
const elementId = 'analyse-cm';
|
||||
|
||||
function getPosition(e: MouseEvent): Coords {
|
||||
let posx = 0;
|
||||
let posy = 0;
|
||||
let posx = 0, posy = 0;
|
||||
if (e.pageX || e.pageY) {
|
||||
posx = e.pageX;
|
||||
posy = e.pageY;
|
||||
|
@ -59,7 +58,8 @@ function action(icon: string, text: string, handler: () => void): VNode {
|
|||
function view(opts: Opts, coords: Coords): VNode {
|
||||
const ctrl = opts.root,
|
||||
node = ctrl.tree.nodeAtPath(opts.path),
|
||||
onMainline = ctrl.tree.pathIsMainline(opts.path);
|
||||
onMainline = ctrl.tree.pathIsMainline(opts.path) && !ctrl.tree.pathIsForcedVariation(opts.path),
|
||||
trans = ctrl.trans.noarg;
|
||||
return h('div#' + elementId + '.visible', {
|
||||
hook: {
|
||||
insert: vnode => positionMenu(vnode.elm as HTMLElement, coords),
|
||||
|
@ -67,12 +67,16 @@ function view(opts: Opts, coords: Coords): VNode {
|
|||
}
|
||||
}, [
|
||||
h('p.title', nodeFullName(node)),
|
||||
onMainline ? null : action('S', ctrl.trans.noarg('promoteVariation'), () => ctrl.promote(opts.path, false)),
|
||||
onMainline ? null : action('E', ctrl.trans.noarg('makeMainLine'), () => ctrl.promote(opts.path, true)),
|
||||
action('q', ctrl.trans.noarg('deleteFromHere'), () => ctrl.deleteNode(opts.path))
|
||||
onMainline ? null : action('S', trans('promoteVariation'), () => ctrl.promote(opts.path, false)),
|
||||
onMainline ? null : action('E', trans('makeMainLine'), () => ctrl.promote(opts.path, true)),
|
||||
action('q', trans('deleteFromHere'), () => ctrl.deleteNode(opts.path))
|
||||
].concat(
|
||||
ctrl.study ? studyView.contextMenu(ctrl.study, opts.path, node) : []
|
||||
));
|
||||
).concat([
|
||||
onMainline ?
|
||||
action('F', 'Force variation', () => ctrl.forceVariation(opts.path, true)) :
|
||||
null
|
||||
]));
|
||||
}
|
||||
|
||||
export default function(e: MouseEvent, opts: Opts): void {
|
||||
|
|
|
@ -21,10 +21,12 @@ export interface TreeWrapper {
|
|||
setGlyphsAt(glyphs: Tree.Glyph[], path: Tree.Path): MaybeNode;
|
||||
setClockAt(clock: Tree.Clock | undefined, path: Tree.Path): MaybeNode;
|
||||
pathIsMainline(path: Tree.Path): boolean;
|
||||
pathIsForcedVariation(path: Tree.Path): boolean;
|
||||
lastMainlineNode(path: Tree.Path): Tree.Node;
|
||||
pathExists(path: Tree.Path): boolean;
|
||||
deleteNodeAt(path: Tree.Path): void;
|
||||
promoteAt(path: Tree.Path, toMainline: boolean): void;
|
||||
forceVariationAt(path: Tree.Path, force: boolean): MaybeNode;
|
||||
getCurrentNodesAfterPly(nodeList: Tree.Node[], mainline: Tree.Node[], ply: number): Tree.Node[];
|
||||
merge(tree: Tree.Node): void;
|
||||
removeCeval(): void;
|
||||
|
@ -93,6 +95,10 @@ export function build(root: Tree.Node): TreeWrapper {
|
|||
return pathIsMainlineFrom(child, treePath.tail(path));
|
||||
}
|
||||
|
||||
function pathIsForcedVariation(path: Tree.Path): boolean {
|
||||
return !!getNodeList(path).find(n => n.forceVariation);
|
||||
}
|
||||
|
||||
function lastMainlineNodeFrom(node: Tree.Node, path: Tree.Path): Tree.Node {
|
||||
if (path === '') return node;
|
||||
const pathId = treePath.head(path);
|
||||
|
@ -192,12 +198,6 @@ export function build(root: Tree.Node): TreeWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
function setClockAt(clock: Tree.Clock | undefined, path: Tree.Path) {
|
||||
return updateAt(path, function(node) {
|
||||
node.clock = clock;
|
||||
});
|
||||
}
|
||||
|
||||
function parentNode(path: Tree.Path): Tree.Node {
|
||||
return nodeAtPath(treePath.init(path));
|
||||
}
|
||||
|
@ -238,14 +238,24 @@ export function build(root: Tree.Node): TreeWrapper {
|
|||
setCommentAt,
|
||||
deleteCommentAt,
|
||||
setGlyphsAt,
|
||||
setClockAt,
|
||||
setClockAt(clock: Tree.Clock | undefined, path: Tree.Path) {
|
||||
return updateAt(path, function(node) {
|
||||
node.clock = clock;
|
||||
});
|
||||
},
|
||||
pathIsMainline,
|
||||
pathIsForcedVariation,
|
||||
lastMainlineNode(path: Tree.Path): Tree.Node {
|
||||
return lastMainlineNodeFrom(root, path);
|
||||
},
|
||||
pathExists,
|
||||
deleteNodeAt,
|
||||
promoteAt,
|
||||
forceVariationAt(path: Tree.Path, force: boolean) {
|
||||
return updateAt(path, function(node) {
|
||||
node.forceVariation = force;
|
||||
});
|
||||
},
|
||||
getCurrentNodesAfterPly,
|
||||
merge(tree: Tree.Node) {
|
||||
ops.merge(root, tree);
|
||||
|
|
Loading…
Reference in New Issue