remember all draw offers - WIP

storeDrawOffers
Thibault Duplessis 2021-03-11 16:28:33 +01:00
parent 93eb5e820c
commit 19b3c16f11
7 changed files with 47 additions and 30 deletions

View File

@ -66,6 +66,18 @@ object BSONHandlers {
)
}
implicit private[game] val gameDrawOffersHandler = tryHandler[GameDrawOffers](
{ case arr: BSONArray =>
Success(arr.values.foldLeft(GameDrawOffers.empty) {
case (offers, BSONInteger(p)) =>
if (p > 0) offers.copy(white = offers.white incl p)
else offers.copy(black = offers.black incl -p)
case (offers, _) => offers
})
},
offers => BSONArray(offers.white ++ offers.black.map(-_))
)
import Player.playerBSONHandler
private val emptyPlayerBuilder = playerBSONHandler.read($empty)
@ -160,7 +172,8 @@ object BSONHandlers {
tournamentId = r strO F.tournamentId,
swissId = r strO F.swissId,
simulId = r strO F.simulId,
analysed = r boolD F.analysed
analysed = r boolD F.analysed,
drawOffers = r.getD(F.drawOffers, GameDrawOffers.empty)
)
)
}

View File

@ -315,15 +315,21 @@ case class Game(
blackPlayer = f(blackPlayer)
)
def drawOffers = metadata.drawOffers
def playerCanOfferDraw(color: Color) =
started && playable &&
turns >= 2 &&
!player(color).isOfferingDraw &&
!opponent(color).isAi &&
!playerHasOfferedDraw(color)
!playerHasOfferedDrawRecently(color)
def playerHasOfferedDraw(color: Color) =
player(color).lastDrawOffer ?? (_ >= turns - 20)
def playerHasOfferedDrawRecently(color: Color) =
drawOffers.lastBy(color) ?? (_ >= turns - 20)
def offerDraw(color: Color) = copy(
metadata = metadata.copy(drawOffers = drawOffers.add(color, turns))
).updatePlayer(color, _.offerDraw)
def playerCouldRematch =
finishedOrAborted &&
@ -735,14 +741,7 @@ object Game {
status = Status.Created,
daysPerTurn = daysPerTurn,
mode = mode,
metadata = Metadata(
source = source.some,
pgnImport = pgnImport,
tournamentId = none,
swissId = none,
simulId = none,
analysed = false
),
metadata = metadata(source).copy(pgnImport = pgnImport),
createdAt = createdAt,
movedAt = createdAt
)
@ -756,7 +755,8 @@ object Game {
tournamentId = none,
swissId = none,
simulId = none,
analysed = false
analysed = false,
drawOffers = GameDrawOffers.empty
)
object BSONFields {
@ -800,6 +800,7 @@ object Game {
val initialFen = "if"
val checkAt = "ck"
val perfType = "pt" // only set on student games for aggregation
val drawOffers = "do"
}
}

View File

