Subversion Repositories Games.Chess Giants

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
99 pmbaty 1
/*
2
    Texel - A UCI chess engine.
3
    Copyright (C) 2012-2014  Peter Ă–sterlund, peterosterlund2@gmail.com
4
 
5
    This program is free software: you can redistribute it and/or modify
6
    it under the terms of the GNU General Public License as published by
7
    the Free Software Foundation, either version 3 of the License, or
8
    (at your option) any later version.
9
 
10
    This program is distributed in the hope that it will be useful,
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
    GNU General Public License for more details.
14
 
15
    You should have received a copy of the GNU General Public License
16
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
*/
18
 
19
/*
20
 * enginecontrol.cpp
21
 *
22
 *  Created on: Mar 4, 2012
23
 *      Author: petero
24
 */
25
 
26
#define _GLIBCXX_USE_NANOSLEEP
27
 
28
#include "enginecontrol.hpp"
29
#include "util/random.hpp"
30
#include "searchparams.hpp"
31
#include "book.hpp"
32
#include "textio.hpp"
33
#include "parameters.hpp"
34
#include "moveGen.hpp"
35
#include "util/logger.hpp"
36
#include "numa.hpp"
37
 
38
#include <iostream>
39
#include <memory>
40
#include <chrono>
41
 
42
 
43
EngineControl::SearchListener::SearchListener(std::ostream& os0)
44
    : os(os0)
45
{
46
}
47
 
48
void
49
EngineControl::SearchListener::notifyDepth(int depth) {
50
//    std::lock_guard<std::mutex> L(Logger::getLogMutex());
51
    os << "info depth " << depth << std::endl;
52
}
53
 
54
void
55
EngineControl::SearchListener::notifyCurrMove(const Move& m, int moveNr) {
56
//    std::lock_guard<std::mutex> L(Logger::getLogMutex());
57
    os << "info currmove " << moveToString(m) << " currmovenumber " << moveNr << std::endl;
58
}
59
 
60
void
61
EngineControl::SearchListener::notifyPV(int depth, int score, int time, U64 nodes, int nps, bool isMate,
62
                                        bool upperBound, bool lowerBound, const std::vector<Move>& pv,
63
                                        int multiPVIndex, U64 tbHits) {
64
//    std::lock_guard<std::mutex> L(Logger::getLogMutex());
65
    std::string pvBuf;
66
    for (size_t i = 0; i < pv.size(); i++) {
67
        pvBuf += ' ';
68
        pvBuf += moveToString(pv[i]);
69
    }
70
    std::string bound;
71
    if (upperBound) {
72
        bound = " upperbound";
73
    } else if (lowerBound) {
74
        bound = " lowerbound";
75
    }
76
    os << "info depth " << depth << " score " << (isMate ? "mate " : "cp ")
77
       << score << bound << " time " << time << " nodes " << nodes
78
       << " nps " << nps;
79
    if (tbHits > 0)
80
        os << " tbhits " << tbHits;
81
    if (multiPVIndex >= 0)
82
        os << " multipv " << (multiPVIndex + 1);
83
    os << " pv" << pvBuf << std::endl;
84
}
85
 
86
void
87
EngineControl::SearchListener::notifyStats(U64 nodes, int nps, U64 tbHits, int time) {
88
    os << "info nodes " << nodes << " nps " << nps;
89
    if (tbHits > 0)
90
        os << " tbhits " << tbHits;
91
    os << " time " << time << std::endl;
92
}
93
 
94
EngineControl::EngineControl(std::ostream& o)
95
    : os(o),
96
      shouldDetach(true),
97
      tt(8),
98
      pd(tt),
99
      randomSeed(0)
100
{
101
    Numa::instance().bindThread(0);
102
    hashParListenerId = UciParams::hash->addListener([this]() {
103
        setupTT();
104
    });
105
    clearHashParListenerId = UciParams::clearHash->addListener([this]() {
106
        tt.clear();
107
        ht.init();
108
    }, false);
109
    et = Evaluate::getEvalHashTables();
110
}
111
 
112
EngineControl::~EngineControl() {
113
    UciParams::hash->removeListener(hashParListenerId);
114
    UciParams::hash->removeListener(clearHashParListenerId);
115
}
116
 
117
void
118
EngineControl::startSearch(const Position& pos, const std::vector<Move>& moves, const SearchParams& sPar) {
119
    stopSearch();
120
    setupPosition(pos, moves);
121
    computeTimeLimit(sPar);
122
    ponder = false;
123
    infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0);
124
    searchMoves = sPar.searchMoves;
125
    startThread(minTimeLimit, maxTimeLimit, maxDepth, maxNodes);
