diff --git a/app/views/swiss/form.scala b/app/views/swiss/form.scala index 446cc2725a..4aae9bf466 100644 --- a/app/views/swiss/form.scala +++ b/app/views/swiss/form.scala @@ -26,7 +26,7 @@ object form { form3.split(fields.name, fields.nbRounds), form3.split(fields.rated, fields.variant), fields.clock, - fields.description, + form3.split(fields.description, fields.position), form3.split( fields.roundInterval, fields.startsAt @@ -60,7 +60,7 @@ object form { form3.split(fields.name, fields.nbRounds), form3.split(fields.rated, fields.variant), fields.clock, - fields.description, + form3.split(fields.description, swiss.settings.position.isDefined option fields.position), form3.split( fields.roundInterval, swiss.isCreated option fields.startsAt @@ -168,8 +168,17 @@ final private class SwissFields(form: Form[_])(implicit ctx: Context) { frag("Tournament description"), help = frag( "Anything special you want to tell the participants? Try to keep it short. Markdown links are available: [name](https://url)" - ).some + ).some, + half = true )(form3.textarea(_)(rows := 4)) + def position = + form3.group( + form("position"), + trans.startPosition(), + klass = "position", + half = true, + help = views.html.tournament.form.positionInputHelp.some + )(form3.input(_)) def startsAt = form3.group( form("startsAt"), diff --git a/app/views/swiss/side.scala b/app/views/swiss/side.scala index d36f2c8518..e193056b48 100644 --- a/app/views/swiss/side.scala +++ b/app/views/swiss/side.scala @@ -50,6 +50,20 @@ object side { st.section(cls := "description")(markdownLinksOrRichText(d)) }, s.looksLikePrize option views.html.tournament.bits.userPrizeDisclaimer(s.createdBy), + s.settings.position.flatMap(lila.tournament.Thematic.byFen) map { pos => + div( + a(targetBlank, href := pos.url)(strong(pos.eco), " ", pos.name), + " • ", + a(href := routes.UserAnalysis.parseArg(pos.fen.replace(" ", "_")))( + trans.analysis() + ) + ) + } orElse s.settings.position.map { fen => + div( + "Custom position • ", + a(href := routes.UserAnalysis.parseArg(fen.value.replace(" ", "_")))(trans.analysis()) + ) + }, teamLink(s.teamId), if (verdicts.relevant) st.section( diff --git a/modules/swiss/src/main/BsonHandlers.scala b/modules/swiss/src/main/BsonHandlers.scala index e6ced7119f..96667501a7 100644 --- a/modules/swiss/src/main/BsonHandlers.scala +++ b/modules/swiss/src/main/BsonHandlers.scala @@ -1,14 +1,15 @@ package lila.swiss -import scala.concurrent.duration._ - import chess.Clock.{ Config => ClockConfig } +import chess.format.FEN import chess.variant.Variant import chess.{ Color, StartingPosition } +import reactivemongo.api.bson._ +import scala.concurrent.duration._ + import lila.db.BSON import lila.db.dsl._ import lila.user.User -import reactivemongo.api.bson._ private object BsonHandlers { @@ -128,6 +129,7 @@ private object BsonHandlers { nbRounds = r.get[Int]("n"), rated = r.boolO("r") | true, description = r.strO("d"), + position = r.getO[FEN]("f"), chatFor = r.intO("c") | Swiss.ChatFor.default, roundInterval = (r.intO("i") | 60).seconds, password = r.strO("p"), @@ -138,6 +140,7 @@ private object BsonHandlers { "n" -> s.nbRounds, "r" -> (!s.rated).option(false), "d" -> s.description, + "f" -> s.position, "c" -> (s.chatFor != Swiss.ChatFor.default).option(s.chatFor), "i" -> s.roundInterval.toSeconds.toInt, "p" -> s.password, diff --git a/modules/swiss/src/main/Swiss.scala b/modules/swiss/src/main/Swiss.scala index 7ba3afcfe5..eec382882e 100644 --- a/modules/swiss/src/main/Swiss.scala +++ b/modules/swiss/src/main/Swiss.scala @@ -8,6 +8,7 @@ import scala.concurrent.duration._ import lila.hub.LightTeam.TeamID import lila.rating.PerfType import lila.user.User +import chess.format.FEN case class Swiss( _id: Swiss.Id, @@ -91,6 +92,7 @@ object Swiss { nbRounds: Int, rated: Boolean, description: Option[String] = None, + position: Option[FEN], chatFor: ChatFor = ChatFor.default, password: Option[String] = None, conditions: SwissCondition.All, diff --git a/modules/swiss/src/main/SwissApi.scala b/modules/swiss/src/main/SwissApi.scala index 8905290d3e..bf493b7a5d 100644 --- a/modules/swiss/src/main/SwissApi.scala +++ b/modules/swiss/src/main/SwissApi.scala @@ -72,6 +72,7 @@ final class SwissApi( nbRounds = data.nbRounds, rated = data.rated | true, description = data.description, + position = data.realPosition, chatFor = data.realChatFor, roundInterval = data.realRoundInterval, password = data.password, @@ -97,6 +98,9 @@ final class SwissApi( nbRounds = data.nbRounds, rated = data.rated | old.settings.rated, description = data.description orElse old.settings.description, + position = + if (old.isCreated || old.settings.position.isDefined) data.realPosition + else old.settings.position, chatFor = data.chatFor | old.settings.chatFor, roundInterval = if (data.roundInterval.isDefined) data.realRoundInterval diff --git a/modules/swiss/src/main/SwissDirector.scala b/modules/swiss/src/main/SwissDirector.scala index d6a7463c4f..5a03db785d 100644 --- a/modules/swiss/src/main/SwissDirector.scala +++ b/modules/swiss/src/main/SwissDirector.scala @@ -84,8 +84,11 @@ final private class SwissDirector( Game .make( chess = chess.Game( - variantOption = Some(swiss.variant), - fen = none + variantOption = Some { + if (swiss.settings.position.isEmpty) swiss.variant + else chess.variant.FromPosition + }, + fen = swiss.settings.position.map(_.value) ) pipe { g => val turns = g.player.fold(0, 1) g.copy( diff --git a/modules/swiss/src/main/SwissForm.scala b/modules/swiss/src/main/SwissForm.scala index d117e05070..2899b65c25 100644 --- a/modules/swiss/src/main/SwissForm.scala +++ b/modules/swiss/src/main/SwissForm.scala @@ -1,6 +1,7 @@ package lila.swiss import chess.Clock.{ Config => ClockConfig } +import chess.format.FEN import chess.variant.Variant import org.joda.time.DateTime import play.api.data._ @@ -28,6 +29,7 @@ final class SwissForm(implicit mode: Mode) { "rated" -> optional(boolean), "nbRounds" -> number(min = minRounds, max = 100), "description" -> optional(clean(nonEmptyText)), + "position" -> optional(lila.common.Form.fen.playableStrict), "chatFor" -> optional(numberIn(chatForChoices.map(_._1))), "roundInterval" -> optional(numberIn(roundIntervals)), "password" -> optional(clean(nonEmptyText)), @@ -46,6 +48,7 @@ final class SwissForm(implicit mode: Mode) { rated = true.some, nbRounds = 7, description = none, + position = none, chatFor = Swiss.ChatFor.default.some, roundInterval = Swiss.RoundInterval.auto.some, password = None, @@ -61,6 +64,7 @@ final class SwissForm(implicit mode: Mode) { rated = s.settings.rated.some, nbRounds = s.settings.nbRounds, description = s.settings.description, + position = s.settings.position, chatFor = s.settings.chatFor.some, roundInterval = s.settings.roundInterval.toSeconds.toInt.some, password = s.settings.password, @@ -137,6 +141,7 @@ object SwissForm { rated: Option[Boolean], nbRounds: Int, description: Option[String], + position: Option[FEN], chatFor: Option[Int], roundInterval: Option[Int], password: Option[String], @@ -160,5 +165,6 @@ object SwissForm { case i => i } }.seconds + def realPosition = position ifTrue realVariant.standard } } diff --git a/ui/site/src/tourForm.ts b/ui/site/src/tourForm.ts index 0b1097cf7c..1d68072449 100644 --- a/ui/site/src/tourForm.ts +++ b/ui/site/src/tourForm.ts @@ -3,7 +3,7 @@ import flatpickr from "flatpickr"; lichess.load.then(() => { const $variant = $('#form3-variant'), - showPosition = () => $('.form3 .position').toggleClass('none', $variant.val() != '1'); + showPosition = () => $('.form3 .position').toggleClass('none', !['1', 'standard'].includes($variant.val() as string)); $variant.on('change', showPosition); showPosition();