Upload mapping.py
Browse files- mapping.py +141 -0
mapping.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Dict, Tuple, Set
|
| 2 |
+
|
| 3 |
+
# --- Constants --- #
|
| 4 |
+
MAX_HALFMOVES = 128 # cap for embedding table size
|
| 5 |
+
MAX_FULLMOVES = 256 # cap for embedding table size
|
| 6 |
+
|
| 7 |
+
# --- Helper Mappings --- #
|
| 8 |
+
PIECE_TO_IDX: Dict[str, int] = {
|
| 9 |
+
'P': 0, 'N': 1, 'B': 2, 'R': 3, 'Q': 4, 'K': 5,
|
| 10 |
+
'p': 6, 'n': 7, 'b': 8, 'r': 9, 'q': 10, 'k': 11,
|
| 11 |
+
'.': 12
|
| 12 |
+
}
|
| 13 |
+
IDX_TO_PIECE: Dict[int, str] = {v: k for k, v in PIECE_TO_IDX.items()}
|
| 14 |
+
EMPTY_SQ_IDX = PIECE_TO_IDX['.']
|
| 15 |
+
# Map algebraic square notation (e.g., 'a1', 'h8') to 0-63 index
|
| 16 |
+
# a1=0, b1=1, ..., h1=7, a2=8, ..., h8=63
|
| 17 |
+
SQUARE_TO_IDX: Dict[str, int] = {
|
| 18 |
+
f"{file}{rank}": (rank - 1) * 8 + (ord(file) - ord('a'))
|
| 19 |
+
for rank in range(1, 9)
|
| 20 |
+
for file in 'abcdefgh'
|
| 21 |
+
}
|
| 22 |
+
IDX_TO_SQUARE: Dict[int, str] = {v: k for k, v in SQUARE_TO_IDX.items()}
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
# --- Coordinate and Notation Helpers ---
|
| 27 |
+
|
| 28 |
+
# Precompute maps for efficiency
|
| 29 |
+
_IDX_TO_COORDS: Dict[int, Tuple[int, int]] = {i: (i // 8, i % 8) for i in range(64)} # (rank, file) 0-7
|
| 30 |
+
_COORDS_TO_IDX: Dict[Tuple[int, int], int] = {v: k for k, v in _IDX_TO_COORDS.items()}
|
| 31 |
+
_IDX_TO_ALG: Dict[int, str] = {
|
| 32 |
+
i: f"{chr(ord('a') + file)}{rank + 1}"
|
| 33 |
+
for i, (rank, file) in _IDX_TO_COORDS.items()
|
| 34 |
+
}
|
| 35 |
+
_ALG_TO_IDX: Dict[str, int] = {v: k for k, v in _IDX_TO_ALG.items()}
|
| 36 |
+
|
| 37 |
+
def _coords_to_alg(r: int, f: int) -> str:
|
| 38 |
+
"""Converts 0-indexed (rank, file) to algebraic notation."""
|
| 39 |
+
if 0 <= r < 8 and 0 <= f < 8:
|
| 40 |
+
return f"{chr(ord('a') + f)}{r + 1}"
|
| 41 |
+
# This should not happen with valid indices, but good for safety
|
| 42 |
+
raise ValueError(f"Invalid coordinates: ({r}, {f})")
|
| 43 |
+
|
| 44 |
+
def generate_structurally_valid_move_map() -> Dict[str, int]:
|
| 45 |
+
"""
|
| 46 |
+
Generates a dictionary mapping chess moves that are geometrically possible
|
| 47 |
+
by *some* standard piece (K, Q, R, B, N, or P) to unique integer indices.
|
| 48 |
+
It excludes moves that are structurally impossible for any piece to make
|
| 49 |
+
in one turn (e.g., a1->h5 for non-knight).
|
| 50 |
+
|
| 51 |
+
Includes standard UCI promotions (e.g., "e7e8q"), replacing the
|
| 52 |
+
corresponding simple pawn move to the final rank (e.g., "e7e8").
|
| 53 |
+
This is based purely on piece movement geometry, not the current board state.
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
Dict[str, int]: A map from the valid UCI move string to a unique
|
| 57 |
+
integer index (0 to N-1). The size N is expected
|
| 58 |
+
to be around 1800-1900.
|
| 59 |
+
"""
|
| 60 |
+
valid_moves: Set[str] = set()
|
| 61 |
+
# Keep track of base moves (like 'e7e8') that are replaced by promotions
|
| 62 |
+
# according to UCI standard.
|
| 63 |
+
promo_base_moves_to_exclude: Set[str] = set()
|
| 64 |
+
|
| 65 |
+
# 1. Generate all geometrically possible non-promotion moves
|
| 66 |
+
for from_idx in range(64):
|
| 67 |
+
from_r, from_f = _IDX_TO_COORDS[from_idx]
|
| 68 |
+
from_alg = _IDX_TO_ALG[from_idx]
|
| 69 |
+
|
| 70 |
+
for to_idx in range(64):
|
| 71 |
+
if from_idx == to_idx:
|
| 72 |
+
continue
|
| 73 |
+
|
| 74 |
+
to_r, to_f = _IDX_TO_COORDS[to_idx]
|
| 75 |
+
to_alg = _IDX_TO_ALG[to_idx]
|
| 76 |
+
dr, df = to_r - from_r, to_f - from_f
|
| 77 |
+
abs_dr, abs_df = abs(dr), abs(df)
|
| 78 |
+
|
| 79 |
+
# Check if the geometry matches any standard piece movement
|
| 80 |
+
# Note: Queen moves are covered by Rook + Bishop checks.
|
| 81 |
+
# Note: Pawn single pushes/captures are covered by King/Rook/Bishop geometry.
|
| 82 |
+
# Note: Pawn double pushes are covered by Rook geometry.
|
| 83 |
+
is_king_move = max(abs_dr, abs_df) == 1
|
| 84 |
+
is_knight_move = (abs_dr == 2 and abs_df == 1) or (abs_dr == 1 and abs_df == 2)
|
| 85 |
+
is_rook_move = dr == 0 or df == 0 # Includes King horiz/vert & pawn double push
|
| 86 |
+
is_bishop_move = abs_dr == abs_df # Includes King diagonal & pawn capture/push
|
| 87 |
+
|
| 88 |
+
if is_king_move or is_knight_move or is_rook_move or is_bishop_move:
|
| 89 |
+
uci_move = f"{from_alg}{to_alg}"
|
| 90 |
+
valid_moves.add(uci_move)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
# 2. Generate promotion moves explicitly and mark base moves for exclusion
|
| 94 |
+
promo_pieces = ['q', 'r', 'b', 'n']
|
| 95 |
+
for from_f in range(8):
|
| 96 |
+
# White promotions (from rank 7 (idx 6) to rank 8 (idx 7))
|
| 97 |
+
from_r_w, to_r_w = 6, 7
|
| 98 |
+
if from_r_w != 7: # Ensure we are on the correct rank before promotion
|
| 99 |
+
from_alg_w = _coords_to_alg(from_r_w, from_f)
|
| 100 |
+
# Possible destinations: push (df=0), capture left (df=-1), capture right (df=1)
|
| 101 |
+
for df in [-1, 0, 1]:
|
| 102 |
+
to_f_w = from_f + df
|
| 103 |
+
if 0 <= to_f_w < 8:
|
| 104 |
+
to_alg_w = _coords_to_alg(to_r_w, to_f_w)
|
| 105 |
+
base_move = f"{from_alg_w}{to_alg_w}"
|
| 106 |
+
#promo_base_moves_to_exclude.add(base_move) # Mark e.g. "e7e8" for exclusion
|
| 107 |
+
for p in promo_pieces:
|
| 108 |
+
valid_moves.add(f"{base_move}{p}") # Add e.g. "e7e8q"
|
| 109 |
+
|
| 110 |
+
# Black promotions (from rank 2 (idx 1) to rank 1 (idx 0))
|
| 111 |
+
from_r_b, to_r_b = 1, 0
|
| 112 |
+
if from_r_b != 0: # Ensure we are on the correct rank before promotion
|
| 113 |
+
from_alg_b = _coords_to_alg(from_r_b, from_f)
|
| 114 |
+
# Possible destinations: push (df=0), capture left (df=-1), capture right (df=1)
|
| 115 |
+
for df in [-1, 0, 1]:
|
| 116 |
+
to_f_b = from_f + df
|
| 117 |
+
if 0 <= to_f_b < 8:
|
| 118 |
+
to_alg_b = _coords_to_alg(to_r_b, to_f_b)
|
| 119 |
+
base_move = f"{from_alg_b}{to_alg_b}"
|
| 120 |
+
#promo_base_moves_to_exclude.add(base_move) # Mark e.g. "e2e1" for exclusion
|
| 121 |
+
for p in promo_pieces:
|
| 122 |
+
valid_moves.add(f"{base_move}{p}") # Add e.g. "e2e1q"
|
| 123 |
+
|
| 124 |
+
# 3. Remove the base moves that were replaced by promotions
|
| 125 |
+
final_valid_moves = valid_moves - promo_base_moves_to_exclude
|
| 126 |
+
|
| 127 |
+
# 4. Add draw claim
|
| 128 |
+
final_valid_moves.add("<claim_draw>")
|
| 129 |
+
|
| 130 |
+
# 5. Create the final map with sorted keys for deterministic indices
|
| 131 |
+
sorted_moves = sorted(list(final_valid_moves))
|
| 132 |
+
move_map = {move: i for i, move in enumerate(sorted_moves)}
|
| 133 |
+
|
| 134 |
+
# Optional: Print the number of moves found for verification
|
| 135 |
+
# print(f"Generated {len(move_map)} structurally valid unique UCI moves.")
|
| 136 |
+
|
| 137 |
+
return move_map
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
UCI_MOVE_TO_IDX = generate_structurally_valid_move_map()
|
| 141 |
+
IDX_TO_UCI_MOVE = {v:k for k,v in UCI_MOVE_TO_IDX.items()}
|