arbitrary starting positions for swiss tournaments

arena-from-position
Thibault Duplessis 2020-10-18 13:59:22 +02:00
parent 0068e53d65
commit e1443f92b7
8 changed files with 50 additions and 9 deletions

View File

@ -26,7 +26,7 @@ object form {
form3.split(fields.name, fields.nbRounds), form3.split(fields.name, fields.nbRounds),
form3.split(fields.rated, fields.variant), form3.split(fields.rated, fields.variant),
fields.clock, fields.clock,
fields.description, form3.split(fields.description, fields.position),
form3.split( form3.split(
fields.roundInterval, fields.roundInterval,
fields.startsAt fields.startsAt
@ -60,7 +60,7 @@ object form {
form3.split(fields.name, fields.nbRounds), form3.split(fields.name, fields.nbRounds),
form3.split(fields.rated, fields.variant), form3.split(fields.rated, fields.variant),
fields.clock, fields.clock,
fields.description, form3.split(fields.description, swiss.settings.position.isDefined option fields.position),
form3.split( form3.split(
fields.roundInterval, fields.roundInterval,
swiss.isCreated option fields.startsAt swiss.isCreated option fields.startsAt
@ -168,8 +168,17 @@ final private class SwissFields(form: Form[_])(implicit ctx: Context) {
frag("Tournament description"), frag("Tournament description"),
help = frag( help = frag(
"Anything special you want to tell the participants? Try to keep it short. Markdown links are available: [name](https://url)" "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)) )(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 = def startsAt =
form3.group( form3.group(
form("startsAt"), form("startsAt"),

View File

@ -50,6 +50,20 @@ object side {
st.section(cls := "description")(markdownLinksOrRichText(d)) st.section(cls := "description")(markdownLinksOrRichText(d))
}, },
s.looksLikePrize option views.html.tournament.bits.userPrizeDisclaimer(s.createdBy), 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), teamLink(s.teamId),
if (verdicts.relevant) if (verdicts.relevant)
st.section( st.section(

View File

@ -1,14 +1,15 @@
package lila.swiss package lila.swiss
import scala.concurrent.duration._
import chess.Clock.{ Config => ClockConfig } import chess.Clock.{ Config => ClockConfig }
import chess.format.FEN
import chess.variant.Variant import chess.variant.Variant
import chess.{ Color, StartingPosition } import chess.{ Color, StartingPosition }
import reactivemongo.api.bson._
import scala.concurrent.duration._
import lila.db.BSON import lila.db.BSON
import lila.db.dsl._ import lila.db.dsl._
import lila.user.User import lila.user.User
import reactivemongo.api.bson._
private object BsonHandlers { private object BsonHandlers {
@ -128,6 +129,7 @@ private object BsonHandlers {
nbRounds = r.get[Int]("n"), nbRounds = r.get[Int]("n"),
rated = r.boolO("r") | true, rated = r.boolO("r") | true,
description = r.strO("d"), description = r.strO("d"),
position = r.getO[FEN]("f"),
chatFor = r.intO("c") | Swiss.ChatFor.default, chatFor = r.intO("c") | Swiss.ChatFor.default,
roundInterval = (r.intO("i") | 60).seconds, roundInterval = (r.intO("i") | 60).seconds,
password = r.strO("p"), password = r.strO("p"),
@ -138,6 +140,7 @@ private object BsonHandlers {
"n" -> s.nbRounds, "n" -> s.nbRounds,
"r" -> (!s.rated).option(false), "r" -> (!s.rated).option(false),
"d" -> s.description, "d" -> s.description,
"f" -> s.position,
"c" -> (s.chatFor != Swiss.ChatFor.default).option(s.chatFor), "c" -> (s.chatFor != Swiss.ChatFor.default).option(s.chatFor),
"i" -> s.roundInterval.toSeconds.toInt, "i" -> s.roundInterval.toSeconds.toInt,
"p" -> s.password, "p" -> s.password,

View File

@ -8,6 +8,7 @@ import scala.concurrent.duration._
import lila.hub.LightTeam.TeamID import lila.hub.LightTeam.TeamID
import lila.rating.PerfType import lila.rating.PerfType
import lila.user.User import lila.user.User
import chess.format.FEN
case class Swiss( case class Swiss(
_id: Swiss.Id, _id: Swiss.Id,
@ -91,6 +92,7 @@ object Swiss {
nbRounds: Int, nbRounds: Int,
rated: Boolean, rated: Boolean,
description: Option[String] = None, description: Option[String] = None,
position: Option[FEN],
chatFor: ChatFor = ChatFor.default, chatFor: ChatFor = ChatFor.default,
password: Option[String] = None, password: Option[String] = None,
conditions: SwissCondition.All, conditions: SwissCondition.All,

View File

@ -72,6 +72,7 @@ final class SwissApi(
nbRounds = data.nbRounds, nbRounds = data.nbRounds,
rated = data.rated | true, rated = data.rated | true,
description = data.description, description = data.description,
position = data.realPosition,
chatFor = data.realChatFor, chatFor = data.realChatFor,
roundInterval = data.realRoundInterval, roundInterval = data.realRoundInterval,
password = data.password, password = data.password,
@ -97,6 +98,9 @@ final class SwissApi(
nbRounds = data.nbRounds, nbRounds = data.nbRounds,
rated = data.rated | old.settings.rated, rated = data.rated | old.settings.rated,
description = data.description orElse old.settings.description, 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, chatFor = data.chatFor | old.settings.chatFor,
roundInterval = roundInterval =
if (data.roundInterval.isDefined) data.realRoundInterval if (data.roundInterval.isDefined) data.realRoundInterval

View File

@ -84,8 +84,11 @@ final private class SwissDirector(
Game Game
.make( .make(
chess = chess.Game( chess = chess.Game(
variantOption = Some(swiss.variant), variantOption = Some {
fen = none if (swiss.settings.position.isEmpty) swiss.variant
else chess.variant.FromPosition
},
fen = swiss.settings.position.map(_.value)
) pipe { g => ) pipe { g =>
val turns = g.player.fold(0, 1) val turns = g.player.fold(0, 1)
g.copy( g.copy(

View File

@ -1,6 +1,7 @@
package lila.swiss package lila.swiss
import chess.Clock.{ Config => ClockConfig } import chess.Clock.{ Config => ClockConfig }
import chess.format.FEN
import chess.variant.Variant import chess.variant.Variant
import org.joda.time.DateTime import org.joda.time.DateTime
import play.api.data._ import play.api.data._
@ -28,6 +29,7 @@ final class SwissForm(implicit mode: Mode) {
"rated" -> optional(boolean), "rated" -> optional(boolean),
"nbRounds" -> number(min = minRounds, max = 100), "nbRounds" -> number(min = minRounds, max = 100),
"description" -> optional(clean(nonEmptyText)), "description" -> optional(clean(nonEmptyText)),
"position" -> optional(lila.common.Form.fen.playableStrict),
"chatFor" -> optional(numberIn(chatForChoices.map(_._1))), "chatFor" -> optional(numberIn(chatForChoices.map(_._1))),
"roundInterval" -> optional(numberIn(roundIntervals)), "roundInterval" -> optional(numberIn(roundIntervals)),
"password" -> optional(clean(nonEmptyText)), "password" -> optional(clean(nonEmptyText)),
@ -46,6 +48,7 @@ final class SwissForm(implicit mode: Mode) {
rated = true.some, rated = true.some,
nbRounds = 7, nbRounds = 7,
description = none, description = none,
position = none,
chatFor = Swiss.ChatFor.default.some, chatFor = Swiss.ChatFor.default.some,
roundInterval = Swiss.RoundInterval.auto.some, roundInterval = Swiss.RoundInterval.auto.some,
password = None, password = None,
@ -61,6 +64,7 @@ final class SwissForm(implicit mode: Mode) {
rated = s.settings.rated.some, rated = s.settings.rated.some,
nbRounds = s.settings.nbRounds, nbRounds = s.settings.nbRounds,
description = s.settings.description, description = s.settings.description,
position = s.settings.position,
chatFor = s.settings.chatFor.some, chatFor = s.settings.chatFor.some,
roundInterval = s.settings.roundInterval.toSeconds.toInt.some, roundInterval = s.settings.roundInterval.toSeconds.toInt.some,
password = s.settings.password, password = s.settings.password,
@ -137,6 +141,7 @@ object SwissForm {
rated: Option[Boolean], rated: Option[Boolean],
nbRounds: Int, nbRounds: Int,
description: Option[String], description: Option[String],
position: Option[FEN],
chatFor: Option[Int], chatFor: Option[Int],
roundInterval: Option[Int], roundInterval: Option[Int],
password: Option[String], password: Option[String],
@ -160,5 +165,6 @@ object SwissForm {
case i => i case i => i
} }
}.seconds }.seconds
def realPosition = position ifTrue realVariant.standard
} }
} }

View File

@ -3,7 +3,7 @@ import flatpickr from "flatpickr";
lichess.load.then(() => { lichess.load.then(() => {
const $variant = $('#form3-variant'), 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); $variant.on('change', showPosition);
showPosition(); showPosition();