complete import rewrite, massive speedup

anand
Thibault Duplessis 2016-02-26 12:49:49 +07:00
parent f14b94e783
commit 34e5697c11
11 changed files with 36 additions and 87 deletions

View File

@ -45,7 +45,8 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
case (Some(w), _, Mate) => s"${playerText(w)} won by checkmate"
case (_, Some(l), Resign | Timeout | Cheat | NoStart) => s"${playerText(l)} resigned"
case (_, Some(l), Outoftime) => s"${playerText(l)} forfeits by time"
case (_, _, Draw | Stalemate) => "Game is a draw"
case (Some(w), _, UnknownFinish) => s"${playerText(w)} won"
case (_, _, Draw | Stalemate | UnknownFinish) => "Game is a draw"
case (_, _, Aborted) => "Game has been aborted"
case (_, _, VariantEnd) => game.variant match {
case chess.variant.KingOfTheHill => "King in the center"
@ -144,7 +145,8 @@ trait GameHelper { self: I18nHelper with UserHelper with AiHelper with StringHel
case Some(p) if p.color.white => trans.whiteResigned()
case _ => trans.blackResigned()
}
case S.Stalemate => trans.stalemate()
case S.UnknownFinish => trans.finished()
case S.Stalemate => trans.stalemate()
case S.Timeout => game.loser match {
case Some(p) if p.color.white => trans.whiteLeftTheGame()
case Some(_) => trans.blackLeftTheGame()

View File

@ -7,16 +7,7 @@
@moreJs = {
@embedJs {
$("#import_game form").submit(function() {
var pgn = $(this).find('textarea').val();
var nbMoves = parseInt(pgn.replace(/\n/g, ' ').replace(/^.+\s(\d+)\..+$/, '$1'), 10);
var delay = 50;
var duration = nbMoves * delay * 2 + 500;
$(this).find('button').hide().end()
.find('.error').hide().end()
.find('.progression').show().animate({
width: '100%'
}, duration);
return true;
setTimeout(function() { $(this).html(lichess.spinnerHtml); }.bind(this), 50);
});
}
}
@ -51,7 +42,6 @@ description = "When pasting a game PGN, you get a browsable replay, a computer a
<label for="import_analyse">@trans.requestAComputerAnalysis()</label>
</p>
<button class="submit text button" type="submit" data-icon="G">@trans.importGame()</button>
<div class="progression"></div>
</form>
</div>
}

@ -1 +1 @@
Subproject commit 506053184734ffe3ab46122c5bf1d01a9dc27c77
Subproject commit 7d6b7cf377ca1a827b9e6fa67ec57383541ae7f8

View File

@ -82,6 +82,7 @@ final class PgnDump(
case Timeout | Outoftime => "Time forfeit"
case Resign | Draw | Stalemate | Mate | VariantEnd => "Normal"
case Cheat => "Rules infraction"
case UnknownFinish => "Unknown"
}
})
) ::: customStartPosition(game.variant).??(List(

View File

@ -48,8 +48,8 @@ case class ImportData(pgn: String, analyse: Option[String]) {
}
val result = tag(_.Result) ifFalse game.situation.end collect {
case "1-0" => Result(Status.Resign, Color.White.some)
case "0-1" => Result(Status.Resign, Color.Black.some)
case "1-0" => Result(Status.UnknownFinish, Color.White.some)
case "0-1" => Result(Status.UnknownFinish, Color.Black.some)
case "1/2-1/2" => Result(Status.Draw, none)
}
@ -60,14 +60,16 @@ case class ImportData(pgn: String, analyse: Option[String]) {
}
val dbGame = Game.make(
game = ChessGame(board = initBoard | (Board init variant)),
game = replay.state,
whitePlayer = Player.white withName name(_.White, _.WhiteElo),
blackPlayer = Player.black withName name(_.Black, _.BlackElo),
mode = Mode.Casual,
variant = variant,
source = Source.Import,
pgnImport = PgnImport.make(user = user, date = date, pgn = pgn).some
).start
).copy(
binaryPgn = BinaryFormat.pgn write replay.state.pgnMoves
).start
Preprocessed(dbGame, replay, result)
}

View File

