unify game API and analysis API

pull/87/head
Thibault Duplessis 2014-06-06 13:52:35 +02:00
parent 5f846df65b
commit a62754f33f
6 changed files with 33 additions and 152 deletions

View File

@ -262,54 +262,6 @@ name | type | default | description
(1) All game statuses: https://github.com/ornicar/scalachess/blob/master/src/main/scala/Status.scala#L16-L25
### `GET /api/analysis` fetch many analysis
This API requires a secret token to work.
Analysis are returned by descendant chronological order.
All parameters are optional.
name | type | default | description
--- | --- | --- | ---
**token** | string | - | security token
**nb** | int | 10 | maximum number of analysis to return
```
> curl http://en.lichess.org/api/analysis?nb=10
```
```javascript
{
"list": [
{
"analysis": [
{
"eval": -26, // board evaluation in centipawns
"move": "e4",
"ply": 1
},
{
"eval": -8,
"move": "b5",
"ply": 2
},
{
"comment": "(-0.08 → -0.66) Inaccuracy. The best move was c4.",
"eval": -66,
"move": "Nfe3",
"ply": 3,
"variation": "c4 bxc4 Nfe3 c5 Qf1 f6 Rxc4 Bb7 b4 Ba6"
},
// ... more moves
],
"game": {
// similar to the game API format, see above
},
"uci": "e2e4 e7e5 d2d4 e5d4 g1f3 g8f6" // UCI compatible game moves
}
]
}
```
### `GET /api/puzzle/<id>` fetch one puzzle
```

View File

@ -9,7 +9,6 @@ object Api extends LilaController {
private val userApi = Env.api.userApi
private val gameApi = Env.api.gameApi
private val analysisApi = Env.api.analysisApi
private val puzzleApi = Env.api.puzzleApi
def user(username: String) = ApiResult { req =>
@ -32,7 +31,7 @@ object Api extends LilaController {
username = get("username", req),
rated = getBoolOpt("rated", req),
analysed = getBoolOpt("analysed", req),
withAnalysis = getBoolOpt("with_analysis", req),
withAnalysis = getBool("with_analysis", req),
token = get("token", req),
nb = getInt("nb", req)
) map (_.some)
@ -41,17 +40,10 @@ object Api extends LilaController {
def game(id: String) = ApiResult { req =>
gameApi.one(
id = id take lila.game.Game.gameIdSize,
withAnalysis = getBoolOpt("with_analysis", req),
withAnalysis = getBool("with_analysis", req),
token = get("token", req))
}
def analysis = ApiResult { req =>
analysisApi.list(
nb = getInt("nb", req),
token = get("token", req)
) map (_.some)
}
def puzzle(id: String) = ApiResult { req =>
(id, parseIntOption(id)) match {
case ("daily", _) => puzzleApi.daily

View File

@ -246,7 +246,6 @@ GET /api/user controllers.Api.users
GET /api/user/:id controllers.Api.user(id: String)
GET /api/game controllers.Api.games
GET /api/game/:id controllers.Api.game(id: String)
GET /api/analysis controllers.Api.analysis
GET /api/puzzle/:id controllers.Api.puzzle(id: String)
POST /api/puzzle controllers.Puzzle.importBatch

View File

@ -1,71 +0,0 @@
package lila.api
import chess.format.pgn.Pgn
import chess.format.UciDump
import chess.Replay
import play.api.libs.json._
import lila.analyse.{ Analysis, AnalysisRepo }
import lila.common.PimpedJson._
import lila.db.api._
import lila.db.Implicits._
import lila.game.{ Game, GameRepo, PgnDump }
import lila.hub.actorApi.{ router => R }
private[api] final class AnalysisApi(
apiToken: String,
makeUrl: Any => Fu[String],
nbAnalysis: () => Fu[Int],
pgnDump: PgnDump) {
private def makeNb(nb: Option[Int]) = math.min(100, nb | 10)
private def makeSkip = nbAnalysis() map { nb => scala.util.Random.nextInt(nb) }
def list(nb: Option[Int], token: Option[String]): Fu[JsObject] =
if (~token != apiToken) fuccess(Json.obj("oh" -> "bummer"))
else makeSkip flatMap { skip =>
AnalysisRepo.skipping(skip, makeNb(nb)) flatMap { as =>
GameRepo games as.map(_.id) flatMap { games =>
games.map { g =>
as find (_.id == g.id) map { _ -> g }
}.flatten.map {
case (a, g) => GameRepo initialFen g.id flatMap { initialFen =>
pgnDump(g) zip makeUrl(R.Watcher(g.id, g.firstPlayer.color.name)) map {
case (pgn, url) => (g, a, url, pgn, initialFen)
}
}
}.sequenceFu map { tuples =>
Json.obj(
"list" -> JsArray(tuples map {
case (game, analysis, url, pgn, fen) => Json.obj(
"game" -> (GameApi.gameToJson(game, url, analysis.some) ++ {
fen ?? { f => Json.obj("initialFen" -> f) }
}),
"analysis" -> AnalysisApi.analysisToJson(analysis, pgn),
"uci" -> uciMovesOf(game, fen).map(_.mkString(" "))
).noNull
})
)
}
}
}
}
private def uciMovesOf(game: Game, initialFen: Option[String]): Option[List[String]] =
Replay(game.pgnMoves mkString " ", initialFen, game.variant).toOption map UciDump.apply
}
private[api] object AnalysisApi {
def analysisToJson(analysis: Analysis, pgn: Pgn) = JsArray(analysis.infoAdvices zip pgn.moves map {
case ((info, adviceOption), move) => Json.obj(
"ply" -> info.ply,
"move" -> move.san,
"eval" -> info.score.map(_.centipawns),
"mate" -> info.mate,
"variation" -> info.variation.isEmpty.fold(JsNull, info.variation mkString " "),
"comment" -> adviceOption.map(_.makeComment(true, true))
).noNull
})
}

View File

@ -44,12 +44,6 @@ final class Env(
val gameApi = new GameApi(
makeUrl = apiUrl,
apiToken = apiToken,
isOnline = userEnv.isOnline)
val analysisApi = new AnalysisApi(
apiToken = apiToken,
makeUrl = apiUrl,
nbAnalysis = () => analyseEnv.cached.nbAnalysis,
pgnDump = pgnDump)
val puzzleApi = new PuzzleApi(

View File

@ -2,20 +2,21 @@ package lila.api
import play.api.libs.json._
import chess.format.pgn.Pgn
import lila.analyse.{ AnalysisRepo, Analysis }
import lila.common.PimpedJson._
import lila.db.api._
import lila.db.Implicits._
import lila.game.Game
import lila.game.Game.{ BSONFields => G }
import lila.game.tube.gameTube
import lila.game.{ Game, PgnDump }
import lila.hub.actorApi.{ router => R }
import makeTimeout.short
private[api] final class GameApi(
makeUrl: Any => Fu[String],
apiToken: String,
isOnline: String => Boolean) {
pgnDump: PgnDump) {
private def makeNb(nb: Option[Int]) = math.min(100, nb | 10)
@ -23,7 +24,7 @@ private[api] final class GameApi(
username: Option[String],
rated: Option[Boolean],
analysed: Option[Boolean],
withAnalysis: Option[Boolean],
withAnalysis: Boolean,
token: Option[String],
nb: Option[Int]): Fu[JsObject] = $find($query(Json.obj(
G.status -> $gte(chess.Status.Mate.id),
@ -37,32 +38,34 @@ private[api] final class GameApi(
def one(
id: String,
withAnalysis: Option[Boolean],
withAnalysis: Boolean,
token: Option[String]): Fu[Option[JsObject]] =
$find byId id map (_.toList) flatMap gamesJson(withAnalysis, token) map (_.headOption)
private def gamesJson(withAnalysis: Option[Boolean], token: Option[String])(games: List[Game]): Fu[List[JsObject]] =
private def gamesJson(withAnalysis: Boolean, token: Option[String])(games: List[Game]): Fu[List[JsObject]] =
AnalysisRepo doneByIds games.map(_.id) flatMap { analysisOptions =>
(games map { g => makeUrl(R.Watcher(g.id, g.firstPlayer.color.name)) }).sequenceFu map { urls =>
val validToken = check(token)
games zip urls zip analysisOptions map {
case ((g, url), analysisOption) =>
GameApi.gameToJson(g, url, analysisOption,
withBlurs = validToken,
withMoveTimes = validToken)
(games map { g => withAnalysis ?? (pgnDump(g) map (_.some)) }).sequenceFu flatMap { pgns =>
(games map { g => makeUrl(R.Watcher(g.id, g.firstPlayer.color.name)) }).sequenceFu map { urls =>
val validToken = check(token)
games zip urls zip analysisOptions zip pgns map {
case (((g, url), analysisOption), pgnOption) =>
gameToJson(g, url, analysisOption, pgnOption,
withAnalysis = withAnalysis,
withBlurs = validToken,
withMoveTimes = validToken)
}
}
}
}
private def check(token: Option[String]) = token ?? (apiToken==)
}
private[api] object GameApi {
def gameToJson(
private def gameToJson(
g: Game,
url: String,
analysisOption: Option[Analysis],
pgnOption: Option[Pgn],
withAnalysis: Boolean,
withBlurs: Boolean = false,
withMoveTimes: Boolean = false) = Json.obj(
"id" -> g.id,
@ -93,6 +96,18 @@ private[api] object GameApi {
)
).noNull
}),
"analysis" -> analysisOption.ifTrue(withAnalysis).|@|(pgnOption).apply {
case (analysis, pgn) => JsArray(analysis.infoAdvices zip pgn.moves map {
case ((info, adviceOption), move) => Json.obj(
"ply" -> info.ply,
"move" -> move.san,
"eval" -> info.score.map(_.centipawns),
"mate" -> info.mate,
"variation" -> info.variation.isEmpty.fold(JsNull, info.variation mkString " "),
"comment" -> adviceOption.map(_.makeComment(true, true))
).noNull
})
},
"winner" -> g.winnerColor.map(_.name),
"url" -> url
).noNull