jacksonthall22 / SAN-strings

Generate all possible Standard Algebraic Notation (SAN) strings for chess moves
6 stars 0 forks source link

Some moves with disambiguators cannot cause check/checkmate #4

Open jacksonthall22 opened 3 months ago

jacksonthall22 commented 3 months ago

From https://chess.stackexchange.com/a/45638/19286:

Even if a disambiguated move is itself possible, it may be incompatible with checks/checkmates. Any move that can't possibly cause new squares to become attacked cannot cause check or checkmate. This is relevant if disambiguation in the move happens to make the start square and end square badly aligned with other pieces of the same type that must have been on certain squares, especially for queens. For example, Qb2a1 cannot cause new attacks because other queens must have been on b1 and a2 as well, so Qb2a1+ and Qb2a1# are impossible; same for Qc3(a1/b2),Qd4(a1/b2), and maybe more that I haven't found systematically. Edit: I'm not sure about bishops, as eg. Bc3b2 doesn't make the bishops attack more squares but may discover check/mate.

phoenizboi commented 2 months ago

Bishop moves can be impossible. An example is Ba1c3+ IMG_0565 The bishop can't make a discovery attack because it started off in the corner I'm sorry I can't do much else to help, but I can try and filter out the impossible moves manually

phoenizboi commented 2 months ago

I think I got everything. I triple checked it and it looks good, but please check it yourself. Here is every invalid move that should be removed:

"Ba1b2+", "Ba1b2#", "Ba1c3+", "Ba1c3#", "Ba1d4+", "Ba1d4#", "Ba8b7+", "Ba8b7#", "Ba8c6+", "Ba8c6#", "Ba8d5+", "Ba8d5#", "Bh1e4+", "Bh1e4#", "Bh1f3+", "Bh1f3#", "Bh1g2+", "Bh1g2#", "Bh8e5+", "Bh8e5#", "Bh8f6+", "Bh8f6#", "Bh8g7+", "Bh8g7#", "Qa1h8+", "Qa1h8#", "Qa8h1+", "Qa8h1#", "Qb2a1+", "Qb2a1#", "Qb2h8+", "Qb2h8#", "Qb7a8+", "Qb7a8#", "Qb7h1+", "Qb7h1#", "Qc3a1+", "Qc3a1#", "Qc3h8+", "Qc3h8#", "Qc4a2+", "Qc4a2#", "Qc5a7+", "Qc5a7#", "Qc6a8+", "Qc6a8#", "Qc6h1+", "Qc6h1#", "Qd3b1+", "Qd3b1#", "Qd4a1+", "Qd4a1#", "Qd4b2+", "Qd4b2#", "Qd4h8+", "Qd4h8#", "Qd5a8+", "Qd5a8#", "Qd5b7+", "Qd5b7#", "Qd5h1+", "Qd5h1#", "Qd6b8+", "Qd6b8#", "Qe3g1+", "Qe3g1#", "Qe4a8+", "Qe4a8#", "Qe4g2+", "Qe4g2#", "Qe4h1+", "Qe4h1#", "Qe5a1+", "Qe5a1#", "Qe5g7+", "Qe5g7#", "Qe5h8+", "Qe5h8#", "Qe6g8+", "Qe6g8#", "Qf3a8+", "Qf3a8#", "Qf3h1+", "Qf3h1#", "Qf4h2+", "Qf4h2#", "Qf5h7+", "Qf5h7#", "Qf6a1+", "Qf6a1#", "Qf6h8+", "Qf6h8#", "Qg2a8+", "Qg2a8#", "Qg2h1+", "Qg2h1#", "Qg7a1+", "Qg7a1#", "Qg7h8+", "Qg7h8#", "Qh1a8+", "Qh1a8#", "Qh8a1+", "Qh8a1#"

phoenizboi commented 2 months ago

Looks like I forgot the moves with captures, here's actually everything:

