<commit_before>package lila.round

import chess.format.{ Forsyth, FEN, Uci }
import chess.Pos.posAt
import chess.{ Status, Role, Color, MoveOrDrop }
import scalaz.Validation.FlatMap._

import actorApi.round.{ HumanPlay, DrawNo, TakebackNo, PlayResult, Cheat, ForecastPlay }
import akka.actor.ActorRef
import lila.game.{ Game, GameRepo, Pov, Progress, UciMemo }
import lila.hub.actorApi.map.Tell
import lila.hub.actorApi.round.MoveEvent
import scala.concurrent.duration._

private[round] final class Player(
    engine: lila.ai.Client,
    fishnetPlayer: lila.fishnet.Player,
    bus: lila.common.Bus,
    finisher: Finisher,
    cheatDetector: CheatDetector,
    uciMemo: UciMemo) {

  def human(play: HumanPlay, round: ActorRef)(pov: Pov): Fu[Events] = play match {
    case HumanPlay(playerId, uci, blur, lag, promiseOption) => pov match {
      case Pov(game, color) if game playableBy color =>
        lila.mon.measure(_.round.move.segment.logic)(applyUci(game, uci, blur, lag)).prefixFailuresWith(s"$pov ")
          .fold(errs => ClientErrorException.future(errs.shows), fuccess).flatMap {
            case (progress, moveOrDrop) =>
              (GameRepo save progress).mon(_.round.move.segment.save) >>-
                (pov.game.hasAi ! uciMemo.add(pov.game, moveOrDrop)) >>-
                notifyMove(moveOrDrop, progress.game) >>
                progress.game.finished.fold(
                  moveFinish(progress.game, color) map { progress.events ::: _ }, {
                    cheatDetector(progress.game) addEffect {
                      case Some(color) => round ! Cheat(color)
                      case None =>
                        if (progress.game.playableByAi) requestFishnet(progress.game)
                        if (pov.opponent.isOfferingDraw) round ! DrawNo(pov.player.id)
                        if (pov.player.isProposingTakeback) round ! TakebackNo(pov.player.id)
                        moveOrDrop.left.toOption.ifTrue(pov.game.forecastable).foreach { move =>
                          round ! ForecastPlay(move)
                        }
                    } inject progress.events
                  }) >>- promiseOption.foreach(_.success(()))
          } addFailureEffect { e =>
            promiseOption.foreach(_ failure e)
          }
      case Pov(game, _) if game.finished           => ClientErrorException.future(s"$pov game is finished")
      case Pov(game, _) if game.aborted            => ClientErrorException.future(s"$pov game is aborted")
      case Pov(game, color) if !game.turnOf(color) => ClientErrorException.future(s"$pov not your turn")
      case _                                       => ClientErrorException.future(s"$pov move refused for some reason")
    }
  }

  def requestFishnet(game: Game) = game.playableByAi ?? fishnetPlayer(game)

  def fishnet(game: Game, fen: FEN, uci: Uci): Fu[Events] =
    if (game.playable && game.player.isAi) {
      if (fen == FEN(Forsyth >> game.toChess))
        applyUci(game, uci, blur = false, lag = 0.millis)
          .fold(errs => ClientErrorException.future(errs.shows), fuccess).flatMap {
            case (progress, moveOrDrop) =>
              (GameRepo save progress) >>-
                uciMemo.add(progress.game, moveOrDrop) >>-
                notifyMove(moveOrDrop, progress.game) >>
                progress.game.finished.fold(
                  moveFinish(progress.game, game.turnColor) map { progress.events ::: _ },
                  fuccess(progress.events)
                )
          }
      else requestFishnet(game)
    }
    else fufail(s"Not AI turn")

  private def applyUci(game: Game, uci: Uci, blur: Boolean, lag: FiniteDuration) = (uci match {
    case Uci.Move(orig, dest, prom) => game.toChess.apply(orig, dest, prom, lag) map {
      case (ncg, move) => ncg -> (Left(move): MoveOrDrop)
    }
    case Uci.Drop(role, pos) => game.toChess.drop(role, pos, lag) map {
      case (ncg, drop) => ncg -> (Right(drop): MoveOrDrop)<commit_msg>fix fishnet FEN check<commit_after>package lila.round

import chess.format.{ Forsyth, FEN, Uci }
import chess.Pos.posAt
import chess.{ Status, Role, Color, MoveOrDrop }
import scalaz.Validation.FlatMap._

import actorApi.round.{ HumanPlay, DrawNo, TakebackNo, PlayResult, Cheat, ForecastPlay }
import akka.actor.ActorRef
import lila.game.{ Game, GameRepo, Pov, Progress, UciMemo }
import lila.hub.actorApi.map.Tell
import lila.hub.actorApi.round.MoveEvent
import scala.concurrent.duration._

private[round] final class Player(
    engine: lila.ai.Client,
    fishnetPlayer: lila.fishnet.Player,
    bus: lila.common.Bus,
    finisher: Finisher,
    cheatDetector: CheatDetector,
    uciMemo: UciMemo) {

  def human(play: HumanPlay, round: ActorRef)(pov: Pov): Fu[Events] = play match {
    case HumanPlay(playerId, uci, blur, lag, promiseOption) => pov match {
      case Pov(game, color) if game playableBy color =>
        lila.mon.measure(_.round.move.segment.logic)(applyUci(game, uci, blur, lag)).prefixFailuresWith(s"$pov ")
          .fold(errs => ClientErrorException.future(errs.shows), fuccess).flatMap {
            case (progress, moveOrDrop) =>
              (GameRepo save progress).mon(_.round.move.segment.save) >>-
                (pov.game.hasAi ! uciMemo.add(pov.game, moveOrDrop)) >>-
                notifyMove(moveOrDrop, progress.game) >>
                progress.game.finished.fold(
                  moveFinish(progress.game, color) map { progress.events ::: _ }, {
                    cheatDetector(progress.game) addEffect {
                      case Some(color) => round ! Cheat(color)
                      case None =>
                        if (progress.game.playableByAi) requestFishnet(progress.game)
                        if (pov.opponent.isOfferingDraw) round ! DrawNo(pov.player.id)
                        if (pov.player.isProposingTakeback) round ! TakebackNo(pov.player.id)
                        moveOrDrop.left.toOption.ifTrue(pov.game.forecastable).foreach { move =>
                          round ! ForecastPlay(move)
                        }
                    } inject progress.events
                  }) >>- promiseOption.foreach(_.success(()))
          } addFailureEffect { e =>
            promiseOption.foreach(_ failure e)
          }
      case Pov(game, _) if game.finished           => ClientErrorException.future(s"$pov game is finished")
      case Pov(game, _) if game.aborted            => ClientErrorException.future(s"$pov game is aborted")
      case Pov(game, color) if !game.turnOf(color) => ClientErrorException.future(s"$pov not your turn")
      case _                                       => ClientErrorException.future(s"$pov move refused for some reason")
    }
  }

  def requestFishnet(game: Game) = game.playableByAi ?? fishnetPlayer(game)

  def fishnet(game: Game, uci: Uci, currentFen: FEN): Fu[Events] =
    if (game.playable && game.player.isAi) {
      if (currentFen == FEN(Forsyth >> game.toChess))
        applyUci(game, uci, blur = false, lag = 0.millis)
          .fold(errs => ClientErrorException.future(errs.shows), fuccess).flatMap {
            case (progress, moveOrDrop) =>
              (GameRepo save progress) >>-
                uciMemo.add(progress.game, moveOrDrop) >>-
                notifyMove(moveOrDrop, progress.game) >>
                progress.game.finished.fold(
                  moveFinish(progress.game, game.turnColor) map { progress.events ::: _ },
                  fuccess(progress.events)
                )
          }
      else requestFishnet(game) >> fufail("Invalid AI move current FEN")
    }
    else fufail("Not AI turn")

  private def applyUci(game: Game, uci: Uci, blur: Boolean, lag: FiniteDuration) = (uci match {
    case Uci.Move(orig, dest, prom) => game.toChess.apply(orig, dest, prom, lag) map {
      case (ncg, move) => ncg -> (Left(move): MoveOrDrop)
    }
    case Uci.Drop(role, pos) => game.toChess.drop(role, pos, lag) map {
      case (ncg, drop) => ncg -> (Right(drop): MoveOrDrop)
{'fraction_non_alphanumeric': 0.12175, 'fraction_numerical': 0.00025, 'mean_word_length': 2.3533109807208716, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5090248', 'n_tokens_mistral': 2528, 'n_tokens_neox': 2458, 'n_words': 521}