lila/modules/round/src/main/Finisher.scala

173 lines
6.0 KiB
Scala
Raw Normal View History

2013-04-08 13:21:03 -06:00
package lila.round
2019-12-13 07:30:20 -07:00
import chess.{ Color, DecayingStats, Status }
2019-12-08 09:58:50 -07:00
import com.github.ghik.silencer.silent
2013-12-21 05:14:50 -07:00
2019-12-02 11:48:11 -07:00
import lila.common.{ Bus, Uptime }
2019-12-13 07:30:20 -07:00
import lila.game.actorApi.{ AbortedBy, FinishGame }
import lila.game.{ Game, GameRepo, Pov, RatingDiffs }
2014-02-17 02:12:19 -07:00
import lila.i18n.I18nKey.{ Select => SelectI18nKey }
2017-01-15 05:26:08 -07:00
import lila.playban.PlaybanApi
import lila.user.{ User, UserRepo }
2013-04-08 13:21:03 -06:00
2019-12-13 07:30:20 -07:00
final private class Finisher(
2019-12-02 09:41:05 -07:00
gameRepo: GameRepo,
userRepo: UserRepo,
2013-04-08 13:21:03 -06:00
messenger: Messenger,
perfsUpdater: PerfsUpdater,
2015-04-25 15:06:44 -06:00
playban: PlaybanApi,
notifier: RoundNotifier,
crosstableApi: lila.game.CrosstableApi,
2019-10-30 02:49:09 -06:00
getSocketStatus: Game => Fu[actorApi.SocketStatus],
isRecentTv: Game.ID => Boolean
) {
2013-04-08 13:21:03 -06:00
def abort(pov: Pov)(implicit proxy: GameProxy): Fu[Events] = apply(pov.game, _.Aborted, None) >>- {
2019-10-30 02:49:09 -06:00
getSocketStatus(pov.game) foreach { ss =>
playban.abort(pov, ss.colorsOnGame)
}
2019-11-29 17:07:51 -07:00
Bus.publish(AbortedBy(pov), "abortGame")
}
2016-03-22 07:06:48 -06:00
def rageQuit(game: Game, winner: Option[Color])(implicit proxy: GameProxy): Fu[Events] =
apply(game, _.Timeout, winner) >>-
2019-12-13 07:30:20 -07:00
winner.?? { color =>
playban.rageQuit(game, !color)
}
2015-04-26 05:04:22 -06:00
def outOfTime(game: Game)(implicit proxy: GameProxy): Fu[Events] = {
if (!game.isCorrespondence && !Uptime.startedSinceSeconds(120) && game.movedAt.isBefore(Uptime.startedAt)) {
logger.info(s"Aborting game last played before JVM boot: ${game.id}")
other(game, _.Aborted, none)
2017-08-23 17:56:39 -06:00
} else {
val winner = Some(!game.player.color) filterNot { color =>
game.variant.insufficientWinningMaterial(game.board, color)
}
apply(game, _.Outoftime, winner) >>-
2019-12-13 07:30:20 -07:00
winner.?? { w =>
playban.flag(game, !w)
}
}
}
def noStart(game: Game)(implicit proxy: GameProxy): Fu[Events] =
game.playerWhoDidNotMove ?? { culprit =>
2019-12-10 14:01:18 -07:00
lila.mon.round.expiration.count.increment()
playban.noStart(Pov(game, culprit))
if (game.isMandatory) apply(game, _.NoStart, Some(!culprit.color))
else apply(game, _.Aborted, None, Some(_.untranslated("Game aborted by server")))
}
2015-04-26 05:04:22 -06:00
def other(
2019-12-13 07:30:20 -07:00
game: Game,
status: Status.type => Status,
winner: Option[Color],
message: Option[SelectI18nKey] = None
)(implicit proxy: GameProxy): Fu[Events] =
apply(game, status, winner, message) >>- playban.other(game, status, winner)
2015-04-26 05:04:22 -06:00
2019-12-13 07:30:20 -07:00
private def recordLagStats(game: Game): Unit =
for {
clock <- game.clock
player <- clock.players.all
lt = player.lag
stats = lt.lagStats
moves = lt.moves if moves > 4
sd <- stats.stdDev
mean = stats.mean if mean > 0
uncompStats = lt.uncompStats
uncompAvg = Math.round(10 * uncompStats.mean)
compEstStdErr <- lt.compEstStdErr
quotaStr = f"${lt.quotaGain.centis / 10}%02d"
compEstOvers = lt.compEstOvers.centis
} {
import lila.mon.round.move.{ lag => lRec }
lRec.mean.record(Math.round(10 * mean))
lRec.stdDev.record(Math.round(10 * sd))
// wikipedia.org/wiki/Coefficient_of_variation#Estimation
lRec.coefVar.record(Math.round((1000f + 250f / moves) * sd / mean))
lRec.uncomped(quotaStr).record(uncompAvg)
uncompStats.stdDev foreach { v =>
lRec.uncompStdDev(quotaStr).record(Math.round(10 * v))
}
lt.lagEstimator match {
case h: DecayingStats => lRec.compDeviation.record(h.deviation.toInt)
}
lRec.compEstStdErr.record(Math.round(1000 * compEstStdErr))
lRec.compEstOverErr.record(Math.round(10f * compEstOvers / moves))
}
2015-04-26 05:04:22 -06:00
private def apply(
2019-12-13 07:30:20 -07:00
game: Game,
makeStatus: Status.type => Status,
@silent winnerC: Option[Color] = None,
message: Option[SelectI18nKey] = None
)(implicit proxy: GameProxy): Fu[Events] = {
2016-03-10 05:25:03 -07:00
val status = makeStatus(Status)
2019-12-13 07:30:20 -07:00
val prog = game.finish(status, winnerC)
if (game.nonAi && game.isCorrespondence) Color.all foreach notifier.gameEnd(prog.game)
2019-12-13 07:30:20 -07:00
lila.mon.game
.finish(
variant = game.variant.key,
source = game.source.fold("unknown")(_.name),
speed = game.speed.name,
mode = game.mode.name,
status = status.name
)
.increment
2017-11-29 21:42:27 -07:00
val g = prog.game
recordLagStats(g)
2017-11-29 21:42:27 -07:00
proxy.save(prog) >>
2019-12-02 09:41:05 -07:00
gameRepo.finish(
2017-11-29 21:42:27 -07:00
id = g.id,
2019-12-08 09:58:50 -07:00
winnerColor = winnerC,
winnerId = winnerC flatMap (g.player(_).userId),
2017-11-29 21:42:27 -07:00
status = prog.game.status
) >>
2019-12-13 07:30:20 -07:00
userRepo
.pair(
g.whitePlayer.userId,
g.blackPlayer.userId
)
.flatMap {
case (whiteO, blackO) => {
2017-11-29 21:42:27 -07:00
val finish = FinishGame(g, whiteO, blackO)
updateCountAndPerfs(finish) map { ratingDiffs =>
message foreach { messenger.system(g, _) }
2019-12-02 09:41:05 -07:00
gameRepo game g.id foreach { newGame =>
2019-08-20 02:30:09 -06:00
newGame foreach proxy.setFinishedGame
2019-11-29 17:07:51 -07:00
Bus.publish(finish.copy(game = newGame | g), "finishGame")
}
2017-11-29 21:42:27 -07:00
prog.events :+ lila.game.Event.EndData(g, ratingDiffs)
2017-01-15 05:26:08 -07:00
}
2017-11-29 21:42:27 -07:00
}
}
2019-08-20 02:30:09 -06:00
}
2013-12-21 05:14:50 -07:00
private def updateCountAndPerfs(finish: FinishGame): Fu[Option[RatingDiffs]] =
2014-04-20 14:26:40 -06:00
(!finish.isVsSelf && !finish.game.aborted) ?? {
(finish.white |@| finish.black).tupled ?? {
case (white, black) =>
crosstableApi.add(finish.game) zip perfsUpdater.save(finish.game, white, black) map {
case _ ~ ratingDiffs => ratingDiffs
}
2014-04-20 14:26:40 -06:00
} zip
(finish.white ?? incNbGames(finish.game)) zip
(finish.black ?? incNbGames(finish.game)) map {
2019-12-13 07:30:20 -07:00
case ratingDiffs ~ _ ~ _ => ratingDiffs
}
2014-04-20 14:26:40 -06:00
}
2013-12-27 06:25:49 -07:00
private def incNbGames(game: Game)(user: User): Funit = game.finished ?? {
val totalTime = (game.hasClock && user.playTime.isDefined) ?? game.durationSeconds
2019-12-13 07:30:20 -07:00
val tvTime = totalTime ifTrue isRecentTv(game.id)
2017-04-15 05:11:31 -06:00
val result =
if (game.winnerUserId has user.id) 1
else if (game.loserUserId has user.id) -1
else 0
2019-12-13 07:30:20 -07:00
userRepo
.incNbGames(user.id, game.rated, game.hasAi, result = result, totalTime = totalTime, tvTime = tvTime)
.void
2013-12-21 05:14:50 -07:00
}
2013-04-08 13:21:03 -06:00
}