<commit_before>package lila.bot

import akka.actor._

import scala.concurrent.duration._
import scala.concurrent.Promise

import chess.format.Uci
import lila.game.{ Game, GameRepo, Pov }
import lila.hub.actorApi.map.Tell
import lila.hub.actorApi.round.{ Abort, BotPlay, RematchNo, RematchYes, Resign }
import lila.round.actorApi.round.{ DrawNo, DrawYes }
import lila.user.User

final class BotPlayer(
    chatActor: ActorSelection,
    isOfferingRematch: Pov => Boolean
)(implicit system: ActorSystem) {

  def apply(pov: Pov, me: User, uciStr: String, offeringDraw: Option[Boolean]): Funit =
    lila.common.Future.delay((pov.game.hasAi ?? 500) millis) {
      Uci(uciStr).fold(fufail[Unit](s"Invalid UCI: $uciStr")) { uci =>
        lila.mon.bot.moves(me.username)()
        if (!pov.isMyTurn) fufail("Not your turn, or game already over")
        else {
          val promise = Promise[Unit]
          if (pov.player.isOfferingDraw && (offeringDraw contains false)) declineDraw(pov)
          else if (!pov.player.isOfferingDraw && (offeringDraw contains true)) offerDraw(pov)
          system.lilaBus.publish(
            Tell(pov.gameId, BotPlay(pov.playerId, uci, promise.some)),
            'roundMapTell
          )
          promise.future
        }
      }
    }

  def chat(gameId: Game.ID, me: User, d: BotForm.ChatData) = fuccess {
    lila.mon.bot.chats(me.username)()
    val chatId = lila.chat.Chat.Id {
      if (d.room == "player") gameId else s"$gameId/w"
    }
    val source = d.room == "spectator" option {
      lila.hub.actorApi.shutup.PublicSource.Watcher(gameId)
    }
    chatActor ! lila.chat.actorApi.UserTalk(chatId, me.id, d.text, publicSource = source)
  }

  def rematchAccept(id: Game.ID, me: User): Fu[Boolean] = rematch(id, me, true)

  def rematchDecline(id: Game.ID, me: User): Fu[Boolean] = rematch(id, me, false)

  private def rematch(id: Game.ID, me: User, accept: Boolean): Fu[Boolean] =
    GameRepo game id map {
      _.flatMap(Pov(_, me)).filter(isOfferingRematch) ?? { pov =>
        // delay so it feels more natural
        lila.common.Future.delay(if (accept) 100.millis else 2.seconds) {
          fuccess {
            system.lilaBus.publish(
              Tell(pov.gameId, (if (accept) RematchYes else RematchNo)(pov.playerId)),
              'roundMapTell
            )
          }
        }(system)
        true
      }
    }

  def abort(pov: Pov): Funit =<commit_msg>fix bot rematch - closes #5461<commit_after>package lila.bot

import akka.actor._

import scala.concurrent.duration._
import scala.concurrent.Promise

import chess.format.Uci
import lila.game.{ Game, GameRepo, Pov }
import lila.hub.actorApi.map.Tell
import lila.hub.actorApi.round.{ Abort, BotPlay, RematchNo, RematchYes, Resign }
import lila.round.actorApi.round.{ DrawNo, DrawYes }
import lila.user.User

final class BotPlayer(
    chatActor: ActorSelection,
    isOfferingRematch: Pov => Boolean
)(implicit system: ActorSystem) {

  def apply(pov: Pov, me: User, uciStr: String, offeringDraw: Option[Boolean]): Funit =
    lila.common.Future.delay((pov.game.hasAi ?? 500) millis) {
      Uci(uciStr).fold(fufail[Unit](s"Invalid UCI: $uciStr")) { uci =>
        lila.mon.bot.moves(me.username)()
        if (!pov.isMyTurn) fufail("Not your turn, or game already over")
        else {
          val promise = Promise[Unit]
          if (pov.player.isOfferingDraw && (offeringDraw contains false)) declineDraw(pov)
          else if (!pov.player.isOfferingDraw && (offeringDraw contains true)) offerDraw(pov)
          system.lilaBus.publish(
            Tell(pov.gameId, BotPlay(pov.playerId, uci, promise.some)),
            'roundMapTell
          )
          promise.future
        }
      }
    }

  def chat(gameId: Game.ID, me: User, d: BotForm.ChatData) = fuccess {
    lila.mon.bot.chats(me.username)()
    val chatId = lila.chat.Chat.Id {
      if (d.room == "player") gameId else s"$gameId/w"
    }
    val source = d.room == "spectator" option {
      lila.hub.actorApi.shutup.PublicSource.Watcher(gameId)
    }
    chatActor ! lila.chat.actorApi.UserTalk(chatId, me.id, d.text, publicSource = source)
  }

  def rematchAccept(id: Game.ID, me: User): Fu[Boolean] = rematch(id, me, true)

  def rematchDecline(id: Game.ID, me: User): Fu[Boolean] = rematch(id, me, false)

  private def rematch(id: Game.ID, me: User, accept: Boolean): Fu[Boolean] =
    GameRepo game id map {
      _.flatMap(Pov(_, me)).filter(p => isOfferingRematch(!p)) ?? { pov =>
        // delay so it feels more natural
        lila.common.Future.delay(if (accept) 100.millis else 2.seconds) {
          fuccess {
            system.lilaBus.publish(
              Tell(pov.gameId, (if (accept) RematchYes else RematchNo)(pov.playerId)),
              'roundMapTell
            )
          }
        }(system)
        true
      }
    }

  def abort(pov: Pov): Funit =
{'fraction_non_alphanumeric': 0.13105004101722723, 'fraction_numerical': 0.0036915504511894994, 'mean_word_length': 2.7835531419705197, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4315476', 'n_tokens_mistral': 1710, 'n_tokens_neox': 1674, 'n_words': 369}