Parallax-Chess-Preview / chess_encoding.py
ParallaxOpen's picture
Upload chess_encoding.py with huggingface_hub
1829c5b verified
Raw History Blame
3.63 kB
#!/usr/bin/env python3
"""Chess encoding: board positions and moves as integer sequences.
No external tokenizer needed — pure chess-aware encoding.
"""
import chess
# Piece values (0 = empty, 1-6 = white, 7-12 = black)
PIECE_VALUES = {
None: 0,
chess.Piece(chess.PAWN, chess.WHITE): 1,
chess.Piece(chess.KNIGHT, chess.WHITE): 2,
chess.Piece(chess.BISHOP, chess.WHITE): 3,
chess.Piece(chess.ROOK, chess.WHITE): 4,
chess.Piece(chess.QUEEN, chess.WHITE): 5,
chess.Piece(chess.KING, chess.WHITE): 6,
chess.Piece(chess.PAWN, chess.BLACK): 7,
chess.Piece(chess.KNIGHT, chess.BLACK): 8,
chess.Piece(chess.BISHOP, chess.BLACK): 9,
chess.Piece(chess.ROOK, chess.BLACK): 10,
chess.Piece(chess.QUEEN, chess.BLACK): 11,
chess.Piece(chess.KING, chess.BLACK): 12,
}
# Special tokens
PAD_TOKEN = 0
BOS_TOKEN = 13
EOS_TOKEN = 14
SEP_TOKEN = 15
SIDE_WHITE_TOKEN = 16
SIDE_BLACK_TOKEN = 17
MOVE_FROM_OFFSET = 18 # 18 + square (0-63) = from-square token
MOVE_TO_OFFSET = 82 # 82 + square (0-63) = to-square token
# Total vocab: 82 + 64 = 146 + promotions
# Promotion tokens: 146 + promo_type(0-3) * 64 + to_square
PROMO_OFFSET = 146 # 146 + promo*64 + to_square
# promo: 0=queen, 1=rook, 2=bishop, 3=knight
VOCAB_SIZE = PROMO_OFFSET + 4 * 64 # = 402
def encode_board(board):
"""Encode chess board to token sequence.
Returns list of ints: [BOS, piece(64x), side, SEP]
"""
tokens = [BOS_TOKEN]
for square in chess.SQUARES:
piece = board.piece_at(square)
tokens.append(PIECE_VALUES[piece])
tokens.append(SIDE_WHITE_TOKEN if board.turn == chess.WHITE else SIDE_BLACK_TOKEN)
tokens.append(SEP_TOKEN)
return tokens
def encode_move(move):
"""Encode a move to token sequence.
Returns [from_square_token, to_square_token] or [from_square_token, promo_token]
"""
from_sq = move.from_square
to_sq = move.to_square
tokens = [MOVE_FROM_OFFSET + from_sq]
if move.promotion is not None:
promo_map = {chess.QUEEN: 0, chess.ROOK: 1, chess.BISHOP: 2, chess.KNIGHT: 3}
promo_type = promo_map[move.promotion]
tokens.append(PROMO_OFFSET + promo_type * 64 + to_sq)
else:
tokens.append(MOVE_TO_OFFSET + to_sq)
return tokens
def decode_move(tokens, board):
"""Decode token sequence back to a chess.Move.
Input: list of token IDs (just the move tokens, not board tokens).
"""
if len(tokens) < 2:
return None
from_token = tokens[0]
to_token = tokens[1]
from_sq = from_token - MOVE_FROM_OFFSET
if from_sq < 0 or from_sq > 63:
return None
if to_token >= PROMO_OFFSET:
promo_offset = to_token - PROMO_OFFSET
promo_type = promo_offset // 64
to_sq = promo_offset % 64
promo_map = {0: chess.QUEEN, 1: chess.ROOK, 2: chess.BISHOP, 3: chess.KNIGHT}
promotion = promo_map.get(promo_type)
move = chess.Move(from_sq, to_sq, promotion=promotion)
else:
to_sq = to_token - MOVE_TO_OFFSET
if to_sq < 0 or to_sq > 63:
return None
move = chess.Move(from_sq, to_sq)
if move in board.legal_moves:
return move
return None
def encode_game(board, move):
"""Encode a board+move pair for training.
Input sequence: board_tokens + move_tokens
Target: move_tokens (shifted by 1)
"""
board_tokens = encode_board(board)
move_tokens = encode_move(move)
full = board_tokens + move_tokens
return full
def decode_position_to_tokens(board):
"""Get board encoding for inference."""
return encode_board(board)