feat(official-bots): park expert bot on tournament server at startup
Build & Test (NowChessSystems) TeamCity build was removed from queue

Park the expert bot on the configured tournament server (default
http://141.37.123.132:8086) on startup, reusing a fixed
TOURNAMENT_BOT_TOKEN when present instead of minting a new identity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 10:18:31 +02:00
parent 0eb96ddf44
commit 2548bf6f94
2 changed files with 35 additions and 4 deletions
@@ -20,7 +20,7 @@ object TournamentBotConfig:
tournamentId <- env.get("TOURNAMENT_ID").filter(_.nonEmpty)
token <- env.get("TOURNAMENT_BOT_TOKEN").filter(_.nonEmpty)
botId <- jwtSubject(token)
serverUrl = env.getOrElse("TOURNAMENT_SERVER_URL", "http://localhost:8089")
serverUrl = env.getOrElse("TOURNAMENT_SERVER_URL", "http://141.37.123.132:8086")
difficulty = env.getOrElse("TOURNAMENT_BOT_DIFFICULTY", "medium")
yield TournamentBotConfig(serverUrl, tournamentId, token, botId, difficulty)
@@ -39,10 +39,11 @@ class TournamentBotGamePlayer:
// scalafix:on DisableSyntax.var
val defaultServerUrl: String =
System.getenv().asScala.getOrElse("TOURNAMENT_SERVER_URL", "http://localhost:8089")
System.getenv().asScala.getOrElse("TOURNAMENT_SERVER_URL", "http://141.37.123.132:8086")
@PostConstruct
def initialize(): Unit =
parkOnStartup()
config match
case None =>
log.info("Tournament bot disabled — set TOURNAMENT_ID and TOURNAMENT_BOT_TOKEN to enable")
@@ -50,6 +51,35 @@ class TournamentBotGamePlayer:
log.infof("Tournament bot enabled — server=%s tournament=%s bot=%s", cfg.serverUrl, cfg.tournamentId, cfg.botId)
startAsync(cfg)
private def parkOnStartup(): Unit =
park(defaultServerUrl, "expert") match
case Some(id) => log.infof("Parked expert bot on %s as id %s", defaultServerUrl, id)
case None => log.warnf("Failed to park expert bot on %s", defaultServerUrl)
private def parkToken(serverUrl: String, difficulty: String): Option[String] =
System.getenv().asScala.get("TOURNAMENT_BOT_TOKEN").filter(_.nonEmpty)
.orElse(registerBot(serverUrl, difficulty).map(_._2))
private def park(serverUrl: String, difficulty: String): Option[String] =
parkToken(serverUrl, difficulty).flatMap { token =>
Try {
val body = s"""{"name":"${botName(difficulty)}"}"""
val response = client
.target(serverUrl)
.path("api")
.path("bots")
.request(MediaType.APPLICATION_JSON)
.header("Authorization", s"Bearer $token")
.post(Entity.entity(body, MediaType.APPLICATION_JSON))
if response.getStatus == 201 || response.getStatus == 200 then
val id = objectMapper.readTree(response.readEntity(classOf[String])).path("id").asText()
response.close()
log.infof("Parked official bot %s as id %s", botName(difficulty), id)
Option(id).filter(_.nonEmpty)
else { log.warnf("Parking bot %s returned status %d", botName(difficulty), response.getStatus); response.close(); None }
}.getOrElse(None)
}
def joinTournament(tournamentId: String, difficulty: String, serverUrl: String): Either[String, String] =
registerBot(serverUrl, difficulty) match
case None => Left("Failed to register bot with tournament server")
@@ -65,10 +95,11 @@ class TournamentBotGamePlayer:
thread.setDaemon(true)
thread.start()
private def botName(difficulty: String): String = s"NowChess ${difficulty.capitalize}"
private def registerBot(serverUrl: String, difficulty: String): Option[(String, String)] =
Try {
val name = s"NowChess ${difficulty.capitalize}"
val body = s"""{"name":"$name","isBot":true}"""
val body = s"""{"name":"${botName(difficulty)}","isBot":true}"""
val response = client
.target(serverUrl)
.path("api")