126
}
127
 
128
void
129
EngineControl::startPonder(const Position& pos, const std::vector<Move>& moves, const SearchParams& sPar) {
130
    stopSearch();
131
    setupPosition(pos, moves);
132
    computeTimeLimit(sPar);
133
    ponder = true;
134
    infinite = false;
135
    startThread(-1, -1, -1, -1);
136
}
137
 
138
void
139
EngineControl::ponderHit() {
140
    std::shared_ptr<Search> mySearch;
141
    {
142
        std::lock_guard<std::mutex> L(threadMutex);
143
        mySearch = sc;
144
    }
145
    if (mySearch) {
146
        if (onePossibleMove) {
147
            if (minTimeLimit > 1) minTimeLimit = 1;
148
            if (maxTimeLimit > 1) maxTimeLimit = 1;
149
        }
150
        mySearch->timeLimit(minTimeLimit, maxTimeLimit);
151
    }
152
    infinite = (maxTimeLimit < 0) && (maxDepth < 0) && (maxNodes < 0);
153
    ponder = false;
154
}
155
 
156
void
157
EngineControl::stopSearch() {
158
    stopThread();
159
}
160
 
161
void
162
EngineControl::newGame() {
163
    randomSeed = Random().nextU64();
164
    tt.clear();
165
    ht.init();
166
}
167
 
168
/**
169
 * Compute thinking time for current search.
170
 */
171
void
172
EngineControl::computeTimeLimit(const SearchParams& sPar) {
173
    minTimeLimit = -1;
174
    maxTimeLimit = -1;
175
    maxDepth = -1;
176
    maxNodes = -1;
177
    if (sPar.infinite) {
178
        minTimeLimit = -1;
179
        maxTimeLimit = -1;
180
        maxDepth = -1;
181
    } else {
182
        if (sPar.depth > 0)
183
            maxDepth = sPar.depth;
184
        if (sPar.mate > 0) {
185
            int md = sPar.mate * 2 - 1;
186
            maxDepth = maxDepth == -1 ? md : std::min(maxDepth, md);
187
        }
188
        if (sPar.nodes > 0)
189
            maxNodes = sPar.nodes;
190
 
191
        if (sPar.moveTime > 0) {
192
             minTimeLimit = maxTimeLimit = sPar.moveTime;
193
        } else if (sPar.wTime || sPar.bTime) {
194
            int moves = sPar.movesToGo;
195
            if (moves == 0)
196
                moves = 999;
197
            moves = std::min(moves, static_cast<int>(timeMaxRemainingMoves)); // Assume at most N more moves until end of game
198
            bool white = pos.isWhiteMove();
199
            int time = white ? sPar.wTime : sPar.bTime;
200
            int inc  = white ? sPar.wInc : sPar.bInc;
201
            const int margin = std::min(static_cast<int>(bufferTime), time * 9 / 10);
202
            int timeLimit = (time + inc * (moves - 1) - margin) / moves;
203
            minTimeLimit = (int)(timeLimit * minTimeUsage * 0.01);
204
            if (UciParams::ponder->getBoolPar()) {
205
                const double ponderHitRate = timePonderHitRate * 0.01;
206
                minTimeLimit = (int)ceil(minTimeLimit / (1 - ponderHitRate));
207
            }
208
            maxTimeLimit = (int)(minTimeLimit * clamp(moves * 0.5, 2.5, static_cast<int>(maxTimeUsage) * 0.01));
209
 
210
            // Leave at least 1s on the clock, but can't use negative time
211
            minTimeLimit = clamp(minTimeLimit, 1, time - margin);
212
            maxTimeLimit = clamp(maxTimeLimit, 1, time - margin);
213
        }
214
    }
215
}
216
 
