end000's picture
Release directly programmed Russian draughts network and reproducible WSPForge experiments
6069338 verified
Raw History Blame Contribute Delete
4.17 kB
from collections import Counter
import numpy as np
from draughts import RussianBoard
from draughts.models import Color
from loguru import logger
logger.remove()
NAMES = RussianBoard.SQUARE_NAMES
def encode(board, perspective):
masks = (board.white_men, board.white_kings, board.black_men, board.black_kings)
if perspective == Color.BLACK:
masks = masks[2:] + masks[:2]
x = np.zeros(128, np.float32)
for p, mask in enumerate(masks):
while mask:
bit = mask & -mask
s = bit.bit_length() - 1
x[p * 32 + (s if perspective == Color.WHITE else 31 - s)] = 1
mask ^= bit
return x
def key(board):
return (board.white_men, board.white_kings, board.black_men, board.black_kings, board.turn.value)
def algebraic_fen(board):
sides = []
for label, man, king in (("W", board.white_men, board.white_kings), ("B", board.black_men, board.black_kings)):
fields = [("K" if king & (1 << s) else "") + NAMES[s] for s in range(32) if (man | king) & (1 << s)]
sides.append(label + ",".join(fields))
return ("W" if board.turn == Color.WHITE else "B") + ":" + ":".join(sides)
def move_text(move):
return (":" if move.captured_list else "-").join(NAMES[s] for s in move.square_list)
class Adjudicator:
def __init__(self, board, max_plies=300):
self.max_plies = max_plies
self.plies = 0
self.seen = Counter({key(board): 1})
self.quiet_kings = 0
self.material_stable = 0
self.regulation_key = None
self.regulation_moves = [0, 0]
self._regulation(board)
def _regulation(self, b):
counts = (b.white_men.bit_count(), b.white_kings.bit_count(), b.black_men.bit_count(), b.black_kings.bit_count())
wm, wk, bm, bk = counts
lone_side = 0 if wm == 0 and wk == 1 else 1 if bm == 0 and bk == 1 else None
if lone_side is None:
new = None
else:
other_m, other_k = (bm, bk) if lone_side == 0 else (wm, wk)
lone_mask = b.white_kings if lone_side == 0 else b.black_kings
diagonal = any((lone_mask >> s) & 1 for s in range(32) if NAMES[s][0] == str("abcdefgh"[int(NAMES[s][1]) - 1]))
if other_k >= 1 and other_m + other_k <= 2:
limit = 5
elif other_k >= 1 and other_m + other_k == 3 and diagonal:
limit = 5
elif other_m == 0 and other_k >= 3:
limit = 15
else:
limit = None
new = (lone_side, other_m, other_k, limit) if limit is not None else None
if new != self.regulation_key:
self.regulation_key = new
self.regulation_moves = [0, 0]
def update(self, board, move, moving_piece, mover):
self.plies += 1
self.seen[key(board)] += 1
self.quiet_kings = self.quiet_kings + 1 if abs(moving_piece) == 2 and not move.captured_list else 0
self.material_stable = 0 if move.captured_list or move.is_promotion else self.material_stable + 1
old = self.regulation_key
self._regulation(board)
if old is not None and old == self.regulation_key:
self.regulation_moves[0 if mover == Color.WHITE else 1] += 1
def outcome(self, board, legal):
if not legal:
return -board.turn.value, "no_legal_moves"
if self.seen[key(board)] >= 3:
return 0, "threefold"
if self.quiet_kings >= 30:
return 0, "15_king_moves"
if self.regulation_key is not None:
lone, _, _, limit = self.regulation_key
if self.regulation_moves[1 - lone] >= limit:
return 0, f"regulation_{limit}"
count = (board.white_men | board.white_kings | board.black_men | board.black_kings).bit_count()
both_kings = bool(board.white_kings and board.black_kings)
if both_kings and ((count <= 5 and self.material_stable >= 60) or (count <= 7 and self.material_stable >= 120)):
return 0, "stable_endgame"
if self.plies >= self.max_plies:
return 0, "experimental_ply_cap"
return None