Change evaluation score to a centi-pawn based integer type
This commit is contained in:
@@ -20,12 +20,12 @@ static enum game_progress endgameness(struct pos const* pos)
|
|||||||
/* piece_value is already defined similarly elsewhere, but this one should
|
/* piece_value is already defined similarly elsewhere, but this one should
|
||||||
* remain independent of minor tweaks in the global table */
|
* remain independent of minor tweaks in the global table */
|
||||||
static int const piece_value[PIECE_COUNT] = {
|
static int const piece_value[PIECE_COUNT] = {
|
||||||
[PIECE_KING] = 0,
|
[PIECE_KING] = 0,
|
||||||
[PIECE_PAWN] = 1,
|
[PIECE_PAWN] = 1,
|
||||||
[PIECE_BISHOP] = 3,
|
[PIECE_BISHOP] = 3,
|
||||||
[PIECE_KNIGHT] = 3,
|
[PIECE_KNIGHT] = 3,
|
||||||
[PIECE_ROOK] = 5,
|
[PIECE_ROOK] = 5,
|
||||||
[PIECE_QUEEN] = 9,
|
[PIECE_QUEEN] = 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
int npm = 0; /* non-pawn material */
|
int npm = 0; /* non-pawn material */
|
||||||
@@ -314,25 +314,25 @@ BITBOARD( \
|
|||||||
|
|
||||||
#define EARLY_POSITIONAL_BONUS_0 \
|
#define EARLY_POSITIONAL_BONUS_0 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.02f, BOARD_CENTER_4X4) \
|
X(PIECE_PAWN, 2, BOARD_CENTER_4X4) \
|
||||||
X(PIECE_KNIGHT, 0.05f, BOARD_CENTER_4X4) \
|
X(PIECE_KNIGHT, 5, BOARD_CENTER_4X4) \
|
||||||
X(PIECE_BISHOP, 0.05f, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \
|
X(PIECE_BISHOP, 5, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \
|
||||||
X(PIECE_KING, 0.15f, REL_KING_CASTLE_KINGSIDE) \
|
X(PIECE_KING, 15, REL_KING_CASTLE_KINGSIDE) \
|
||||||
X(PIECE_QUEEN, -0.15f, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \
|
X(PIECE_QUEEN, -15, RANK_MASK_3 | RANK_MASK_4 | RANK_MASK_5 | RANK_MASK_6) \
|
||||||
X(PIECE_ROOK, 0.10f, FILE_MASK_D | FILE_MASK_E) \
|
X(PIECE_ROOK, 10, FILE_MASK_D | FILE_MASK_E) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define EARLY_POSITIONAL_BONUS_1 \
|
#define EARLY_POSITIONAL_BONUS_1 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.02f, BOARD_CENTER_2X2) \
|
X(PIECE_PAWN, 2, BOARD_CENTER_2X2) \
|
||||||
X(PIECE_BISHOP, 0.05f, REL_BISHOP_KING_ATTACK) \
|
X(PIECE_BISHOP, 5, REL_BISHOP_KING_ATTACK) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define EARLY_POSITIONAL_BONUS_2 \
|
#define EARLY_POSITIONAL_BONUS_2 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, -0.18f, ~REL_EARLY_PAWN_STRUCTURE) \
|
X(PIECE_PAWN, -18, ~REL_EARLY_PAWN_STRUCTURE) \
|
||||||
X(PIECE_KNIGHT, -0.10f, REL_UNDEVELOPED_KNIGHTS) \
|
X(PIECE_KNIGHT, -10, REL_UNDEVELOPED_KNIGHTS) \
|
||||||
X(PIECE_BISHOP, -0.10f, REL_UNDEVELOPED_BISHOPS) \
|
X(PIECE_BISHOP, -10, REL_UNDEVELOPED_BISHOPS) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define EARLY_POSITIONAL_BONUS_3 \
|
#define EARLY_POSITIONAL_BONUS_3 \
|
||||||
@@ -345,58 +345,58 @@ BITBOARD( \
|
|||||||
|
|
||||||
#define MIDDLE_POSITIONAL_BONUS_0 \
|
#define MIDDLE_POSITIONAL_BONUS_0 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.02f, BOARD_CENTER_4X4) \
|
X(PIECE_PAWN, 2, BOARD_CENTER_4X4) \
|
||||||
X(PIECE_KNIGHT, 0.05f, BOARD_CENTER_4X4) \
|
X(PIECE_KNIGHT, 5, BOARD_CENTER_4X4) \
|
||||||
X(PIECE_BISHOP, 0.05f, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \
|
X(PIECE_BISHOP, 5, BOARD_CENTER_6X6 & (DIAGONAL_A1_H8 | DIAGONAL_A8_H1)) \
|
||||||
X(PIECE_KING, 0.15f, REL_KING_CASTLE_KINGSIDE) \
|
X(PIECE_KING, 15, REL_KING_CASTLE_KINGSIDE) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define MIDDLE_POSITIONAL_BONUS_1 \
|
#define MIDDLE_POSITIONAL_BONUS_1 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.02f, BOARD_CENTER_2X2) \
|
X(PIECE_PAWN, 2, BOARD_CENTER_2X2) \
|
||||||
X(PIECE_BISHOP, 0.07f, REL_BISHOP_KING_ATTACK) \
|
X(PIECE_BISHOP, 7, REL_BISHOP_KING_ATTACK) \
|
||||||
X(PIECE_QUEEN, 0.07f, REL_BISHOP_KING_ATTACK) \
|
X(PIECE_QUEEN, 7, REL_BISHOP_KING_ATTACK) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define MIDDLE_POSITIONAL_BONUS_2 \
|
#define MIDDLE_POSITIONAL_BONUS_2 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.02f, REL_PAWN_KINGSIDE) \
|
X(PIECE_PAWN, 2, REL_PAWN_KINGSIDE) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define MIDDLE_POSITIONAL_BONUS_3 \
|
#define MIDDLE_POSITIONAL_BONUS_3 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_BISHOP, 0.05f, BOARD_CENTER_6X6) \
|
X(PIECE_BISHOP, 5, BOARD_CENTER_6X6) \
|
||||||
X(PIECE_KNIGHT, 0.05f, BOARD_CENTER_6X6) \
|
X(PIECE_KNIGHT, 5, BOARD_CENTER_6X6) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
/* ------------------------------- end game -------------------------------- */
|
/* ------------------------------- end game -------------------------------- */
|
||||||
|
|
||||||
#define LATE_POSITIONAL_BONUS_0 \
|
#define LATE_POSITIONAL_BONUS_0 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.30f, REL_RANK_7 | REL_RANK_6 | REL_RANK_5) \
|
X(PIECE_PAWN, 30, REL_RANK_7 | REL_RANK_6 | REL_RANK_5) \
|
||||||
X(PIECE_KING, 0.10f, BOARD_CENTER_6X6) \
|
X(PIECE_KING, 10, BOARD_CENTER_6X6) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define LATE_POSITIONAL_BONUS_1 \
|
#define LATE_POSITIONAL_BONUS_1 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.30f, REL_RANK_7 | REL_RANK_6) \
|
X(PIECE_PAWN, 30, REL_RANK_7 | REL_RANK_6) \
|
||||||
X(PIECE_KING, 0.10f, BOARD_CENTER_4X4) \
|
X(PIECE_KING, 10, BOARD_CENTER_4X4) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define LATE_POSITIONAL_BONUS_2 \
|
#define LATE_POSITIONAL_BONUS_2 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_PAWN, 0.70f, REL_RANK_7) \
|
X(PIECE_PAWN, 70, REL_RANK_7) \
|
||||||
X(PIECE_KING, 0.10f, BOARD_CENTER_2X2) \
|
X(PIECE_KING, 10, BOARD_CENTER_2X2) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
#define LATE_POSITIONAL_BONUS_3 \
|
#define LATE_POSITIONAL_BONUS_3 \
|
||||||
/* piece bonus area*/ \
|
/* piece bonus area*/ \
|
||||||
X(PIECE_KING, -0.50f, ~BOARD_CENTER_6X6) \
|
X(PIECE_KING, -50, ~BOARD_CENTER_6X6) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
struct posmod {
|
struct posmod {
|
||||||
Bb64 const area;
|
Bb64 const area;
|
||||||
float const val;
|
Score16 const val;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline struct posmod positional_modifier(Side8 pl, enum game_progress st, size_t layer, Piece8 pz)
|
static inline struct posmod positional_modifier(Side8 pl, enum game_progress st, size_t layer, Piece8 pz)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#define MOVE_MAX 128
|
||||||
|
|
||||||
#define MOVE_CASTLE_KINGSIDE_WHITE (struct move) \
|
#define MOVE_CASTLE_KINGSIDE_WHITE (struct move) \
|
||||||
{.from = SQ_E1, .to = SQ_G1}
|
{.from = SQ_E1, .to = SQ_G1}
|
||||||
|
|
||||||
@@ -13,19 +15,10 @@
|
|||||||
{.from = SQ_E8, .to = SQ_C8}
|
{.from = SQ_E8, .to = SQ_C8}
|
||||||
|
|
||||||
static inline
|
static inline
|
||||||
struct move move_make(struct pos const* restrict pos,
|
struct move move_make(Index8 from, Index8 to)
|
||||||
Piece8 piece,
|
|
||||||
Index8 from,
|
|
||||||
Index8 to,
|
|
||||||
uint8_t add_attr)
|
|
||||||
{
|
{
|
||||||
(void)piece;
|
return (struct move){.from = from, .to = to};
|
||||||
(void)pos;
|
|
||||||
(void)add_attr;
|
|
||||||
|
|
||||||
return (struct move){.from = from, .to = to, .attr = add_attr};
|
|
||||||
}
|
}
|
||||||
#define MOVE_MAX 128
|
|
||||||
|
|
||||||
enum move_gen_type {
|
enum move_gen_type {
|
||||||
MG_ALL,
|
MG_ALL,
|
||||||
@@ -64,7 +57,7 @@ static void all_pseudolegal_from_piece(struct pos const* restrict pos,
|
|||||||
while (move_mask) {
|
while (move_mask) {
|
||||||
Sq8 const to = bitboard_pop_lsb(&move_mask);
|
Sq8 const to = bitboard_pop_lsb(&move_mask);
|
||||||
assuming(*out_count < MOVE_MAX); \
|
assuming(*out_count < MOVE_MAX); \
|
||||||
out[(*out_count)++] = move_make(pos, piece, from, to, 0);
|
out[(*out_count)++] = move_make(from, to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,7 +81,7 @@ static void all_pseudolegal_pawn_moves_##side(struct pos const* restrict pos,\
|
|||||||
while (sp) {\
|
while (sp) {\
|
||||||
Sq8 const to = bitboard_pop_lsb(&sp);\
|
Sq8 const to = bitboard_pop_lsb(&sp);\
|
||||||
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 1);\
|
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 1);\
|
||||||
out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\
|
out[(*out_count)++] = move_make(from, to);\
|
||||||
}\
|
}\
|
||||||
\
|
\
|
||||||
Bb64 dp = pawn_double_push_##side(piece_mask & pawn_rank, ~all_occ)\
|
Bb64 dp = pawn_double_push_##side(piece_mask & pawn_rank, ~all_occ)\
|
||||||
@@ -97,7 +90,7 @@ static void all_pseudolegal_pawn_moves_##side(struct pos const* restrict pos,\
|
|||||||
while (dp) {\
|
while (dp) {\
|
||||||
Sq8 const to = bitboard_pop_lsb(&dp);\
|
Sq8 const to = bitboard_pop_lsb(&dp);\
|
||||||
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 2);\
|
Sq8 const from = SQ_SHIFT_##inverse_direction(to, 2);\
|
||||||
out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\
|
out[(*out_count)++] = move_make(from, to);\
|
||||||
}\
|
}\
|
||||||
}
|
}
|
||||||
DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(white, black, SIDE_WHITE, SOUTH, RANK_MASK_2)
|
DEFINE_ALL_PSEUDOLEGAL_PAWN_MOVES(white, black, SIDE_WHITE, SOUTH, RANK_MASK_2)
|
||||||
@@ -125,7 +118,7 @@ static void all_pseudolegal_pawn_attacks_##side(struct pos const* restrict pos,\
|
|||||||
Sq8 const to = bitboard_pop_lsb(&ratk);\
|
Sq8 const to = bitboard_pop_lsb(&ratk);\
|
||||||
Sq8 const from = SQ_SHIFT_WEST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
|
Sq8 const from = SQ_SHIFT_WEST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
|
||||||
assuming(*out_count < MOVE_MAX); \
|
assuming(*out_count < MOVE_MAX); \
|
||||||
out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\
|
out[(*out_count)++] = move_make(from, to);\
|
||||||
}\
|
}\
|
||||||
\
|
\
|
||||||
Bb64 latk = pawn_attacks_left_##side(piece_mask)\
|
Bb64 latk = pawn_attacks_left_##side(piece_mask)\
|
||||||
@@ -136,7 +129,7 @@ static void all_pseudolegal_pawn_attacks_##side(struct pos const* restrict pos,\
|
|||||||
Sq8 const to = bitboard_pop_lsb(&latk);\
|
Sq8 const to = bitboard_pop_lsb(&latk);\
|
||||||
Sq8 const from = SQ_SHIFT_EAST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
|
Sq8 const from = SQ_SHIFT_EAST(SQ_SHIFT_##inverse_direction(to, 1), 1);\
|
||||||
assuming(*out_count < MOVE_MAX); \
|
assuming(*out_count < MOVE_MAX); \
|
||||||
out[(*out_count)++] = move_make(pos, PIECE_PAWN, from, to, 0);\
|
out[(*out_count)++] = move_make(from, to);\
|
||||||
}\
|
}\
|
||||||
}
|
}
|
||||||
DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(white, black, SIDE_WHITE, SOUTH)
|
DEFINE_ALL_PSEUDOLEGAL_PAWN_ATTACKS(white, black, SIDE_WHITE, SOUTH)
|
||||||
|
|||||||
14
engine-tt.h
14
engine-tt.h
@@ -1,12 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "engine-types.h"
|
||||||
|
|
||||||
struct search_option {
|
struct search_option {
|
||||||
uint64_t hash; // + 64 (64 / 8)
|
uint64_t hash;
|
||||||
float score; // + 32 (96 / 12)
|
Score16 score;
|
||||||
struct move move; // + 32 (128 / 16)
|
struct move move;
|
||||||
int depth : 4; // + 4 (132 / 17)
|
int depth : 4;
|
||||||
int init : 1; // + 1 (133 / 17)
|
int init : 1;
|
||||||
enum tt_flag {TT_EXACT, TT_LOWER, TT_UPPER} flag : 3; // +3 (136 / 17)
|
enum tt_flag {TT_EXACT, TT_LOWER, TT_UPPER} flag : 3;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define TT_ADDRESS_BITS 24
|
#define TT_ADDRESS_BITS 24
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
/* --- Score16 (centipawns) ---- */
|
||||||
|
|
||||||
|
typedef int16_t Score16;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SCORE_INF = 9999,
|
||||||
|
SCORE_CHECKMATE = 999,
|
||||||
|
};
|
||||||
|
|
||||||
/* ----------- Index8 ----------- */
|
/* ----------- Index8 ----------- */
|
||||||
typedef uint8_t Index8;
|
typedef uint8_t Index8;
|
||||||
|
|
||||||
@@ -195,13 +204,13 @@ static char const* side_str[SIDE_COUNT] = {
|
|||||||
/* https://en.wikipedia.org/wiki/X_macro */
|
/* https://en.wikipedia.org/wiki/X_macro */
|
||||||
/* enum value white char white unicode black char black unicode */
|
/* enum value white char white unicode black char black unicode */
|
||||||
#define PIECES \
|
#define PIECES \
|
||||||
X(PIECE_EMPTY, 0.0f, ' ', ' ', ' ', ' ') \
|
X(PIECE_EMPTY, 0, ' ', ' ', ' ', ' ') \
|
||||||
X(PIECE_PAWN, 1.0f, 'P', 0x2659, 'p', 0x265F) \
|
X(PIECE_PAWN, 100, 'P', 0x2659, 'p', 0x265F) \
|
||||||
X(PIECE_KNIGHT, 3.1f, 'N', 0x2658, 'n', 0x265E) \
|
X(PIECE_KNIGHT, 301, 'N', 0x2658, 'n', 0x265E) \
|
||||||
X(PIECE_BISHOP, 3.2f, 'B', 0x2657, 'b', 0x265D) \
|
X(PIECE_BISHOP, 302, 'B', 0x2657, 'b', 0x265D) \
|
||||||
X(PIECE_ROOK, 5.0f, 'R', 0x2656, 'r', 0x265C) \
|
X(PIECE_ROOK, 500, 'R', 0x2656, 'r', 0x265C) \
|
||||||
X(PIECE_QUEEN, 9.0f, 'Q', 0x2655, 'q', 0x265B) \
|
X(PIECE_QUEEN, 900, 'Q', 0x2655, 'q', 0x265B) \
|
||||||
X(PIECE_KING, 16.0f, 'K', 0x2654, 'k', 0x265A) \
|
X(PIECE_KING, 1600, 'K', 0x2654, 'k', 0x265A) \
|
||||||
/**/
|
/**/
|
||||||
|
|
||||||
typedef enum piece : uint8_t {
|
typedef enum piece : uint8_t {
|
||||||
@@ -213,7 +222,7 @@ typedef enum piece : uint8_t {
|
|||||||
#undef X
|
#undef X
|
||||||
} Piece8;
|
} Piece8;
|
||||||
|
|
||||||
static float piece_value[PIECE_COUNT] = {
|
static Score16 piece_value[PIECE_COUNT] = {
|
||||||
#define X(e, v, wc, wu, bc, bu) [e] = v,
|
#define X(e, v, wc, wu, bc, bu) [e] = v,
|
||||||
PIECES
|
PIECES
|
||||||
#undef X
|
#undef X
|
||||||
@@ -259,7 +268,6 @@ static int const piece_unicode[SIDE_COUNT][PIECE_COUNT] = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* ----------- moves ----------- */
|
/* ----------- moves ----------- */
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
|||||||
253
engine.h
253
engine.h
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
/* temp performance counter */
|
/* temp performance counter */
|
||||||
static uint64_t g_ab_node_volume = 0;
|
static uint64_t g_ab_node_volume = 0;
|
||||||
|
static uint64_t g_pvs_re_search = 0;
|
||||||
|
static uint64_t g_pvs_probes = 0;
|
||||||
|
|
||||||
#include "engine-macros.h"
|
#include "engine-macros.h"
|
||||||
#include "engine-types.h"
|
#include "engine-types.h"
|
||||||
@@ -23,9 +25,6 @@ static uint64_t g_ab_node_volume = 0;
|
|||||||
|
|
||||||
/* --------------------------- MOVE SEARCH --------------------------------- */
|
/* --------------------------- MOVE SEARCH --------------------------------- */
|
||||||
|
|
||||||
#define SCORE_INF 1e10f
|
|
||||||
#define SCORE_CHECKMATE 999.0f
|
|
||||||
|
|
||||||
/* for initial ordering of moves in alphabeta search */
|
/* for initial ordering of moves in alphabeta search */
|
||||||
static void move_compute_appeal(struct move* restrict m,
|
static void move_compute_appeal(struct move* restrict m,
|
||||||
struct pos const* restrict pos,
|
struct pos const* restrict pos,
|
||||||
@@ -43,91 +42,84 @@ static void move_compute_appeal(struct move* restrict m,
|
|||||||
|
|
||||||
/* TODO: remove branch */
|
/* TODO: remove branch */
|
||||||
if (n) {
|
if (n) {
|
||||||
m->appeal = (uint8_t)(16.0f*(float)n - piece_value[atk]);
|
m->appeal = (uint8_t)(16*n - piece_value[atk]);
|
||||||
} else {
|
} else {
|
||||||
m->appeal = 0;
|
m->appeal = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static float board_score_heuristic(struct pos const* pos)
|
static Score16 board_score_heuristic(struct pos const* pos)
|
||||||
{
|
{
|
||||||
/* this function always evaluates from white's perspective before
|
/* this function always evaluates from white's perspective before
|
||||||
eventually flipping the sign based on `pos` */
|
eventually flipping the sign based on `pos` */
|
||||||
float score = 0.0f;
|
Score16 score = 0;
|
||||||
|
|
||||||
Bb64 const occw = ~pos->pieces[SIDE_WHITE][PIECE_EMPTY];
|
Bb64 const occw = ~pos->pieces[SIDE_WHITE][PIECE_EMPTY];
|
||||||
Bb64 const occb = ~pos->pieces[SIDE_BLACK][PIECE_EMPTY];
|
Bb64 const occb = ~pos->pieces[SIDE_BLACK][PIECE_EMPTY];
|
||||||
|
|
||||||
enum game_progress const gp = endgameness(pos);
|
enum game_progress const gp = endgameness(pos);
|
||||||
|
|
||||||
if (pos->pieces[SIDE_WHITE][PIECE_KING] == 0) {
|
if (pos->pieces[SIDE_WHITE][PIECE_KING]) {
|
||||||
score -= SCORE_CHECKMATE;
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
if (pos->pieces[SIDE_BLACK][PIECE_KING] == 0) {
|
|
||||||
score += SCORE_CHECKMATE;
|
score += SCORE_CHECKMATE;
|
||||||
goto end;
|
}
|
||||||
|
if (pos->pieces[SIDE_BLACK][PIECE_KING]) {
|
||||||
|
score -= SCORE_CHECKMATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Piece8 p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
|
for (Piece8 p = PIECE_BEGIN; p < PIECE_COUNT; ++p) {
|
||||||
/* raw material value */
|
/* raw material value */
|
||||||
score += piece_value[p] *
|
score += piece_value[p] *
|
||||||
((float)bitboard_popcount(pos->pieces[SIDE_WHITE][p]) -
|
((Score16)bitboard_popcount(pos->pieces[SIDE_WHITE][p]) -
|
||||||
(float)bitboard_popcount(pos->pieces[SIDE_BLACK][p]));
|
(Score16)bitboard_popcount(pos->pieces[SIDE_BLACK][p]));
|
||||||
|
|
||||||
/* very minor advantage for threat projection to break tied moves */
|
|
||||||
//if (p != PIECE_PAWN) {
|
|
||||||
// score += 0.001f * (
|
|
||||||
// (float)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_WHITE][p], occall))
|
|
||||||
// - (float)bitboard_popcount(non_pawn_piece_attacks(p, pos->pieces[SIDE_BLACK][p], occall)));
|
|
||||||
//}
|
|
||||||
|
|
||||||
/* positional bonus, see evaluations.h */
|
/* positional bonus, see evaluations.h */
|
||||||
for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) {
|
for (size_t i = 0; i < POSITIONAL_MODIFIER_COUNT; ++i) {
|
||||||
score += positional_modifier(SIDE_WHITE, gp, i, p).val *
|
score += positional_modifier(SIDE_WHITE, gp, i, p).val *
|
||||||
(
|
(
|
||||||
(float)bitboard_popcount(
|
(Score16)bitboard_popcount(
|
||||||
pos->pieces[SIDE_WHITE][p]
|
pos->pieces[SIDE_WHITE][p]
|
||||||
& positional_modifier(SIDE_WHITE, gp, i, p).area)
|
& positional_modifier(SIDE_WHITE, gp, i, p).area)
|
||||||
- (float)bitboard_popcount(
|
- (Score16)bitboard_popcount(
|
||||||
pos->pieces[SIDE_BLACK][p]
|
pos->pieces[SIDE_BLACK][p]
|
||||||
& positional_modifier(SIDE_BLACK, gp, i, p).area)
|
& positional_modifier(SIDE_BLACK, gp, i, p).area)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
score += 0.10f * (float)bitboard_more_than_one(pos->pieces[SIDE_WHITE][PIECE_BISHOP]);
|
/* bishop pair */
|
||||||
score -= 0.10f * (float)bitboard_more_than_one(pos->pieces[SIDE_BLACK][PIECE_BISHOP]);
|
score += 10 * (Score16)bitboard_more_than_one(pos->pieces[SIDE_WHITE][PIECE_BISHOP]);
|
||||||
|
score -= 10 * (Score16)bitboard_more_than_one(pos->pieces[SIDE_BLACK][PIECE_BISHOP]);
|
||||||
|
|
||||||
/* pawns defending pieces are desired */
|
/* pawns defending pieces are desired */
|
||||||
score += 0.03f * (
|
score += 3 * (
|
||||||
(float)bitboard_popcount(
|
(Score16)bitboard_popcount(
|
||||||
pawn_attacks_white(pos->pieces[SIDE_WHITE][PIECE_PAWN]) & occw
|
pawn_attacks_white(pos->pieces[SIDE_WHITE][PIECE_PAWN]) & occw
|
||||||
)
|
)
|
||||||
- (float)bitboard_popcount(
|
- (Score16)bitboard_popcount(
|
||||||
pawn_attacks_black(pos->pieces[SIDE_BLACK][PIECE_PAWN]) & occb
|
pawn_attacks_black(pos->pieces[SIDE_BLACK][PIECE_PAWN]) & occb
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
/* stacked pawns are bad */
|
/* stacked pawns are bad */
|
||||||
const float k = 0.30f;
|
/*
|
||||||
|
const Score16 k = 30;
|
||||||
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
|
for (enum file_index fi = FILE_INDEX_BEGIN; fi < FILE_INDEX_COUNT; ++fi) {
|
||||||
uint64_t wstk = bitboard_popcount(pos->pieces[SIDE_WHITE][PIECE_PAWN] & FILE_MASK(fi));
|
uint64_t wstk = bitboard_popcount(pos->pieces[SIDE_WHITE][PIECE_PAWN] & FILE_MASK(fi));
|
||||||
uint64_t bstk = bitboard_popcount(pos->pieces[SIDE_BLACK][PIECE_PAWN] & FILE_MASK(fi));
|
uint64_t bstk = bitboard_popcount(pos->pieces[SIDE_BLACK][PIECE_PAWN] & FILE_MASK(fi));
|
||||||
|
|
||||||
score -= k * (float)(wstk - (wstk == 1));
|
score -= k * (Score16)(wstk - (wstk == 1));
|
||||||
score += k * (float)(bstk - (bstk == 1));
|
score += k * (Score16)(bstk - (bstk == 1));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
end:
|
Score16 sign = (pos->moving_side == SIDE_WHITE) ? 1 : -1;
|
||||||
float sign = (pos->moving_side == SIDE_WHITE) ? 1.0f : -1.0f;
|
|
||||||
|
|
||||||
return sign*score;
|
return sign*score;
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
struct move moves_linear_search(struct move moves[restrict static MOVE_MAX],
|
struct move moves_linear_search(struct move moves[restrict static MOVE_MAX],
|
||||||
size_t* restrict move_count)
|
size_t* restrict move_count)
|
||||||
{
|
{
|
||||||
size_t best = 0;
|
size_t best = 0;
|
||||||
assuming(*move_count > 0);
|
assuming(*move_count > 0);
|
||||||
@@ -146,21 +138,21 @@ struct move moves_linear_search(struct move moves[restrict static MOVE_MAX]
|
|||||||
|
|
||||||
/* quiescence is a deep search that only considers captures */
|
/* quiescence is a deep search that only considers captures */
|
||||||
static
|
static
|
||||||
float quiesce(struct pos const* pos,
|
Score16 quiesce(struct pos const* pos,
|
||||||
Piece8 mailbox[restrict static SQ_COUNT],
|
Piece8 mailbox[restrict static SQ_COUNT],
|
||||||
Side8 us,
|
Side8 us,
|
||||||
float alpha,
|
Score16 alpha,
|
||||||
float beta,
|
Score16 beta,
|
||||||
int8_t depth)
|
int8_t depth)
|
||||||
{
|
{
|
||||||
if (pos->pieces[us][PIECE_KING] == 0) {
|
if (pos->pieces[us][PIECE_KING] == 0) {
|
||||||
return -(SCORE_CHECKMATE + (float)depth);
|
return -(SCORE_CHECKMATE + depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
Side8 const them = other_side(us);
|
Side8 const them = other_side(us);
|
||||||
|
|
||||||
float score = board_score_heuristic(pos);
|
Score16 score = board_score_heuristic(pos);
|
||||||
float highscore = score;
|
Score16 highscore = score;
|
||||||
|
|
||||||
if (highscore >= beta) {
|
if (highscore >= beta) {
|
||||||
return highscore;
|
return highscore;
|
||||||
@@ -196,7 +188,7 @@ float quiesce(struct pos const* pos,
|
|||||||
hist.length = 0;
|
hist.length = 0;
|
||||||
(void)move_piece(&poscpy, us, &hist, mailbox_cpy, m);
|
(void)move_piece(&poscpy, us, &hist, mailbox_cpy, m);
|
||||||
|
|
||||||
score = -quiesce(&poscpy, mailbox_cpy, them, -beta, -alpha, depth - 1);
|
score = (Score16)-quiesce(&poscpy, mailbox_cpy, them, (Score16)(-beta), (Score16)(-alpha), depth - 1);
|
||||||
|
|
||||||
if (score >= beta) {
|
if (score >= beta) {
|
||||||
highscore = score;
|
highscore = score;
|
||||||
@@ -222,8 +214,8 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
Piece8 mailbox[restrict static SQ_COUNT],
|
Piece8 mailbox[restrict static SQ_COUNT],
|
||||||
Side8 us,
|
Side8 us,
|
||||||
int8_t depth,
|
int8_t depth,
|
||||||
float alpha,
|
Score16 alpha,
|
||||||
float beta
|
Score16 beta
|
||||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||||
,
|
,
|
||||||
struct searching_flag const* restrict searching
|
struct searching_flag const* restrict searching
|
||||||
@@ -241,7 +233,7 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
|
|
||||||
if (pos->pieces[us][PIECE_KING] == 0) {
|
if (pos->pieces[us][PIECE_KING] == 0) {
|
||||||
return (struct search_option) {
|
return (struct search_option) {
|
||||||
.score = -(SCORE_CHECKMATE + (float)depth),
|
.score = -(SCORE_CHECKMATE + depth),
|
||||||
.move = (struct move){0},
|
.move = (struct move){0},
|
||||||
.depth = 0,
|
.depth = 0,
|
||||||
.hash = pos->hash,
|
.hash = pos->hash,
|
||||||
@@ -251,7 +243,7 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (depth <= 0) {
|
if (depth <= 0) {
|
||||||
float sc = quiesce(pos, mailbox, us, alpha, beta, 0);
|
Score16 sc = quiesce(pos, mailbox, us, alpha, beta, 0);
|
||||||
return (struct search_option){
|
return (struct search_option){
|
||||||
.score = sc,
|
.score = sc,
|
||||||
.move = (struct move){0},
|
.move = (struct move){0},
|
||||||
@@ -262,12 +254,12 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
float const alpha_orig = alpha;
|
Score16 const alpha_orig = alpha;
|
||||||
|
|
||||||
struct move moves[MOVE_MAX];
|
struct move moves[MOVE_MAX];
|
||||||
size_t move_count = 0;
|
size_t move_count = 0;
|
||||||
|
|
||||||
float best_score = -SCORE_INF;
|
Score16 best_score = -SCORE_INF;
|
||||||
struct move best_move = MOVE_NULL;
|
struct move best_move = MOVE_NULL;
|
||||||
bool has_principal_move = false;
|
bool has_principal_move = false;
|
||||||
|
|
||||||
@@ -295,6 +287,8 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
has_principal_move = true;
|
has_principal_move = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
enum move_gen_type const types[] = {MG_CAPTURES, MG_CHECKS, MG_QUIETS};
|
enum move_gen_type const types[] = {MG_CAPTURES, MG_CHECKS, MG_QUIETS};
|
||||||
for (size_t i = 0; i < sizeof types / sizeof *types; ++i) {
|
for (size_t i = 0; i < sizeof types / sizeof *types; ++i) {
|
||||||
all_pseudolegal_moves(pos, types[i], us, &move_count, moves);
|
all_pseudolegal_moves(pos, types[i], us, &move_count, moves);
|
||||||
@@ -318,30 +312,77 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
|
|
||||||
enum move_result r = move_piece(&pos_cpy, us, hist, mailbox_cpy, m);
|
enum move_result r = move_piece(&pos_cpy, us, hist, mailbox_cpy, m);
|
||||||
|
|
||||||
float score = 0.0;
|
Score16 score;
|
||||||
|
|
||||||
if (r == MR_STALEMATE || r == MR_REPEATS) {
|
if (r == MR_STALEMATE || r == MR_REPEATS) {
|
||||||
score = 0.0;
|
score = 0;
|
||||||
} else {
|
} else {
|
||||||
struct search_option so = alphabeta_search(&pos_cpy,
|
if (first) {
|
||||||
hist,
|
struct search_option so = alphabeta_search(&pos_cpy,
|
||||||
tt,
|
hist,
|
||||||
mailbox_cpy,
|
tt,
|
||||||
other_side(us),
|
mailbox_cpy,
|
||||||
depth - 1,
|
other_side(us),
|
||||||
-beta,
|
depth - 1,
|
||||||
-alpha
|
(Score16)(-beta),
|
||||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
(Score16)(-alpha)
|
||||||
,
|
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||||
searching
|
,
|
||||||
#endif
|
searching
|
||||||
);
|
#endif
|
||||||
if (!so.init) {
|
);
|
||||||
hist->length = old_hist_len;
|
if (!so.init) {
|
||||||
return so;
|
hist->length = old_hist_len;
|
||||||
|
return so;
|
||||||
|
}
|
||||||
|
score = (Score16)-so.score;
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
struct search_option so;
|
||||||
|
assuming(alpha < beta);
|
||||||
|
g_pvs_probes += 1;
|
||||||
|
so = alphabeta_search(&pos_cpy,
|
||||||
|
hist,
|
||||||
|
tt,
|
||||||
|
mailbox_cpy,
|
||||||
|
other_side(us),
|
||||||
|
depth - 1,
|
||||||
|
(Score16)(-alpha - 1),
|
||||||
|
(Score16)(-alpha)
|
||||||
|
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||||
|
,
|
||||||
|
searching
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
if (!so.init) {
|
||||||
|
hist->length = old_hist_len;
|
||||||
|
return so;
|
||||||
|
}
|
||||||
|
if ((Score16)-so.score > alpha) {
|
||||||
|
g_pvs_re_search += 1;
|
||||||
|
|
||||||
|
so = alphabeta_search(&pos_cpy,
|
||||||
|
hist,
|
||||||
|
tt,
|
||||||
|
mailbox_cpy,
|
||||||
|
other_side(us),
|
||||||
|
depth - 1,
|
||||||
|
(Score16)(-beta),
|
||||||
|
(Score16)(-alpha)
|
||||||
|
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||||
|
,
|
||||||
|
searching
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
if (!so.init) {
|
||||||
|
hist->length = old_hist_len;
|
||||||
|
return so;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
score = (Score16)-so.score;
|
||||||
}
|
}
|
||||||
score = -so.score;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hist->length = old_hist_len;
|
hist->length = old_hist_len;
|
||||||
|
|
||||||
if (score > best_score) {
|
if (score > best_score) {
|
||||||
@@ -360,7 +401,7 @@ struct search_option alphabeta_search(struct pos const* pos,
|
|||||||
finish_search:
|
finish_search:
|
||||||
|
|
||||||
if (IS_MOVE_NULL(best_move)) {
|
if (IS_MOVE_NULL(best_move)) {
|
||||||
return (struct search_option){ .init = true, .move = MOVE_NULL, .score = -(SCORE_CHECKMATE + (float)depth) };
|
return (struct search_option){ .init = true, .move = MOVE_NULL, .score = -(SCORE_CHECKMATE + depth) };
|
||||||
}
|
}
|
||||||
|
|
||||||
enum tt_flag flag = TT_EXACT;
|
enum tt_flag flag = TT_EXACT;
|
||||||
@@ -387,8 +428,8 @@ finish_search:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
struct search_result {struct move move; float score;}
|
struct search_result {struct move move; Score16 score;}
|
||||||
search(
|
search(
|
||||||
struct board* restrict b,
|
struct board* restrict b,
|
||||||
Side8 us,
|
Side8 us,
|
||||||
int8_t max_depth
|
int8_t max_depth
|
||||||
@@ -401,12 +442,6 @@ struct search_result {struct move move; float score;}
|
|||||||
struct move moves[MOVE_MAX];
|
struct move moves[MOVE_MAX];
|
||||||
size_t move_count = 0;
|
size_t move_count = 0;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
for (size_t i = 0; i < MOVE_MAX; ++i) {
|
|
||||||
moves[i] = MOVE_POISONED;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
all_pseudolegal_moves(&b->pos, MG_ALL, us, &move_count, moves);
|
all_pseudolegal_moves(&b->pos, MG_ALL, us, &move_count, moves);
|
||||||
|
|
||||||
assuming(move_count);
|
assuming(move_count);
|
||||||
@@ -414,8 +449,10 @@ struct search_result {struct move move; float score;}
|
|||||||
struct move best_move = moves[0];
|
struct move best_move = moves[0];
|
||||||
|
|
||||||
g_ab_node_volume = 0;
|
g_ab_node_volume = 0;
|
||||||
|
g_pvs_re_search = 0;
|
||||||
|
g_pvs_probes = 0;
|
||||||
|
|
||||||
float score = 0.0;
|
Score16 score = 0;
|
||||||
|
|
||||||
for (int8_t d = 1;
|
for (int8_t d = 1;
|
||||||
d <= max_depth
|
d <= max_depth
|
||||||
@@ -425,11 +462,27 @@ struct search_result {struct move move; float score;}
|
|||||||
;
|
;
|
||||||
++d)
|
++d)
|
||||||
{
|
{
|
||||||
float window = 2*SCORE_INF; /* temp debug solution */
|
Score16 window;
|
||||||
float alpha = score - window;
|
|
||||||
float beta = score + window;
|
if (d == 1) {
|
||||||
|
window = SCORE_INF;
|
||||||
|
} else {
|
||||||
|
window = 20 + 8*d;
|
||||||
|
}
|
||||||
|
#ifdef FEATURE_USE_PRINTF
|
||||||
|
fprintf(stderr, "depth: %hhd - window %hd\n", d, window);
|
||||||
|
#endif
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
|
int al = (int)score - (int)window;
|
||||||
|
int bt = (int)score + (int)window;
|
||||||
|
|
||||||
|
if (al < -SCORE_INF) al = -SCORE_INF;
|
||||||
|
if (bt > SCORE_INF) bt = SCORE_INF;
|
||||||
|
|
||||||
|
Score16 alpha = (Score16)al;
|
||||||
|
Score16 beta = (Score16)bt;
|
||||||
|
|
||||||
struct search_option so =
|
struct search_option so =
|
||||||
alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta
|
alphabeta_search(&b->pos, &b->hist, &b->tt, b->mailbox, us, d, alpha, beta
|
||||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||||
@@ -438,42 +491,34 @@ struct search_result {struct move move; float score;}
|
|||||||
);
|
);
|
||||||
|
|
||||||
#ifdef FEATURE_STOPPABLE_SEARCH
|
#ifdef FEATURE_STOPPABLE_SEARCH
|
||||||
if (!searching_still(searching)) {
|
if (!searching_still(searching)) goto stop_search;
|
||||||
goto breakbreak;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (IS_MOVE_NULL(so.move)) {
|
if (IS_MOVE_NULL(so.move)) goto stop_search;
|
||||||
goto breakbreak;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (so.score > alpha && so.score < beta) {
|
if (so.score >= alpha && so.score <= beta) {
|
||||||
score = so.score;
|
score = so.score;
|
||||||
best_move = so.move;
|
best_move = so.move;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
window *= 2;
|
if (window < SCORE_INF/2) {
|
||||||
|
|
||||||
if (so.score <= alpha) {
|
|
||||||
alpha = -SCORE_INF;
|
|
||||||
beta = score + window;
|
|
||||||
} else {
|
|
||||||
alpha = score - window;
|
|
||||||
beta = SCORE_INF;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
#ifdef FEATURE_USE_PRINTF
|
#ifdef FEATURE_USE_PRINTF
|
||||||
fprintf(stderr, "depth: %hhd\n", d);
|
fprintf(stderr, "depth: %hhd - expanding window to %hd\n", d, window);
|
||||||
#endif
|
#endif
|
||||||
|
window *= 2;
|
||||||
|
} else {
|
||||||
|
window = SCORE_INF;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
breakbreak:
|
|
||||||
|
|
||||||
#undef SCORE_INF
|
stop_search:
|
||||||
|
|
||||||
#ifdef FEATURE_USE_PRINTF
|
#ifdef FEATURE_USE_PRINTF
|
||||||
fprintf(stderr, "nodes searched: %'llu\n", g_ab_node_volume);
|
fprintf(stderr, "nodes searched: %'llu\n", g_ab_node_volume);
|
||||||
|
fprintf(stderr, "pvs re-searches: %'llu\n", g_pvs_re_search);
|
||||||
|
fprintf(stderr, "pvs probes: %'llu\n", g_pvs_probes);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return (struct search_result){.move = best_move, .score = score};
|
return (struct search_result){.move = best_move, .score = score};
|
||||||
|
|||||||
4
tests.c
4
tests.c
@@ -470,12 +470,12 @@ int main()
|
|||||||
struct search_result sr = search(b, b->pos.moving_side, 25, &searching);
|
struct search_result sr = search(b, b->pos.moving_side, 25, &searching);
|
||||||
|
|
||||||
struct move move = sr.move;
|
struct move move = sr.move;
|
||||||
double const score = sr.score;
|
Score16 const score = sr.score;
|
||||||
|
|
||||||
printf("move %d: {\n"
|
printf("move %d: {\n"
|
||||||
" .from = %s, (%s)\n"
|
" .from = %s, (%s)\n"
|
||||||
" .to = %s,\n"
|
" .to = %s,\n"
|
||||||
" .score = %lf,\n"
|
" .score = %d,\n"
|
||||||
" .mask = "
|
" .mask = "
|
||||||
"",
|
"",
|
||||||
turn,
|
turn,
|
||||||
|
|||||||
Reference in New Issue
Block a user