217
void
218
EngineControl::startThread(int minTimeLimit, int maxTimeLimit, int maxDepth, int maxNodes) {
219
    Search::SearchTables st(tt, kt, ht, *et);
220
    sc = std::make_shared<Search>(pos, posHashList, posHashListSize, st, pd, nullptr, treeLog);
221
    sc->setListener(std::make_shared<SearchListener>(os));
222
    sc->setStrength(UciParams::strength->getIntPar(), randomSeed);
223
    std::shared_ptr<MoveList> moves(std::make_shared<MoveList>());
224
    MoveGen::pseudoLegalMoves(pos, *moves);
225
    MoveGen::removeIllegal(pos, *moves);
226
    if (searchMoves.size() > 0)
227
        moves->filter(searchMoves);
228
    onePossibleMove = false;
229
    if ((moves->size < 2) && !infinite) {
230
        onePossibleMove = true;
231
        if (!ponder) {
232
            if (maxTimeLimit > 0) {
233
                maxTimeLimit = clamp(maxTimeLimit/100, 1, 100);
234
                minTimeLimit = clamp(minTimeLimit/100, 1, 100);
235
            } else {
236
                if ((maxDepth < 0) || (maxDepth > 2))
237
                    maxDepth = 2;
238
            }
239
        }
240
    }
241
    pd.addRemoveWorkers(UciParams::threads->getIntPar() - 1);
242
    pd.wq.resetSplitDepth();
243
    pd.startAll();
244
    sc->timeLimit(minTimeLimit, maxTimeLimit);
245
    tt.nextGeneration();
246
    bool ownBook = UciParams::ownBook->getBoolPar();
247
    bool analyseMode = UciParams::analyseMode->getBoolPar();
248
    int maxPV = (infinite || analyseMode) ? UciParams::multiPV->getIntPar() : 1;
249
    int minProbeDepth = UciParams::minProbeDepth->getIntPar();
250
    if (analyseMode) {
251
        Evaluate eval(*et);
252
        int evScore = eval.evalPosPrint(pos) * (pos.isWhiteMove() ? 1 : -1);
253
        std::stringstream ss;
254
        ss.precision(2);
255
        ss << std::fixed << (evScore / 100.0);
256
        os << "info string Eval: " << ss.str() << std::endl;
257
    }
258
    auto f = [this,ownBook,analyseMode,moves,maxDepth,maxNodes,maxPV,minProbeDepth]() {
259
        Numa::instance().bindThread(0);
260
        Move m;
261
        if (ownBook && !analyseMode) {
262
            Book book(false);
263
            book.getBookMove(pos, m);
264
        }
265
        if (m.isEmpty())
266
            m = sc->iterativeDeepening(*moves, maxDepth, maxNodes, false, maxPV, false, minProbeDepth);
267
        while (ponder || infinite) {
268
            // We should not respond until told to do so. Just wait until
269
            // we are allowed to respond.
270
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
271
        }
272
        Move ponderMove = getPonderMove(pos, m);
273
        std::lock_guard<std::mutex> L(threadMutex);
274
        os << "bestmove " << moveToString(m);
275
        if (!ponderMove.isEmpty())
276
            os << " ponder " << moveToString(ponderMove);
277
        os << std::endl;
278
        if (shouldDetach) {
279
            engineThread->detach();
280
            pd.stopAll();
281
            pd.fhInfo.reScale();
282
        }
283
        engineThread.reset();
284
        sc.reset();
285
        for (auto& p : pendingOptions)
286
            setOption(p.first, p.second, false);
287
        pendingOptions.clear();
288
    };
289
    shouldDetach = true;
290
    {
291
        std::lock_guard<std::mutex> L(threadMutex);
292
        engineThread = std::make_shared<std::thread>(f);
293
    }
294
}
295
 
296
void
297
EngineControl::stopThread() {
298
    std::shared_ptr<std::thread> myThread;
299
    {
300
        std::lock_guard<std::mutex> L(threadMutex);
301
        myThread = engineThread;
302
        if (myThread) {
303
            sc->timeLimit(0, 0);
304
            infinite = false;
305
            ponder = false;
306
            shouldDetach = false;
307
        }
308
    }
309
    if (myThread)
310
        myThread->join();
311
    pd.stopAll();
312
    pd.fhInfo.reScale();
313
}
314
 
315
void
316
EngineControl::setupTT() {
317
    int hashSizeMB = UciParams::hash->getIntPar();
318
    U64 nEntries = hashSizeMB > 0 ? ((U64)hashSizeMB) * (1 << 20) / sizeof(TranspositionTable::TTEntry)
319
                                  : (U64)1024;
320
    int logSize = 0;
321
    while (nEntries > 1) {
322
        logSize++;
323
        nEntries /= 2;
324
    }
325
    logSize++;
326
    while (true) {
327
        try {
328
            logSize--;
329
            if (logSize <= 0)
330
                break;
331
            tt.reSize(logSize);
332
            break;
333
        } catch (const std::bad_alloc& /*ex*/) { // Pierre-Marie Baty -- unreferenced variable
334
        }
335
    }
336
}
337
 
338
void
339
EngineControl::setupPosition(Position pos, const std::vector<Move>& moves) {
340
    UndoInfo ui;
341
    posHashList.resize(200 + moves.size());
342
    posHashListSize = 0;
343
    for (size_t i = 0; i < moves.size(); i++) {
344
        const Move& m = moves[i];
345
        posHashList[posHashListSize++] = pos.zobristHash();
346
        pos.makeMove(m, ui);
347
        if (pos.getHalfMoveClock() == 0)
348
            posHashListSize = 0;
349
    }
350
    this->pos = pos;
351
}
352
 