"Ba1b2+", "Ba1b2#", "Ba1c3+", "Ba1c3#", "Ba1d4+", "Ba1d4#", "Ba8b7+", "Ba8b7#", "Ba8c6+", "Ba8c6#", "Ba8d5+", "Ba8d5#", "Bh1e4+", "Bh1e4#", "Bh1f3+", "Bh1f3#", "Bh1g2+", "Bh1g2#", "Bh8e5+", "Bh8e5#", "Bh8f6+", "Bh8f6#", "Bh8g7+", "Bh8g7#", "Qa1h8+", "Qa1h8#", "Qa8h1+", "Qa8h1#", "Qb2a1+", "Qb2a1#", "Qb2h8+", "Qb2h8#", "Qb7a8+", "Qb7a8#", "Qb7h1+", "Qb7h1#", "Qc3a1+", "Qc3a1#", "Qc3h8+", "Qc3h8#", "Qc4a2+", "Qc4a2#", "Qc5a7+", "Qc5a7#", "Qc6a8+", "Qc6a8#", "Qc6h1+", "Qc6h1#", "Qd3b1+", "Qd3b1#", "Qd4a1+", "Qd4a1#", "Qd4b2+", "Qd4b2#", "Qd4h8+", "Qd4h8#", "Qd5a8+", "Qd5a8#", "Qd5b7+", "Qd5b7#", "Qd5h1+", "Qd5h1#", "Qd6b8+", "Qd6b8#", "Qe3g1+", "Qe3g1#", "Qe4a8+", "Qe4a8#", "Qe4g2+", "Qe4g2#", "Qe4h1+", "Qe4h1#", "Qe5a1+", "Qe5a1#", "Qe5g7+", "Qe5g7#", "Qe5h8+", "Qe5h8#", "Qe6g8+", "Qe6g8#", "Qf3a8+", "Qf3a8#", "Qf3h1+", "Qf3h1#", "Qf4h2+", "Qf4h2#", "Qf5h7+", "Qf5h7#", "Qf6a1+", "Qf6a1#", "Qf6h8+", "Qf6h8#", "Qg2a8+", "Qg2a8#", "Qg2h1+", "Qg2h1#", "Qg7a1+", "Qg7a1#", "Qg7h8+", "Qg7h8#", "Qh1a8+", "Qh1a8#", "Qh8a1+", "Qh8a1#", "Ba1xb2+", "Ba1xb2#", "Ba1xc3+", "Ba1xc3#", "Ba1xd4+", "Ba1xd4#", "Ba8xb7+", "Ba8xb7#", "Ba8xc6+", "Ba8xc6#", "Ba8xd5+", "Ba8xd5#", "Bh1xe4+", "Bh1xe4#", "Bh1xf3+", "Bh1xf3#", "Bh1xg2+", "Bh1xg2#", "Bh8xe5+", "Bh8xe5#", "Bh8xf6+", "Bh8xf6#", "Bh8xg7+", "Bh8xg7#", "Qa1xh8+", "Qa1xh8#", "Qa8xh1+", "Qa8xh1#", "Qb2xa1+", "Qb2xa1#", "Qb2xh8+", "Qb2xh8#", "Qb7xa8+", "Qb7xa8#", "Qb7xh1+", "Qb7xh1#", "Qc3xa1+", "Qc3xa1#", "Qc3xh8+", "Qc3xh8#", "Qc4xa2+", "Qc4xa2#", "Qc5xa7+", "Qc5xa7#", "Qc6xa8+", "Qc6xa8#", "Qc6xh1+", "Qc6xh1#", "Qd3xb1+", "Qd3xb1#", "Qd4xa1+", "Qd4xa1#", "Qd4xb2+", "Qd4xb2#", "Qd4xh8+", "Qd4xh8#", "Qd5xa8+", "Qd5xa8#", "Qd5xb7+", "Qd5xb7#", "Qd5xh1+", "Qd5xh1#", "Qd6xb8+", "Qd6xb8#", "Qe3xg1+", "Qe3xg1#", "Qe4xa8+", "Qe4xa8#", "Qe4xg2+", "Qe4xg2#", "Qe4xh1+", "Qe4xh1#", "Qe5xa1+", "Qe5xa1#", "Qe5xg7+", "Qe5xg7#", "Qe5xh8+", "Qe5xh8#", "Qe6xg8+", "Qe6xg8#", "Qf3xa8+", "Qf3xa8#", "Qf3xh1+", "Qf3xh1#", "Qf4xh2+", "Qf4xh2#", "Qf5xh7+", "Qf5xh7#", "Qf6xa1+", "Qf6xa1#", "Qf6xh8+", "Qf6xh8#", "Qg2xa8+", "Qg2xa8#", "Qg2xh1+", "Qg2xh1#", "Qg7xa1+", "Qg7xa1#", "Qg7xh8+", "Qg7xh8#", "Qh1xa8+", "Qh1xa8#", "Qh8xa1+", "Qh8xa1#"

