Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

+Allow asking for draw after opponent asked (Closes #13138) #13182

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion modules/game/src/main/Game.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,8 @@ case class Game(
ply >= 2 &&
!player(color).isOfferingDraw &&
!opponent(color).isAi &&
!playerHasOfferedDrawRecently(color) &&
(!playerHasOfferedDrawRecently(color) ||
!playerIsMostRecentDrawer(color)) &&
!swissPreventsDraw &&
!rulePreventsDraw

Expand All @@ -316,6 +317,9 @@ case class Game(
def playerHasOfferedDrawRecently(color: Color) =
drawOffers.lastBy(color).exists(_ >= ply - 20)

def playerIsMostRecentDrawer(color: Color) =
drawOffers.lastAddedDrawColorIs(color)

def offerDraw(color: Color) = copy(
metadata = metadata.copy(drawOffers = drawOffers.add(color, ply))
).updatePlayer(color, _.offerDraw)
Expand Down
10 changes: 7 additions & 3 deletions modules/game/src/main/Metadata.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,16 @@ private[game] object Metadata:
val empty =
Metadata(None, None, None, None, None, analysed = false, GameDrawOffers.empty, rules = Set.empty)

case class GameDrawOffers(white: Set[Ply], black: Set[Ply]):
case class GameDrawOffers(white: Set[Ply], black: Set[Ply], lastDrawColor: Option[Color]):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't that new lastDrawColor superfluous? it seems it can be inferred from the existing values - highest ply of the 2 sets

Copy link
Contributor Author

@TheMadSword TheMadSword Jul 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

If you want to limit at once per ply per player a draw request, then yes I could change it to do that.

I thought about using the Sets too at first, but then, just imagine,
On a ply "x" :
1- W offers draw, "x" is added to the white set. B decline the offer.
2- B offers draw, "x" is added to the black set. W decline the offer.
3- Who can now offer ? Because looking at the sets, you have no way of knowing [server-side] who offered last, since (the set maximums) are equals. How can we know which was last ? Then let's say we allow to offer when they are equals (max-ply-offer-W == max-ply-offer-B). Then we still don't have a way of knowing who did the last one server-side (and a player could be double-offering in a row). We could start modifying the sets to represent that it is as if a draw was not done for a player during a ply, but this would cause other problems down the line for sure (plus as the draw offers get normalized/annotated [another issue I created, they aren't always networked/visible when reloading #13183], and they would then be missing.

Imagine the scenario where the position is very complex. W sees it is drawish and offers a draw. B declines immediately. Then realized it is drawish and offers a draw. B then cancel as he thought he was wrong (OR W think he was wrong and decline the offer as he thought he was wrong initially). Then both people realized it's a draw. The game is now forced to progressed with two people wanting to draw. I am not sure this is wanted as explained via refering to the wiki in the issue.

It all comes down to whether we want to allow more than one draw offer per player per ply. I think allowing it gives greater freedom to the player with a not so high complexity.

Copy link
Member

@lenguyenthanh lenguyenthanh Jul 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to limit at once per ply per player a draw request, then yes I could change it to do that.

If we go this road, We should consider FIDE rule, that only allow offer the draw in your turn. But it also has some other special case I think.

But, if we don't want to do that (keep the current behavior), in case of both offer draw at a same Ply. We can make them wait til next ply in order to offer another draw. How does that sound?

Copy link
Contributor Author

@TheMadSword TheMadSword Jul 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm good for whatever; I'd just want the official ruling ^^.

EDIT 27-08-2023 : Still awaiting the official ruling.

RE-EDIT 28-09-2023 : Still awaiting official ruling.

RE-RE-EDIT 23-11-2023 : Still awaiting official ruling.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case of both offer draw at a same Ply. We can make them wait til next ply in order to offer another draw.

I prefer the this approach. It makes sense. keep the current behavior, and doesn't required big changes. So, I guess you can just go for it.


def lastBy(color: Color): Option[Ply] = color.fold(white, black).maxOption(intOrdering)

def lastAddedDrawColorIs(color: Color) =
lastDrawColor.contains(color)

def add(color: Color, ply: Ply) =
color.fold(copy(white = white incl ply), copy(black = black incl ply))
color.fold(copy(white = white incl ply, lastDrawColor = Some(color)),
copy(black = black incl ply, lastDrawColor = Some(color)))

def isEmpty = this == GameDrawOffers.empty

Expand All @@ -49,7 +53,7 @@ case class GameDrawOffers(white: Set[Ply], black: Set[Ply]):
def normalizedPlies: Set[Ply] = normalize(chess.White) ++ normalize(chess.Black)

object GameDrawOffers:
val empty = GameDrawOffers(Set.empty, Set.empty)
val empty = GameDrawOffers(Set.empty, Set.empty, None)

enum GameRule:
case NoAbort, NoRematch, NoGiveTime, NoClaimWin, NoEarlyDraw
Expand Down
5 changes: 4 additions & 1 deletion ui/round/src/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ export function make(send: SocketSend, ctrl: RoundController): RoundSocket {
if (ctrl.isPlaying()) {
ctrl.data.player.offeringDraw = by === ctrl.data.player.color;
const fromOp = (ctrl.data.opponent.offeringDraw = by === ctrl.data.opponent.color);
if (fromOp) ctrl.opponentRequest('draw', 'yourOpponentOffersADraw');
if (fromOp) {
ctrl.opponentRequest('draw', 'yourOpponentOffersADraw');
ctrl.data.player.lastDrawOfferAtPly = undefined;
}
}
if (by) {
let ply = ctrl.lastPly();
Expand Down
Loading