study flat tree WIP

studyFlatChapter
Thibault Duplessis 2021-02-02 13:17:48 +01:00
parent 29f506a41e
commit b45b6b2c4a
10 changed files with 116 additions and 92 deletions

View File

@ -82,7 +82,7 @@ final private class RelaySync(
toMainline = true
)(who) >> chapterRepo.setRelayPath(chapter.id, path)
} >> newNode.?? { node =>
lila.common.Future.fold(node.mainline)(Position(chapter, path).ref) { case (position, n) =>
lila.common.Future.fold(node.mainline.toList)(Position(chapter, path).ref) { case (position, n) =>
studyApi.addNode(
studyId = study.id,
position = position,

View File

@ -213,22 +213,25 @@ object BSONHandlers {
score = r.getO[Score]("e"),
clock = r.getO[Centis]("l"),
crazyData = r.getO[Crazyhouse.Data]("z"),
children = StudyFlatTree.rootChildren(fullReader.doc)
children = StudyFlatTree.reader.rootChildren(fullReader.doc)
)
}
def writes(w: Writer, s: Root) = ???
// $doc(
// "p" -> s.ply,
// "f" -> s.fen,
// "c" -> w.boolO(s.check),
// "h" -> s.shapes.value.nonEmpty.option(s.shapes),
// "co" -> s.comments.value.nonEmpty.option(s.comments),
// "ga" -> s.gamebook,
// "g" -> s.glyphs.nonEmpty,
// "e" -> s.score,
// "l" -> s.clock,
// "z" -> s.crazyData
// )
def writes(w: Writer, r: Root) = $doc(
StudyFlatTree.writer.rootChildren(r) appended {
"" -> $doc(
"p" -> r.ply,
"f" -> r.fen,
"c" -> r.check.some.filter(identity),
"h" -> r.shapes.value.nonEmpty.option(r.shapes),
"co" -> r.comments.value.nonEmpty.option(r.comments),
"ga" -> r.gamebook,
"g" -> r.glyphs.nonEmpty,
"e" -> r.score,
"l" -> r.clock,
"z" -> r.crazyData
)
}
)
}
implicit val PathBSONHandler = BSONStringHandler.as[Path](Path.apply, _.toString)

View File

@ -28,7 +28,7 @@ final class GifExport(
chapter.tags(_.Black),
chapter.tags(_.BlackElo).map(elo => s"($elo)")
).flatten.mkString(" "),
"frames" -> framesRec(chapter.root :: chapter.root.mainline, Json.arr())
"frames" -> framesRec(chapter.root +: chapter.root.mainline, Json.arr())
)
)
.stream() flatMap {
@ -39,10 +39,10 @@ final class GifExport(
}
@annotation.tailrec
private def framesRec(nodes: List[RootOrNode], arr: JsArray): JsArray =
private def framesRec(nodes: Vector[RootOrNode], arr: JsArray): JsArray =
nodes match {
case Nil => arr
case node :: tail =>
case node +: tail =>
framesRec(
tail,
arr :+ Json

View File

@ -22,7 +22,7 @@ sealed trait RootOrNode {
val score: Option[Score]
def addChild(node: Node): RootOrNode
def fullMoveNumber = 1 + ply / 2
def mainline: List[Node]
def mainline: Vector[Node]
def color = chess.Color.fromPly(ply)
def moveOption: Option[Uci.WithSan]
}
@ -70,7 +70,7 @@ case class Node(
def toggleGlyph(glyph: Glyph) = copy(glyphs = glyphs toggle glyph)
def mainline: List[Node] = this :: children.first.??(_.mainline)
def mainline: Vector[Node] = this +: children.first.??(_.mainline)
def updateMainlineLast(f: Node => Node): Node =
children.first.fold(f(this)) { main =>
@ -120,14 +120,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 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 {
@ -302,7 +302,7 @@ object Node {
copy(children = children.update(main updateMainlineLast f))
}
lazy val mainline: List[Node] = children.first.??(_.mainline)
lazy val mainline: Vector[Node] = children.first.??(_.mainline)
def lastMainlinePly = Chapter.Ply(mainline.lastOption.??(_.ply))

View File

@ -2,7 +2,7 @@ package lila.study
import chess.format.UciCharPair
case class Path(ids: List[UciCharPair]) extends AnyVal {
case class Path(ids: Vector[UciCharPair]) extends AnyVal {
def head: Option[UciCharPair] = ids.headOption
@ -14,10 +14,11 @@ case class Path(ids: List[UciCharPair]) extends AnyVal {
def isEmpty = ids.isEmpty
def +(node: Node): Path = Path(ids :+ node.id)
def +(more: Path): Path = Path(ids ::: more.ids)
def +(id: UciCharPair): Path = Path(ids appended id)
def +(node: Node): Path = Path(ids appended node.id)
def +(more: Path): Path = Path(ids appendedAll more.ids)
def prepend(id: UciCharPair) = Path(id :: ids)
def prepend(id: UciCharPair) = Path(ids prepended id)
def intersect(other: Path): Path =
Path {
@ -40,7 +41,7 @@ object Path {
UciCharPair(p(0), b)
}
}
.toList
.toVector
}
val root = Path("")

View File

@ -26,7 +26,7 @@ final class PgnDump(
def ofChapter(study: Study, flags: WithFlags)(chapter: Chapter) =
Pgn(
tags = makeTags(study, chapter),
turns = toTurns(chapter.root)(flags),
turns = toTurns(chapter.root)(flags).toList,
initial = Initial(
chapter.root.comments.list.map(_.text.value) ::: shapeComment(chapter.root.shapes).toList
)
@ -105,7 +105,7 @@ object PgnDump {
result = none,
variations = flags.variations ?? {
variations.view.map { child =>
toTurns(child.mainline, noVariations)
toTurns(child.mainline, noVariations).toList
}.toList
},
secondsLeft = flags.clocks ?? node.clock.map(_.roundSeconds)
@ -138,34 +138,37 @@ object PgnDump {
black = second map { node2move(_, first.children.variations) }
)
def toTurns(root: Node.Root)(implicit flags: WithFlags): List[chessPgn.Turn] =
def toTurns(root: Node.Root)(implicit flags: WithFlags): Vector[chessPgn.Turn] =
toTurns(root.mainline, root.children.variations)
def toTurns(line: List[Node], variations: Variations)(implicit flags: WithFlags): List[chessPgn.Turn] = {
def toTurns(
line: Vector[Node],
variations: Variations
)(implicit flags: WithFlags): Vector[chessPgn.Turn] = {
line match {
case Nil => Nil
case first :: rest if first.ply % 2 == 0 =>
case Vector() => Vector()
case first +: rest if first.ply % 2 == 0 =>
chessPgn.Turn(
number = 1 + (first.ply - 1) / 2,
white = none,
black = node2move(first, variations).some
) :: toTurnsFromWhite(rest, first.children.variations)
) +: toTurnsFromWhite(rest, first.children.variations)
case l => toTurnsFromWhite(l, variations)
}
}.filterNot(_.isEmpty)
def toTurnsFromWhite(line: List[Node], variations: Variations)(implicit
def toTurnsFromWhite(line: Vector[Node], variations: Variations)(implicit
flags: WithFlags
): List[chessPgn.Turn] =
): Vector[chessPgn.Turn] =
line
.grouped(2)
.foldLeft(variations -> List.empty[chessPgn.Turn]) { case variations ~ turns ~ pair =>
.foldLeft(variations -> Vector.empty[chessPgn.Turn]) { case variations ~ turns ~ pair =>
pair.headOption.fold(variations -> turns) { first =>
pair
.lift(1)
.getOrElse(first)
.children
.variations -> (toTurn(first, pair lift 1, variations) :: turns)
.variations -> (toTurn(first, pair lift 1, variations) +: turns)
}
}
._2

View File

@ -57,7 +57,7 @@ object ServerEval {
case Study.WithChapter(_, chapter) =>
(complete ?? chapterRepo.completeServerEval(chapter)) >> {
lila.common.Future
.fold(chapter.root.mainline zip analysis.infoAdvices)(Path.root) {
.fold(chapter.root.mainline.zip(analysis.infoAdvices).toList)(Path.root) {
case (path, (node, (info, advOpt))) =>
info.eval.score
.ifTrue {

View File

@ -296,34 +296,35 @@ final class StudyApi(
}
}
def promote(studyId: Study.Id, position: Position.Ref, toMainline: Boolean)(who: Who) =
sequenceStudyWithChapter(studyId, position.chapterId) { case Study.WithChapter(study, chapter) =>
Contribute(who.u, study) {
chapter.updateRoot { root =>
root.withChildren { children =>
if (toMainline) children.promoteToMainlineAt(position.path)
else children.promoteUpAt(position.path).map(_._1)
}
} match {
case Some(newChapter) =>
chapterRepo.update(newChapter) >>-
sendTo(study.id)(_.promote(position, toMainline, who)) >>
newChapter.root.children
.nodesOn {
newChapter.root.mainlinePath.intersect(position.path)
}
.collect {
case (node, path) if node.forceVariation =>
doForceVariation(Study.WithChapter(study, newChapter), path, force = false, who)
}
.sequenceFu
.void
case None =>
fufail(s"Invalid promoteToMainline $studyId $position") >>-
reloadSriBecauseOf(study, who.sri, chapter.id)
}
}
}
def promote(studyId: Study.Id, position: Position.Ref, toMainline: Boolean)(who: Who): Funit =
??? // TODO
// sequenceStudyWithChapter(studyId, position.chapterId) { case Study.WithChapter(study, chapter) =>
// Contribute(who.u, study) {
// chapter.updateRoot { root =>
// root.withChildren { children =>
// if (toMainline) children.promoteToMainlineAt(position.path)
// else children.promoteUpAt(position.path).map(_._1)
// }
// } match {
// case Some(newChapter) =>
// chapterRepo.update(newChapter) >>-
// sendTo(study.id)(_.promote(position, toMainline, who)) >>
// newChapter.root.children
// .nodesOn {
// newChapter.root.mainlinePath.intersect(position.path)
// }
// .collect {
// case (node, path) if node.forceVariation =>
// doForceVariation(Study.WithChapter(study, newChapter), path, force = false, who)
// }
// .sequenceFu
// .void
// case None =>
// fufail(s"Invalid promoteToMainline $studyId $position") >>-
// reloadSriBecauseOf(study, who.sri, chapter.id)
// }
// }
// }
def forceVariation(studyId: Study.Id, position: Position.Ref, force: Boolean)(who: Who): Funit =
sequenceStudyWithChapter(studyId, position.chapterId) { sc =>

View File

@ -17,27 +17,43 @@ private object StudyFlatTree {
}.copy(children = children | Node.emptyChildren)
}
def rootChildren(flatTree: Bdoc): Children = traverse {
flatTree.elements.toList
.collect {
case el if el.name.nonEmpty => FlatNode(Path(el.name), el.value.asOpt[Bdoc].get)
object reader {
def rootChildren(flatTree: Bdoc): Children = traverse {
flatTree.elements.toList
.collect {
case el if el.name.nonEmpty => FlatNode(Path(el.name), el.value.asOpt[Bdoc].get)
}
.sortBy(-_.depth)
}
private def traverse(children: List[FlatNode]): Children =
children
.foldLeft(Map.empty[Path, Children]) { case (allChildren, flat) =>
update(allChildren, flat)
}
.get(Path.root) | Node.emptyChildren
// assumes that node has a greater depth than roots (sort beforehand)
private def update(roots: Map[Path, Children], flat: FlatNode): Map[Path, Children] = {
val node = flat.toNodeWithChildren(roots get flat.path)
roots.removed(flat.path).updatedWith(flat.path.init) {
case None => Children(Vector(node)).some
case Some(siblings) => siblings.addNode(node).some
}
.sortBy(-_.depth)
}
}
private def traverse(children: List[FlatNode]): Children =
children
.foldLeft(Map.empty[Path, Children]) { case (allChildren, flat) =>
update(allChildren, flat)
}
.get(Path.root) | Node.emptyChildren
object writer {
// assumes that node has a greater depth than roots (sort beforehand)
private def update(roots: Map[Path, Children], flat: FlatNode): Map[Path, Children] = {
val node = flat.toNodeWithChildren(roots get flat.path)
roots.removed(flat.path).updatedWith(flat.path.init) {
case None => Children(Vector(node)).some
case Some(siblings) => siblings.addNode(node).some
def rootChildren(root: Node.Root): Vector[(String, Bdoc)] =
root.children.nodes.flatMap { traverse(_, Path.root) }
def traverse(node: Node, parentPath: Path): Vector[(String, Bdoc)] = {
val path = parentPath + node.id
node.children.nodes.flatMap {
traverse(_, path)
} appended (path.toString -> writeNode(node))
}
}
}

View File

@ -8,7 +8,7 @@ object Dependencies {
val scalalib = "com.github.ornicar" %% "scalalib" % "7.0.2"
val hasher = "com.roundeights" %% "hasher" % "1.2.1"
val jodaTime = "joda-time" % "joda-time" % "2.10.9"
val chess = "org.lichess" %% "scalachess" % "10.1.7"
val chess = "org.lichess" %% "scalachess" % "10.2.0"
val compression = "org.lichess" %% "compression" % "1.6"
val maxmind = "com.sanoma.cda" %% "maxmind-geoip2-scala" % "1.3.1-THIB"
val prismic = "io.prismic" %% "scala-kit" % "1.2.19-THIB213"