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

Changes to isDraw and isGameOver (to fix #112) and fixes for games that include put/remove #417

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

gavin-lb
Copy link
Contributor

Changes

@gavin-lb gavin-lb changed the title Fix feature/strict fide adherence Fixed #144 and #112 Jun 14, 2023
gavin-lb and others added 2 commits June 14, 2023 18:46
Updated parameter to be an optional object of shape `{ strict?: boolean }` to better align with conventions already established elsewhere in the library.
@jhlywa
Copy link
Owner

jhlywa commented Jun 15, 2023

Thanks for the PR @gavin-lb. I'll do my best to review these changes (and familiarize myself with the latest rule updates) sometime this weekend. Since we're technically pre-1.0.0, I'm comfortable breaking backwards compatibility for some of these edge-case draws - meaning we might not need to include the strict parameter on isDraw and isGameOver.

@gavin-lb
Copy link
Contributor Author

Well I think it's probably more common for people to want auto claiming of draws on threefold repetition or 50-move rule anyway, so still makes sense for that to be the default (for example, I'm pretty sure Lichess does it that way). But for those that want to implement a stricter adherence to the FIDE rules as written, the option and bug fix are necessary.

Since this method is very likely going to be called after every single move (for end of game detection) I think it makes sense to optimise it a little bit. Instead of replaying the entire game every turn to determine whether the position has been repeated, we simply keep track of position counts as the game progresses. We just need to make sure to properly update the count when a move is undone or the position reset.
The `load` method used to unnecessarily call `_updateCastlingRights`, `_updateEnPassantSquare`, `_updateSetup` and `fen` methods for every piece it placed on the board (because `put` method called these methods). Optimised  `load` by implementing a private `_put` method which doesn't call these methods, then changed publicly exposed API method `put` to be a wrapper around `_put` that calls these methods if `put` was successful.
@gavin-lb
Copy link
Contributor Author

Added a couple optimisations to this PR, but let me know if you would rather they be a separate PR instead.

@gavin-lb
Copy link
Contributor Author

gavin-lb commented Jun 15, 2023

Fixed a bug with the new _getRepetitionCount implementation: it wasn't adding positions to the record when loading from PGN (sorry ignore the commit name when I said FEN, I meant PGN).

However, it is a little unclear to me what the behaviour should be with regards to repetitions and put/remove. There is no precedence set since the previous implementation didn't consider these pieces at all (since they are not added to _history) - which I suppose could have been considered a bug. But it leaves unanswered how the new implementation should handle these cases. At present, _positionCounts will not be updated on the turn in which pieces are put or removed from the board, but all subsequent turns will include the put/removed pieces in the position. Although, again, since these pieces are not added to the history, things will be messed up if a piece is put/removed from the board and then moves are undone (the piece will remain on the board and the previous positions will not be correctly removed from _positionCounts and indeed previous moves might now be illegal with the addition/removal of the piece). Any thought on how to handle these cases?

@gavin-lb
Copy link
Contributor Author

gavin-lb commented Jun 16, 2023

Using put/remove already break a lot of functionality. Because they are not in the _history but still affect _board: pgn, history and undo methods all potentially produce non-sense output when a game includes a put/remove (particularly regarding SAN).

I think the only obvious way to amend the aforementioned problem with _positionCounts when undoing, as well as the existing problems with pgn, history and undo in general, is to keep track of put/remove calls by adding them to _history.

This will allow undo to properly undo the put/remove, therefore allowing _positionCounts to properly remove previous positions from the record and also allowing history to generate accurate output. The pgn method will still not really be able to generate proper output because (as far as I can tell) there is no way to specify arbitrarily adding/removing pieces from the board in the PGN specification. However, it would at least allow the note of additions/removals to the board via a PGN comment and, whilst the output would still not be a valid PGN as per the specification, it would at least produce something that makes more sense (all the SAN moves would be accurate if accounting for the added/removed piece).

