diff --git a/modules/api/src/main/GameApiV2.scala b/modules/api/src/main/GameApiV2.scala index 5fdda24b55..fd820e6e40 100644 --- a/modules/api/src/main/GameApiV2.scala +++ b/modules/api/src/main/GameApiV2.scala @@ -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))) }) ) diff --git a/modules/api/src/main/PgnDump.scala b/modules/api/src/main/PgnDump.scala index 44355b5f04..e4441f2d4c 100644 --- a/modules/api/src/main/PgnDump.scala +++ b/modules/api/src/main/PgnDump.scala @@ -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] } diff --git a/modules/game/src/main/PgnDump.scala b/modules/game/src/main/PgnDump.scala index b7b80637bc..6a8f1c436e 100644 --- a/modules/game/src/main/PgnDump.scala +++ b/modules/game/src/main/PgnDump.scala @@ -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, diff --git a/modules/team/src/main/package.scala b/modules/team/src/main/package.scala index 13ff3762a8..5d2b236f0f 100644 --- a/modules/team/src/main/package.scala +++ b/modules/team/src/main/package.scala @@ -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] } diff --git a/modules/tournament/src/main/PlayerRepo.scala b/modules/tournament/src/main/PlayerRepo.scala index 7e5e226f63..79d919f31a 100644 --- a/modules/tournament/src/main/PlayerRepo.scala +++ b/modules/tournament/src/main/PlayerRepo.scala @@ -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]() diff --git a/modules/tournament/src/main/TournamentRepo.scala b/modules/tournament/src/main/TournamentRepo.scala index bede7d965e..3fe596f410 100644 --- a/modules/tournament/src/main/TournamentRepo.scala +++ b/modules/tournament/src/main/TournamentRepo.scala @@ -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) =