diff --git a/quiesce.cpp b/quiesce.cpp new file mode 100644 index 0000000..9a2d46c --- /dev/null +++ b/quiesce.cpp @@ -0,0 +1,252 @@ +#include "rodent.h" +#include "param.h" +#include + + +//#define USE_QS_HASH + +int Quiesce(POS *p, int ply, int alpha, int beta, int *pv) { + + eData e; + int best, score, move, new_pv[MAX_PLY]; + MOVES m[1]; + UNDO u[1]; + int op = Opp(p->side); + + // Use dedicated quiescence search function when in check + + if (InCheck(p)) return QuiesceFlee(p, ply, alpha, beta, pv); + + // Statistics and attempt at quick exit + + nodes++; + CheckTimeout(); + if (abort_search) return 0; + *pv = 0; + if (IsDraw(p)) return DrawScore(p); + if (ply >= MAX_PLY - 1) return Eval.Return(p, &e, 1); + + // Get a stand-pat score and adjust bounds + // (exiting if eval exceeds beta) + + best = Eval.Return(p, &e, 1); + + //Correct self-side score by depth for human opponent + if ((Param.riskydepth > 0) && (ply >= Param.riskydepth) && (p->side == root_side) && (abs(best) > 100) && (abs(best) < 1000)){ + int eval_adj = best<0 ? round(1.0*best*(nodes > 100 ? 0.5 : 1)*Param.riskydepth/ply) : round(1.0*best*(nodes > 100 ? 2 : 1)*ply/Param.riskydepth); + if (eval_adj>1000) eval_adj = 1000; + best = eval_adj; + } + + if (best >= beta) return best; + if (best > alpha) alpha = best; + +#ifdef USE_QS_HASH + // Transposition table read + + if (TransRetrieve(p->hash_key, &move, &score, alpha, beta, 0, ply)) + return score; +#endif + + InitCaptures(p, m); + + // Main loop + + while ((move = NextCapture(m))) { + + // Pruning in quiescence search + // (not applicable if we are capturing last enemy piece) + + if (p->cnt[op][N] + p->cnt[op][B] + p->cnt[op][R] + p->cnt[op][Q] > 1) { + + // 1. Delta pruning + + if (best + tp_value[TpOnSq(p, Tsq(move))] + 300 < alpha) continue; + + // 2. SEE-based pruning of bad captures + + if (BadCapture(p, move)) continue; + } + + p->DoMove(move, u); + if (Illegal(p)) { p->UndoMove(move, u); continue; } + + score = -Quiesce(p, ply + 1, -beta, -alpha, new_pv); + + p->UndoMove(move, u); + if (abort_search) return 0; + + // Beta cutoff + + if (score >= beta) { +#ifdef USE_QS_HASH + TransStore(p->hash_key, *pv, best, LOWER, 0, ply); +#endif + return score; + } + + // Adjust alpha and score + + if (score > best) { + best = score; + if (score > alpha) { + alpha = score; + BuildPv(pv, new_pv, move); + } + } + } + +#ifdef USE_QS_HASH + if (*pv) TransStore(p->hash_key, *pv, best, EXACT, 0, ply); + else TransStore(p->hash_key, 0, best, UPPER, 0, ply); +#endif + + return best; +} + +// @QuiesceChecks() searches good and equal captures +// plus checking moves with non-negative SEE. +// Move selection is performed within NextCaptureOrCheck() + +int QuiesceChecks(POS *p, int ply, int alpha, int beta, int *pv) +{ + eData e; + int stand_pat, best, score, move, new_pv[MAX_PLY]; + int is_pv = (beta > alpha + 1); + MOVES m[1]; + UNDO u[1]; + + if (InCheck(p)) return QuiesceFlee(p, ply, alpha, beta, pv); + + nodes++; + CheckTimeout(); + if (abort_search) return 0; + *pv = 0; + + if (IsDraw(p)) return DrawScore(p); + + if (ply >= MAX_PLY - 1) + return Eval.Return(p, &e, 1); + + best = stand_pat = Eval.Return(p, &e, 1); + + if (best >= beta) return best; + if (best > alpha) alpha = best; + + if (TransRetrieve(p->hash_key, &move, &score, alpha, beta, 0, ply)) + return score; + + InitCaptures(p, m); + while ((move = NextCaptureOrCheck(m))) { + + p->DoMove(move, u); + if (Illegal(p)) { p->UndoMove(move, u); continue; } + + score = -Quiesce(p, ply + 1, -beta, -alpha, new_pv); + + p->UndoMove(move, u); + if (abort_search) return 0; + + if (score >= beta) { + TransStore(p->hash_key, move, score, LOWER, 0, ply); + return score; + } + + if (score > best) { + best = score; + if (score > alpha) { + alpha = score; + BuildPv(pv, new_pv, move); + } + } + } + + if (*pv) TransStore(p->hash_key, *pv, best, EXACT, 0, ply); + else TransStore(p->hash_key, 0, best, UPPER, 0, ply); + + return best; +} + +// @QuiesceFlee() quiescence search function dedicated to escsping +// from check. Can be called only while side to move is in check. + +int QuiesceFlee(POS *p, int ply, int alpha, int beta, int *pv) { + + eData e; + int best, score, move, fl_mv_type, new_pv[MAX_PLY]; + int is_pv = (beta > alpha + 1); + + MOVES m[1]; + UNDO u[1]; + + // Periodically check for timeout, ponderhit or stop command + + nodes++; + CheckTimeout(); + + // Quick exit on a timeout or on a statically detected draw + + if (abort_search) return 0; + if (ply) *pv = 0; + if (IsDraw(p) ) return DrawScore(p); + + // Retrieving data from transposition table. We hope for a cutoff + // or at least for a move to improve move ordering. + + move = 0; + if (TransRetrieve(p->hash_key, &move, &score, alpha, beta, 0, ply)) + return score; + + // Safeguard against exceeding ply limit + + if (ply >= MAX_PLY - 1) + return Eval.Return(p, &e, 1); + + // Init moves and variables before entering main loop + + best = -INF; + InitMoves(p, m, move, -1, ply); + + // Main loop + + while ((move = NextMove(m, &fl_mv_type))) { + p->DoMove(move, u); + if (Illegal(p)) { p->UndoMove(move, u); continue; } + + score = -Quiesce(p, ply, -beta, -alpha, new_pv); + + + p->UndoMove(move, u); + if (abort_search) return 0; + + // Beta cutoff + + if (score >= beta) { + TransStore(p->hash_key, move, score, LOWER, 0, ply); + return score; + } + + // Updating score and alpha + + if (score > best) { + best = score; + if (score > alpha) { + alpha = score; + BuildPv(pv, new_pv, move); + } + } + + } // end of the main loop + + // Return correct checkmate/stalemate score + + if (best == -INF) + return InCheck(p) ? -MATE + ply : DrawScore(p); + + // Save score in the transposition table + + if (*pv) TransStore(p->hash_key, *pv, best, EXACT, 0, ply); + else TransStore(p->hash_key, 0, best, UPPER, 0, ply); + + return best; +} diff --git a/rodent.h b/rodent.h new file mode 100644 index 0000000..8f05332 --- /dev/null +++ b/rodent.h @@ -0,0 +1,512 @@ +/* +Rodent, a UCI chess playing engine derived from Sungorus 1.4 +Copyright (C) 2009-2011 Pablo Vazquez (Sungorus author) +Copyright (C) 2011-2016 Pawel Koziol + +Rodent is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, +or (at your option) any later version. + +Rodent is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty +of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +// bench: 696.552 +// bench 12: 7.372.859 +// bench 15: 33.672.522 31.2 2.498 +// REGEX to count all the lines under MSVC 13: ^(?([^\r\n])\s)*[^\s+?/]+[^\n]*$ +// 6.185 lines of code +// 0.9.50: 56,3% vs 0.9.33 + +#pragma once +#define PROG_NAME "Rodent II 0.9.68 risky" + +enum eColor{WC, BC, NO_CL}; +enum ePieceType{P, N, B, R, Q, K, NO_TP}; +enum ePiece{WP, BP, WN, BN, WB, BBi, WR, BR, WQ, BQ, WK, BK, NO_PC}; +enum eFile {FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H}; +enum eRank {RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8}; +enum eCastleFlag { W_KS = 1, W_QS = 2, B_KS = 4, B_QS = 8 }; +enum eMoveType {NORMAL, CASTLE, EP_CAP, EP_SET, N_PROM, B_PROM, R_PROM, Q_PROM}; +enum eHashEntry{NONE, UPPER, LOWER, EXACT}; +enum eMoveFlag {MV_NORMAL, MV_HASH, MV_CAPTURE, MV_KILLER, MV_BADCAPT}; +enum eFactor {F_ATT, F_MOB, F_PST, F_PAWNS, F_PASSERS, F_TROPISM, F_OUTPOST, F_LINES, F_PRESSURE, F_OTHERS, N_OF_FACTORS }; +enum eDynFactor {DF_OWN_ATT, DF_OPP_ATT, DF_OWN_MOB, DF_OPP_MOB}; +enum eAsymmetric {SD_ATT, SD_MOB, OPP_ATT, OPP_MOB}; + +enum eSquare{ + A1, B1, C1, D1, E1, F1, G1, H1, + A2, B2, C2, D2, E2, F2, G2, H2, + A3, B3, C3, D3, E3, F3, G3, H3, + A4, B4, C4, D4, E4, F4, G4, H4, + A5, B5, C5, D5, E5, F5, G5, H5, + A6, B6, C6, D6, E6, F6, G6, H6, + A7, B7, C7, D7, E7, F7, G7, H7, + A8, B8, C8, D8, E8, F8, G8, H8, + NO_SQ +}; + +typedef unsigned long long U64; + +#define MAX_PLY 64 +#define MAX_MOVES 256 +#define INF 32767 +#define MATE 32000 +#define MAX_EVAL 29999 +#define MAX_INT 2147483646 +#define HIST_LIMIT (1 << 15) + +#define RANK_1_BB (U64)0x00000000000000FF +#define RANK_2_BB (U64)0x000000000000FF00 +#define RANK_3_BB (U64)0x0000000000FF0000 +#define RANK_4_BB (U64)0x00000000FF000000 +#define RANK_5_BB (U64)0x000000FF00000000 +#define RANK_6_BB (U64)0x0000FF0000000000 +#define RANK_7_BB (U64)0x00FF000000000000 +#define RANK_8_BB (U64)0xFF00000000000000 + +static const U64 bbRelRank[2][8] = { { RANK_1_BB, RANK_2_BB, RANK_3_BB, RANK_4_BB, RANK_5_BB, RANK_6_BB, RANK_7_BB, RANK_8_BB }, + { RANK_8_BB, RANK_7_BB, RANK_6_BB, RANK_5_BB, RANK_4_BB, RANK_3_BB, RANK_2_BB, RANK_1_BB } }; + +static const U64 bbHomeZone[2] = { RANK_1_BB | RANK_2_BB | RANK_3_BB | RANK_4_BB, + RANK_8_BB | RANK_7_BB | RANK_6_BB | RANK_5_BB }; + +static const U64 bbAwayZone[2] = { RANK_8_BB | RANK_7_BB | RANK_6_BB | RANK_5_BB, + RANK_1_BB | RANK_2_BB | RANK_3_BB | RANK_4_BB }; + +#define FILE_A_BB (U64)0x0101010101010101 +#define FILE_B_BB (U64)0x0202020202020202 +#define FILE_C_BB (U64)0x0404040404040404 +#define FILE_D_BB (U64)0x0808080808080808 +#define FILE_E_BB (U64)0x1010101010101010 +#define FILE_F_BB (U64)0x2020202020202020 +#define FILE_G_BB (U64)0x4040404040404040 +#define FILE_H_BB (U64)0x8080808080808080 + +#define bbWhiteSq (U64)0x55AA55AA55AA55AA +#define bbBlackSq (U64)0xAA55AA55AA55AA55 + +#define DIAG_A1H8_BB (U64)0x8040201008040201 +#define DIAG_A8H1_BB (U64)0x0102040810204080 +#define DIAG_B8H2_BB (U64)0x0204081020408000 + +#define bbNotA (U64)0xfefefefefefefefe // ~FILE_A_BB +#define bbNotH (U64)0x7f7f7f7f7f7f7f7f // ~FILE_H_BB + +#define ShiftNorth(x) (x<<8) +#define ShiftSouth(x) (x>>8) +#define ShiftWest(x) ((x & bbNotA)>>1) +#define ShiftEast(x) ((x & bbNotH)<<1) +#define ShiftNW(x) ((x & bbNotA)<<7) +#define ShiftNE(x) ((x & bbNotH)<<9) +#define ShiftSW(x) ((x & bbNotA)>>9) +#define ShiftSE(x) ((x & bbNotH)>>7) + +#define JustOne(bb) (bb && !(bb & (bb-1))) +#define MoreThanOne(bb) ( bb & (bb - 1) ) + +#define SCALE(x,y) ((x*y)/100) +#define SIDE_RANDOM (~((U64)0)) + +#define START_POS "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -" + +#define SqBb(x) ((U64)1 << (x)) + +#define Cl(x) ((x) & 1) +#define Tp(x) ((x) >> 1) +#define Pc(x, y) (((y) << 1) | (x)) + +#define File(x) ((x) & 7) +#define Rank(x) ((x) >> 3) +#define Sq(x, y) (((y) << 3) | (x)) + +#define Abs(x) ((x) > 0 ? (x) : -(x)) +#define Max(x, y) ((x) > (y) ? (x) : (y)) +#define Min(x, y) ((x) < (y) ? (x) : (y)) + +#define Fsq(x) ((x) & 63) +#define Tsq(x) (((x) >> 6) & 63) +#define MoveType(x) ((x) >> 12) +#define IsProm(x) ((x) & 0x4000) +#define PromType(x) (((x) >> 12) - 3) + +#define Opp(x) ((x) ^ 1) + +#define InCheck(p) Attacked(p, KingSq(p, p->side), Opp(p->side)) +#define Illegal(p) Attacked(p, KingSq(p, Opp(p->side)), p->side) +#define MayNull(p) (((p)->cl_bb[(p)->side] & ~((p)->tp_bb[P] | (p)->tp_bb[K])) != 0) + +#define PcBb(p, x, y) ((p)->cl_bb[x] & (p)->tp_bb[y]) +#define OccBb(p) ((p)->cl_bb[WC] | (p)->cl_bb[BC]) +#define UnoccBb(p) (~OccBb(p)) +#define TpOnSq(p, x) (Tp((p)->pc[x])) +#define KingSq(p, x) ((p)->king_sq[x]) +#define IsOnSq(p, sd, pc, sq) ( PcBb(p, sd, pc) & SqBb(sq) ) + +#ifdef _WIN32 +#define FORCEINLINE __forceinline +#else +#define FORCEINLINE __inline +#endif + +#define USE_FIRST_ONE_INTRINSICS + +// Compiler and architecture dependent versions of FirstOne() function, +// triggered by defines at the top of this file. +#ifdef USE_FIRST_ONE_INTRINSICS +#ifdef _WIN32 +#include +#ifdef _WIN64 +#pragma intrinsic(_BitScanForward64) +#endif + +#ifdef _MSC_VER +#ifndef _WIN64 +const int lsb_64_table[64] = +{ + 63, 30, 3, 32, 59, 14, 11, 33, + 60, 24, 50, 9, 55, 19, 21, 34, + 61, 29, 2, 53, 51, 23, 41, 18, + 56, 28, 1, 43, 46, 27, 0, 35, + 62, 31, 58, 4, 5, 49, 54, 6, + 15, 52, 12, 40, 7, 42, 45, 16, + 25, 57, 48, 13, 10, 39, 8, 44, + 20, 47, 38, 22, 17, 37, 36, 26 +}; + +/** +* bitScanForward +* @author Matt Taylor (2003) +* @param bb bitboard to scan +* @precondition bb != 0 +* @return index (0..63) of least significant one bit +*/ +static int FORCEINLINE bitScanForward(U64 bb) { + unsigned int folded; + bb ^= bb - 1; + folded = (int)bb ^ (bb >> 32); + return lsb_64_table[folded * 0x78291ACF >> 26]; +} +#endif +#endif +static int FORCEINLINE FirstOne(U64 x) { +#ifndef _WIN64 + return bitScanForward(x); +#else + unsigned long index = -1; + _BitScanForward64(&index, x); + return index; +#endif +} + +#elif defined(__GNUC__) + +static int FORCEINLINE FirstOne(U64 x) { + int tmp = __builtin_ffsll(x); + if (tmp == 0) return -1; + else return tmp - 1; +} + +#endif + +#else +#define FirstOne(x) bit_table[(((x) & (~(x) + 1)) * (U64)0x0218A392CD3D5DBF) >> 58] // first "1" in a bitboard +#endif + + +#define REL_SQ(sq,cl) ( sq ^ (cl * 56) ) +#define RelSqBb(sq,cl) ( SqBb(REL_SQ(sq,cl) ) ) + +typedef struct { + int ttp; + int castle_flags; + int ep_sq; + int rev_moves; + U64 hash_key; + U64 pawn_key; +} UNDO; + +typedef class { +private: + U64 p_attacks[2][64]; + U64 n_attacks[64]; + U64 k_attacks[64]; + + U64 FillOcclSouth(U64 bbStart, U64 bbBlock); + U64 FillOcclNorth(U64 bbStart, U64 bbBlock); + U64 FillOcclEast(U64 bbStart, U64 bbBlock); + U64 FillOcclWest(U64 bbStart, U64 bbBlock); + U64 FillOcclNE(U64 bbStart, U64 bbBlock); + U64 FillOcclNW(U64 bbStart, U64 bbBlock); + U64 FillOcclSE(U64 bbStart, U64 bbBlock); + U64 FillOcclSW(U64 bbStart, U64 bbBlock); + U64 GetBetween(int sq1, int sq2); + +public: + U64 bbBetween[64][64]; + void Init(void); + U64 ShiftFwd(U64 bb, int sd); + U64 ShiftSideways(U64 bb); + U64 GetWPControl(U64 bb); + U64 GetBPControl(U64 bb); + U64 GetDoubleWPControl(U64 bb); + U64 GetDoubleBPControl(U64 bb); + U64 GetFrontSpan(U64 bb, int sd); + U64 FillNorth(U64 bb); + U64 FillSouth(U64 bb); + U64 FillNorthSq(int sq); + U64 FillSouthSq(int sq); + U64 FillNorthExcl(U64 bb); + U64 FillSouthExcl(U64 bb); + + int PopCnt(U64); + int PopFirstBit(U64 * bb); + + U64 PawnAttacks(int sd, int sq); + U64 KingAttacks(int sq); + U64 KnightAttacks(int sq); + U64 RookAttacks(U64 occ, int sq); + U64 BishAttacks(U64 occ, int sq); + U64 QueenAttacks(U64 occ, int sq); +} cBitBoard; + +extern cBitBoard BB; + +typedef class { +public: + U64 cl_bb[2]; + U64 tp_bb[6]; + int pc[64]; + int king_sq[2]; + int mg_sc[2]; + int eg_sc[2]; + int cnt[2][6]; + int phase; + int side; + int castle_flags; + int ep_sq; + int rev_moves; + int head; + U64 hash_key; + U64 pawn_key; + U64 rep_list[256]; + + U64 Pawns(int sd); + U64 Knights(int sd); + U64 Bishops(int sd); + U64 Rooks(int sd); + U64 Queens(int sd); + U64 Kings(int sd); + U64 StraightMovers(int sd); + U64 DiagMovers(int sd); + int PawnEndgame(void); + + void DoMove(int move, UNDO * u); + void DoNull(UNDO * u); + void UndoMove(int move, UNDO * u); + void UndoNull(UNDO * u); +} POS; + +typedef class { +public: + U64 passed[2][64]; + U64 adjacent[8]; + U64 supported[2][64]; + U64 king_zone[2][64]; + void Init(void); +} cMask; + +extern cMask Mask; + +typedef struct { + int mg[2][N_OF_FACTORS]; + int eg[2][N_OF_FACTORS]; + U64 bbAllAttacks[2]; + U64 bbEvAttacks[2]; + U64 bbPawnTakes[2]; + U64 bbTwoPawnsTake[2]; + U64 bbPawnCanTake[2]; +} eData; + +typedef class { +private: + void Add(eData *e, int sd, int factor, int mg_bonus, int eg_bonus); + void Add(eData *e, int sd, int factor, int bonus); + void ScoreMaterial(POS * p, eData *e, int sd); + void ScorePassers(POS * p, eData *e, int sd); + void ScorePieces(POS * p, eData *e, int sd); + void ScoreHanging(POS *p, eData *e, int sd); + void ScorePatterns(POS *p, eData *e); + void ScoreKing(POS *p, eData *e, int sd); + void ScoreUnstoppable(eData *e, POS *p); + void ScoreKingFile(POS * p, int sd, U64 bbFile, int *shelter, int *storm); + int ScoreFileShelter(U64 bbOwnPawns, int sd); + int ScoreFileStorm(U64 bbOppPawns, int sd); + int ScoreChains(POS *p, int sd); + void ScoreOutpost(POS * p, eData *e, int sd, int pc, int sq); + void ScorePawns(POS * p, eData *e, int sd); + void FullPawnEval(POS * p, eData *e, int use_hash); + +public: + int prog_side; + void Init(void); + int Return(POS * p, eData * e, int use_hash); + void Print(POS *p); +} cEval; + +extern cEval Eval; + +typedef struct { + POS *p; + int phase; + int trans_move; + int ref_move; + int killer1; + int killer2; + int *next; + int *last; + int move[MAX_MOVES]; + int value[MAX_MOVES]; + int *badp; + int bad[MAX_MOVES]; +} MOVES; + +typedef struct { + U64 key; + short date; + short move; + short score; + unsigned char flags; + unsigned char depth; +} ENTRY; + +void AllocTrans(int mbsize); +int Attacked(POS *p, int sq, int sd); +U64 AttacksFrom(POS *p, int sq); +U64 AttacksTo(POS *p, int sq); +int BadCapture(POS *p, int move); +void Bench(int depth); +void BuildPv(int *dst, int *src, int move); +void CheckTimeout(void); +int CheckmateHelper(POS *p); +void ClearEvalHash(void); +void ClearPawnHash(void); +void ClearHist(void); +void ClearTrans(void); +void DecreaseHistory(POS *p, int move, int depth); +void DisplayCurrmove(int move, int tried); +void DisplayPv(int score, int *pv); +void DisplaySpeed(void); +int DrawScore(POS * p); +int EloToSpeed(int elo); +int EloToBlur(int elo); +int *GenerateCaptures(POS *p, int *list); +int *GenerateQuiet(POS *p, int *list); +int *GenerateQuietChecks(POS *p, int *list); +U64 GetNps(int elapsed); +int GetDrawFactor(POS *p, int sd); +void UpdateHistory(POS *p, int last_move, int move, int depth, int ply); +void Init(void); +void InitSearch(void); +void InitCaptures(POS *p, MOVES *m); +void InitMoves(POS *p, MOVES *m, int trans_move, int ref_move, int ply); +void InitWeights(void); +int InputAvailable(void); +U64 InitHashKey(POS * p); +U64 InitPawnKey(POS * p); +void Iterate(POS *p, int *pv); +int Legal(POS *p, int move); +void MoveToStr(int move, char *move_str); +void PrintMove(int move); +int MvvLva(POS *p, int move); +int NextCapture(MOVES *m); +int NextCaptureOrCheck(MOVES * m); +int NextMove(MOVES *m, int *flag); +void ParseGo(POS *, char *); +void ParseMoves(POS *p, char *ptr); +void ParsePosition(POS *, char *); +void ParseSetoption(char *); +int Perft(POS *p, int ply, int depth); +void PrintBoard(POS *p); +char *ParseToken(char *, char *); +void PvToStr(int *, char *); +int Quiesce(POS *p, int ply, int alpha, int beta, int *pv); +int QuiesceChecks(POS *p, int ply, int alpha, int beta, int *pv); +int QuiesceFlee(POS *p, int ply, int alpha, int beta, int *pv); +U64 Random64(void); +void ReadLine(char *str, int n); +void ResetEngine(void); +int IsDraw(POS * p); +int KPKdraw(POS *p, int sd); +void ScoreCaptures(MOVES *); +void ScoreQuiet(MOVES *m); +void SetWeight(int weight_name, int value); +int Widen(POS *p, int depth, int * pv, int lastScore); +int Refutation(int move); +void ReadPersonality(char *fileName); +int SearchRoot(POS *p, int ply, int alpha, int beta, int depth, int *pv); +int Search(POS *p, int ply, int alpha, int beta, int depth, int was_null, int last_move, int last_capt_sq, int node_type, int *pv); +int SelectBest(MOVES *m); +void SetPosition(POS *p, char *epd); +void SetAsymmetricEval(int sd); +int StrToMove(POS *p, char *move_str); +int Swap(POS *p, int from, int to); +void Think(POS *p, int *pv); +void TrimHistory(void); +int Timeout(void); +int TransRetrieve(U64 key, int *move, int *score, int alpha, int beta, int depth, int ply); +void TransStore(U64 key, int move, int score, int flags, int depth, int ply); +void UciLoop(void); + +extern int castle_mask[64]; +extern const int bit_table[64]; +extern const int tp_value[7]; +extern const int phase_value[7]; +extern int refutation[64][64]; +extern int root_side; +extern int history[12][64]; +extern int killer[MAX_PLY][2]; +extern U64 zob_piece[12][64]; +extern U64 zob_castle[16]; +extern U64 zob_ep[8]; +extern int pondering; +extern int root_depth; +extern U64 nodes; +extern int abort_search; +extern ENTRY *tt; +extern int tt_size; +extern int tt_mask; +extern int tt_date; + +extern int weights[N_OF_FACTORS]; +extern int dyn_weights[5]; +extern int curr_weights[2][2]; +extern int panel_style; +extern int verbose; +extern int time_percentage; +extern int use_book; +extern int hist_limit; +extern int hist_perc; +extern int fl_reading_personality; +extern int fl_separate_books; +extern int fl_elo_slider; + +int DifferentBishops(POS * p); +int NotOnBishColor(POS * p, int bishSide, int sq); +int PcMatNone(POS *p, int sd); +int PcMat1Minor(POS *p, int sd); +int PcMat2Minors(POS *p, int sd); +int PcMatNN(POS *p, int sd); +int PcMatBN(POS *p, int sd); +int PcMatB(POS *p, int sd); +int PcMatQ(POS *p, int sd); +int PcMatR(POS *p, int sd); +int PcMatRm(POS *p, int sd); +int PcMatRR(POS *p, int sd); +int PcMatRRm(POS *p, int sd); diff --git a/search.cpp b/search.cpp new file mode 100644 index 0000000..0123702 --- /dev/null +++ b/search.cpp @@ -0,0 +1,933 @@ +/* +Rodent, a UCI chess playing engine derived from Sungorus 1.4 +Copyright (C) 2009-2011 Pablo Vazquez (Sungorus author) +Copyright (C) 2011-2016 Pawel Koziol + +Rodent is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation, either version 3 of the License, +or (at your option) any later version. + +Rodent is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty +of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include "rodent.h" +#include "param.h" +#include "timer.h" +#include "book.h" + +#define PV_NODE 0 +#define CUT_NODE 1 +#define ALL_NODE -1 +#define NEW_NODE(type) (-(type)) + +double lmr_size[2][MAX_PLY][MAX_MOVES]; +int lmp_limit[6] = { 0, 4, 8, 12, 36, 48 }; +int fut_margin[7] = { 0, 100, 150, 200, 250, 300, 350 }; +int razor_margin[5] = { 0, 300, 360, 420, 480 }; +int root_side; +int fl_has_choice; + +// switches to facilitate debugging + +static const int use_aspiration = 1; +static const int use_nullmove = 1; +static const int use_null_verification = 1; +static const int use_beta_pruning = 1; +static const int use_futility = 1; +static const int use_razoring = 1; +static const int use_lmp = 1; +static const int use_lmr = 1; +static const int lmr_hist_adjustement = 1; + +void InitSearch(void) { + + // Set depth of late move reduction using modified Stockfish formula + + for (int dp = 0; dp < MAX_PLY; dp++) + for (int mv = 0; mv < MAX_MOVES; mv++) { + + double r = log((double)dp) * log((double)Min(mv,63)) / 2; + if (r < 0.80) r = 0; + + lmr_size[0][dp][mv] = r; // zero window node + lmr_size[1][dp][mv] = Max(r -1, 0); // principal variation node + + for (int node = 0; node <= 1; node++) { + if (lmr_size[node][dp][mv] < 1) lmr_size[node][dp][mv] = 0; // ultra-small reductions make no sense + + if (lmr_size[node][dp][mv] > dp - 1) // reduction cannot exceed actual depth + lmr_size[node][dp][mv] = dp - 1; + } + } +} + +void Think(POS *p, int *pv) { + + pv[1] = 0; // fixing rare glitch + + // Play a move from opening book, if applicable + + if (use_book) { + pv[0] = GuideBook.GetPolyglotMove(p, 1); + if (pv[0]) return; + + pv[0] = MainBook.GetPolyglotMove(p, 1); + if (pv[0]) return; + } + + // Set basic data + + ClearHist(); + tt_date = (tt_date + 1) & 255; + nodes = 0; + abort_search = 0; + verbose = 1; + Timer.SetStartTime(); + + // Search + + Iterate(p, pv); +} + +void Iterate(POS *p, int *pv) { + + int val = 0, cur_val = 0; + U64 nps = 0; + Timer.SetIterationTiming(); + + int max_root_depth = Timer.GetData(MAX_DEPTH); + + root_side = p->side; + SetAsymmetricEval(p->side); + + // Are we operating in slowdown mode or on node limit? + + Timer.special_mode = 0; + if (Timer.nps_limit + || Timer.GetData(MAX_NODES) > 0) Timer.special_mode = 1; + + // Search with increasing depth + + for (root_depth = 1; root_depth <= max_root_depth; root_depth++) { + int elapsed = Timer.GetElapsedTime(); + if (elapsed) nps = nodes * 1000 / elapsed; + +#if defined _WIN32 || defined _WIN64 + printf("info depth %d time %d nodes %I64d nps %I64d\n", root_depth, elapsed, nodes, nps); +#else + printf("info depth %d time %d nodes %lld nps %lld\n", root_depth, elapsed, nodes, nps); +#endif + + if (use_aspiration) cur_val = Widen(p, root_depth, pv, cur_val); + else cur_val = SearchRoot(p, 0, -INF, INF, root_depth, pv); // full window search + + // don't search too deep with only one move available + + if (root_depth == 8 + && !fl_has_choice + && !Timer.IsInfiniteMode() ) break; + + // abort search on finding checkmate score + + if (cur_val > MAX_EVAL || cur_val < -MAX_EVAL) { + int maxMateDepth = (MATE - Abs(cur_val) + 1) + 1; + maxMateDepth *= 4; + maxMateDepth /= 3; + if (maxMateDepth <= root_depth) break; + } + + if (abort_search || Timer.FinishIteration()) break; + val = cur_val; + } +} + +int Widen(POS *p, int depth, int * pv, int lastScore) { + + // Function performs aspiration search, progressively widening the window. + // Code structure modelled after Senpai 1.0. + + int cur_val = lastScore, alpha, beta; + + if (depth > 6 && lastScore < MAX_EVAL) { + for (int margin = 10; margin < 500; margin *= 2) { + alpha = lastScore - margin; + beta = lastScore + margin; + cur_val = SearchRoot(p, 0, alpha, beta, depth, pv); + if (abort_search) break; + if (cur_val < alpha) Timer.OnFailLow(); + if (cur_val > alpha && cur_val < beta) + return cur_val; // we have finished within the window + if (cur_val > MAX_EVAL) break; // verify mate searching with infinite bounds + } + } + + cur_val = SearchRoot(p, 0, -INF, INF, depth, pv); // full window search + return cur_val; +} + +int SearchRoot(POS *p, int ply, int alpha, int beta, int depth, int *pv) { + + int best, score, move, new_depth, new_pv[MAX_PLY]; + int fl_check, fl_prunable_move, fl_mv_type, reduction; + int mv_tried = 0, quiet_tried = 0; + int mv_played[MAX_MOVES]; + int mv_hist_score; + int victim, last_capt; + int move_change = 0; + + fl_has_choice = 0; + + MOVES m[1]; + UNDO u[1]; + + // Periodically check for timeout, ponderhit or stop command + + nodes++; + CheckTimeout(); + + // Quick exit + + if (abort_search) return 0; + + // Retrieving data from transposition table. We hope for a cutoff + // or at least for a move to improve move ordering. + + move = 0; + if (TransRetrieve(p->hash_key, &move, &score, alpha, beta, depth, ply)) { + + // For move ordering purposes, a cutoff from hash is treated + // exactly like a cutoff from search + + if (score >= beta) UpdateHistory(p, -1, move, depth, ply); + + // Root node is a pv node, so we return only exact scores + + if (score > alpha && score < beta) + return score; + } + + // Are we in check? Knowing that is useful when it comes + // to pruning/reduction decisions + + fl_check = InCheck(p); + + // Init moves and variables before entering main loop + + best = -INF; + InitMoves(p, m, move, Refutation(-1), ply); + + // Main loop + + while ((move = NextMove(m, &fl_mv_type))) { + + mv_hist_score = history[p->pc[Fsq(move)]][Tsq(move)]; + victim = TpOnSq(p, Tsq(move)); + if (victim != NO_TP) last_capt = Tsq(move); + else last_capt = -1; + + p->DoMove(move, u); + if (Illegal(p)) { p->UndoMove(move, u); continue; } + + // Update move statistics (needed for reduction/pruning decisions) + + mv_played[mv_tried] = move; + mv_tried++; + if (mv_tried > 1) fl_has_choice = 1; // we have a choice between at least two root moves + if (depth > 16 && verbose) DisplayCurrmove(move, mv_tried); + if (fl_mv_type == MV_NORMAL) quiet_tried++; + fl_prunable_move = !InCheck(p) && (fl_mv_type == MV_NORMAL); + + // Set new search depth + + new_depth = depth - 1 + InCheck(p); + + // Late move reduction + + reduction = 0; + + if (use_lmr + && depth >= 2 + && mv_tried > 3 + && mv_hist_score < hist_limit + && alpha > -MAX_EVAL && beta < MAX_EVAL + && !fl_check + && fl_prunable_move + && lmr_size[1][depth][mv_tried] > 0 + && MoveType(move) != CASTLE ) { + + reduction = lmr_size[1][depth][mv_tried]; + + // increase reduction on bad history score + + if (mv_hist_score < 0 + && new_depth - reduction > 2 + && lmr_hist_adjustement) + reduction++; + + new_depth -= reduction; + } + + re_search: + + // PVS + + if (best == -INF) + score = -Search(p, ply + 1, -beta, -alpha, new_depth, 0, move, last_capt, NEW_NODE(PV_NODE), new_pv); + else { + score = -Search(p, ply + 1, -alpha - 1, -alpha, new_depth, 0, move, last_capt, CUT_NODE, new_pv); + if (!abort_search && score > alpha && score < beta) + score = -Search(p, ply + 1, -beta, -alpha, new_depth, 0, move, last_capt, PV_NODE, new_pv); + } + + // Reduced move scored above alpha - we need to re-search it + + if (reduction && score > alpha) { + new_depth += reduction; + reduction = 0; + goto re_search; + } + + p->UndoMove(move, u); + if (abort_search) return 0; + + // Beta cutoff + + if (score >= beta) { + if (!fl_check) { + UpdateHistory(p, -1, move, depth, ply); + for (int mv = 0; mv < mv_tried; mv++) + DecreaseHistory(p, mv_played[mv], depth); + } + TransStore(p->hash_key, move, score, LOWER, depth, ply); + + // Update search time depending on whether the first move has changed + + if (depth > 4) { + if (pv[0] != move) Timer.OnNewRootMove(); + else Timer.OnOldRootMove(); + } + + // Change the best move and show the new pv + + BuildPv(pv, new_pv, move); + DisplayPv(score, pv); + + return score; + } + + // Updating score and alpha + + if (score > best) { + best = score; + + if (score > alpha) { + alpha = score; + + // Update search time depending on whether the first move has changed + + if (depth > 4) { + if (pv[0] != move) Timer.OnNewRootMove(); + else Timer.OnOldRootMove(); + } + + // Change the best move and show the new pv + + BuildPv(pv, new_pv, move); + DisplayPv(score, pv); + } + } + + } // end of the main loop + + // Return correct checkmate/stalemate score + + if (best == -INF) + return InCheck(p) ? -MATE + ply : DrawScore(p); + + // Save score in the transposition table + + if (*pv) { + if (!fl_check) { + UpdateHistory(p, -1, *pv, depth, ply); + for (int mv = 0; mv < mv_tried; mv++) + DecreaseHistory(p, mv_played[mv], depth); + } + TransStore(p->hash_key, *pv, best, EXACT, depth, ply); + + } else + TransStore(p->hash_key, 0, best, UPPER, depth, ply); + + return best; +} + +int Search(POS *p, int ply, int alpha, int beta, int depth, int was_null, int last_move, int last_capt_sq, int node_type, int *pv) { + + eData e; + int best, score, null_score, move, new_depth, new_pv[MAX_PLY]; + int fl_check, fl_prunable_node, fl_prunable_move, fl_mv_type, reduction; + int is_pv = (node_type == PV_NODE); + int mv_tried = 0, quiet_tried = 0, fl_futility = 0; + + int mv_played[MAX_MOVES]; + int mv_hist_score; + int victim, last_capt; + + MOVES m[1]; + UNDO u[1]; + + assert(ply > 0); + + // Quiescence search entry point + + if (depth <= 0) + return QuiesceChecks(p, ply, alpha, beta, pv); + + // Periodically check for timeout, ponderhit or stop command + + nodes++; + CheckTimeout(); + + // Quick exit on a timeout or on a statically detected draw + + if (abort_search) return 0; + if (ply) *pv = 0; + if (IsDraw(p)) return DrawScore(p); + + // Mate distance pruning + + int checkmatingScore = MATE - ply; + if (checkmatingScore < beta) { + beta = checkmatingScore; + if (alpha >= checkmatingScore) + return alpha; + } + + int checkmatedScore = -MATE + ply; + if (checkmatedScore > alpha) { + alpha = checkmatedScore; + if (beta <= checkmatedScore) + return beta; + } + + // Retrieving data from transposition table. We hope for a cutoff + // or at least for a move to improve move ordering. + + move = 0; + if (TransRetrieve(p->hash_key, &move, &score, alpha, beta, depth, ply)) { + + // For move ordering purposes, a cutoff from hash is treated + // exactly like a cutoff from search + + if (score >= beta) UpdateHistory(p, last_move, move, depth, ply); + + // In pv nodes only exact scores are returned. This is done because + // there is much more pruning and reductions in zero-window nodes, + // so retrieving such scores in pv nodes works like retrieving scores + // from slightly lower depth. + + if (!is_pv || (score > alpha && score < beta)) + return score; + } + + // Safeguard against exceeding ply limit + + if (ply >= MAX_PLY - 1) + return Eval.Return(p, &e, 1); + + // Are we in check? Knowing that is useful when it comes + // to pruning/reduction decisions + + fl_check = InCheck(p); + + // INTERNAL ITERATIVE DEEPENING - we try to get a hash move to improve move ordering + + if (!move && is_pv && depth >= 6 && !fl_check) { + Search(p, ply, alpha, beta, depth - 2, 0, 0, -1, PV_NODE, new_pv); + if (abort_search) return 0; + TransRetrieve(p->hash_key, &move, &score, alpha, beta, depth, ply); + } + + if (!move && node_type == CUT_NODE && depth >= 6 && !fl_check) { + Search(p, ply, alpha, beta, depth - 4, 0, 0, -1, CUT_NODE, new_pv); + if (abort_search) return 0; + TransRetrieve(p->hash_key, &move, &score, alpha, beta, depth, ply); + } + + // Can we prune this node? + + fl_prunable_node = !fl_check + && !is_pv + && alpha > -MAX_EVAL + && beta < MAX_EVAL; + + // Get evaluation score if we expect it to be needed + // for pruning/reduction decisions + + int eval = 0; + if (fl_prunable_node + && (!was_null || depth <= 6) ) eval = Eval.Return(p, &e, 1); + + //Correct self-side score by depth for human opponent + if (fl_prunable_node && (Param.riskydepth > 0) && (ply >= Param.riskydepth) && (p->side == root_side) && (abs(eval) > 100) && (abs(eval) < 1000)){ + int eval_adj = eval<0 ? round(1.0*eval*(nodes > 100 ? 0.5 : 1)*Param.riskydepth/ply) : round(1.0*eval*(nodes > 100 ? 2 : 1)*ply/Param.riskydepth); + if (eval_adj>1000) eval_adj = 1000; + eval = eval_adj; + } + + // Beta pruning / static null move + + if (use_beta_pruning + && fl_prunable_node + && depth <= 3 // TODO: Tune me! + && !was_null) { + int sc = eval - 120 * depth; // TODO: Tune me! + if (sc > beta) return sc; + } + + // Null move + + if (use_nullmove + && fl_prunable_node + && depth > 1 + && !was_null + && MayNull(p) + ) { + if (eval > beta) { + + new_depth = depth - ((823 + 67 * depth) / 256); // simplified Stockfish formula + + // omit null move search if normal search to the same depth wouldn't exceed beta + // (sometimes we can check it for free via hash table) + + if (TransRetrieve(p->hash_key, &move, &null_score, alpha, beta, new_depth, ply)) { + if (null_score < beta) goto avoid_null; + } + + p->DoNull(u); + if (new_depth > 0) score = -Search(p, ply + 1, -beta, -beta + 1, new_depth, 1, 0, -1, NEW_NODE(node_type), new_pv); + else score = -QuiesceChecks(p, ply + 1, -beta, -beta + 1, new_pv); + p->UndoNull(u); + + // Verification search (nb. immediate null move within it is prohibited) + + if (new_depth > 6 && score >= beta && use_null_verification) + score = Search(p, ply, alpha, beta, new_depth - 5, 1, move, -1, CUT_NODE, new_pv); + + if (abort_search ) return 0; + if (score >= beta) return score; + } + } + + avoid_null: + + // end of null move code + + // Razoring based on Toga II 3.0 + + if (use_razoring + && fl_prunable_node + && !move + && !was_null + && !(p->Pawns(p->side) & bbRelRank[p->side][RANK_7]) // no pawns to promote in one move + && depth <= 4) { + int threshold = beta - razor_margin[depth]; + if (eval < threshold) { + score = QuiesceChecks(p, ply, alpha, beta, pv); + if (score < threshold) return score; + } + } + + // end of razoring code + + // Init moves and variables before entering main loop + + best = -INF; + InitMoves(p, m, move, Refutation(last_move), ply); + + // Main loop + + while ((move = NextMove(m, &fl_mv_type))) { + + // Gather data about the move + + mv_hist_score = history[p->pc[Fsq(move)]][Tsq(move)]; + victim = TpOnSq(p, Tsq(move)); + if (victim != NO_TP) last_capt = Tsq(move); + else last_capt = -1; + + // Set futility pruning flag before the first applicable move is tried + + if (fl_mv_type == MV_NORMAL + && quiet_tried == 0) { + if (use_futility + && fl_prunable_node + && depth <= 6) { + if (eval + fut_margin[depth] < beta) fl_futility = 1; + } + } + + p->DoMove(move, u); + if (Illegal(p)) { p->UndoMove(move, u); continue; } + + // Update move statistics + // (needed for reduction/pruning decisions and for updating history score) + + mv_played[mv_tried] = move; + mv_tried++; + if (fl_mv_type == MV_NORMAL) quiet_tried++; + + // Can we prune this move? + + fl_prunable_move = !InCheck(p) + && (fl_mv_type == MV_NORMAL) + && (mv_hist_score < hist_limit); + + // Set new search depth + + new_depth = depth - 1; + + // Extensions (applied at pv node or at relatively low depth) + + if (is_pv || depth < 9) { + new_depth += InCheck(p); // check extension, pv or low depth + if (is_pv && Tsq(move) == last_capt_sq) new_depth += 1; // recapture extension in pv + if (is_pv && depth < 6 && TpOnSq(p,Tsq(move)) == P // pawn to 7th extension at the tips of pv + && (SqBb(Tsq(move)) & (RANK_2_BB | RANK_7_BB) ) ) new_depth += 1; + } + + // Futility pruning + + if (fl_futility + && fl_prunable_move + && mv_tried > 1) { + p->UndoMove(move, u); continue; + } + + // Late move pruning + + if (use_lmp + && fl_prunable_node + && fl_prunable_move + && quiet_tried > lmp_limit[depth] + && depth <= 3 + && MoveType(move) != CASTLE ) { + p->UndoMove(move, u); continue; + } + + // Late move reduction + + reduction = 0; + + if (use_lmr + && depth >= 2 + && mv_tried > 3 + && alpha > -MAX_EVAL && beta < MAX_EVAL + && !fl_check + && fl_prunable_move + && lmr_size[is_pv][depth][mv_tried] > 0 + && MoveType(move) != CASTLE ) { + + // read reduction size from the table + + reduction = lmr_size[is_pv][depth][mv_tried]; + + // increase reduction on bad history score + + if (mv_hist_score < 0 + && new_depth - reduction > 2 + && lmr_hist_adjustement) + reduction++; + + // reduce search depth + + new_depth -= reduction; + } + + if (use_lmr + && depth >= 2 + && mv_tried > 3 + && alpha > -MAX_EVAL && beta < MAX_EVAL + && !fl_check + && !InCheck(p) + && (fl_mv_type == MV_BADCAPT) + && lmr_size[is_pv][depth][mv_tried] > 0 + && !is_pv) { + reduction = 1; + new_depth -= reduction; + } + + // a place to come back if reduction scores above alpha + + re_search: + + // PVS + + if (best == -INF) + score = -Search(p, ply + 1, -beta, -alpha, new_depth, 0, move, last_capt, NEW_NODE(node_type), new_pv); + else { + score = -Search(p, ply + 1, -alpha - 1, -alpha, new_depth, 0, move, last_capt, CUT_NODE, new_pv); + if (!abort_search && score > alpha && score < beta) + score = -Search(p, ply + 1, -beta, -alpha, new_depth, 0, move, last_capt, PV_NODE, new_pv); + } + + // Reduced move scored above alpha - we need to re-search it + + if (reduction + && score > alpha) { + new_depth += reduction; + reduction = 0; + if (node_type == ALL_NODE) node_type = CUT_NODE; + goto re_search; + } + + // Undo move + + p->UndoMove(move, u); + if (abort_search) return 0; + + // Beta cutoff + + if (score >= beta) { + if (!fl_check) { + UpdateHistory(p, last_move, move, depth, ply); + for (int mv = 0; mv < mv_tried; mv++) + DecreaseHistory(p, mv_played[mv], depth); + } + TransStore(p->hash_key, move, score, LOWER, depth, ply); + + return score; + } + + // Updating score and alpha + + if (score > best) { + best = score; + if (score > alpha) { + alpha = score; + BuildPv(pv, new_pv, move); + } + } + + } // end of the main loop + + // Return correct checkmate/stalemate score + + if (best == -INF) + return InCheck(p) ? -MATE + ply : DrawScore(p); + + // Save score in the transposition table + + if (*pv) { + if (!fl_check) { + UpdateHistory(p, last_move, *pv, depth, ply); + for (int mv = 0; mv < mv_tried; mv++) + DecreaseHistory(p, mv_played[mv], depth); + } + TransStore(p->hash_key, *pv, best, EXACT, depth, ply); + } else + TransStore(p->hash_key, 0, best, UPPER, depth, ply); + + return best; +} + +int IsDraw(POS *p) { + + // Draw by 50 move rule + + if (p->rev_moves > 100) return 1; + + // Draw by repetition + + for (int i = 4; i <= p->rev_moves; i += 2) + if (p->hash_key == p->rep_list[p->head - i]) + return 1; + + // With no major pieces on the board, we have some heuristic draws to consider + + if (p->cnt[WC][Q] + p->cnt[BC][Q] + p->cnt[WC][R] + p->cnt[BC][R] == 0) { + + // Draw by insufficient material (bare kings or Km vs K) + + if (!Illegal(p)) { + if (p->cnt[WC][P] + p->cnt[BC][P] == 0) { + if (p->cnt[WC][N] + p->cnt[BC][N] + p->cnt[WC][B] + p->cnt[BC][B] <= 1) return 1; // KmK + } + } + + // Trivially drawn KPK endgames + + if (p->PawnEndgame() ) { + if (p->cnt[WC][P] + p->cnt[BC][P] == 1) { + + if (p->cnt[WC][P] == 1 ) return KPKdraw(p, WC); // exactly one white pawn + if (p->cnt[BC][P] == 1 ) return KPKdraw(p, BC); // exactly one black pawn + } + } // pawns only + } + + + return 0; // default: no draw +} + +int KPKdraw(POS *p, int sd) { + + int op = Opp(sd); + U64 bbPawn = p->Pawns(sd); + U64 bbStrongKing = p->Kings(sd); + U64 bbWeakKing = p->Kings(op); + + // opposition through a pawn + + if (p->side == sd + && (bbWeakKing & BB.ShiftFwd(bbPawn, sd)) + && (bbStrongKing & BB.ShiftFwd(bbPawn, op)) + ) return 1; + + // weaker side can create opposition through a pawn in one move + + if (p->side == op + && (BB.KingAttacks(p->king_sq[op]) & BB.ShiftFwd(bbPawn, sd)) + && (bbStrongKing & BB.ShiftFwd(bbPawn, op)) + ) if (!Illegal(p)) return 1; + + // opposition next to a pawn + + if (p->side == sd + && (bbStrongKing & BB.ShiftSideways(bbPawn)) + && (bbWeakKing & BB.ShiftFwd(BB.ShiftFwd(bbStrongKing,sd) ,sd)) + ) return 1; + + // TODO: pawn checks king + + return 0; +} + +void DisplayCurrmove(int move, int tried) { + + printf("info currmove "); + PrintMove(move); + printf(" currmovenumber %d \n", tried); +} + +void DisplaySpeed(void) { + + int elapsed = Timer.GetElapsedTime(); + U64 nps = GetNps(elapsed); +#if defined _WIN32 || defined _WIN64 + printf("info time %d nodes %I64d nps %I64d \n", elapsed, nodes, nps); +#else + printf("info time %d nodes %lld nps %lld \n", elapsed, nodes, nps); +#endif + +} + +void DisplayPv(int score, int *pv) { + + char *type, pv_str[512]; + int elapsed = Timer.GetElapsedTime(); + U64 nps = GetNps(elapsed); + + type = "mate"; + if (score < -MAX_EVAL) + score = (-MATE - score) / 2; + else if (score > MAX_EVAL) + score = (MATE - score + 1) / 2; + else + type = "cp"; + + PvToStr(pv, pv_str); +#if defined _WIN32 || defined _WIN64 + printf("info depth %d time %d nodes %I64d nps %I64d score %s %d pv %s\n", + root_depth, elapsed, nodes, nps, type, score, pv_str); +#else + printf("info depth %d time %d nodes %lld nps %lld score %s %d pv %s\n", + root_depth, elapsed, nodes, nps, type, score, pv_str); +#endif +} + +void CheckTimeout(void) { + + char command[80]; + int time; + U64 nps; + + // Report search speed + + if (!(nodes % 1000000)) DisplaySpeed(); + + // We check for timeout or new commands only every so often, + // to save some time, unless the engine is operating + // in the weakening mode or has received "go nodes" command. + // In that cases, we check for timeout as often as we can. + + if (!Timer.special_mode || Timer.nps_limit > 65535) { + if (nodes & 4095 || root_depth == 1) + return; + } + + if (Timer.GetData(MAX_NODES) > 0 + && nodes >= Timer.GetData(MAX_NODES) ) { + abort_search = 1; + return; + } + + // Slowdown loop + + if (Timer.nps_limit && root_depth > 1) { + time = Timer.GetElapsedTime() + 1; + nps = GetNps(time); + while ((int)nps > Timer.nps_limit) { + Timer.WasteTime(10); + time = Timer.GetElapsedTime() + 1; + nps = GetNps(time); + if (Timeout()) { + abort_search = 1; + return; + } + } + } + + // Process commands that might terminate the search + + if (InputAvailable()) { + ReadLine(command, sizeof(command)); + + if (strcmp(command, "stop") == 0) + abort_search = 1; + else if (strcmp(command, "ponderhit") == 0) + pondering = 0; + } + + // Have we already used our allocated time? + + if (Timeout()) abort_search = 1; +} + +int Timeout() { + + return (!pondering && !Timer.IsInfiniteMode() && Timer.TimeHasElapsed()); +} + +U64 GetNps(int elapsed) { + + U64 nps = 0; + if (elapsed) nps = (nodes * 1000) / elapsed; + return nps; +} + +int DrawScore(POS * p) { + + if (p->side == root_side) return -Param.draw_score; + else return Param.draw_score; +} diff --git a/sources/src/eval.cpp b/sources/src/eval.cpp index 8ef90fa..e4da0c0 100644 --- a/sources/src/eval.cpp +++ b/sources/src/eval.cpp @@ -190,6 +190,7 @@ void cParam::Default(void) { forwardness = 0; book_filter = 20; eval_blur = 0; + riskydepth = 0; } // @DynamicInit() - here we initialize stuff that might be changed @@ -794,6 +795,20 @@ void cEval::ScoreUnstoppable(eData *e, POS * p) { if (b_dist < w_dist-1) Add(e, BC, F_PASSERS, 0, 500); } +int cEval::EvalScaleByDepth(POS *p, int ply, int eval){ + int eval_adj = eval; + //Correct self-side score by depth for human opponent + if ((Param.riskydepth > 0) && (ply >= Param.riskydepth) && (p->side == root_side) && (abs(eval) > Param.draw_score) && (abs(eval) < 1000)){ + eval_adj = eval<0 ? round(1.0*eval*(nodes > 100 ? 0.5 : 1)*Param.riskydepth/ply) : round(1.0*eval*(nodes > 100 ? 2 : 1)*ply/Param.riskydepth); + if (eval_adj>1000) eval_adj = 1000; + } + else if ((Param.riskydepth > 0) && (ply >= Param.riskydepth) && (p->side != root_side) && (abs(eval) > Param.draw_score) && (abs(eval) < 1000)){ + eval_adj = eval<0 ? round(1.0*eval*(nodes > 100 ? 2 : 1)*ply/Param.riskydepth) : round(1.0*eval*(nodes > 100 ? 0.5 : 1)*Param.riskydepth/ply); + if (eval_adj>1000) eval_adj = 1000; + } + return eval_adj; +} + int cEval::Return(POS *p, eData * e, int use_hash) { assert(prog_side == WC || prog_side == BC); @@ -975,4 +990,4 @@ void cEval::Print(POS * p) { printf(" | %4d (%3d) | %4d (%4d, %4d) | %4d (%4d, %4d) |\n", total, weights[fc], mg_score, e.mg[WC][fc], e.mg[BC][fc], eg_score, e.eg[WC][fc], e.eg[BC][fc]); } printf("-----------------------------------------------------------------\n"); -} \ No newline at end of file +} diff --git a/sources/src/main.cpp b/sources/src/main.cpp index daddf5c..face2aa 100644 --- a/sources/src/main.cpp +++ b/sources/src/main.cpp @@ -90,6 +90,7 @@ int main() { MainBook.bookName = "books/rodent.bin"; GuideBook.bookName = "books/guide.bin"; ReadPersonality("basic.ini"); + ReadPersonality("rodent.txt"); #elif __linux || __unix // if we are on Linux // first check, if compiler got told where books and settings are stored @@ -98,6 +99,7 @@ int main() { char nameMainbook[20] = "/rodent.bin"; char nameGuidebook[20]= "/guide.bin"; char namePersonality[20]= "/basic.ini"; + char namePersonality2[20]= "/rodent.txt"; // process Mainbook strcpy(path, ""); // first clear strcpy(path, STR(BOOKPATH)); // copy path from c preprocessor here @@ -113,10 +115,16 @@ int main() { strcpy(path, STR(BOOKPATH)); strcat(path, namePersonality); ReadPersonality(path); + + strcpy(path, ""); + strcpy(path, STR(BOOKPATH)); + strcat(path, namePersonality2); + ReadPersonality(path); #else // if no path was given than we assume that files are stored at /usr/share/rodentII MainBook.bookName = "/usr/share/rodentII/rodent.bin"; GuideBook.bookName = "/usr/share/rodentII/guide.bin"; ReadPersonality("/usr/share/rodentII/basic.ini"); + ReadPersonality("/usr/share/rodentII/rodent.txt"); #endif #else @@ -126,6 +134,7 @@ int main() { MainBook.bookName = "books/rodent.bin"; GuideBook.bookName = "books/guide.bin"; ReadPersonality("basic.ini"); + ReadPersonality("rodent.txt"); #endif MainBook.OpenPolyglot(); GuideBook.OpenPolyglot(); diff --git a/sources/src/param.h b/sources/src/param.h index dad9a9d..4a9b5eb 100644 --- a/sources/src/param.h +++ b/sources/src/param.h @@ -85,6 +85,7 @@ typedef class { int r_mob_eg[15]; int q_mob_mg[28]; int q_mob_eg[28]; + int riskydepth; void DynamicInit(void); void Default(void); } cParam; diff --git a/sources/src/quiesce.cpp b/sources/src/quiesce.cpp index 423d1f3..e5fee19 100644 --- a/sources/src/quiesce.cpp +++ b/sources/src/quiesce.cpp @@ -1,4 +1,7 @@ #include "rodent.h" +#include "param.h" +#include + //#define USE_QS_HASH @@ -21,12 +24,20 @@ int Quiesce(POS *p, int ply, int alpha, int beta, int *pv) { if (abort_search) return 0; *pv = 0; if (IsDraw(p)) return DrawScore(p); - if (ply >= MAX_PLY - 1) return Eval.Return(p, &e, 1); + if (ply >= MAX_PLY - 1) return Eval.EvalScaleByDepth(p,ply,Eval.Return(p, &e, 1)); // Get a stand-pat score and adjust bounds // (exiting if eval exceeds beta) - best = Eval.Return(p, &e, 1); + best = Eval.EvalScaleByDepth(p,ply,Eval.Return(p, &e, 1)); + + //Correct self-side score by depth for human opponent + if ((Param.riskydepth > 0) && (ply >= Param.riskydepth) && (p->side == root_side) && (abs(best) > 100) && (abs(best) < 1000)){ + int eval_adj = best<0 ? round(1.0*best*(nodes > 100 ? 0.5 : 1)*Param.riskydepth/ply) : round(1.0*best*(nodes > 100 ? 2 : 1)*ply/Param.riskydepth); + if (eval_adj>1000) eval_adj = 1000; + best = eval_adj; + } + if (best >= beta) return best; if (best > alpha) alpha = best; @@ -115,9 +126,9 @@ int QuiesceChecks(POS *p, int ply, int alpha, int beta, int *pv) if (IsDraw(p)) return DrawScore(p); if (ply >= MAX_PLY - 1) - return Eval.Return(p, &e, 1); + return Eval.EvalScaleByDepth(p,ply,Eval.Return(p, &e, 1)); - best = stand_pat = Eval.Return(p, &e, 1); + best = stand_pat = Eval.EvalScaleByDepth(p,ply,Eval.Return(p, &e, 1)); if (best >= beta) return best; if (best > alpha) alpha = best; @@ -189,7 +200,7 @@ int QuiesceFlee(POS *p, int ply, int alpha, int beta, int *pv) { // Safeguard against exceeding ply limit if (ply >= MAX_PLY - 1) - return Eval.Return(p, &e, 1); + return Eval.EvalScaleByDepth(p,ply,Eval.Return(p, &e, 1)); // Init moves and variables before entering main loop diff --git a/sources/src/rodent.h b/sources/src/rodent.h index 0eff333..0872609 100644 --- a/sources/src/rodent.h +++ b/sources/src/rodent.h @@ -25,7 +25,7 @@ along with this program. If not, see . // 0.9.50: 56,3% vs 0.9.33 #pragma once -#define PROG_NAME "Rodent II 0.9.68" +#define PROG_NAME "Rodent II 0.9.68 risky" enum eColor{WC, BC, NO_CL}; enum ePieceType{P, N, B, R, Q, K, NO_TP}; @@ -352,12 +352,12 @@ typedef class { void ScoreOutpost(POS * p, eData *e, int sd, int pc, int sq); void ScorePawns(POS * p, eData *e, int sd); void FullPawnEval(POS * p, eData *e, int use_hash); - public: int prog_side; void Init(void); int Return(POS * p, eData * e, int use_hash); void Print(POS *p); + int EvalScaleByDepth(POS *p, int ply, int eval); } cEval; extern cEval Eval; @@ -509,4 +509,4 @@ int PcMatQ(POS *p, int sd); int PcMatR(POS *p, int sd); int PcMatRm(POS *p, int sd); int PcMatRR(POS *p, int sd); -int PcMatRRm(POS *p, int sd); \ No newline at end of file +int PcMatRRm(POS *p, int sd); diff --git a/sources/src/search.cpp b/sources/src/search.cpp index dd27990..38bcba2 100644 --- a/sources/src/search.cpp +++ b/sources/src/search.cpp @@ -443,7 +443,7 @@ int Search(POS *p, int ply, int alpha, int beta, int depth, int was_null, int la // Safeguard against exceeding ply limit if (ply >= MAX_PLY - 1) - return Eval.Return(p, &e, 1); + return Eval.EvalScaleByDepth(p,ply,Eval.Return(p, &e, 1)); // Are we in check? Knowing that is useful when it comes // to pruning/reduction decisions @@ -477,6 +477,11 @@ int Search(POS *p, int ply, int alpha, int beta, int depth, int was_null, int la int eval = 0; if (fl_prunable_node && (!was_null || depth <= 6) ) eval = Eval.Return(p, &e, 1); + + //Correct self-side score by depth for human opponent + if (fl_prunable_node){ + eval = Eval.EvalScaleByDepth(p,ply,eval); + } // Beta pruning / static null move @@ -923,4 +928,4 @@ int DrawScore(POS * p) { if (p->side == root_side) return -Param.draw_score; else return Param.draw_score; -} \ No newline at end of file +} diff --git a/sources/src/uci.cpp b/sources/src/uci.cpp index 8c49224..641a54b 100644 --- a/sources/src/uci.cpp +++ b/sources/src/uci.cpp @@ -119,7 +119,10 @@ void UciLoop(void) { printf("option name Contempt type spin default %d min -250 max 250\n", Param.draw_score); printf("option name SlowMover type spin default %d min 10 max 500\n", time_percentage); printf("option name Selectivity type spin default %d min 0 max 200\n", hist_perc); - printf("option name OwnBook type check default true\n"); + + printf("option name RiskyDepth type spin default %d min 0 max 10\n", Param.riskydepth); + + printf("option name OwnBook type check default true\n"); printf("option name GuideBookFile type string default guide.bin\n"); printf("option name MainBookFile type string default rodent.bin\n"); printf("option name BookFilter type spin default %d min 0 max 5000000\n", Param.book_filter); @@ -331,6 +334,9 @@ void ParseSetoption(char *ptr) { } else if (strcmp(name, "Selectivity") == 0 || strcmp(name, "selectivity") == 0) { hist_perc = atoi(value); hist_limit = -HIST_LIMIT + ((HIST_LIMIT * hist_perc) / 100); + } else if (strcmp(name, "RiskyDepth") == 0 || strcmp(name, "riskydepth") == 0) { + Param.riskydepth = atoi(value); + ResetEngine(); } else if (strcmp(name, "GuideBookFile") == 0 || strcmp(name, "guidebookfile") == 0) { if (!fl_separate_books || !fl_reading_personality) { GuideBook.ClosePolyglot();