remove puzzle spaced repetition - closes #3112

pull/3259/head
Thibault Duplessis 2017-07-09 12:04:34 +02:00
parent 3ff4238508
commit badb7abf9d
6 changed files with 28 additions and 123 deletions

View File

@ -125,7 +125,6 @@ puzzle {
collection {
puzzle = puzzle
round = puzzle_round
learning = puzzle_learning
vote = puzzle_vote
head = puzzle_head
}

View File

@ -17,7 +17,6 @@ final class Env(
private val settings = new {
val CollectionPuzzle = config getString "collection.puzzle"
val CollectionRound = config getString "collection.round"
val CollectionLearning = config getString "collection.learning"
val CollectionVote = config getString "collection.vote"
val CollectionHead = config getString "collection.head"
val ApiToken = config getString "api.token"
@ -38,7 +37,6 @@ final class Env(
lazy val api = new PuzzleApi(
puzzleColl = puzzleColl,
roundColl = roundColl,
learningColl = learningColl,
voteColl = voteColl,
headColl = headColl,
puzzleIdMin = PuzzleIdMin,
@ -78,7 +76,6 @@ final class Env(
private[puzzle] lazy val puzzleColl = db(CollectionPuzzle)
private[puzzle] lazy val roundColl = db(CollectionRound)
private[puzzle] lazy val learningColl = db(CollectionLearning)
private[puzzle] lazy val voteColl = db(CollectionVote)
private[puzzle] lazy val headColl = db(CollectionHead)
}

View File

@ -16,32 +16,30 @@ private[puzzle] final class Finisher(
def apply(puzzle: Puzzle, user: User, result: Result): Fu[(Round, Mode)] =
api.head.find(user) flatMap {
case Some(PuzzleHead(_, Some(c), _)) if c == puzzle.id =>
api.head.solved(user, puzzle.id) >>
api.learning.update(user, puzzle, result).flatMap { isLearning =>
val userRating = user.perfs.puzzle.toRating
val puzzleRating = puzzle.perf.toRating
updateRatings(userRating, puzzleRating,
result = result.win.fold(Glicko.Result.Win, Glicko.Result.Loss),
isLearning = isLearning)
val date = DateTime.now
val puzzlePerf = puzzle.perf.addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id} user")(puzzleRating)
val userPerf = user.perfs.puzzle.addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id}")(userRating, date)
val a = new Round(
puzzleId = puzzle.id,
userId = user.id,
date = date,
result = result,
rating = user.perfs.puzzle.intRating,
ratingDiff = userPerf.intRating - user.perfs.puzzle.intRating
)
(api.round add a) >> {
puzzleColl.update(
$id(puzzle.id),
$inc(Puzzle.BSONFields.attempts -> $int(1)) ++
$set(Puzzle.BSONFields.perf -> PuzzlePerf.puzzlePerfBSONHandler.write(puzzlePerf))
) zip UserRepo.setPerf(user.id, PerfType.Puzzle, userPerf)
} inject (a -> Mode.Rated)
}
api.head.solved(user, puzzle.id) >> {
val userRating = user.perfs.puzzle.toRating
val puzzleRating = puzzle.perf.toRating
updateRatings(userRating, puzzleRating,
result = result.win.fold(Glicko.Result.Win, Glicko.Result.Loss))
val date = DateTime.now
val puzzlePerf = puzzle.perf.addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id} user")(puzzleRating)
val userPerf = user.perfs.puzzle.addOrReset(_.puzzle.crazyGlicko, s"puzzle ${puzzle.id}")(userRating, date)
val a = new Round(
puzzleId = puzzle.id,
userId = user.id,
date = date,
result = result,
rating = user.perfs.puzzle.intRating,
ratingDiff = userPerf.intRating - user.perfs.puzzle.intRating
)
(api.round add a) >> {
puzzleColl.update(
$id(puzzle.id),
$inc(Puzzle.BSONFields.attempts -> $int(1)) ++
$set(Puzzle.BSONFields.perf -> PuzzlePerf.puzzlePerfBSONHandler.write(puzzlePerf))
) zip UserRepo.setPerf(user.id, PerfType.Puzzle, userPerf)
} inject (a -> Mode.Rated)
}
case _ =>
incPuzzleAttempts(puzzle)
val a = new Round(
@ -62,7 +60,7 @@ private[puzzle] final class Finisher(
def incPuzzleAttempts(puzzle: Puzzle) =
puzzleColl.incFieldUnchecked($id(puzzle.id), Puzzle.BSONFields.attempts)
private def updateRatings(u1: Rating, u2: Rating, result: Glicko.Result, isLearning: Boolean) {
private def updateRatings(u1: Rating, u2: Rating, result: Glicko.Result) {
val results = new RatingPeriodResults()
result match {
case Glicko.Result.Draw => results.addDraw(u1, u2)
@ -72,11 +70,6 @@ private[puzzle] final class Finisher(
try {
val (r1, r2) = (u1.getRating, u2.getRating)
system.updateRatings(results)
if (isLearning) {
def mitigate(prev: Double, next: Rating) = next.setRating((next.getRating + prev) / 2)
mitigate(r1, u1)
mitigate(r2, u2)
}
// never take away more than 30 rating points - it just causes upsets
List(r1 -> u1, r2 -> u2).foreach {
case (prev, next) if next.getRating - prev < -30 => next.setRating(prev - 30)

View File

@ -1,35 +0,0 @@
package lila.puzzle
case class Learning(
_id: String, // userId
stackA: List[PuzzleId],
stackB: List[PuzzleId]
) { // puzzleIds being learnt
def id = _id
def nextPuzzleId = (stackA.lastOption, stackB.lastOption, scala.util.Random.nextInt(5)) match {
case (Some(a), _, r) if r != 0 => a.some
case (_, Some(b), r) if r == 0 => b.some
case (Some(a), _, _) => a.some
case (_, Some(b), _) => b.some
case _ => none
}
def failed(puzzleId: PuzzleId): Learning = copy(
stackA = (puzzleId :: stackA.filter(puzzleId !=)) take 50,
stackB = stackB.filter(puzzleId !=)
)
def solved(puzzleId: PuzzleId): Learning =
if (stackA has puzzleId) copy(
stackA = stackA.filter(puzzleId !=),
stackB = (puzzleId :: stackB) take 50
)
else if (stackB has puzzleId)
copy(stackB = stackB.filter(puzzleId !=))
else this
def contains(puzzleId: PuzzleId) =
stackA.has(puzzleId) || stackB.has(puzzleId)
}

View File

@ -11,7 +11,6 @@ import Puzzle.{ BSONFields => F }
private[puzzle] final class PuzzleApi(
puzzleColl: Coll,
roundColl: Coll,
learningColl: Coll,
voteColl: Coll,
headColl: Coll,
puzzleIdMin: PuzzleId,
@ -78,36 +77,6 @@ private[puzzle] final class PuzzleApi(
def add(a: Round) = roundColl insert a void
}
object learning {
private implicit val learningBSONHandler = reactivemongo.bson.Macros.handler[Learning]
def find(user: User): Fu[Option[Learning]] = learningColl.byId[Learning](user.id)
def add(l: Learning) = learningColl insert l void
def update(user: User, puzzle: Puzzle, result: Result): Fu[Boolean] =
if (result.win) solved(user, puzzle.id)
else failed(user, puzzle.id)
def solved(user: User, puzzleId: PuzzleId): Fu[Boolean] = learning find user flatMap {
case None => fuccess(false)
case Some(l) =>
learningColl.update($id(l.id), l solved puzzleId) inject l.contains(puzzleId)
}
def failed(user: User, puzzleId: PuzzleId): Fu[Boolean] = learning find user flatMap {
case None => learning add Learning(user.id, List(puzzleId), List()) inject false
case Some(l) =>
learningColl.update($id(l.id), l failed puzzleId) inject l.contains(puzzleId)
}
def nextPuzzle(user: User): Fu[Option[Puzzle]] = learning find user flatMap {
case None => fuccess(none)
case Some(l) => l.nextPuzzleId ?? puzzle.find
}
}
object vote {
def value(id: PuzzleId, user: User): Fu[Option[Boolean]] =
@ -146,17 +115,7 @@ private[puzzle] final class PuzzleApi(
def find(user: User): Fu[Option[PuzzleHead]] = headColl.byId[PuzzleHead](user.id)
def add(h: PuzzleHead) = headColl update (
$id(h.id),
h,
upsert = true
) void
def addLearning(user: User, puzzleId: PuzzleId) = headColl update (
$id(user.id),
$set(PuzzleHead.BSONFields.current -> puzzleId.some),
upsert = true
) void
def add(h: PuzzleHead) = headColl.update($id(h.id), h, upsert = true) void
def addNew(user: User, puzzleId: PuzzleId) = add(PuzzleHead(user.id, puzzleId.some, puzzleId))

View File

@ -29,17 +29,9 @@ private[puzzle] final class Selector(
api.head.find(user) flatMap {
case Some(PuzzleHead(_, Some(c), _)) => api.puzzle.find(c)
case _ =>
val isLearn = scala.util.Random.nextInt(7) == 0
val next = if (isLearn) api.learning.nextPuzzle(user) flatMap {
case None => newPuzzleForUser(user)
case p => fuccess(p)
newPuzzleForUser(user) flatMap { next =>
next.?? { p => api.head.addNew(user, p.id) } inject next
}
else newPuzzleForUser(user)
(next flatMap {
case Some(p) if isLearn => api.head.addLearning(user, p.id)
case Some(p) => api.head.addNew(user, p.id)
case _ => fuccess(none)
}) >> next
}
}
}.mon(_.puzzle.selector.time) flatten "No puzzles available" addEffect { puzzle =>