jacksonthall22 commented 2 months ago

Hey, thanks! Can you share the code you used to generate this? That logic is ultimately how we should decide if the list is right. Or feel free to fork and submit a PR.

phoenizboi commented 2 months ago

Ok im actually completely new to github and don't know how to write programs. I did this all manually. Also i discovered that i messed it up once again, because i found out that captures can allow for a piece to block, and then be captured to reveal a check, making some moves that i listed actually possible. I am very sorry, as you probably expected actual solid proof and all you got was this

anyways here's the updated list:

"Ba1b2+", "Ba1b2#", "Ba1c3+", "Ba1c3#", "Ba1d4+", "Ba1d4#", "Ba8b7+", "Ba8b7#", "Ba8c6+", "Ba8c6#", "Ba8d5+", "Ba8d5#", "Bh1e4+", "Bh1e4#", "Bh1f3+", "Bh1f3#", "Bh1g2+", "Bh1g2#", "Bh8e5+", "Bh8e5#", "Bh8f6+", "Bh8f6#", "Bh8g7+", "Bh8g7#", "Qa1h8+", "Qa1h8#", "Qa8h1+", "Qa8h1#", "Qb2a1+", "Qb2a1#", "Qb2h8+", "Qb2h8#", "Qb7a8+", "Qb7a8#", "Qb7h1+", "Qb7h1#", "Qc3a1+", "Qc3a1#", "Qc3h8+", "Qc3h8#", "Qc4a2+", "Qc4a2#", "Qc5a7+", "Qc5a7#", "Qc6a8+", "Qc6a8#", "Qc6h1+", "Qc6h1#", "Qd3b1+", "Qd3b1#", "Qd4a1+", "Qd4a1#", "Qd4b2+", "Qd4b2#", "Qd4h8+", "Qd4h8#", "Qd5a8+", "Qd5a8#", "Qd5b7+", "Qd5b7#", "Qd5h1+", "Qd5h1#", "Qd6b8+", "Qd6b8#", "Qe3g1+", "Qe3g1#", "Qe4a8+", "Qe4a8#", "Qe4g2+", "Qe4g2#", "Qe4h1+", "Qe4h1#", "Qe5a1+", "Qe5a1#", "Qe5g7+", "Qe5g7#", "Qe5h8+", "Qe5h8#", "Qe6g8+", "Qe6g8#", "Qf3a8+", "Qf3a8#", "Qf3h1+", "Qf3h1#", "Qf4h2+", "Qf4h2#", "Qf5h7+", "Qf5h7#", "Qf6a1+", "Qf6a1#", "Qf6h8+", "Qf6h8#", "Qg2a8+", "Qg2a8#", "Qg2h1+", "Qg2h1#", "Qg7a1+", "Qg7a1#", "Qg7h8+", "Qg7h8#", "Qh1a8+", "Qh1a8#", "Qh8a1+", "Qh8a1#", "Qa1xh8+", "Qa1xh8#", "Qa8xh1+", "Qa8xh1#", "Qb2xa1+", "Qb2xa1#", "Qb2xh8+", "Qb2xh8#", "Qb7xa8+", "Qb7xa8#", "Qb7xh1+", "Qb7xh1#", "Qc3xa1+", "Qc3xa1#", "Qc3xh8+", "Qc3xh8#", "Qc6xa8+", "Qc6xa8#", "Qc6xh1+", "Qc6xh1#", "Qd4xa1+", "Qd4xa1#", "Qd4xh8+", "Qd4xh8#", "Qd5xa8+", "Qd5xa8#", "Qd5xh1+", "Qd5xh1#", "Qe4xa8+", "Qe4xa8#", "Qe4xh1+", "Qe4xh1#", "Qe5xa1+", "Qe5xa1#", "Qe5xh8+", "Qe5xh8#", "Qf3xa8+", "Qf3xa8#", "Qf3xh1+", "Qf3xh1#", "Qf6xa1+", "Qf6xa1#", "Qf6xh8+", "Qf6xh8#", "Qg2xa8+", "Qg2xa8#", "Qg2xh1+", "Qg2xh1#", "Qg7xa1+", "Qg7xa1#", "Qg7xh8+", "Qg7xh8#", "Qh1xa8+", "Qh1xa8#", "Qh8xa1+", "Qh8xa1#"

phoenizboi commented 2 months ago

I can at least show you my logic if that helps

phoenizboi commented 2 months ago

I discovered that moves like Raa1+ and Raa1# are also impossible, so I added them to my list. Yeah im really sorry and this probably isn't helpful. Im just making a fool of myself at this point, but i'll keep updating my list and posting it here when i inevitably find something new i missed:

"Raa1+", "Raa1#", "Raa2+", "Raa2#", "Raa3+", "Raa3#", "Raa4+", "Raa4#", "Raa5+", "Raa5#", "Raa6+", "Raa6#", "Raa7+", "Raa7#", "Raa8+", "Raa8#", "Rah1+", "Rah1#", "Rah2+", "Rah2#", "Rah3+", "Rah3#", "Rah4+", "Rah4#", "Rah5+", "Rah5#", "Rah6+", "Rah6#", "Rah7+", "Rah7#", "Rah8+", "Rah8#", "Rha1+", "Rha1#", "Rha2+", "Rha2#", "Rha3+", "Rha3#", "Rha4+", "Rha4#", "Rha5+", "Rha5#", "Rha6+", "Rha6#", "Rha7+", "Rha7#", "Rha8+", "Rha8#", "Rhh1+", "Rhh1#", "Rhh2+", "Rhh2#", "Rhh3+", "Rhh3#", "Rhh4+", "Rhh4#", "Rhh5+", "Rhh5#", "Rhh6+", "Rhh6#", "Rhh7+", "Rhh7#", "Rhh8+", "Rhh8#", "Ba1b2+", "Ba1b2#", "Ba1c3+", "Ba1c3#", "Ba1d4+", "Ba1d4#", "Ba8b7+", "Ba8b7#", "Ba8c6+", "Ba8c6#", "Ba8d5+", "Ba8d5#", "Bh1e4+", "Bh1e4#", "Bh1f3+", "Bh1f3#", "Bh1g2+", "Bh1g2#", "Bh8e5+", "Bh8e5#", "Bh8f6+", "Bh8f6#", "Bh8g7+", "Bh8g7#", "Qa1h8+", "Qa1h8#", "Qa8h1+", "Qa8h1#", "Qb2a1+", "Qb2a1#", "Qb2h8+", "Qb2h8#", "Qb7a8+", "Qb7a8#", "Qb7h1+", "Qb7h1#", "Qc3a1+", "Qc3a1#", "Qc3h8+", "Qc3h8#", "Qc4a2+", "Qc4a2#", "Qc5a7+", "Qc5a7#", "Qc6a8+", "Qc6a8#", "Qc6h1+", "Qc6h1#", "Qd3b1+", "Qd3b1#", "Qd4a1+", "Qd4a1#", "Qd4b2+", "Qd4b2#", "Qd4h8+", "Qd4h8#", "Qd5a8+", "Qd5a8#", "Qd5b7+", "Qd5b7#", "Qd5h1+", "Qd5h1#", "Qd6b8+", "Qd6b8#", "Qe3g1+", "Qe3g1#", "Qe4a8+", "Qe4a8#", "Qe4g2+", "Qe4g2#", "Qe4h1+", "Qe4h1#", "Qe5a1+", "Qe5a1#", "Qe5g7+", "Qe5g7#", "Qe5h8+", "Qe5h8#", "Qe6g8+", "Qe6g8#", "Qf3a8+", "Qf3a8#", "Qf3h1+", "Qf3h1#", "Qf4h2+", "Qf4h2#", "Qf5h7+", "Qf5h7#", "Qf6a1+", "Qf6a1#", "Qf6h8+", "Qf6h8#", "Qg2a8+", "Qg2a8#", "Qg2h1+", "Qg2h1#", "Qg7a1+", "Qg7a1#", "Qg7h8+", "Qg7h8#", "Qh1a8+", "Qh1a8#", "Qh8a1+", "Qh8a1#", "Raxa1+", "Raxa1#", "Raxa8+", "Raxa8#", "Raxh1+", "Raxh1#", "Raxh8+", "Raxh8#", "Rhxa1+", "Rhxa1#", "Rhxa8+", "Rhxa8#", "Rhxh1+", "Rhxh1#", "Rhxh8+", "Rhxh8#", "Qa1xh8+", "Qa1xh8#", "Qa8xh1+", "Qa8xh1#", "Qb2xa1+", "Qb2xa1#", "Qb2xh8+", "Qb2xh8#", "Qb7xa8+", "Qb7xa8#", "Qb7xh1+", "Qb7xh1#", "Qc3xa1+", "Qc3xa1#", "Qc3xh8+", "Qc3xh8#", "Qc6xa8+", "Qc6xa8#", "Qc6xh1+", "Qc6xh1#", "Qd4xa1+", "Qd4xa1#", "Qd4xh8+", "Qd4xh8#", "Qd5xa8+", "Qd5xa8#", "Qd5xh1+", "Qd5xh1#", "Qe4xa8+", "Qe4xa8#", "Qe4xh1+", "Qe4xh1#", "Qe5xa1+", "Qe5xa1#", "Qe5xh8+", "Qe5xh8#", "Qf3xa8+", "Qf3xa8#", "Qf3xh1+", "Qf3xh1#", "Qf6xa1+", "Qf6xa1#", "Qf6xh8+", "Qf6xh8#", "Qg2xa8+", "Qg2xa8#", "Qg2xh1+", "Qg2xh1#", "Qg7xa1+", "Qg7xa1#", "Qg7xh8+", "Qg7xh8#", "Qh1xa8+", "Qh1xa8#", "Qh8xa1+", "Qh8xa1#"

jacksonthall22 commented 2 months ago

Wow, that's very diligent of you, but unfortunately I will need code/pseudocode example to make changes. I just haven't had time to get back into this repo yet, but it should be something along the lines of:

  1. Start with the generated list of SANs without any + or # symbols - I am confident the logic for these is correct now
  2. Loop through these strings and extract the piece type, destination square, whether it's a capture, and the file and/or rank disambiguators (if any)

Then either: 3a. Brute force solution (easy): a. Generate all possible piece configurations with pairs/triplets of that piece type that would cause the SAN to require the observed disambiguation b. Check if, in any of these configurations, making the given SAN move causes new squares to be attacked (note that a capture could make this true instead of false, at least for file-disambiguated moves, ex. Baxc3 as opposed to Bac3 with bishops on a1 and e1). If so, we need to add this SAN with both + and # appended.

or:

3b. Logical solution (harder): a. Come up with some logic to understand what must be true of any configuration causing the SAN to require the observed disambiguation. My intuition (which can't be trusted) says any configuration requiring the observed disambiguation is "equivalent" to any other (if any configuration causes new squares to be attacked, they all will - this will either need solid logic to be convincing or a counterexample). If that's the case, generate a single configuration that causes this disambiguation. b. Check if making the given SAN move in this configuration causes new squares to be attacked. If so, add this SAN with both + and # appended.

phoenizboi commented 2 months ago

I tried to come up with some sort of logic, it's not very good but it's the best I have for now.

This issue happens when the disambiguations force the pieces into an orientation where no new pieces are attacked by the move. For a capture to also have this problem, I found that the only way is for the move to land on a corner, which will become obvious as I explain.

For pawns: Pawns can only be single disambiguated by file, and for them to be disambiguated, a capture is required. Pawns only attack the squares on the rank one ahead of them. Every time a pawn moves, it moves forward one rank, so a pawn will always attack new squares. Promotion will always attack new squares. Therefore, disambiguation for pawns can always result in a check or checkmate. IMG_0610

For knights: For a knight to not attack any new squares, they must all be either off of the board or occupied by another knight. Double disambiguation requires the knights to be arranged in a 5x3 rectangle, which forces the destination square to not be on the edge of the board, allowing for newly attacked squares: IMG_0611

Single disambiguation is a little promising, because the corners block all but two squares: IMG_0612

However, this is easily countered with a discovered check: IMG_0613

So all the cases are covered: knight moves can always cause check or checkmate.

The other pieces (rooks, bishops, and queens) can have this issue. However, captures must be treated seperately.

Rooks: Rooks have this problem every time they start on an edge AND both rooks are on different ranks and files. they don't start on the edge of the board, they can reveal a discovered check: IMG_0614

If the rooks can occupy the same rank or file, they can attack new squares:

IMG_0615

But if they start on an edge, and they are forced to lie on different ranks and files, then the other rook already covers the squares that the moved rook tries to attack:

IMG_0616

Edit: so while making this and trying to figure out what exactly can cause both issues, i found more moves that don't work, and i must extend my list again :(

Rank disambiguation never causes this issue as it lets the rooks lie on the same file.

The move must end on the a or h file because otherwise the other rook can lie on the same rank.

So you can just check for both requirements: if both rooks MUST lie on different ranks and files, and if the moving rook MUST start on an edge.

For captures: the same restrictions apply, but the move must also end on a corner. Otherwise, a piece can be captured to reveal a check:

IMG_0617

Bishops: The move must start on a corner to have this problem. That's it.

IMG_0618

For this to happen, the move must be doubly disambiguated. If it isn't, it allows for the bishop to not start on the corner:

IMG_0619

And if it doesn't start on a corner, then discovered check is always possible.

Also, none of the bishop captures have this problem, because they can just capture and reveal a check:

IMG_0627

So, simply search for all the bishop moves that start in the corner and don't capture a piece.

Queens: there are a couple ways this can happen, but both ways require double disambiguation. The simplest is if the queen moves diagonally into a corner:

IMG_0620

The edge of the board forces the other queens to be in spots that block/cover the moved queen. The diagonal is always attacked. Also, the corner also blocks potential new directions the queen would attack. Here's an example of how the queen could attack new squares if it doesn't land on a corner:

IMG_0621

The blue lines are always covered, but the red lines that were blocked by the corner are now exposed. Therefore, it must land on a corner, except for ONE SINGLE random edge case I found (but for a good reason):

IMG_0622

The other queens just so happen to cover the exposed red lines. If they were one square further away from the corner, the queen could move to a spot that doesn't block the moved queen:

IMG_0623

And if the queens were further apart, they don't cover the red lines:

IMG_0621

Also, this square can be shifted around a bit:

IMG_0624 IMG_0625

If it is fully pushed against the corner, that's just the first case of the queen landing in the corner. For captures, most of the cases still apply, except for the outlier that i just described. Because the queen no longer lands on the corner, it can reveal a check by capturing a piece:

IMG_0626

Queens are a little complicated, and I can't really come up with a rigid proof for these. However, the basic idea is that single disambiguation gives too much wiggle room, and the edge of the board must be used to force the queens into awkward positions that block the moving queen.

phoenizboi commented 2 months ago

I'm just gonna paste the ones that I missed, instead of everything again. I'm almost positive that this is everything, because I solidified my logic, but then again I have made changes like 5 times so

"Rbxa1+", "Rbxa1#", "Rbxa8+", "Rbxa8#", "Rbxh1+", "Rbxh1#", "Rbxh8+", "Rbxh8#", "Rcxa1+", "Rcxa1#", "Rcxa8+", "Rcxa8#", "Rcxh1+", "Rcxh1#", "Rcxh8+", "Rcxh8#", "Rdxa1+", "Rdxa1#", "Rdxa8+", "Rdxa8#", "Rdxh1+", "Rdxh1#", "Rdxh8+", "Rdxh8#", "Rexa1+", "Rexa1#", "Rexa8+", "Rexa8#", "Rexh1+", "Rexh1#", "Rexh8+", "Rexh8#", "Rfxa1+", "Rfxa1#", "Rfxa8+", "Rfxa8#", "Rfxh1+", "Rfxh1#", "Rfxh8+", "Rfxh8#", "Rgxa1+", "Rgxa1#", "Rgxa8+", "Rgxa8#", "Rgxh1+", "Rgxh1#", "Rgxh8+", "Rgxh8#"