353
/**
354
 * Try to find a move to ponder from the transposition table.
355
 */
356
Move
357
EngineControl::getPonderMove(Position pos, const Move& m) {
358
    Move ret;
359
    if (m.isEmpty())
360
        return ret;
361
    UndoInfo ui;
362
    pos.makeMove(m, ui);
363
    TranspositionTable::TTEntry ent;
364
    ent.clear();
365
    tt.probe(pos.historyHash(), ent);
366
    if (ent.getType() != TType::T_EMPTY) {
367
        ent.getMove(ret);
368
        MoveList moves;
369
        MoveGen::pseudoLegalMoves(pos, moves);
370
        MoveGen::removeIllegal(pos, moves);
371
        bool contains = false;
372
        for (int mi = 0; mi < moves.size; mi++)
373
            if (moves[mi].equals(ret)) {
374
                contains = true;
375
                break;
376
            }
377
        if  (!contains)
378
            ret = Move();
379
    }
380
    pos.unMakeMove(m, ui);
381
    return ret;
382
}
383
 
384
std::string
385
EngineControl::moveToString(const Move& m) {
386
    if (m.isEmpty())
387
        return "0000";
388
    std::string ret = TextIO::squareToString(m.from());
389
    ret += TextIO::squareToString(m.to());
390
    switch (m.promoteTo()) {
391
    case Piece::WQUEEN:
392
    case Piece::BQUEEN:
393
        ret += 'q';
394
        break;
395
    case Piece::WROOK:
396
    case Piece::BROOK:
397
        ret += 'r';
398
        break;
399
    case Piece::WBISHOP:
400
    case Piece::BBISHOP:
401
        ret += 'b';
402
        break;
403
    case Piece::WKNIGHT:
404
    case Piece::BKNIGHT:
405
        ret += 'n';
406
        break;
407
    default:
408
        break;
409
    }
410
    return ret;
411
}
412
 
413
void
414
EngineControl::printOptions(std::ostream& os) {
415
    std::vector<std::string> parNames;
416
    Parameters::instance().getParamNames(parNames);
417
    for (const auto& pName : parNames) {
418
        std::shared_ptr<Parameters::ParamBase> p = Parameters::instance().getParam(pName);
419
        switch (p->type) {
420
        case Parameters::CHECK: {
421
            const Parameters::CheckParam& cp = dynamic_cast<const Parameters::CheckParam&>(*p.get());
422
            os << "option name " << cp.name << " type check default "
423
               << (cp.defaultValue?"true":"false") << std::endl;
424
            break;
425
        }
426
        case Parameters::SPIN: {
427
            const Parameters::SpinParam& sp = dynamic_cast<const Parameters::SpinParam&>(*p.get());
428
            os << "option name " << sp.name << " type spin default "
429
               << sp.getDefaultValue() << " min " << sp.getMinValue()
430
               << " max " << sp.getMaxValue() << std::endl;
431
            break;
432
        }
433
        case Parameters::COMBO: {
434
            const Parameters::ComboParam& cp = dynamic_cast<const Parameters::ComboParam&>(*p.get());
435
            os << "option name " << cp.name << " type combo default " << cp.defaultValue;
436
            for (size_t i = 0; i < cp.allowedValues.size(); i++)
437
                os << " var " << cp.allowedValues[i];
438
            os << std::endl;
439
            break;
440
        }
441
        case Parameters::BUTTON:
442
            os << "option name " << p->name << " type button" << std::endl;
443
            break;
444
        case Parameters::STRING: {
445
            const Parameters::StringParam& sp = dynamic_cast<const Parameters::StringParam&>(*p.get());
446
            os << "option name " << sp.name << " type string default "
447
               << sp.defaultValue << std::endl;
448
            break;
449
        }
450
        }
451
    }
452
}
453
 
454
void
455
EngineControl::setOption(const std::string& optionName, const std::string& optionValue,
456
                         bool deferIfBusy) {
457
    Parameters& params = Parameters::instance();
458
    if (deferIfBusy) {
459
        std::lock_guard<std::mutex> L(threadMutex);
460
        if (engineThread) {
461
            if (params.getParam(optionName))
462
                pendingOptions[optionName] = optionValue;
463
            return;
464
        }
465
    }
466
    std::shared_ptr<Parameters::ParamBase> par = params.getParam(optionName);
467
    if (par && par->type == Parameters::STRING && optionValue == "<empty>")
468
        params.set(optionName, "");
469
    else
470
        params.set(optionName, optionValue);
471
}