swiss WIP
parent
efd3bdf72f
commit
a9104e7f1f
|
@ -102,7 +102,14 @@ object replay {
|
|||
main(cls := "analyse")(
|
||||
st.aside(cls := "analyse__side")(
|
||||
views.html.game
|
||||
.side(pov, initialFen, none, simul = simul, userTv = userTv, bookmarked = bookmarked)
|
||||
.side(
|
||||
pov,
|
||||
initialFen,
|
||||
none,
|
||||
simul = simul,
|
||||
userTv = userTv,
|
||||
bookmarked = bookmarked
|
||||
)
|
||||
),
|
||||
chatOption.map(_ => views.html.chat.frag),
|
||||
div(cls := "analyse__board main-board")(chessgroundBoard),
|
||||
|
|
|
@ -130,16 +130,13 @@ object side {
|
|||
a(cls := "text", dataIcon := "g", href := routes.Tournament.show(t.tour.id))(t.tour.name()),
|
||||
div(cls := "clock", dataTime := t.tour.secondsToFinish)(div(cls := "time")(t.tour.clockStatus))
|
||||
)
|
||||
} orElse {
|
||||
game.tournamentId map { tourId =>
|
||||
} orElse game.tournamentId.map { tourId =>
|
||||
st.section(cls := "game__tournament-link")(tournamentLink(tourId))
|
||||
} orElse game.swissId.map { swissId =>
|
||||
st.section(cls := "game__tournament-link")(
|
||||
a(href := routes.Tournament.show(tourId), dataIcon := "g", cls := "text")(
|
||||
tournamentIdToName(tourId)
|
||||
views.html.swiss.bits.link(lila.swiss.Swiss.Id(swissId))
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
simul.map { sim =>
|
||||
} orElse simul.map { sim =>
|
||||
st.section(cls := "game__simul-link")(
|
||||
a(href := routes.Simul.show(sim.id))(sim.fullName)
|
||||
)
|
||||
|
|
|
@ -15,6 +15,8 @@ object jsI18n {
|
|||
g.variant.exotic ?? variantTranslations
|
||||
} ++ {
|
||||
g.isTournament ?? tournamentTranslations
|
||||
} ++ {
|
||||
g.isSwiss ?? swissTranslations
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +40,11 @@ object jsI18n {
|
|||
trans.standing
|
||||
).map(_.key)
|
||||
|
||||
private val swissTranslations = Vector(
|
||||
trans.backToTournament,
|
||||
trans.viewTournament
|
||||
).map(_.key)
|
||||
|
||||
private val baseTranslations = Vector(
|
||||
trans.flipBoard,
|
||||
trans.aiNameLevelAiLevel,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
db.swiss_pairing.dropIndexes()
|
||||
db.swiss.ensureIndex({teamId:1,startsAt:1})
|
||||
db.swiss_player.ensureIndex({s:1,u:1})
|
||||
db.swiss_player.ensureIndex({s:1,c:-1})
|
||||
|
|
|
@ -14,6 +14,7 @@ object Iso {
|
|||
type IntIso[B] = Iso[Int, B]
|
||||
type BooleanIso[B] = Iso[Boolean, B]
|
||||
type DoubleIso[B] = Iso[Double, B]
|
||||
type FloatIso[B] = Iso[Float, B]
|
||||
|
||||
def apply[A, B](f: A => B, t: B => A): Iso[A, B] = new Iso[A, B] {
|
||||
val from = f
|
||||
|
@ -23,6 +24,7 @@ object Iso {
|
|||
def string[B](from: String => B, to: B => String): StringIso[B] = apply(from, to)
|
||||
def int[B](from: Int => B, to: B => Int): IntIso[B] = apply(from, to)
|
||||
def double[B](from: Double => B, to: B => Double): DoubleIso[B] = apply(from, to)
|
||||
def float[B](from: Float => B, to: B => Float): FloatIso[B] = apply(from, to)
|
||||
|
||||
def strings(sep: String): StringIso[Strings] = Iso[String, Strings](
|
||||
str => Strings(str.split(sep).iterator.map(_.trim).to(List)),
|
||||
|
|
|
@ -42,6 +42,11 @@ trait Handlers {
|
|||
def doubleAnyValHandler[A](to: A => Double, from: Double => A): BSONHandler[A] =
|
||||
doubleIsoHandler(Iso(from, to))
|
||||
|
||||
def floatIsoHandler[A](implicit iso: FloatIso[A]): BSONHandler[A] =
|
||||
BSONFloatHandler.as[A](iso.from, iso.to)
|
||||
def floatAnyValHandler[A](to: A => Float, from: Float => A): BSONHandler[A] =
|
||||
floatIsoHandler(Iso(from, to))
|
||||
|
||||
def dateIsoHandler[A](implicit iso: Iso[DateTime, A]): BSONHandler[A] =
|
||||
BSONJodaDateTimeHandler.as[A](iso.from, iso.to)
|
||||
|
||||
|
|
|
@ -41,7 +41,10 @@ private object BsonHandlers {
|
|||
v => BSONString(v.fen)
|
||||
)
|
||||
implicit val swissPointsHandler = intAnyValHandler[Swiss.Points](_.double, Swiss.Points.apply)
|
||||
implicit val swissScoreHandler = doubleAnyValHandler[Swiss.Score](_.value, Swiss.Score.apply)
|
||||
implicit val swissTieBreakHandler = doubleAnyValHandler[Swiss.TieBreak](_.value, Swiss.TieBreak.apply)
|
||||
implicit val swissPerformanceHandler =
|
||||
floatAnyValHandler[Swiss.Performance](_.value, Swiss.Performance.apply)
|
||||
implicit val swissScoreHandler = intAnyValHandler[Swiss.Score](_.value, Swiss.Score.apply)
|
||||
implicit val playerNumberHandler = intAnyValHandler[SwissPlayer.Number](_.value, SwissPlayer.Number.apply)
|
||||
implicit val roundNumberHandler = intAnyValHandler[SwissRound.Number](_.value, SwissRound.Number.apply)
|
||||
implicit val swissIdHandler = stringAnyValHandler[Swiss.Id](_.value, Swiss.Id.apply)
|
||||
|
@ -57,6 +60,8 @@ private object BsonHandlers {
|
|||
rating = r int rating,
|
||||
provisional = r boolD provisional,
|
||||
points = r.get[Swiss.Points](points),
|
||||
tieBreak = r.get[Swiss.TieBreak](tieBreak),
|
||||
performance = r.getO[Swiss.Performance](performance),
|
||||
score = r.get[Swiss.Score](score)
|
||||
)
|
||||
def writes(w: BSON.Writer, o: SwissPlayer) = $doc(
|
||||
|
@ -67,6 +72,8 @@ private object BsonHandlers {
|
|||
rating -> o.rating,
|
||||
provisional -> w.boolO(o.provisional),
|
||||
points -> o.points,
|
||||
tieBreak -> o.tieBreak,
|
||||
performance -> o.performance,
|
||||
score -> o.score
|
||||
)
|
||||
}
|
||||
|
|
|
@ -54,6 +54,16 @@ final class Env(
|
|||
|
||||
lazy val getName = new GetSwissName(cache.name.sync)
|
||||
|
||||
lila.common.Bus.subscribeFun(
|
||||
"finishGame",
|
||||
"adjustCheater",
|
||||
"adjustBooster"
|
||||
) {
|
||||
case lila.game.actorApi.FinishGame(game, _, _) => api finishGame game
|
||||
// case lila.hub.actorApi.mod.MarkCheater(userId, true) => api.ejectLame(userId, _)
|
||||
// case lila.hub.actorApi.mod.MarkBooster(userId) => api.ejectLame(userId, Nil)
|
||||
}
|
||||
|
||||
ResilientScheduler(
|
||||
every = Every(2 seconds),
|
||||
atMost = AtMost(15 seconds),
|
||||
|
|
|
@ -67,7 +67,7 @@ final private class PairingSystem(executable: String) {
|
|||
val pairing = pairings get rn
|
||||
List(
|
||||
95 -> pairing.map(_ opponentOf p.number).??(_.toString),
|
||||
97 -> pairing.map(_ colorOf p.number).??(_.fold("w", "n")),
|
||||
97 -> pairing.map(_ colorOf p.number).??(_.fold("w", "b")),
|
||||
99 -> pairing.flatMap(_.winner).map(p.number ==).fold("=") {
|
||||
case true => "1"
|
||||
case false => "0"
|
||||
|
|
|
@ -72,7 +72,13 @@ object Swiss {
|
|||
def value: Float = double / 2f
|
||||
def +(p: Points) = Points(double + p.double)
|
||||
}
|
||||
case class Score(value: Double) extends AnyVal
|
||||
case class TieBreak(value: Double) extends AnyVal
|
||||
case class Performance(value: Float) extends AnyVal
|
||||
case class Score(value: Int) extends AnyVal
|
||||
|
||||
def makeScore(points: Points, tieBreak: TieBreak, perf: Performance) = Score(
|
||||
(points.value * 10000000 + tieBreak.value * 10000 + perf.value).toInt
|
||||
)
|
||||
|
||||
def makeId = Id(scala.util.Random.alphanumeric take 8 mkString)
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@ package lila.swiss
|
|||
import org.joda.time.DateTime
|
||||
import ornicar.scalalib.Zero
|
||||
import reactivemongo.api._
|
||||
import reactivemongo.api.bson._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.{ GreatPlayer, WorkQueues }
|
||||
import lila.db.dsl._
|
||||
import lila.hub.LightTeam.TeamID
|
||||
import lila.game.Game
|
||||
import lila.user.User
|
||||
|
||||
final class SwissApi(
|
||||
|
@ -88,6 +90,32 @@ final class SwissApi(
|
|||
def featuredInTeam(teamId: TeamID): Fu[List[Swiss]] =
|
||||
colls.swiss.ext.find($doc("teamId" -> teamId)).sort($sort desc "startsAt").list[Swiss](5)
|
||||
|
||||
private[swiss] def finishGame(game: Game): Funit = game.swissId ?? { swissId =>
|
||||
Sequencing(Swiss.Id(swissId))(startedById) { swiss =>
|
||||
colls.pairing.byId[SwissPairing](game.id) flatMap {
|
||||
_ ?? { pairing =>
|
||||
val winner = game.winnerColor
|
||||
.map(_.fold(pairing.white, pairing.black))
|
||||
.flatMap(playerNumberHandler.writeOpt)
|
||||
colls.pairing.updateField($id(game.id), SwissPairing.Fields.status, winner | BSONNull).void >>
|
||||
scoring.recompute(swiss) >>
|
||||
isReadyForNextRound(swiss).flatMap {
|
||||
_ ?? colls.swiss.updateField($id(swiss.id), "nextRoundAt", DateTime.now.plusSeconds(10)).void
|
||||
} >>-
|
||||
socket.reload(swiss.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def isReadyForNextRound(swiss: Swiss) =
|
||||
SwissPairing
|
||||
.fields { f =>
|
||||
!colls.pairing.exists(
|
||||
$doc(f.swissId -> swiss.id, f.round -> swiss.round, f.status -> SwissPairing.ongoing)
|
||||
)
|
||||
}
|
||||
|
||||
private[swiss] def destroy(swiss: Swiss): Funit =
|
||||
colls.swiss.delete.one($id(swiss.id)) >>
|
||||
colls.pairing.delete.one($doc(SwissPairing.Fields.swissId -> swiss.id)) >>
|
||||
|
|
|
@ -26,6 +26,8 @@ object SwissPairing {
|
|||
case object Ongoing extends Ongoing
|
||||
type Status = Either[Ongoing, Option[SwissPlayer.Number]]
|
||||
|
||||
val ongoing: Status = Left(Ongoing)
|
||||
|
||||
case class Pending(
|
||||
white: SwissPlayer.Number,
|
||||
black: SwissPlayer.Number
|
||||
|
|
|
@ -11,11 +11,17 @@ case class SwissPlayer(
|
|||
rating: Int,
|
||||
provisional: Boolean,
|
||||
points: Swiss.Points,
|
||||
tieBreak: Swiss.TieBreak,
|
||||
performance: Option[Swiss.Performance],
|
||||
score: Swiss.Score
|
||||
) {
|
||||
def is(uid: User.ID): Boolean = uid == userId
|
||||
def is(user: User): Boolean = is(user.id)
|
||||
def is(other: SwissPlayer): Boolean = is(other.userId)
|
||||
|
||||
def recomputeScore = copy(
|
||||
score = Swiss.makeScore(points, tieBreak, performance | Swiss.Performance(rating.toFloat))
|
||||
)
|
||||
}
|
||||
|
||||
object SwissPlayer {
|
||||
|
@ -29,7 +35,8 @@ object SwissPlayer {
|
|||
number: SwissPlayer.Number,
|
||||
user: User,
|
||||
perfLens: Perfs => Perf
|
||||
): SwissPlayer = new SwissPlayer(
|
||||
): SwissPlayer =
|
||||
new SwissPlayer(
|
||||
id = makeId(swissId, user.id),
|
||||
swissId = swissId,
|
||||
number = number,
|
||||
|
@ -37,8 +44,10 @@ object SwissPlayer {
|
|||
rating = perfLens(user.perfs).intRating,
|
||||
provisional = perfLens(user.perfs).provisional,
|
||||
points = Swiss.Points(0),
|
||||
tieBreak = Swiss.TieBreak(0),
|
||||
performance = none,
|
||||
score = Swiss.Score(0)
|
||||
)
|
||||
).recomputeScore
|
||||
|
||||
case class Number(value: Int) extends AnyVal with IntValue
|
||||
|
||||
|
@ -63,6 +72,8 @@ object SwissPlayer {
|
|||
val rating = "r"
|
||||
val provisional = "pr"
|
||||
val points = "p"
|
||||
val tieBreak = "t"
|
||||
val performance = "e"
|
||||
val score = "c"
|
||||
}
|
||||
def fields[A](f: Fields.type => A): A = f(Fields)
|
||||
|
|
|
@ -30,13 +30,23 @@ final class SwissScoring(
|
|||
}
|
||||
playerMap = SwissPlayer.toMap(playersWithPoints)
|
||||
players = playersWithPoints.map { p =>
|
||||
p.copy(score = Swiss.Score {
|
||||
(~pairingMap.get(p.number)).values.foldLeft(0d) {
|
||||
case (score, pairing) =>
|
||||
def opponentPoints = playerMap.get(pairing opponentOf p.number).??(_.points.value)
|
||||
score + pairing.winner.map(p.number.==).fold(opponentPoints / 2) { _ ?? opponentPoints }
|
||||
val playerPairings = (~pairingMap.get(p.number)).values
|
||||
val (tieBreak, perfSum) = playerPairings.foldLeft(0f -> 0f) {
|
||||
case ((tieBreak, perfSum), pairing) =>
|
||||
val opponent = playerMap.get(pairing opponentOf p.number)
|
||||
val opponentPoints = opponent.??(_.points.value)
|
||||
val result = pairing.winner.map(p.number.==)
|
||||
val newTieBreak = tieBreak + result.fold(opponentPoints / 2) { _ ?? opponentPoints }
|
||||
val newPerf = perfSum + opponent.??(_.rating) + result.?? { win =>
|
||||
if (win) 500 else -500
|
||||
}
|
||||
})
|
||||
newTieBreak -> newPerf
|
||||
}
|
||||
p.copy(
|
||||
tieBreak = Swiss.TieBreak(tieBreak),
|
||||
performance = playerPairings.nonEmpty option Swiss.Performance(perfSum / playerPairings.size)
|
||||
)
|
||||
.recomputeScore
|
||||
}
|
||||
_ <- SwissPlayer.fields { f =>
|
||||
prevPlayers
|
||||
|
|
|
@ -106,5 +106,7 @@ final class SwissStandingApi(
|
|||
bestWithRank(id, nb, (page - 1) * nb)
|
||||
|
||||
private[swiss] def best(id: Swiss.Id, nb: Int, skip: Int = 0): Fu[List[SwissPlayer]] =
|
||||
colls.player.ext.find($doc("s" -> id)).sort($sort desc "s").skip(skip).list[SwissPlayer](nb)
|
||||
SwissPlayer.fields { f =>
|
||||
colls.player.ext.find($doc(f.swissId -> id)).sort($sort desc f.score).skip(skip).list[SwissPlayer](nb)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,8 @@
|
|||
letter-spacing: .1em;
|
||||
& > * {
|
||||
display: inline-block;
|
||||
margin: 0 .8em;
|
||||
text-align: center;
|
||||
}
|
||||
score {
|
||||
opacity: 0.7;
|
||||
|
|
Loading…
Reference in New Issue