add player team data to /api/tournament/:id/games - for #6756

pull/6920/head
Thibault Duplessis 2020-07-02 12:17:57 +02:00
parent 0034917aa6
commit 15072542ba
6 changed files with 98 additions and 48 deletions

View File

@ -12,6 +12,7 @@ import lila.common.config.MaxPerSecond
import lila.common.Json.jodaWrites
import lila.common.{ HTTPRequest, LightUser }
import lila.db.dsl._
import lila.team.GameTeams
import lila.game.JsonView._
import lila.game.PgnDump.WithFlags
import lila.game.{ Game, PerfPicker, Query }
@ -21,7 +22,9 @@ import lila.user.User
final class GameApiV2(
pgnDump: PgnDump,
gameRepo: lila.game.GameRepo,
tournamentRepo: lila.tournament.TournamentRepo,
pairingRepo: lila.tournament.PairingRepo,
playerRepo: lila.tournament.PlayerRepo,
swissApi: lila.swiss.SwissApi,
analysisRepo: lila.analyse.AnalysisRepo,
getLightUser: LightUser.Getter
@ -119,41 +122,58 @@ final class GameApiV2(
.via(preparationFlow(config))
def exportByTournament(config: ByTournamentConfig): Source[String, _] =
pairingRepo
.sortedCursor(
tournamentId = config.tournamentId,
batchSize = config.perSecond.value
)
.documentSource()
.grouped(config.perSecond.value)
.throttle(1, 1 second)
.mapAsync(1) { pairings =>
gameRepo.gameOptionsFromSecondary(pairings.map(_.gameId)) map {
_.zip(pairings) collect {
case (Some(game), pairing) => game -> pairing
Source futureSource {
tournamentRepo.isTeamBattle(config.tournamentId) map { isTeamBattle =>
pairingRepo
.sortedCursor(
tournamentId = config.tournamentId,
batchSize = config.perSecond.value
)
.documentSource()
.grouped(config.perSecond.value)
.throttle(1, 1 second)
.mapAsync(1) { pairings =>
isTeamBattle.?? {
playerRepo.teamsOfPlayers(config.tournamentId, pairings.flatMap(_.users).distinct).dmap(_.toMap)
} flatMap { playerTeams =>
gameRepo.gameOptionsFromSecondary(pairings.map(_.gameId)) map {
_.zip(pairings) collect {
case (Some(game), pairing) =>
(
game,
pairing,
playerTeams.get(pairing.user1) |@| playerTeams.get(
pairing.user2
) apply chess.Color.Map.apply[String]
)
}
}
}
}
}
}
.mapConcat(identity)
.mapAsync(4) {
case (game, pairing) => enrich(config.flags)(game) dmap { _ -> pairing }
}
.mapAsync(4) {
case ((game, fen, analysis), pairing) =>
config.format match {
case Format.PGN => pgnDump.formatter(config.flags)(game, fen, analysis)
case Format.JSON =>
def addBerserk(color: chess.Color)(json: JsObject) =
if (pairing berserkOf color)
json deepMerge Json.obj("players" -> Json.obj(color.name -> Json.obj("berserk" -> true)))
else json
toJson(game, fen, analysis, config.flags) dmap
addBerserk(chess.White) dmap
addBerserk(chess.Black) dmap { json =>
s"${Json.stringify(json)}\n"
.mapConcat(identity)
.mapAsync(4) {
case (game, pairing, teams) => enrich(config.flags)(game) dmap { (_, pairing, teams) }
}
.mapAsync(4) {
case ((game, fen, analysis), pairing, teams) =>
config.format match {
case Format.PGN => pgnDump.formatter(config.flags)(game, fen, analysis, teams)
case Format.JSON =>
def addBerserk(color: chess.Color)(json: JsObject) =
if (pairing berserkOf color)
json deepMerge Json.obj(
"players" -> Json.obj(color.name -> Json.obj("berserk" -> true))
)
else json
toJson(game, fen, analysis, config.flags, teams) dmap
addBerserk(chess.White) dmap
addBerserk(chess.Black) dmap { json =>
s"${Json.stringify(json)}\n"
}
}
}
}
}
def exportBySwiss(config: BySwissConfig): Source[String, _] =
swissApi
@ -169,9 +189,9 @@ final class GameApiV2(
.mapAsync(4) {
case (game, fen, analysis) =>
config.format match {
case Format.PGN => pgnDump.formatter(config.flags)(game, fen, analysis)
case Format.PGN => pgnDump.formatter(config.flags)(game, fen, analysis, None)
case Format.JSON =>
toJson(game, fen, analysis, config.flags) dmap { json =>
toJson(game, fen, analysis, config.flags, None) dmap { json =>
s"${Json.stringify(json)}\n"
}
}
@ -180,7 +200,9 @@ final class GameApiV2(
private def preparationFlow(config: Config) =
Flow[Game]
.mapAsync(4)(enrich(config.flags))
.mapAsync(4)(formatterFor(config).tupled)
.mapAsync(4) {
case (game, fen, analysis) => formatterFor(config)(game, fen, analysis, None)
}
private def enrich(flags: WithFlags)(game: Game) =
gameRepo initialFen game flatMap { initialFen =>
@ -202,8 +224,8 @@ final class GameApiV2(
}
private def jsonFormatter(flags: WithFlags) =
(game: Game, initialFen: Option[FEN], analysis: Option[Analysis]) =>
toJson(game, initialFen, analysis, flags) dmap { json =>
(game: Game, initialFen: Option[FEN], analysis: Option[Analysis], teams: Option[GameTeams]) =>
toJson(game, initialFen, analysis, flags, teams) dmap { json =>
s"${Json.stringify(json)}\n"
}
@ -211,7 +233,8 @@ final class GameApiV2(
g: Game,
initialFen: Option[FEN],
analysisOption: Option[Analysis],
withFlags: WithFlags
withFlags: WithFlags,
teams: Option[GameTeams] = None
): Fu[JsObject] =
for {
lightUsers <- gameLightUsers(g)
@ -240,6 +263,7 @@ final class GameApiV2(
.add("provisional" -> p.provisional)
.add("aiLevel" -> p.aiLevel)
.add("analysis" -> analysisOption.flatMap(analysisJson.player(g pov p.color)))
.add("team" -> teams.map(_(p.color)))
// .add("moveCentis" -> withFlags.moveTimes ?? g.moveTimes(p.color).map(_.map(_.centis)))
})
)

View File

@ -3,8 +3,9 @@ package lila.api
import chess.format.FEN
import chess.format.pgn.Pgn
import lila.analyse.{ Analysis, Annotator }
import lila.game.PgnDump.WithFlags
import lila.game.Game
import lila.game.PgnDump.WithFlags
import lila.team.GameTeams
final class PgnDump(
val dumper: lila.game.PgnDump,
@ -16,8 +17,14 @@ final class PgnDump(
implicit private val lang = lila.i18n.defaultLang
def apply(game: Game, initialFen: Option[FEN], analysis: Option[Analysis], flags: WithFlags): Fu[Pgn] =
dumper(game, initialFen, flags) flatMap { pgn =>
def apply(
game: Game,
initialFen: Option[FEN],
analysis: Option[Analysis],
flags: WithFlags,
teams: Option[GameTeams] = None
): Fu[Pgn] =
dumper(game, initialFen, flags, teams) flatMap { pgn =>
if (flags.tags) (game.simulId ?? simulApi.idToName) map { simulName =>
simulName
.orElse(game.tournamentId flatMap getTournamentName.get)
@ -52,11 +59,17 @@ final class PgnDump(
}
def formatter(flags: WithFlags) =
(game: Game, initialFen: Option[FEN], analysis: Option[Analysis]) =>
toPgnString(game, initialFen, analysis, flags)
(game: Game, initialFen: Option[FEN], analysis: Option[Analysis], teams: Option[GameTeams]) =>
toPgnString(game, initialFen, analysis, flags, teams)
def toPgnString(game: Game, initialFen: Option[FEN], analysis: Option[Analysis], flags: WithFlags) =
apply(game, initialFen, analysis, flags) dmap { pgn =>
def toPgnString(
game: Game,
initialFen: Option[FEN],
analysis: Option[Analysis],
flags: WithFlags,
teams: Option[GameTeams] = None
) =
apply(game, initialFen, analysis, flags, teams) dmap { pgn =>
// merge analysis & eval comments
// 1. e4 { [%eval 0.17] } { [%clk 0:00:30] }
// 1. e4 { [%eval 0.17] [%clk 0:00:30] }

View File

@ -15,12 +15,17 @@ final class PgnDump(
import PgnDump._
def apply(game: Game, initialFen: Option[FEN], flags: WithFlags): Fu[Pgn] = {
def apply(
game: Game,
initialFen: Option[FEN],
flags: WithFlags,
teams: Option[Color.Map[String]] = None
): Fu[Pgn] = {
val imported = game.pgnImport.flatMap { pgni =>
Parser.full(pgni.pgn).toOption
}
val tagsFuture =
if (flags.tags) tags(game, initialFen, imported, withOpening = flags.opening)
if (flags.tags) tags(game, initialFen, imported, withOpening = flags.opening, teams = teams)
else fuccess(Tags(Nil))
tagsFuture map { ts =>
val turns = flags.moves ?? {
@ -75,7 +80,8 @@ final class PgnDump(
game: Game,
initialFen: Option[FEN],
imported: Option[ParsedPgn],
withOpening: Boolean
withOpening: Boolean,
teams: Option[Color.Map[String]] = None
): Fu[Tags] =
gameLightUsers(game) map {
case (wu, bu) =>
@ -110,6 +116,8 @@ final class PgnDump(
bu.flatMap(_.title).map { t =>
Tag(_.BlackTitle, t)
},
teams.map { t => Tag("WhiteTeam", t.white) },
teams.map { t => Tag("BlackTeam", t.black) },
Tag(_.Variant, game.variant.name.capitalize).some,
Tag.timeControl(game.clock.map(_.config)).some,
Tag(_.ECO, game.opening.fold("?")(_.opening.eco)).some,

View File

@ -3,4 +3,6 @@ package lila
package object team extends PackageObject {
private[team] def logger = lila.log("team")
type GameTeams = chess.Color.Map[Team.ID]
}

View File

@ -155,7 +155,7 @@ final class PlayerRepo(coll: Coll)(implicit ec: scala.concurrent.ExecutionContex
def countTeamPlayers(tourId: Tournament.ID, teamId: TeamID): Fu[Int] =
coll.countSel($doc("tid" -> tourId, "t" -> teamId))
def teamsOfPlayers(tourId: Tournament.ID, userIds: List[User.ID]): Fu[List[(User.ID, TeamID)]] =
def teamsOfPlayers(tourId: Tournament.ID, userIds: Seq[User.ID]): Fu[List[(User.ID, TeamID)]] =
coll.ext
.find($doc("tid" -> tourId, "uid" $in userIds), $doc("_id" -> false, "uid" -> true, "t" -> true))
.list[Bdoc]()

View File

@ -183,6 +183,9 @@ final class TournamentRepo(val coll: Coll, playerCollName: CollName)(implicit
def teamBattleOf(tourId: Tournament.ID): Fu[Option[TeamBattle]] =
coll.primitiveOne[TeamBattle]($id(tourId), "teamBattle")
def isTeamBattle(tourId: Tournament.ID): Fu[Boolean] =
coll.exists($id(tourId) ++ $doc("teamBattle" $exists true))
def featuredGameId(tourId: Tournament.ID) = coll.primitiveOne[Game.ID]($id(tourId), "featured")
private def startingSoonSelect(aheadMinutes: Int) =