This change would require some alterations across the whole library and would technically affect backwards compatibility. However, it would only affect those users using put/remove as well as undo/history/pgn and given the currently broken state of those methods with put/remove, I don't expect many people are doing this. The backwards compatibility of users that don't use put/remove will be unaffected.

@jhlywa
Copy link
Owner

jhlywa commented Jun 18, 2023

Well I think it's probably more common for people to want auto claiming of draws on threefold repetition or 50-move rule anyway, so still makes sense for that to be the default (for example, I'm pretty sure Lichess does it that way). But for those that want to implement a stricter adherence to the FIDE rules as written, the option and bug fix are necessary.

Yep, you're right. I remember now. I'm in agreement with you here.

As for the API changes to isDraw and isGameOver, would it be better to modify the Chess constructor to take an optional strictFideAdherence flag (or something similar)? I don't know if I have a strong preference either way, but I'd like to explore all options when modifying the API.

Added a couple optimisations to this PR, but let me know if you would rather they be a separate PR instead.

@gavin-lb Let's split those into a new PR and I'll merge them today.

However, it is a little unclear to me what the behaviour should be with regards to repetitions and put/remove.

My opinion is that .put(), .remove(), and .clear() should be considered destructive operations and any call should reset history, move counters, positions counts, etc.. These methods don't do this right now - my fault - but they should. I honestly haven't given much thought to the use-cases involving these methods, so I could be totally wrong here.

Required updating history, undo, pgn and loadPgn methods to work with the new _history structure:
 - undo method now accepts an optional argument of shape { untilMove?: boolean  } (defaults to { untilMove: true }). When true (ie. default behaviour) it will automatically undo all put/removes until the previous move (inclusive). When false, it will only undo the last move/put/remove.
  - history method now accepts an onlyMoves property of its options (defaults to true). When true, history will return only the previous moves (ie. its previous behaviour). When false, history will return all previous moves, puts and removes (where each element is an object of shape { historyType: 'move' | 'put' | 'remove', move: Move | Put | Remove } to indicate which type of history entry it is)
 - pgn method now generates PGNs with noted put/removes as PGN comments. Although, technically these are still not valid PGNs as per the spec, they make more sense than the previous implementation and will be read properly by loadPgn which will perform the necessary put/removes as the game is loaded.
There are lots of places where eslint annoying thinks something might be null when it definitely isn't (eg. the return from .pop() in a while loop where .length > 0), so it is very useful to be able to tell the linter that it isn't null.
@gavin-lb
Copy link
Contributor Author

As for the API changes to isDraw and isGameOver, would it be better to modify the Chess constructor to take an optional strictFideAdherence flag (or something similar)? I don't know if I have a strong preference either way, but I'd like to explore all options when modifying the API.

That's certainly another viable approach. I'm not sure which is preferable really. I suppose passing a flag to the constructor would be less flexible but would promote greater consistency.

Let's split those into a new PR and I'll merge them today.

I've submitted a new PR with only the optimisations (although, the bug fix is a dependency of the isThreefoldRepetition optimisation, so I've also included that).

I will repurpose this PR for changes to the API methods isDraw and isGameOver and how to potentially solve the issues with put/remove.

My opinion is that .put(), .remove(), and .clear() should be considered destructive operations and any call should reset history, move counters, positions counts, etc.. These methods don't do this right now - my fault - but they should. I honestly haven't given much thought to the use-cases involving these methods, so I could be totally wrong here.

Ah that would be a good way to deal with it as it would create no ambiguity at all. But I suppose it is less powerful since clearing the history removes a lot of functionality from the user.

I will push some commits to this PR that show how the solution of keeping track of put/remove calls by adding them to _history might be implemented.

@gavin-lb gavin-lb changed the title Fixed #144 and #112 Changes to isDraw and isGameOver (to fix #112) and fixes for games that include put/remove Jun 18, 2023
@gavin-lb
Copy link
Contributor Author

Latest commits show how _history could be updated to include put/removes, including updates to pgn and loadPgn methods to they can output and read from pgns with put/removes commented like this:

'1. e4 {Chess.js: w p put on e2} {Chess.js: w p removed from d2} e5'

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.

2 participants