API challenge the AI - closes #6449

pull/6459/head
Thibault Duplessis 2020-04-23 12:34:25 -06:00
parent e4578072a1
commit f4e755582c
7 changed files with 122 additions and 4 deletions

View File

@ -187,9 +187,9 @@ final class Challenge(
def apiCreate(userId: String) = ScopedBody(_.Challenge.Write, _.Bot.Play, _.Board.Play) {
implicit req => me =>
implicit val lang = lila.i18n.I18nLangPicker(req, me.lang)
implicit val lang = reqLang
env.setup.forms.api.bindFromRequest.fold(
jsonFormErrorDefaultLang,
err => BadRequest(apiFormError(err)).fuccess,
config =>
ChallengeIpRateLimit(HTTPRequest lastRemoteAddress req) {
ChallengeUserRateLimit(me.id) {

View File

@ -245,6 +245,19 @@ final class Setup(
}
}
def apiAi = ScopedBody(_.Challenge.Write, _.Bot.Play, _.Board.Play) { implicit req => me =>
implicit val lang = reqLang
PostRateLimit(HTTPRequest lastRemoteAddress req) {
forms.apiAi.bindFromRequest.fold(
jsonFormError,
config =>
processor.apiAi(config, me) map { pov =>
Created(env.game.jsonView(pov.game, config.fen)) as JSON
}
)
}
}
private def process[A](form: Context => Form[A])(op: A => BodyContext[_] => Fu[Pov]) =
OpenBody { implicit ctx =>
PostRateLimit(HTTPRequest lastRemoteAddress ctx.req) {

View File

@ -544,6 +544,7 @@ GET /api/account/email controllers.Account.apiEmail
GET /api/account/kid controllers.Account.apiKid
POST /api/account/kid controllers.Account.apiKidPost
GET /api/account/preferences controllers.Pref.apiGet
POST /api/challenge/ai controllers.Setup.apiAi
POST /api/challenge/:user controllers.Challenge.apiCreate(user: String)
POST /api/challenge/$id<\w{8}>/accept controllers.Challenge.apiAccept(id: String)
POST /api/challenge/$id<\w{8}>/decline controllers.Challenge.apiDecline(id: String)

View File

@ -0,0 +1,80 @@
package lila.setup
import chess.Clock
import chess.format.FEN
import lila.game.{ Game, Player, Pov, Source }
import lila.lobby.Color
import lila.user.User
final case class ApiAiConfig(
variant: chess.variant.Variant,
clock: Option[Clock.Config],
daysO: Option[Int],
color: Color,
level: Int,
fen: Option[FEN] = None
) extends Config
with Positional {
val strictFen = false
def >> = (level, variant.key.some, clock, daysO, color.name.some, fen.map(_.value)).some
val days = ~daysO
val increment = clock.??(_.increment.roundSeconds)
val time = clock.??(_.limit.roundSeconds / 60)
val timeMode =
if (clock.isDefined) TimeMode.RealTime
else if (daysO.isDefined) TimeMode.Correspondence
else TimeMode.Unlimited
def game(user: Option[User]) =
fenGame { chessGame =>
val perfPicker = lila.game.PerfPicker.mainOrDefault(
chess.Speed(chessGame.clock.map(_.config)),
chessGame.situation.board.variant,
makeDaysPerTurn
)
Game
.make(
chess = chessGame,
whitePlayer = creatorColor.fold(
Player.make(chess.White, user, perfPicker),
Player.make(chess.White, level.some)
),
blackPlayer = creatorColor.fold(
Player.make(chess.Black, level.some),
Player.make(chess.Black, user, perfPicker)
),
mode = chess.Mode.Casual,
source = if (chessGame.board.variant.fromPosition) Source.Position else Source.Ai,
daysPerTurn = makeDaysPerTurn,
pgnImport = None
)
.sloppy
} start
def pov(user: Option[User]) = Pov(game(user), creatorColor)
}
object ApiAiConfig extends BaseConfig {
// lazy val clockLimitSeconds: Set[Int] = Set(0, 15, 30, 45, 60, 90) ++ (2 to 180).view.map(60 *).toSet
def <<(
l: Int,
v: Option[String],
cl: Option[Clock.Config],
d: Option[Int],
c: Option[String],
pos: Option[String]
) =
new ApiAiConfig(
variant = chess.variant.Variant.orDefault(~v),
clock = cl,
daysO = d,
color = Color.orDefault(~c),
level = l,
fen = pos map FEN
)
}

View File

@ -15,7 +15,7 @@ final case class ApiConfig(
color: Color,
position: Option[FEN] = None,
acceptByToken: Option[String] = None
) extends {
) {
val strictFen = false

View File

@ -147,6 +147,22 @@ final class FormFactory(
)(ApiConfig.<<)(_.>>).verifying("invalidFen", _.validFen)
)
lazy val apiAi = Form(
mapping(
"level" -> level,
"variant" -> optional(text.verifying(Variant.byKey.contains _)),
"clock" -> optional(
mapping(
"limit" -> number.verifying(ApiConfig.clockLimitSeconds.contains _),
"increment" -> increment
)(chess.Clock.Config.apply)(chess.Clock.Config.unapply)
),
"days" -> optional(days),
"color" -> optional(color),
"fen" -> fenField
)(ApiAiConfig.<<)(_.>>).verifying("invalidFen", _.validFen)
)
def savedConfig(implicit ctx: UserContext): Fu[UserConfig] =
ctx.me.fold(anonConfigRepo config ctx.req)(userConfigRepo.config)
}

View File

@ -4,7 +4,7 @@ import lila.common.Bus
import lila.common.config.Max
import lila.game.Pov
import lila.lobby.actorApi.{ AddHook, AddSeek }
import lila.user.UserContext
import lila.user.{ User, UserContext }
final private[setup] class Processor(
gameCache: lila.game.Cached,
@ -28,6 +28,14 @@ final private[setup] class Processor(
} inject pov
}
def apiAi(config: ApiAiConfig, me: User): Fu[Pov] = {
val pov = config pov me.some
(gameRepo insertDenormalized pov.game) >>-
onStart(pov.gameId) >> {
pov.game.player.isAi ?? fishnetPlayer(pov.game)
} inject pov
}
def hook(
configBase: HookConfig,
sri: lila.socket.Socket.Sri,