@ -96,11 +96,11 @@ object GameDiff {
BSONHandlers.clockBSONWrite(a.createdAt, c).toOption
}
)
dTry(drawOffers, _.drawOffers, BSONHandlers.gameDrawOffersHandler.writeTry)
for (i <- 0 to 1) {
import Player.BSONFields._
val name = s"p$i."
val player: Game => Player = if (i == 0) (_.whitePlayer) else (_.blackPlayer)
dOpt(s"$name$lastDrawOffer", player(_).lastDrawOffer, (l: Option[Int]) => l flatMap w.intO)
dOpt(s"$name$isOfferingDraw", player(_).isOfferingDraw, w.boolO)
dOpt(s"$name$proposeTakebackAt", player(_).proposeTakebackAt, w.intO)
dTry(s"$name$blursBits", player(_).blurs, Blurs.BlursBSONHandler.writeTry)

View File

@ -327,8 +327,6 @@ final class GameRepo(val coll: Coll)(implicit ec: scala.concurrent.ExecutionCont
F.positionHashes -> true,
F.playingUids -> true,
F.unmovedRooks -> true,
("p0." + Player.BSONFields.lastDrawOffer) -> true,
("p1." + Player.BSONFields.lastDrawOffer) -> true,
("p0." + Player.BSONFields.isOfferingDraw) -> true,
("p1." + Player.BSONFields.isOfferingDraw) -> true,
("p0." + Player.BSONFields.proposeTakebackAt) -> true,

View File

@ -2,6 +2,7 @@ package lila.game
import java.security.MessageDigest
import lila.db.ByteArray
import chess.Color
private[game] case class Metadata(
source: Option[Source],
@ -9,7 +10,8 @@ private[game] case class Metadata(
tournamentId: Option[String],
swissId: Option[String],
simulId: Option[String],
analysed: Boolean
analysed: Boolean,
drawOffers: GameDrawOffers
) {
def pgnDate = pgnImport flatMap (_.date)
@ -21,7 +23,20 @@ private[game] case class Metadata(
private[game] object Metadata {
val empty = Metadata(None, None, None, None, None, analysed = false)
val empty = Metadata(None, None, None, None, None, analysed = false, GameDrawOffers.empty)
}
// plies
case class GameDrawOffers(white: Set[Int], black: Set[Int]) {
def lastBy(color: Color): Option[Int] = color.fold(white, black).maxOption
def add(color: Color, ply: Int) =
color.fold(copy(white = white incl ply), copy(black = black incl ply))
}
object GameDrawOffers {
val empty = GameDrawOffers(Set.empty, Set.empty)
}
case class PgnImport(

View File

@ -14,7 +14,6 @@ case class Player(
aiLevel: Option[Int],
isWinner: Option[Boolean] = None,
isOfferingDraw: Boolean = false,
lastDrawOffer: Option[Int] = None,
proposeTakebackAt: Int = 0, // ply when takeback was proposed
userId: Player.UserId = None,
rating: Option[Int] = None,
@ -49,11 +48,7 @@ case class Player(
def finish(winner: Boolean) = copy(isWinner = winner option true)
def offerDraw(turn: Int) =
copy(
isOfferingDraw = true,
lastDrawOffer = Some(turn)
)
def offerDraw = copy(isOfferingDraw = true)
def removeDrawOffer = copy(isOfferingDraw = false)
@ -165,7 +160,6 @@ object Player {
val aiLevel = "ai"
val isOfferingDraw = "od"
val lastDrawOffer = "ld"
val proposeTakebackAt = "ta"
val rating = "e"
val ratingDiff = "d"
@ -206,7 +200,6 @@ object Player {
aiLevel = r intO aiLevel,
isWinner = win,
isOfferingDraw = r boolD isOfferingDraw,
lastDrawOffer = r intO lastDrawOffer,
proposeTakebackAt = r intD proposeTakebackAt,
userId = userId,
rating = r intO rating flatMap ratingRange,
@ -222,7 +215,6 @@ object Player {
BSONDocument(
aiLevel -> p.aiLevel,
isOfferingDraw -> w.boolO(p.isOfferingDraw),
lastDrawOffer -> p.lastDrawOffer,
proposeTakebackAt -> w.intO(p.proposeTakebackAt),
rating -> p.rating,
ratingDiff -> p.ratingDiff,

View File

@ -41,9 +41,7 @@ final private[round] class Drawer(
case Pov(g, color) if g playerCanOfferDraw color =>
proxy.save {
messenger.system(g, color.fold(trans.whiteOffersDraw, trans.blackOffersDraw).txt())
Progress(g) map { g =>
g.updatePlayer(color, _ offerDraw g.turns)
}
Progress(g) map { _ offerDraw color }
} >>- publishDrawOffer(pov) inject List(Event.DrawOffer(by = color.some))
case _ => fuccess(List(Event.ReloadOwner))
}