Skip to content

Commit

Permalink
Add lichess inputs v3 with tests, cpp and python implementation (#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
QueensGambit authored Sep 6, 2023
1 parent b566c04 commit 41ea151
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 34 deletions.
2 changes: 1 addition & 1 deletion DeepCrazyhouse/configs/main_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
# Active mode for different input & output representations.
# Each mode is only compatible with a certain network input-/output representation:
# Available modes: 0: MODE_CRAZYHOUSE (crazyhouse only mode, no 960) available versions [1, 2, 3]
# 1: MODE_LICHESS (all available lichess variants) available versions [1, 2 (last_moves)]
# 1: MODE_LICHESS (all available lichess variants) available versions [1, 2 (last_moves), 3 (last_moves+fx-features)]
# 2: MODE_CHESS (chess only mode, with 960) available versions [1, 2, 3]
"mode": 0,
"version": 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,36 @@ def board_to_planes(board: chess.Board, board_occ, normalize=True, last_moves=No
planes[channel + 1, :, :] = 1
channel += 1

channel = set_additional_custom_features(planes, board, channel, mirror, normalize, include_king=False)

if main_config["mode"] == MODE_CHESS:
assert channel == NB_CHANNELS_TOTAL

return planes


def set_additional_custom_features(planes, board, channel: int, mirror: bool, normalize: bool, include_king: bool,
channel_piece_mask=CHANNEL_PIECE_MASK, channel_checkerboard=CHANNEL_CHECKERBOARD,
channel_material_diff=CHANNEL_MATERIAL_DIFF, channel_opp_bishops=CHANNEL_OPP_BISHOPS,
channel_checkers=CHANNEL_CHECKERS, channel_material_count=CHANNEL_MATERIAL_COUNT):
"""
Sets additional custom features also called FX-features.
:param planes: Planes that will be modified in place
:param board: Board object
:param channel: Starting channel
:param mirror: If the planes should be mirrored
:param normalize: Decides if the planes shoudl be normalized
:param include_king: Decides if the king should be included in the material features (necessary for e.g. anti-chess)
:return none
"""

me = board.turn
you = not board.turn
colors = [me, you]

# Channel: 37 - 38
# All white pieces and black pieces in a single map
assert channel == CHANNEL_PIECE_MASK
assert channel == channel_piece_mask
for color in colors:
# the PIECE_TYPE is an integer list in python-chess
for piece_type in chess.PIECE_TYPES:
Expand All @@ -211,32 +238,34 @@ def board_to_planes(board: chess.Board, board_occ, normalize=True, last_moves=No
# set the bit at the right position
planes[channel, row, col] = 1
channel += 1

# Channel: 39
# Checkerboard
assert(channel == CHANNEL_CHECKERBOARD)
assert (channel == channel_checkerboard)
planes[channel, :, :] = checkerboard()
channel += 1

# Channel: 40 - 44
# Relative material difference (negative if less pieces than opponent and positive if more)
# iterate over all pieces except the king
assert channel == CHANNEL_MATERIAL_DIFF
for piece_type in chess.PIECE_TYPES[:-1]:

assert channel == channel_material_diff
if include_king:
piece_types = chess.PIECE_TYPES
else:
piece_types = chess.PIECE_TYPES[:-1]

for piece_type in piece_types:
material_count = len(board.pieces(piece_type, me)) - len(board.pieces(piece_type, you))
planes[channel, :, :] = material_count / NORMALIZE_PIECE_NUMBER if normalize else material_count
channel += 1

# Channel: 45
# Opposite color bishops
assert (channel == CHANNEL_OPP_BISHOPS)
assert (channel == channel_opp_bishops)
if opposite_colored_bishops(board):
planes[channel, :, :] = 1
channel += 1

# Channel: 46
# Checkers
assert channel == CHANNEL_CHECKERS
assert channel == channel_checkers
board_checkers = checkers(board)
if board_checkers:
# iterate over the piece mask and receive every position square of it
Expand All @@ -245,19 +274,14 @@ def board_to_planes(board: chess.Board, board_occ, normalize=True, last_moves=No
# set the bit at the right position
planes[channel, row, col] = 1
channel += 1

# Channel: 47 - 51
# Material
assert channel == CHANNEL_MATERIAL_COUNT
for piece_type in chess.PIECE_TYPES[:-1]:
assert channel == channel_material_count
for piece_type in piece_types:
material_count = len(board.pieces(piece_type, me))
planes[channel, :, :] = material_count / NORMALIZE_PIECE_NUMBER if normalize else material_count
channel += 1

if main_config["mode"] == MODE_CHESS:
assert channel == NB_CHANNELS_TOTAL

return planes
return channel


def set_no_progress_counter(board, channel, planes, normalized_input, max_nb_no_progress):
Expand Down
10 changes: 8 additions & 2 deletions DeepCrazyhouse/src/domain/variants/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,16 @@
NB_POLICY_MAP_CHANNELS = 84
if VERSION == 1:
NB_LAST_MOVES = 0
else:
else: # VERSION == 2 or VERSION == 3
NB_LAST_MOVES = 8
if VERSION == 1:
NB_CHANNELS_PER_HISTORY_ITEM = 0
else:
else: # VERSION == 2 or VERSION == 3
NB_CHANNELS_PER_HISTORY_ITEM = 2
if VERSION == 3:
NB_CHANNELS_FX = 17
else:
NB_CHANNELS_FX = 0
elif MODE == MODE_XIANGQI:
NB_CHANNELS_POS = 26
NB_CHANNELS_CONST = 2
Expand Down Expand Up @@ -183,6 +187,8 @@
NB_LABELS_POLICY_MAP = NB_POLICY_MAP_CHANNELS * BOARD_HEIGHT * BOARD_WIDTH
NB_CHANNELS_HISTORY = NB_LAST_MOVES * NB_CHANNELS_PER_HISTORY_ITEM
NB_CHANNELS_TOTAL = NB_CHANNELS_POS + NB_CHANNELS_CONST + NB_CHANNELS_VARIANTS + NB_CHANNELS_HISTORY
if MODE == MODE_LICHESS:
NB_CHANNELS_TOTAL += NB_CHANNELS_FX

# define the number of different pieces one can have in his pocket (the king/general is excluded)
if MODE == MODE_XIANGQI:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def planes_to_board(planes, normalized_input):
"""
Converts a board in plane representation to the python chess board representation
see get_planes_of_board() for input encoding description
! Board is always returned with WHITE to move and move number and no progress counter = 0 !
! Board is always returned with WHITE to move and move number = 0 !
:param planes: Input plane representation
:param normalized_input: Defines if the inputs are normalized to [0,1]
Expand Down
9 changes: 8 additions & 1 deletion DeepCrazyhouse/src/domain/variants/input_representation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
import DeepCrazyhouse.src.domain.variants.classical_chess.v3.input_representation as chess_v3
import DeepCrazyhouse.src.domain.variants.crazyhouse.v2.input_representation as crazyhouse_v2
import DeepCrazyhouse.src.domain.variants.crazyhouse.v3.input_representation as crazyhouse_v3
import DeepCrazyhouse.src.domain.variants.lichess.v3.input_representation as lichess_v3
from DeepCrazyhouse.src.domain.variants.default_input_representation import default_board_to_planes,\
default_normalize_input_planes, default_planes_to_board
from DeepCrazyhouse.src.domain.variants.constants import (
MODES,
VERSION,
MODE_CRAZYHOUSE,
MODE_CHESS,
MODE_LICHESS,
NB_LAST_MOVES,
chess, NB_CHANNELS_TOTAL, BOARD_HEIGHT, BOARD_WIDTH)
from DeepCrazyhouse.configs.main_config import main_config
Expand Down Expand Up @@ -51,6 +53,8 @@ def board_to_planes(board, board_occ=0, normalize=True, mode=MODE_CRAZYHOUSE, la
return crazyhouse_v2.board_to_planes(board, board_occ, normalize, last_moves)
if mode == MODE_CRAZYHOUSE and VERSION == 3:
return crazyhouse_v3.board_to_planes(board, board_occ, normalize, last_moves)
if mode == MODE_LICHESS and VERSION == 3:
return lichess_v3.board_to_planes(board, board_occ, normalize, last_moves)

return default_board_to_planes(board, board_occ, last_moves, mode, normalize)

Expand Down Expand Up @@ -81,6 +85,8 @@ def planes_to_board(planes, normalized_input=False, mode=MODE_CRAZYHOUSE):
return crazyhouse_v2.planes_to_board(planes, normalized_input)
if mode == MODE_CRAZYHOUSE and VERSION == 3:
return crazyhouse_v3.planes_to_board(planes, normalized_input)
if mode == MODE_LICHESS and VERSION == 3:
return lichess_v3.planes_to_board(planes, normalized_input)

return default_planes_to_board(planes, normalized_input, mode)

Expand All @@ -104,6 +110,8 @@ def normalize_input_planes(x):
return crazyhouse_v2.normalize_input_planes(x)
if MODE == MODE_CRAZYHOUSE and VERSION == 3:
return crazyhouse_v3.normalize_input_planes(x)
if MODE == MODE_LICHESS and VERSION == 3:
return lichess_v3.normalize_input_planes(x)

return default_normalize_input_planes(x)

Expand All @@ -128,7 +136,6 @@ def get_planes_statistics(board: chess.Board, normalize: bool, last_moves_uci: l

planes = board_to_planes(board, board_occ=board_occ, normalize=normalize, mode=main_config['mode'],
last_moves=last_moves)
planes = normalize_input_planes(planes)
planes = planes.flatten()
stats = {}
stats['sum'] = planes.sum()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,4 @@ def board_to_planes(board, board_occ=0, normalize=True):
"""

# return the plane representation of the given board
return variants.board_to_planes(board, board_occ, normalize, mode=MODE_LICHESS)
return variants.board_to_planes(board, board_occ, normalize, mode=MODE_LICHESS, last_moves=None)
Empty file.
130 changes: 130 additions & 0 deletions DeepCrazyhouse/src/domain/variants/lichess/v3/input_representation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""
@file: input_representation.py
Created on 17.08.23
@project: CrazyAra
@author: queensgambit
Input representation v3 which is compatible to all available lichess variants and passed to the neural network.
"""
import numpy as np
import chess
from DeepCrazyhouse.src.domain.variants.default_input_representation import default_board_to_planes, default_planes_to_board
from DeepCrazyhouse.src.domain.variants.constants import MODE_LICHESS, NB_CHANNELS_FX, BOARD_HEIGHT, BOARD_WIDTH
from DeepCrazyhouse.src.domain.variants.classical_chess.v3.input_representation import set_additional_custom_features

NORMALIZE_POCKETS = 16
NORMALIZE_PIECE_NUMBER = 8
NORMALIZE_50_MOVE_RULE = 50

NB_PLAYERS = 2
CHANNEL_POCKETS = 14
CHANNEL_COLOR_INFO = 27
CHANNEL_TOTAL_MOVE_COUNTER = 28
CHANNEL_NO_PROGRESS = 33
CHANNEL_CUSTOM_FEATURES = 63
#CHANNEL_MATERIAL_DIFF = 66
#CHANNEL_MATERIAL_COUNT = 74

CHANNEL_PIECE_MASK = 37 + 26
CHANNEL_CHECKERBOARD = 39 + 26
CHANNEL_MATERIAL_DIFF = 40 + 26
CHANNEL_OPP_BISHOPS = 46 + 26
CHANNEL_CHECKERS = 47 + 26
CHANNEL_MATERIAL_COUNT = 48 + 26


def board_to_planes(board, board_occ=0, normalize=True, last_moves=None):
"""
Gets the plane representation of a given board state.
(No history of past board positions is used.)
## Chess Variants:
Feature | Planes
Chess variant input planes features as in lichess v2.0 | 63
---
63 planes
(but set color info and total move counter planes to 0)
###
Additional features as in chess inputs v3.0
P1 pieces | 1 | A grouped mask of all WHITE pieces |
P2 pieces | 1 | A grouped mask of all BLACK pieces |
Checkerboard | 1 | A chess board pattern |
P1 Material Diff | 6 | (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING), normalized with 8
Opposite Color Bishops | 1 | Indicates if they are only two bishops and the bishops are opposite color |
Checkers | 1 | Indicates all pieces giving check |
P1 Material Count | 6 | (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING), normalized with 8 |
---
17 planes
Total : 63 + 17 = 80 planes
:param board: Board handle (Python-chess object)
:param board_occ: Sets how often the board state has occurred before (by default 0)
:param normalize: True if the inputs shall be normalized to the range [0.-1.]
;param last_moves: List of last moves played
:return: planes - the plane representation of the current board state
"""

# return the plane representation of the given board
planes = default_board_to_planes(board, board_occ, normalize=False, mode=MODE_LICHESS, last_moves=last_moves)
# set color info and total move counter to 0
planes[CHANNEL_COLOR_INFO, :, :] = 0
planes[CHANNEL_TOTAL_MOVE_COUNTER, :, :] = 0

# mirror all bitboard entries for the black player
mirror = board.turn == chess.BLACK and board.uci_variant != "racingkings"

planes_fx = np.zeros((NB_CHANNELS_FX, BOARD_HEIGHT, BOARD_WIDTH))
planes = np.concatenate((planes, planes_fx), axis=0)

set_additional_custom_features(planes, board, CHANNEL_CUSTOM_FEATURES, mirror, normalize=False, include_king=True,
channel_piece_mask=CHANNEL_PIECE_MASK, channel_checkerboard=CHANNEL_CHECKERBOARD,
channel_material_diff=CHANNEL_MATERIAL_DIFF, channel_opp_bishops=CHANNEL_OPP_BISHOPS,
channel_checkers=CHANNEL_CHECKERS, channel_material_count=CHANNEL_MATERIAL_COUNT)
if normalize:
normalize_input_planes(planes)

return planes


def normalize_input_planes(planes):
"""
Normalizes input planes to range [0,1]. Works in place / meaning the input parameter x is manipulated
:param planes: Input planes representation
:return: The normalized planes
"""
channel = CHANNEL_POCKETS
for _ in range(NB_PLAYERS):
for _ in chess.PIECE_TYPES[:-1]: # exclude the king for the pocket pieces
planes[channel, :, :] /= NORMALIZE_POCKETS
channel += 1
channel = CHANNEL_MATERIAL_DIFF
for _ in chess.PIECE_TYPES:
planes[channel, :, :] /= NORMALIZE_PIECE_NUMBER
channel += 1
planes[CHANNEL_NO_PROGRESS, :, :] /= NORMALIZE_50_MOVE_RULE
channel = CHANNEL_MATERIAL_COUNT
for _ in chess.PIECE_TYPES:
planes[channel, :, :] /= NORMALIZE_PIECE_NUMBER
channel += 1

return planes


def planes_to_board(planes, normalized_input):
"""
Converts a board in plane representation to the python chess board representation
see get_planes_of_board() for input encoding description
! Board is always returned with WHITE to move and move number = 0 !
:param planes: Input plane representation
:param normalized_input: Defines if the inputs are normalized to [0,1]
:return: python chess board object
"""
return default_planes_to_board(planes, normalized_input)
Loading

0 comments on commit 41ea151

Please sign in to comment.