2016-03-11 20:32:21 -07:00
|
|
|
package lila.fishnet
|
|
|
|
|
2021-11-17 00:48:10 -07:00
|
|
|
import chess.format.Uci
|
2019-12-13 07:30:20 -07:00
|
|
|
import chess.{ Black, Clock, White }
|
2021-11-17 00:48:10 -07:00
|
|
|
import scala.concurrent.duration._
|
2016-03-11 20:32:21 -07:00
|
|
|
|
2021-11-17 00:48:10 -07:00
|
|
|
import lila.common.{ Bus, Future, ThreadLocalRandom }
|
2016-03-11 20:32:21 -07:00
|
|
|
import lila.game.{ Game, GameRepo, UciMemo }
|
2021-11-05 02:26:21 -06:00
|
|
|
import lila.hub.actorApi.map.Tell
|
|
|
|
import lila.hub.actorApi.round.FishnetPlay
|
2016-03-11 20:32:21 -07:00
|
|
|
|
2021-04-13 02:12:51 -06:00
|
|
|
final class FishnetPlayer(
|
2019-10-21 08:37:53 -06:00
|
|
|
redis: FishnetRedis,
|
2021-11-05 02:26:21 -06:00
|
|
|
openingBook: FishnetOpeningBook,
|
2019-12-01 19:04:35 -07:00
|
|
|
gameRepo: GameRepo,
|
2016-09-05 08:15:24 -06:00
|
|
|
uciMemo: UciMemo,
|
2017-02-14 08:34:07 -07:00
|
|
|
val maxPlies: Int
|
2020-06-24 03:37:18 -06:00
|
|
|
)(implicit
|
|
|
|
ec: scala.concurrent.ExecutionContext,
|
|
|
|
system: akka.actor.ActorSystem
|
|
|
|
) {
|
2016-03-12 10:00:27 -07:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
def apply(game: Game): Funit =
|
|
|
|
game.aiLevel ?? { level =>
|
|
|
|
Future.delay(delayFor(game) | 0.millis) {
|
2021-11-05 02:26:21 -06:00
|
|
|
openingBook(game, level) flatMap {
|
|
|
|
case Some(move) =>
|
|
|
|
fuccess {
|
2021-11-19 23:32:25 -07:00
|
|
|
Bus.publish(Tell(game.id, FishnetPlay(move, game.playedTurns)), "roundSocket")
|
2021-11-05 02:26:21 -06:00
|
|
|
}
|
|
|
|
case None => makeWork(game, level) addEffect redis.request void
|
|
|
|
}
|
2019-12-13 07:30:20 -07:00
|
|
|
}
|
2020-09-21 01:28:28 -06:00
|
|
|
} recover { case e: Exception =>
|
|
|
|
logger.info(e.getMessage)
|
2018-05-08 14:53:05 -06:00
|
|
|
}
|
2016-03-11 20:32:21 -07:00
|
|
|
|
2019-12-13 07:30:20 -07:00
|
|
|
private val delayFactor = 0.011f
|
2018-05-08 14:53:05 -06:00
|
|
|
private val defaultClock = Clock(300, 0)
|
|
|
|
|
2018-12-26 19:56:14 -07:00
|
|
|
private def delayFor(g: Game): Option[FiniteDuration] =
|
2019-11-26 20:02:46 -07:00
|
|
|
if (!g.bothPlayersHaveMoved) 2.seconds.some
|
2019-12-13 07:30:20 -07:00
|
|
|
else
|
|
|
|
for {
|
|
|
|
pov <- g.aiPov
|
|
|
|
clock = g.clock | defaultClock
|
|
|
|
totalTime = clock.estimateTotalTime.centis
|
|
|
|
if totalTime > 20 * 100
|
|
|
|
delay = (clock.remainingTime(pov.color).centis atMost totalTime) * delayFactor
|
|
|
|
accel = 1 - ((g.turns - 20) atLeast 0 atMost 100) / 150f
|
|
|
|
sleep = (delay * accel) atMost 500
|
|
|
|
if sleep > 25
|
|
|
|
millis = sleep * 10
|
2021-06-24 05:08:23 -06:00
|
|
|
randomized = millis + millis * (ThreadLocalRandom.nextDouble() - 0.5)
|
2019-12-13 07:30:20 -07:00
|
|
|
divided = randomized / (if (g.turns > 9) 1 else 2)
|
|
|
|
} yield divided.millis
|
2018-05-08 14:53:05 -06:00
|
|
|
|
2016-03-12 05:56:44 -07:00
|
|
|
private def makeWork(game: Game, level: Int): Fu[Work.Move] =
|
2018-01-23 08:41:42 -07:00
|
|
|
if (game.situation playable true)
|
2019-12-01 19:04:35 -07:00
|
|
|
if (game.turns <= maxPlies) gameRepo.initialFen(game) zip uciMemo.get(game) map {
|
2019-12-13 07:30:20 -07:00
|
|
|
case (initialFen, moves) =>
|
|
|
|
Work.Move(
|
|
|
|
_id = Work.makeId,
|
|
|
|
game = Work.Game(
|
|
|
|
id = game.id,
|
|
|
|
initialFen = initialFen,
|
|
|
|
studyId = none,
|
|
|
|
variant = game.variant,
|
|
|
|
moves = moves mkString " "
|
|
|
|
),
|
|
|
|
level =
|
|
|
|
if (level < 3 && game.clock.exists(_.config.limit.toSeconds < 60)) 3
|
|
|
|
else level,
|
|
|
|
clock = game.clock.map { clk =>
|
|
|
|
Work.Clock(
|
|
|
|
wtime = clk.remainingTime(White).centis,
|
|
|
|
btime = clk.remainingTime(Black).centis,
|
|
|
|
inc = clk.incrementSeconds
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
2016-03-12 01:43:52 -07:00
|
|
|
}
|
2016-03-12 10:00:27 -07:00
|
|
|
else fufail(s"[fishnet] Too many moves (${game.turns}), won't play ${game.id}")
|
2016-03-24 04:12:05 -06:00
|
|
|
else fufail(s"[fishnet] invalid position on ${game.id}")
|
2016-03-11 20:32:21 -07:00
|
|
|
}
|