Skip to content

Commit 26e4720

Browse files
authored
some features and improvements (#3)
fixed some bugs implemented LMR and NMP, features, etc. Passed STC: -------------------------------------------------- Results of new vs base (10+0.1, NULL, 16MB, UHO_Lichess_4852_v1.epd): Elo: 51.67 +/- 8.40, nElo: 92.31 +/- 14.79 LOS: 100.00 %, DrawRatio: 47.36 %, PairsRatio: 2.82 Games: 2120, Wins: 517, Losses: 204, Draws: 1399, Points: 1216.5 (57.38 %) Ptnml(0-2): [8, 138, 502, 357, 55], WL/DD Ratio: 0.11 LLR: 2.95 (100.2%) (-2.94, 2.94) [0.00, 2.00] -------------------------------------------------- Passed LTC: -------------------------------------------------- Results of new vs base (60+0.6, NULL, 16MB, UHO_Lichess_4852_v1.epd): Elo: 108.69 +/- 10.86, nElo: 199.39 +/- 18.67 LOS: 100.00 %, DrawRatio: 41.80 %, PairsRatio: 12.34 Games: 1330, Wins: 461, Losses: 58, Draws: 811, Points: 866.5 (65.15 %) Ptnml(0-2): [1, 28, 278, 283, 75], WL/DD Ratio: 0.11 LLR: 2.95 (100.1%) (-2.94, 2.94) [0.00, 2.00] --------------------------------------------------
1 parent 4e5461e commit 26e4720

9 files changed

Lines changed: 149 additions & 47 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ project(cppchess_engine)
55
set(CMAKE_CXX_STANDARD 17)
66
set(CMAKE_CXX_STANDARD_REQUIRED ON)
77
include(FetchContent)
8+
set(BUILD_TESTING OFF)
89
FetchContent_Declare(
910
chesslib
1011
GIT_REPOSITORY https://github.com/winapiadmin/chesslib.git

eval.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ Value eg_king_table[64] = {-74, -35, -18, -18, -11, 15, 4, -17, -12, 17, 14,
9696
23, 16, 7, -9, -27, -11, 4, 13, 14, 4, -5,
9797
-17, -53, -34, -21, -11, -28, -14, -24, -43};
9898

99-
Value *mg_pesto_table[] = {
100-
{0}, mg_pawn_table, mg_knight_table, mg_bishop_table,
101-
mg_rook_table, mg_queen_table, mg_king_table};
99+
Value *mg_pesto_table[] = {nullptr, mg_pawn_table, mg_knight_table,
100+
mg_bishop_table, mg_rook_table, mg_queen_table,
101+
mg_king_table};
102102

103-
Value *eg_pesto_table[] = {
104-
{0}, eg_pawn_table, eg_knight_table, eg_bishop_table,
105-
eg_rook_table, eg_queen_table, eg_king_table};
103+
Value *eg_pesto_table[] = {nullptr, eg_pawn_table, eg_knight_table,
104+
eg_bishop_table, eg_rook_table, eg_queen_table,
105+
eg_king_table};
106106

107107
Value eval(const chess::Board &board) {
108108
int pieceCount[10] = {

eval.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
#pragma once
2-
#include <cstdint>
3-
#include <cstdlib>
42
#include <fwd_decl.h>
53
using Value = int;
64
namespace engine {

main.cpp

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@
22
#include "uci.h"
33
#include "ucioption.h"
44
#include <iostream>
5+
#include <thread>
56
using namespace engine;
7+
extern std::thread searchThread;
68
int main() {
79
options.add("Move Overhead", Option(10, 0, 1000));
8-
options.add( //
9-
"Hash", Option(16, 1, 1 << 25, [](const Option &o) {
10-
search::tt.resize((o.operator int()));
11-
return std::nullopt;
12-
}));
10+
options.add("Hash", Option(16, 1, 1 << 25, [](const Option &o) {
11+
search::tt.resize((o.operator int()));
12+
return std::nullopt;
13+
}));
1314

14-
options.add( //
15-
"Clear Hash", Option(+[](const Option &) {
16-
search::tt.clear();
17-
return std::nullopt;
18-
}));
15+
options.add("Clear Hash", Option(+[](const Option &) {
16+
if (searchThread.joinable()) {
17+
std::cout
18+
<< "info string In search, do not modify hash table\n";
19+
return std::nullopt;
20+
}
21+
search::tt.clear();
22+
23+
return std::nullopt;
24+
}));
1925
loop();
20-
}
26+
}

score.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "score.h"
22
#include <cassert>
3+
#include <cmath>
34
namespace engine {
45
Score::Score(Value v) {
56
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
@@ -14,4 +15,4 @@ Score::Score(Value v) {
1415
score = (v > 0) ? Mate{distance} : Mate{-distance};
1516
}
1617
}
17-
} // namespace engine
18+
} // namespace engine

search.cpp

Lines changed: 93 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66
#include <atomic>
77
#include <moves_io.h>
88
#include <position.h>
9+
#include <printers.h>
910
using namespace chess;
1011
namespace engine {
1112
TranspositionTable search::tt(16);
1213
std::atomic<bool> stopSearch{false};
13-
void search::stop() {
14-
tt.clear();
15-
stopSearch.store(true, std::memory_order_relaxed);
16-
}
14+
void search::stop() { stopSearch.store(true, std::memory_order_relaxed); }
1715
struct Session {
1816
timeman::TimeManagement tm;
1917
timeman::LimitsType tc;
@@ -29,7 +27,11 @@ void update_pv(Move *pv, Move move, const Move *childPv) {
2927
}
3028
Value qsearch(Board &board, Value alpha, Value beta, Session &session,
3129
int ply = 0) {
30+
if (session.tm.elapsed() >= session.tm.optimum() ||
31+
stopSearch.load(std::memory_order_relaxed))
32+
return VALUE_NONE;
3233
session.nodes++;
34+
session.seldepth = std::max(session.seldepth, ply);
3335
int standPat = eval::eval(board);
3436
Value maxScore = standPat;
3537
if (maxScore >= beta)
@@ -40,8 +42,11 @@ Value qsearch(Board &board, Value alpha, Value beta, Session &session,
4042
board.legals<MoveGenType::CAPTURE>(moves);
4143
for (Move move : moves) {
4244
board.doMove(move);
43-
Value score = -qsearch(board, -beta, -alpha, session, ply + 1);
45+
Value score = qsearch(board, -beta, -alpha, session, ply + 1);
4446
board.undoMove();
47+
if (score == VALUE_NONE)
48+
return VALUE_NONE;
49+
score = -score;
4550
if (score >= beta)
4651
return score;
4752
if (score > maxScore)
@@ -51,7 +56,7 @@ Value qsearch(Board &board, Value alpha, Value beta, Session &session,
5156
}
5257
return maxScore;
5358
}
54-
Value doSearch(Board &board, int depth, Value alpha, Value beta,
59+
Value doSearch(Board board, int depth, Value alpha, Value beta,
5560
Session &session, int ply = 0) {
5661
if (ply >= MAX_PLY - 1)
5762
return eval::eval(board);
@@ -97,7 +102,7 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta,
97102
preferred = Move(entry->getMove());
98103
}
99104
if (depth == 0) {
100-
return qsearch(board, alpha, beta, session, ply + 1);
105+
return qsearch(board, alpha, beta, session, ply);
101106
}
102107
Value maxScore = -VALUE_INFINITE;
103108
Movelist moves;
@@ -107,20 +112,73 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta,
107112
return board.checkers() ? -MATE(ply) : 0;
108113
}
109114
movepick::orderMoves(board, moves, preferred, ply);
110-
for (Move move : moves) {
115+
if (bool useNMP = depth >= 3 && !board.checkers() && ply > 0) {
116+
int R = 2 + depth / 6;
117+
board.doNullMove();
118+
Value score =
119+
doSearch(board, depth - 1 - R, -beta, -beta + 1, session, ply + 1);
120+
121+
if (score == VALUE_NONE) {
122+
board.undoMove();
123+
return VALUE_NONE;
124+
}
125+
score = -score;
126+
board.undoMove();
127+
if (score >= beta)
128+
return score;
129+
}
130+
for (size_t i = 0; i < moves.size(); ++i) {
131+
Move move = moves[i];
132+
133+
bool isCapture = board.isCapture(move);
134+
bool givesCheck = board.givesCheck(move) != CheckType::NO_CHECK;
135+
136+
// --- LMR reduction ---
137+
int reduction = 0;
138+
if (i >= 3 && depth >= 3 && !isCapture && !givesCheck) {
139+
reduction = 1 + (int)(i / 6) + (depth / 8);
140+
141+
// history heuristic: good moves get reduced less
142+
if (movepick::historyHeuristic[(int)move.from()][(int)move.to()] > 0)
143+
reduction--;
144+
145+
reduction = std::max(0, reduction);
146+
reduction = std::min(reduction, depth - 2);
147+
}
111148

112149
board.doMove(move);
113150

114-
Value childScore =
115-
doSearch(board, depth - 1, -beta, -alpha, session, ply + 1);
151+
Value score;
116152

117-
board.undoMove();
153+
if (i == 0) {
154+
// --- First move: full window (PVS root move) ---
155+
score = -doSearch(board, depth - 1, -beta, -alpha, session, ply + 1);
118156

119-
// ---- ABORT PROPAGATION ----
120-
if (childScore == VALUE_NONE)
121-
return VALUE_NONE;
157+
if (score == VALUE_NONE) {
158+
board.undoMove();
159+
return VALUE_NONE;
160+
}
161+
} else {
162+
// --- Null-window search (PVS + LMR) ---
163+
score = doSearch(board, depth - 1 - reduction, -alpha - 1, -alpha,
164+
session, ply + 1);
165+
if (score == VALUE_NONE) {
166+
board.undoMove();
167+
return VALUE_NONE;
168+
}
169+
score = -score;
170+
// --- Re-search if it improves alpha ---
171+
if (score > alpha) {
172+
score = doSearch(board, depth - 1, -beta, -alpha, session, ply + 1);
173+
if (score == VALUE_NONE) {
174+
board.undoMove();
175+
return VALUE_NONE;
176+
}
177+
score = -score;
178+
}
179+
}
122180

123-
Value score = -childScore;
181+
board.undoMove();
124182

125183
if (score > maxScore) {
126184
maxScore = score;
@@ -129,18 +187,20 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta,
129187

130188
if (score > alpha) {
131189
alpha = score;
132-
if (!board.isCapture(move))
190+
191+
if (!isCapture)
133192
movepick::historyHeuristic[(int)move.from()][(int)move.to()] +=
134193
depth * depth;
135194
}
195+
136196
if (alpha >= beta) {
137-
if (!board.isCapture(move)) {
197+
// killer moves
198+
if (!isCapture) {
138199
if (movepick::killerMoves[ply][0] != move) {
139200
movepick::killerMoves[ply][1] = movepick::killerMoves[ply][0];
140201
movepick::killerMoves[ply][0] = move;
141202
}
142203
}
143-
144204
break;
145205
}
146206

@@ -165,6 +225,7 @@ Value doSearch(Board &board, int depth, Value alpha, Value beta,
165225
}
166226
void search::search(const chess::Board &board,
167227
const timeman::LimitsType timecontrol) {
228+
stopSearch = false;
168229
static double originalTimeAdjust = -1;
169230
Session session;
170231
session.tc = timecontrol;
@@ -178,12 +239,11 @@ void search::search(const chess::Board &board,
178239
// since MAX_PLY=64
179240
session.pv[_][j] = Move::none();
180241
}
181-
session.nodes = 0;
182242
auto board_ = board;
183243
Value score_ =
184244
doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session);
185245
if (session.tm.elapsed() >= session.tm.optimum() ||
186-
stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE)
246+
stopSearch.load(std::memory_order_relaxed) || abs(score_) == VALUE_NONE)
187247
break;
188248
InfoFull info{};
189249
info.depth = i;
@@ -195,6 +255,18 @@ void search::search(const chess::Board &board,
195255
info.timeMs = session.tm.elapsed();
196256
info.multiPV = 1;
197257
info.score = score_;
258+
TTEntry *entry = tt.lookup(board.hash());
259+
if (entry)
260+
switch (entry->getFlag()) {
261+
case LOWERBOUND:
262+
info.bound = "lowerbound";
263+
break;
264+
case UPPERBOUND:
265+
info.bound = "upperbound";
266+
break;
267+
default:
268+
break;
269+
}
198270
std::string pv = "";
199271
for (Move *m = session.pv[0]; *m != Move::none(); m++)
200272
pv += chess::uci::moveToUci(*m, board.chess960()) + " ";
@@ -233,7 +305,7 @@ void search::search(const chess::Board &board,
233305
info.nodes = 1;
234306
info.score = 0;
235307
info.multiPV = 1;
236-
info.pv = chess::uci::moveToUci(best, board.chess960());
308+
info.pv = std::string(chess::uci::moveToUci(best, board.chess960()));
237309
report(info);
238310

239311
report(chess::uci::moveToUci(best, board.chess960()));

tt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ TTEntry *TranspositionTable::lookup(uint64_t hash) {
6868
TTEntry &e0 = table[index], &e1 = table[index + 1];
6969
// Check the entries
7070
for (TTEntry *e : {&e0, &e1}) {
71-
if (e->key == hash)
71+
if (e->key == hash && e->getGeneration() == this->time)
7272
return e;
7373
}
7474
return nullptr;

tt.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ struct TTEntry {
3737
<< GEN_SHIFT;
3838

3939
// getters
40-
inline uint16_t getScore() const noexcept {
41-
return static_cast<uint16_t>((pack & SCORE_MASK) >> SCORE_SHIFT);
40+
inline int16_t getScore() const noexcept {
41+
return static_cast<int16_t>((pack & SCORE_MASK) >> SCORE_SHIFT);
4242
}
4343

4444
inline uint8_t getDepth() const noexcept {
@@ -176,4 +176,4 @@ class TranspositionTable {
176176
return (used * 1000) / buckets;
177177
}
178178
};
179-
} // namespace engine
179+
} // namespace engine

uci.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
#include <position.h>
88
#include <printers.h>
99
#include <sstream>
10+
#include <thread>
1011
using namespace engine;
1112
chess::Position pos;
1213
OptionsMap engine::options;
14+
std::thread searchThread;
1315
void handlePosition(std::istringstream &is) {
16+
if (searchThread.joinable()) {
17+
std::cout << "info string In search, do not modify position\n";
18+
return;
19+
}
1420
std::string token, fen;
1521

1622
is >> token;
@@ -70,7 +76,17 @@ timeman::LimitsType parse_limits(std::istream &is) {
7076

7177
return limits;
7278
}
73-
void handleGo(std::istringstream &ss) { search::search(pos, parse_limits(ss)); }
79+
80+
void handleGo(std::istringstream &ss) {
81+
if (searchThread.joinable()) {
82+
search::stop();
83+
searchThread.join();
84+
}
85+
86+
searchThread = std::thread([ss = std::move(ss)]() mutable {
87+
search::search(pos, parse_limits(ss));
88+
});
89+
}
7490
template <typename... Ts> struct overload : Ts... {
7591
using Ts::operator()...;
7692
};
@@ -160,7 +176,7 @@ void engine::loop() {
160176
break;
161177
} else if (token == "position") {
162178
handlePosition(ss);
163-
break; // rest belongs to position
179+
break;
164180
} else if (token == "go") {
165181
handleGo(ss);
166182
break; // rest belongs to go
@@ -169,8 +185,13 @@ void engine::loop() {
169185
break;
170186
} else if (token == "stop") {
171187
search::stop();
188+
if (searchThread.joinable())
189+
searchThread.join();
172190
break;
173191
} else if (token == "quit") {
192+
search::stop();
193+
if (searchThread.joinable())
194+
searchThread.join();
174195
return;
175196
} else if (token == "setoption") {
176197
options.setoption(ss);
@@ -181,4 +202,7 @@ void engine::loop() {
181202
}
182203
}
183204
}
205+
search::stop();
206+
if (searchThread.joinable())
207+
searchThread.join();
184208
}

0 commit comments

Comments
 (0)