Minimax.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. #include "Minimax.h"
  2. #include "ChessGame.h"
  3. #include "Search.h"
  4. #include <iostream>
  5. #include <functional>
  6. #include <limits>
  7. #include <chrono>
  8. using namespace chessy;
  9. size_t chessy::perft(std::ostream& out, ChessGame& chessGame, int depth)
  10. {
  11. size_t result = 0;
  12. std::function<void(const MoveInfo&, ChessGame&, int, bool)> searcher;
  13. std::function<void(ChessGame&, int, bool)> iterate;
  14. // apply a move and search deeper
  15. searcher = [&result, &iterate] (const MoveInfo& mi, ChessGame& cg,
  16. int depth, bool print)
  17. {
  18. if (depth > 1) {
  19. UndoInfo ui = cg.doMove(mi);
  20. size_t interRes = result;
  21. iterate(cg, depth - 1, false);
  22. if (print)
  23. std::cout << mi.move.asString() << ": " <<
  24. (result - interRes) << std::endl;
  25. cg.undoMove(ui);
  26. }
  27. else {
  28. result++;
  29. }
  30. };
  31. // iterate through all positions in the current position
  32. iterate = [&searcher] (ChessGame& cg, int depth, bool print)
  33. {
  34. Search<decltype(searcher)&> search (searcher, cg);
  35. if (cg.getTurn() == WHITE_SIDE)
  36. search.iterateAll<WHITE_SIDE>(cg, depth, print);
  37. else
  38. search.iterateAll<BLACK_SIDE>(cg, depth, print);
  39. };
  40. using namespace std::chrono;
  41. auto begin = high_resolution_clock::now();
  42. iterate(chessGame, depth, true);
  43. auto end = high_resolution_clock::now();
  44. auto millis = duration_cast<milliseconds>(end - begin);
  45. double seconds = millis.count() / 1000.0;
  46. size_t nodesPerSecond = (result * 1000 * 1000 * 1000) /
  47. duration_cast<nanoseconds>(end - begin).count();
  48. out << "Searched " << result << " nodes in " << seconds <<
  49. " seconds. (at " << nodesPerSecond << " nodes per second)" << std::endl;
  50. return result;
  51. }
  52. #include <iostream>
  53. using namespace std;
  54. std::pair<Move, MoveValue> chessy::miniMax(ChessGame& cg, int depth)
  55. {
  56. std::vector<Move> moves;
  57. moves.reserve(200);
  58. if (cg.getTurn() == WHITE_SIDE) {
  59. generateAllMoves<WHITE_SIDE>(cg, moves);
  60. orderMoves<WHITE_SIDE>(cg, moves);
  61. }
  62. else {
  63. generateAllMoves<BLACK_SIDE>(cg, moves);
  64. orderMoves<BLACK_SIDE>(cg, moves);
  65. }
  66. const Board& b = cg.getBoard();
  67. auto isCheck = [&cg, &b] () {
  68. return cg.getTurn() == BLACK_SIDE ? b.isCheck<WHITE_SIDE>() :
  69. b.isCheck<BLACK_SIDE>();
  70. };
  71. Move bestMove{ 0, 0 };
  72. MoveValue alpha = -1e+30;
  73. MoveValue beta = 1e+30;
  74. for (Move move : moves) {
  75. MoveInfo mi{ move, cg };
  76. UndoInfo ui = cg.doMove(mi);
  77. MoveValue val;
  78. if (isCheck()) {
  79. cg.undoMove(ui);
  80. continue;
  81. }
  82. else {
  83. val = -negamaxImplementation(cg, depth - 1, -beta, -alpha);
  84. }
  85. //cout << move.asString() << ": " << val << ", " << best << ((val > best) ? " good" : " bad") << endl;
  86. //MoveValue val = 0.0;
  87. cg.undoMove(ui);
  88. if(val > alpha) {
  89. alpha = val;
  90. bestMove = move;
  91. }
  92. }
  93. return { bestMove, alpha }; //negamaxImplementation(chessGame, 5);
  94. }
  95. MoveValue chessy::negamaxImplementation(ChessGame& cg, int depth,
  96. chessy::MoveValue alpha, chessy::MoveValue beta)
  97. {
  98. if (depth <= 0) {
  99. if (cg.getTurn() == WHITE_SIDE)
  100. return evaluate<WHITE_SIDE>(cg);
  101. else
  102. return evaluate<BLACK_SIDE>(cg);
  103. }
  104. const Board& b = cg.getBoard();
  105. auto isCheck = [&cg, &b] (Side turn) {
  106. return turn == BLACK_SIDE ? b.isCheck<WHITE_SIDE>() :
  107. b.isCheck<BLACK_SIDE>();
  108. };
  109. std::vector<Move> moves;
  110. if (cg.getTurn() == WHITE_SIDE) {
  111. generateAllMoves<WHITE_SIDE>(cg, moves);
  112. orderMoves<WHITE_SIDE>(cg, moves);
  113. }
  114. else {
  115. generateAllMoves<BLACK_SIDE>(cg, moves);
  116. orderMoves<BLACK_SIDE>(cg, moves);
  117. }
  118. bool thereIsMove = false;
  119. for (Move move : moves) {
  120. MoveInfo mi{ move, cg };
  121. UndoInfo ui = cg.doMove(mi);
  122. MoveValue val;
  123. if (isCheck(cg.getTurn()))
  124. val = -1e+30;
  125. else {
  126. val = -negamaxImplementation(cg, depth - 1, -beta, -alpha);
  127. thereIsMove = true;
  128. }
  129. cg.undoMove(ui);
  130. if(val >= beta)
  131. return beta;
  132. if(val > alpha)
  133. alpha = val;
  134. }
  135. if (!thereIsMove)
  136. return isCheck(otherSide(cg.getTurn())) ? -1e+30 : 0.0;
  137. return alpha;
  138. }
  139. template<Side side>
  140. MoveValue chessy::evaluate(const ChessGame& game)
  141. {
  142. MoveValue piecePoints = 0;
  143. const Board& bd = game.getBoard();
  144. Bitboard p = bd.getPawns<side>();
  145. Bitboard n = bd.getKnights<side>();
  146. Bitboard b = bd.getBishops<side>();
  147. Bitboard r = bd.getRooks<side>();
  148. Bitboard q = bd.getQueens<side>();
  149. Bitboard k = bd.getKing<side>();
  150. for (auto pawn : PositionSet{ p })
  151. piecePoints += 0.5 / 8.0 * pawn.getRow() + 1.0;
  152. piecePoints += 1 * p.popcount();
  153. piecePoints += 3 * n.popcount();
  154. piecePoints += 3 * b.popcount();
  155. piecePoints += 5 * r.popcount();
  156. piecePoints += 9 * q.popcount();
  157. constexpr Side other = otherSide(side);
  158. p = bd.getPawns<other>();
  159. n = bd.getKnights<other>();
  160. b = bd.getBishops<other>();
  161. r = bd.getRooks<other>();
  162. q = bd.getQueens<other>();
  163. k = bd.getKing<other>();
  164. for (auto pawn : PositionSet{ p })
  165. piecePoints += 0.5 / 8.0 * (7 - pawn.getRow()) + 1.0;
  166. piecePoints -= 1 * p.popcount();
  167. piecePoints -= 3 * n.popcount();
  168. piecePoints -= 3 * b.popcount();
  169. piecePoints -= 5 * r.popcount();
  170. piecePoints -= 9 * q.popcount();
  171. return piecePoints;
  172. }
  173. /*
  174. template MiniMax::BestMove MiniMax::minimax<WHITE_SIDE>(int);
  175. template MiniMax::BestMove MiniMax::minimax<BLACK_SIDE>(int);
  176. Move MiniMax::calculateBest(int depth)
  177. {
  178. if (game.getTurn() == WHITE_SIDE) {
  179. BestMove bm = minimax<WHITE_SIDE>(depth);
  180. return bm.move;
  181. }
  182. else
  183. return minimax<BLACK_SIDE>(depth).move;
  184. }
  185. template<Side side>
  186. float MiniMax::evaluate(void) const
  187. {
  188. int piecePoints = 0;
  189. Board& bd = game.getBoard();
  190. Bitboard p = bd.getPawns<side>();
  191. Bitboard n = bd.getKnights<side>();
  192. Bitboard b = bd.getBishops<side>();
  193. Bitboard r = bd.getRooks<side>();
  194. Bitboard q = bd.getQueens<side>();
  195. Bitboard k = bd.getKing<side>();
  196. piecePoints += 1 * p.popcount();
  197. piecePoints += 3 * n.popcount();
  198. piecePoints += 3 * b.popcount();
  199. piecePoints += 4 * r.popcount();
  200. piecePoints += 6 * q.popcount();
  201. if (k == Bitboard(0ULL))
  202. piecePoints -= 100000;
  203. const Side other = otherSide(side);
  204. p = bd.getPawns<other>();
  205. n = bd.getKnights<other>();
  206. b = bd.getBishops<other>();
  207. r = bd.getRooks<other>();
  208. q = bd.getQueens<other>();
  209. k = bd.getKing<other>();
  210. piecePoints -= 1 * p.popcount();
  211. piecePoints -= 3 * n.popcount();
  212. piecePoints -= 3 * b.popcount();
  213. piecePoints -= 4 * r.popcount();
  214. piecePoints -= 6 * q.popcount();
  215. if (k == Bitboard(0ULL))
  216. piecePoints += 100000;
  217. return piecePoints;
  218. }
  219. template<Side side>
  220. MiniMax::BestMove MiniMax::minimax(int depth)
  221. {
  222. if (depth == 0) {
  223. return { {0, 0}, -evaluate<side>() };
  224. }
  225. BestMove bestMove = { {0, 0}, -std::numeric_limits<float>::infinity() };
  226. Board& board = game.getBoard();
  227. Bitboard friends;
  228. Bitboard enemies;
  229. if (side == WHITE_SIDE) {
  230. friends = board.getWhites();
  231. enemies = board.getBlacks();
  232. }
  233. else {
  234. friends = board.getBlacks();
  235. enemies = board.getWhites();
  236. }
  237. const Board temp = board;
  238. PawnPushGenerator<side> mg{ game };
  239. for (Move push : mg) {
  240. Bitboard& pawns = board.getPawns<side>();
  241. Bitboard pTemp = pawns;
  242. pawns.applyMove(push);
  243. BestMove m = minimax<otherSide(side)>(depth - 1);
  244. m.move = push;
  245. bestMove.overwriteIfBetter(m);
  246. pawns = pTemp;
  247. }
  248. PawnDoublePushGenerator<side> dp{ game };
  249. for (Move push : dp) {
  250. Bitboard& pawns = board.getPawns<side>();
  251. Bitboard pTemp = pawns;
  252. pawns.applyMove(push);
  253. BestMove m = minimax<otherSide(side)>(depth - 1);
  254. m.move = push;
  255. bestMove.overwriteIfBetter(m);
  256. pawns = pTemp;
  257. }
  258. PawnCaptureGenerator<side, LEFT> pl{ game };
  259. for (Move capture : pl) {
  260. Bitboard& pawns = board.getPawns<side>();
  261. board.removeAt(capture.destination);
  262. pawns.applyMove(capture);
  263. BestMove m = minimax<otherSide(side)>(depth - 1);
  264. m.move = capture;
  265. bestMove.overwriteIfBetter(m);
  266. board = temp;
  267. }
  268. PawnCaptureGenerator<side, RIGHT> pr{ game };
  269. for (Move capture : pr) {
  270. Bitboard& pawns = board.getPawns<side>();
  271. board.removeAt(capture.destination);
  272. pawns.applyMove(capture);
  273. BestMove m = minimax<otherSide(side)>(depth - 1);
  274. m.move = capture;
  275. bestMove.overwriteIfBetter(m);
  276. board = temp;
  277. }
  278. PromotionGenerator<side> pg{ game };
  279. for (Move promotion : pg) {
  280. Bitboard& pawns = board.getPawns<side>();
  281. board.removeAt(promotion.destination);
  282. pawns.applyMove(promotion);
  283. BestMove m = minimax<otherSide(side)>(depth - 1);
  284. m.move = promotion;
  285. bestMove.overwriteIfBetter(m);
  286. board = temp;
  287. }
  288. Bitboard& ns = board.getKnights<side>();
  289. PositionSet knights { ns };
  290. for (auto knight : knights) {
  291. for (auto pos : KnightMoveGenerator{ knight, friends }) {
  292. Move move = { knight, pos };
  293. board.removeAt(move.destination);
  294. ns.applyMove(move);
  295. BestMove m = minimax<otherSide(side)>(depth - 1);
  296. m.move = move;
  297. bestMove.overwriteIfBetter(m);
  298. board = temp;
  299. }
  300. }
  301. Bitboard& bs = board.getBishops<side>();
  302. PositionSet bishops { bs };
  303. for (auto bishop : bishops) {
  304. for (auto pos : PrimitiveBishopMoveGenerator{ bishop, enemies, friends }) {
  305. Move move = { bishop, pos };
  306. board.removeAt(move.destination);
  307. bs.applyMove(move);
  308. BestMove m = minimax<otherSide(side)>(depth - 1);
  309. m.move = move;
  310. bestMove.overwriteIfBetter(m);
  311. board = temp;
  312. }
  313. }
  314. Bitboard& rs = board.getRooks<side>();
  315. PositionSet rooks { rs };
  316. for (auto rook : rooks) {
  317. for (auto pos : PrimitiveRookMoveGenerator{ rook, enemies, friends }) {
  318. Move move = { rook, pos };
  319. board.removeAt(move.destination);
  320. rs.applyMove(move);
  321. BestMove m = minimax<otherSide(side)>(depth - 1);
  322. m.move = move;
  323. bestMove.overwriteIfBetter(m);
  324. board = temp;
  325. }
  326. }
  327. Bitboard& qs = board.getQueens<side>();
  328. PositionSet queens { qs };
  329. for (auto queen : queens) {
  330. for (auto pos : PrimitiveQueenMoveGenerator{ queen, enemies, friends }) {
  331. Move move = { queen, pos };
  332. board.removeAt(move.destination);
  333. qs.applyMove(move);
  334. BestMove m = minimax<otherSide(side)>(depth - 1);
  335. m.move = move;
  336. bestMove.overwriteIfBetter(m);
  337. board = temp;
  338. }
  339. }
  340. Bitboard& king = board.getKing<side>();
  341. Index kingIndex = king.getLeastSignificantBit();
  342. for (auto pos : KingMoveGenerator{ king, friends }) {
  343. Move move = { kingIndex, pos };
  344. board.removeAt(pos);
  345. king.applyMove(move);
  346. BestMove m = minimax<otherSide(side)>(depth - 1);
  347. m.move = move;
  348. //if (depth >= 3)
  349. // std::cout << m.move.asString() << " " << bestMove.value << " -> " << m.value << std::endl;
  350. bestMove.overwriteIfBetter(m);
  351. board = temp;
  352. }
  353. if (side == WHITE_SIDE) {
  354. if (game.getCanCastleKingSide(side)) {
  355. if((board.getWhites().bits & 0x6) == 0) {
  356. Move kingMove = {3, 1};
  357. Move rookMove = {0, 2};
  358. king.applyMove(kingMove);
  359. rs.applyMove(rookMove);
  360. board = temp;
  361. }
  362. }
  363. }
  364. for (auto pos : CastlingGenerator<side>{ game }) {
  365. Move move = { kingIndex, pos };
  366. king.applyMove(move);
  367. BestMove m = minimax<otherSide(side)>(depth - 1);
  368. m.move = move;
  369. //if (depth >= 3)
  370. // std::cout << m.move.asString() << " " << bestMove.value << " -> " << m.value << std::endl;
  371. bestMove.overwriteIfBetter(m);
  372. board = temp;
  373. }
  374. float v = evaluate<side>();
  375. bestMove.value *= 0.9f;
  376. bestMove.value += v * 0.2f;
  377. return -bestMove;
  378. }
  379. */