@ -5,7 +5,7 @@ import scala.concurrent.Future
import akka.actor.ActorRef
import akka.pattern.{ ask, after }
import chess.{ Color, MoveOrDrop, Status }
import chess.{ Color, MoveOrDrop, Status, Situation }
import makeTimeout.large
import lila.db.api._
@ -23,44 +23,37 @@ final class Importer(
def gameExists(processing: => Fu[Game]): Fu[Game] =
GameRepo.findPgnImport(data.pgn) flatMap { _.fold(processing)(fuccess) }
def applyResult(game: Game, result: Result) {
result match {
case Result(Status.Draw, _) => roundMap ! Tell(game.id, ImportDraw)
case Result(Status.Resign, Some(color)) => roundMap ! Tell(game.id, ImportResign(!color))
case _ =>
def applyResult(game: Game, result: Option[Result], situation: Situation): Game =
if (game.finished("finished")) game
else situation.status("status") match {
case Some(status) => game.finish(status, situation.winner).game
case _ => result("status").fold(game) {
case Result(Status.Draw, _) => game.finish(Status.Draw, None).game
case Result(Status.Resign, winner) => game.finish(Status.Resign, winner).game
case Result(Status.UnknownFinish, winner) => game.finish(Status.UnknownFinish, winner).game
case _ => game
}
}
}
def applyMoves(pov: Pov, moves: List[MoveOrDrop]): Funit = moves match {
case Nil => after(delay, scheduler)(funit)
case move :: rest =>
after(delay, scheduler)(Future(applyMove(pov, move))) >> applyMoves(!pov, rest)
}
def applyMove(pov: Pov, move: MoveOrDrop) {
roundMap ! Tell(pov.gameId, ImportPlay(
playerId = pov.playerId,
move.fold(_.toUci, _.toUci)))
}
gameExists {
(data preprocess user).future flatMap {
case Preprocessed(g, replay, result) =>
val game = forceId.fold(g)(g.withId).start
val started = forceId.fold(g)(g.withId).start
val game = applyResult(started, result, replay.state.situation)
(GameRepo insertDenormalized game) >> {
game.pgnImport.flatMap(_.user).isDefined ?? GameRepo.setImportCreatedAt(game)
} >> applyMoves(Pov(game, Color.white), replay.chronoMoves) >>-
(result foreach { r => applyResult(game, r) }) >>
(GameRepo game game.id).map(_ | game)
} >> {
GameRepo.finish(
id = game.id,
winnerColor = game.winnerColor,
winnerId = None,
status = game.status)
} inject game
}
}
}
def inMemory(data: ImportData): Valid[Game] = data.preprocess(user = none).map {
case Preprocessed(game, replay, _) =>
game.copy(
id = "synthetic",
binaryPgn = lila.game.BinaryFormat.pgn write replay.state.pgnMoves
)
case Preprocessed(game, replay, _) => game withId "synthetic"
}
}

View File

@ -89,7 +89,7 @@ object Termination {
case S.Stalemate => Stalemate
case S.Mate | S.VariantEnd => Checkmate
case S.Cheat => Resignation
case S.Created | S.Started | S.Aborted | S.NoStart =>
case S.Created | S.Started | S.Aborted | S.NoStart | S.UnknownFinish =>
logwarn(s"[insight] Unfinished game in the insight indexer")
Resignation
}

View File

@ -5,7 +5,7 @@ import chess.Pos.posAt
import chess.{ Status, Role, Color, MoveOrDrop }
import scalaz.Validation.FlatMap._
import actorApi.round.{ HumanPlay, AiPlay, ImportPlay, DrawNo, TakebackNo, PlayResult, Cheat, ForecastPlay }
import actorApi.round.{ HumanPlay, AiPlay, DrawNo, TakebackNo, PlayResult, Cheat, ForecastPlay }
import akka.actor.ActorRef
import lila.game.{ Game, GameRepo, Pov, Progress, UciMemo }
import lila.hub.actorApi.map.Tell
@ -61,19 +61,6 @@ private[round] final class Player(
}
}
def importMove(play: ImportPlay)(pov: Pov): Fu[Events] = play match {
case ImportPlay(playerId, uci) => pov match {
case Pov(game, color) if game.turnOf(color) && game.playableEvenImported =>
game.toChess(uci).future.flatMap {
case (newChessGame, moveOrDrop) =>
val progress = game.update(newChessGame, moveOrDrop)
(GameRepo save progress) >>-
(progress.game.finished ?? moveFinish(progress.game, color)) inject Nil
}
case _ => ClientErrorException.future(s"$pov import move refused for some reason")
}
}
def ai(game: Game): Fu[Progress] =
(game.playable && game.player.isAi).fold(
engine.play(game, game.aiLevel | 1) flatMap {

View File

@ -63,11 +63,6 @@ private[round] final class Round(
}
} >>- monitorMove((nowMillis - p.atMillis).toInt.some)
case p: ImportPlay =>
handle(p.playerId) { pov =>
player.importMove(p)(pov)
}
case AiPlay => handle { game =>
game.playableByAi ?? {
player ai game map (_.events)
@ -82,14 +77,6 @@ private[round] final class Round(
pov.game.resignable ?? finisher.other(pov.game, _.Resign, Some(!pov.color))
}
case ImportResign(color) => handle(color) { pov =>
(pov.game.isPgnImport && !pov.game.finished) ?? finisher.other(pov.game, _.Resign, Some(!color))
}
case ImportDraw => handle { game =>
(game.isPgnImport && !game.finished) ?? finisher.other(game, _.Draw, none)
}
case GoBerserk(color) => handle(color) { pov =>
pov.game.goBerserk(color) ?? { progress =>
messenger.system(pov.game, (_.untranslated(

View File

@ -101,10 +101,6 @@ case class HumanPlay(
val atMillis = nowMillis
}
case class ImportPlay(playerId: String, uci: Uci)
case class ImportResign(color: Color)
case object ImportDraw
case object AiPlay
case class PlayResult(events: Events, fen: String, lastMove: Option[String])

View File

@ -44,12 +44,3 @@
#import_game form .wait {
display: none;
}
#import_game .progression {
margin: 20px 0;
width: 0;
display: none;
height: 2em;
border-radius: 5px;
border: 1px solid #888;
}