Skip to content

Conversation

@johndoknjas
Copy link
Contributor

Just something that'll be useful in making my insufficient material stuff (from lichess-org/lila#17114) handle antichess properly.

@johndoknjas johndoknjas marked this pull request as draft June 8, 2025 06:49
InsufficientMatingMaterial.pawnBlockedByPawn(pawn, position) && cannotAttackBishop

private def AllOnSameColourSquares(position: Position): Boolean =
position.white.squares.forall(_.isLight) == position.black.squares.forall(_.isLight)
Copy link
Collaborator

Choose a reason for hiding this comment

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

the name of this function is misleading.

In a position where both players have pieces on light and dark squares - like the initial position - then AllOnSameColourSquares returns true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, missed that

position.white.squares.forall(_.isLight) == position.black.squares.forall(_.isLight)

private def justOneKnightEach(position: Position): Boolean =
position.onlyKnights && position.white.squares.size == 1 && position.black.squares.size == 1
Copy link
Collaborator

@ornicar ornicar Jun 8, 2025

Choose a reason for hiding this comment

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

instead of requesting the list of squares then counting the list, you could do a much faster bit count

@johndoknjas johndoknjas marked this pull request as ready for review June 8, 2025 07:37
InsufficientMatingMaterial(position, !position.color)

def playerHasInsufficientMaterial(position: Position): Boolean =
throw Exception("`playerHasInsufficientMaterial` is not implemented for this variant")
Copy link
Member

Choose a reason for hiding this comment

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

We usually don't throw exceptions. If this function is only applicable to Antichess, maybe we should have this as public function in Antichess.

But tbh, I'm not sure. Do you think the issue we had in lichess-org/lila#17114 only happen in Antichess? I haven't verified but I think it could happen to other variants as well.

Copy link
Contributor Author

@johndoknjas johndoknjas Jun 8, 2025

Choose a reason for hiding this comment

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

@lenguyenthanh All other variants have a king on the board though, so reasoning about tempi would have to account for possible triangulations.

EDIT - in Horde.scala, it seems horde stalemates are treated as insufficient material cases. So there turn may matter:

image

If imminent fortresses in Horde were considered by lichess, where a king is running after a pawn that will inevitably be stalemated, then turn would matter there too.

johndoknjas and others added 4 commits June 8, 2025 15:50
Co-authored-by: Thanh Le <lenguyenthanh@hotmail.com>
Co-authored-by: Thanh Le <lenguyenthanh@hotmail.com>
…r and use `opponentHasInsufficientMaterial`.
hasInsufficientMaterial(position.board, !position.color) || hordeClosedPosition(position)

override def playerHasInsufficientMaterial(position: Position): Boolean =
throw Exception("`playerHasInsufficientMaterial` is not implemented yet for Horde")
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can't have Variant offer a playerHasInsufficientMaterial(position: Position): Boolean function that sometimes throws an exception.

This would either create bugs, or force us to code defensively by catching exceptions. It's not how we do things 'round here.

If Variant.playerHasInsufficientMaterial can't always return a Boolean, then it must return an Option[Boolean] to let the type system know what's up, and force callers to act accordingly.

Copy link
Contributor Author

@johndoknjas johndoknjas Jun 10, 2025

Choose a reason for hiding this comment

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

@ornicar Updated to do this. Before merging, do you think it'd be good to add tests in each variant for playerHasInsufficientMaterial?

  • In most variants, this would just look like making sure the result is the same as opponentHasInsufficientMaterial (with colours swapped, not preserving turn). Maybe these two checks could be grouped in a function?
  • In Horde, a test would just make sure the function returns None.
  • In Antichess, there are already tests from this PR.

Copy link
Collaborator

@ornicar ornicar Jun 10, 2025

Choose a reason for hiding this comment

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

making sure the result is the same as opponentHasInsufficientMaterial (with colours swapped, not preserving turn).

That's exactly what the function does so I don't think the test would add any value.

We don't need to test it, but are we 100% sure that this affirmation is true?

For all variants except Antichess and Horde, considering turn isn't needed

If you are certain that it's true for standard chess especially, then I think the PR is good to go.

Copy link
Member

Choose a reason for hiding this comment

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

Variant.playerHasInsufficientMaterial could return Boolean with false mean we don't know.

We have few insufficientMaterial functions. I think we should think about their semantic. I'm thinking about something like:

isInsufficientMaterial == opponentHasInsufficientMaterial && playerHasInsufficientMaterial

Copy link
Collaborator

Choose a reason for hiding this comment

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

Variant.playerHasInsufficientMaterial could return Boolean with false mean we don't know.

I disagree, I think there's a useful distinction between "no" and "I don't know", that Option[Boolean] provides here. The caller decides whether to treat them differently or not.

Copy link
Member

@lenguyenthanh lenguyenthanh Jun 11, 2025

Choose a reason for hiding this comment

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

Variant.playerHasInsufficientMaterial could return Boolean with false mean we don't know.

I disagree, I think there's a useful distinction between "no" and "I don't know", that Option[Boolean] provides here. The caller decides whether to treat them differently or not.

Correct me if I'm wrong but I believe both isInsufficientMaterial and opponentHasInsufficientMaterial return false negative sometime as we only check it with our best effort. So we already use the semantic of false means we know it's false or we don't know.

Copy link
Member

Choose a reason for hiding this comment

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

I believe for these *HasInsufficientMaterial we want to guarantee two things:

  • No false positive
  • They have to be consistent between each other

Copy link
Member

Choose a reason for hiding this comment

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

We don't need to test it, but are we 100% sure that this affirmation is true?

For all variants except Antichess and Horde, considering turn isn't needed

If you are certain that it's true for standard chess especially, then I think the PR is good to go.

I'm not sure this is 100 % true either

@ornicar ornicar merged commit 48576fa into lichess-org:master Jun 11, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants