Apply one SAN move for the side to move. Returns (new_board, from, to, new_ep).
(board, san, white, ep)
| 123 | |
| 124 | |
| 125 | def _apply(board, san, white, ep): |
| 126 | """Apply one SAN move for the side to move. Returns (new_board, from, to, new_ep).""" |
| 127 | m = san.rstrip("+#!?") |
| 128 | b = _clone(board) |
| 129 | |
| 130 | if m in ("O-O", "O-O-O", "0-0", "0-0-0"): |
| 131 | row = 7 if white else 0 |
| 132 | k = "K" if white else "k" |
| 133 | rk = "R" if white else "r" |
| 134 | if m in ("O-O", "0-0"): |
| 135 | b[row][4], b[row][6], b[row][7], b[row][5] = "", k, "", rk |
| 136 | return b, (row, 4), (row, 6), None |
| 137 | b[row][4], b[row][2], b[row][0], b[row][3] = "", k, "", rk |
| 138 | return b, (row, 4), (row, 2), None |
| 139 | |
| 140 | promo = "" |
| 141 | if "=" in m: |
| 142 | m, promo = m.split("=") |
| 143 | promo = promo[0] |
| 144 | m = m.replace("x", "") |
| 145 | dest = m[-2:] |
| 146 | tr, tc = _sq(dest) |
| 147 | head = m[:-2] |
| 148 | kind = head[0] if head and head[0] in "NBRQK" else "P" |
| 149 | hint = head[1:] if kind != "P" else head # disambiguation (file/rank), or pawn's from-file |
| 150 | want = kind if white else kind.lower() |
| 151 | |
| 152 | candidates = [] |
| 153 | for r in range(8): |
| 154 | for c in range(8): |
| 155 | if board[r][c] == want and _reaches(board, want, r, c, tr, tc, ep): |
| 156 | candidates.append((r, c)) |
| 157 | for ch in hint: # filter by file/rank disambiguation |
| 158 | if ch.isalpha(): |
| 159 | candidates = [(r, c) for (r, c) in candidates if c == ord(ch) - ord("a")] |
| 160 | elif ch.isdigit(): |
| 161 | candidates = [(r, c) for (r, c) in candidates if r == 8 - int(ch)] |
| 162 | if len(candidates) > 1: # remaining ambiguity = pins; keep only king-safe moves |
| 163 | safe = [] |
| 164 | for r, c in candidates: |
| 165 | t = _clone(board) |
| 166 | t[tr][tc] = t[r][c] |
| 167 | t[r][c] = "" |
| 168 | kp = _king(t, white) |
| 169 | if kp and not _attacked(t, kp[0], kp[1], not white): |
| 170 | safe.append((r, c)) |
| 171 | if safe: |
| 172 | candidates = safe |
| 173 | fr, fc = candidates[0] |
| 174 | |
| 175 | piece = b[fr][fc] |
| 176 | new_ep = None |
| 177 | if kind == "P": |
| 178 | if (tr, tc) == ep and board[tr][tc] == "": # en passant: remove passed pawn |
| 179 | b[fr][tc] = "" |
| 180 | if abs(tr - fr) == 2: # double push sets the en-passant square |
| 181 | new_ep = ((fr + tr) // 2, fc) |
| 182 | if promo: |