Skip to content

Commit

Permalink
Merge pull request #46 from megalodon-chess/endgame
Browse files Browse the repository at this point in the history
Queen Mate
  • Loading branch information
phuang1024 authored Mar 31, 2021
2 parents 9fc4781 + f30b795 commit a03cfe8
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 1 deletion.
170 changes: 170 additions & 0 deletions src/endgame.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//
// Megalodon
// UCI chess engine
// Copyright the Megalodon developers
//
// This program 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.
//
// This program 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 <https://www.gnu.org/licenses/>.
//

#include <iostream>
#include <vector>
#include <string>
#include "bitboard.hpp"
#include "endgame.hpp"
#include "eval.hpp"

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;


namespace Endgame {
bool cnt_match(const vector<char> c1, const vector<char> c2) {
for (char i = 0; i < 10; i++) {
if (c1[i] != c2[i]) return false;
}
return true;
}

vector<char> get_cnts(const Position& pos) {
const char wpc = Bitboard::popcnt(pos.wp);
const char wnc = Bitboard::popcnt(pos.wn);
const char wbc = Bitboard::popcnt(pos.wb);
const char wrc = Bitboard::popcnt(pos.wr);
const char wqc = Bitboard::popcnt(pos.wq);
const char bpc = Bitboard::popcnt(pos.bp);
const char bnc = Bitboard::popcnt(pos.bn);
const char bbc = Bitboard::popcnt(pos.bb);
const char brc = Bitboard::popcnt(pos.br);
const char bqc = Bitboard::popcnt(pos.bq);
return {wpc, wnc, wbc, wrc, wqc, bpc, bnc, bbc, brc, bqc};
}

int eg_type(const Position& pos) {
const vector<char> counts = get_cnts(pos);

if (pos.turn && cnt_match(W_KQvK, counts)) {
return 1;
} else if (!pos.turn && cnt_match(B_KQvK, counts)) {
return 1;
}

return 0;
}


Move bestmove(const Position& pos, const vector<Move>& moves, const int& eg) {
const vector<char> counts = get_cnts(pos);
if (pos.turn) {
switch (eg) {
case 1: return kqvk(moves, pos, pos.wk, pos.wq, pos.bk);
}
} else {
switch (eg) {
case 1: return kqvk(moves, pos, pos.bk, pos.bq, pos.wk);
}
}
}


Move kqvk(const vector<Move>& moves, const Position& pos, const U64& _ck, const U64& _cq, const U64& _ok) {
const Location ck = Bitboard::first_bit(_ck);
const Location cq = Bitboard::first_bit(_cq);
const Location ok = Bitboard::first_bit(_ok);
const char ckp = (ck.y<<3) + ck.x;
const char cqp = (cq.y<<3) + cq.x;
const char okp = (ok.y<<3) + ok.x;

char corner_dist = 16;
Location closest_corner = CORNERS[0];
for (const auto& corner: CORNERS) {
const Location cor = Location(corner);
const char dist = abs(ok.x-cor.x) + abs(ok.y-cor.y);
if (dist < corner_dist) {
corner_dist = dist;
closest_corner = cor;
}
}

if (corner_dist <= 1 && ((abs(cq.x-ok.x)==1 && abs(cq.y-closest_corner.y) == 3) || (abs(cq.y-ok.y)==1 && abs(cq.x-closest_corner.x) == 3))) {
const char dx = abs(cq.x-ok.x);
const char dy = abs(cq.y-ok.y);
if ((dx==1 && (dy==2||dy==3)) || (dy==1 && (dx==2||dx==3))) { // King is cornered by queen.
Location k_target;
if (closest_corner.x > 0) k_target.x = 5;
else k_target.x = 2;
if (closest_corner.y > 0) k_target.y = 5;
else k_target.y = 2;
const char k_target_pos = (k_target.y<<3) + k_target.x;

if (k_target_pos == ckp) { // Do checkmating move
for (const auto& move: moves) {
const char ok_diff = abs(okp-move.to);
const char ck_diff = abs(ckp-move.to);
if ((ok_diff==9 || ok_diff==7) && (ck_diff==9 || ck_diff==7)) return move;
}
} else { // Move king closer
char closest = 16;
Move best_move = moves[0];
for (const auto& move: moves) {
if (move.from == ckp) {
const Location to = Location(move.to);
const char delta = abs(k_target.x-to.x) + abs(k_target.y-to.y);
if (delta < closest) {
closest = delta;
best_move = move;
}
}
}
return best_move;
}
}
}

char best_dist = 16;
char least_move_dist = 16;
Move best_move = moves[0];
for (const auto& move: moves) {
if (move.from == cqp) {
const Position new_pos = Bitboard::push(pos, move);
const vector<Move> new_moves = Bitboard::legal_moves(new_pos, Bitboard::attacked(new_pos, !new_pos.turn));
if (new_moves.size() == 0) {
continue; // Continue if move results in stalemate.
}

const Location to = Location(move.to);
const char dx = abs(to.x-ok.x);
const char dy = abs(to.y-ok.y);
if ((dx==0 || dx==1) && (dy==0 || dy==1)) continue; // If move goes to opponent's king
if (dx == 0 || dy == 0 || dx == dy) continue; // Don't check king

const char dist = dx + dy;
const char move_dist = std::max(abs(to.x-cq.x), abs(to.y-cq.y));

if (dist < best_dist) {
best_move = move;
best_dist = dist;
if (move_dist < least_move_dist) least_move_dist = move_dist;
} else if (dist <= best_dist && move_dist < least_move_dist) {
least_move_dist = move_dist;
best_move = move;
}
}
}

return best_move;
}
}
46 changes: 46 additions & 0 deletions src/endgame.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Megalodon
// UCI chess engine
// Copyright the Megalodon developers
//
// This program 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.
//
// This program 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 <https://www.gnu.org/licenses/>.
//

#pragma once

#include <iostream>
#include <vector>
#include <tuple>
#include <string>
#include "bitboard.hpp"

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;

namespace Endgame {
const vector<char> W_KQvK = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0};
const vector<char> B_KQvK = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
const vector<char> CORNERS = {0, 7, 56, 63};

bool cnt_match(const vector<char>, const vector<char>);
vector<char> get_cnts(const Position&);
int eg_type(const Position&);

Move bestmove(const Position&, const vector<Move>&, const int&);

Move kqvk(const vector<Move>&, const Position&, const U64&, const U64&, const U64&);
}
8 changes: 8 additions & 0 deletions src/search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "options.hpp"
#include "utils.hpp"
#include "hash.hpp"
#include "endgame.hpp"

using std::cin;
using std::cout;
Expand Down Expand Up @@ -164,6 +165,13 @@ namespace Search {
}

SearchInfo search(const Options& options, const Position& pos, const int& depth, const double& movetime, bool& searching) {
const int eg = Endgame::eg_type(pos);
if (eg != 0) {
const vector<Move> moves = Bitboard::legal_moves(pos, Bitboard::attacked(pos, !pos.turn));
const Move best_move = Endgame::bestmove(pos, moves, eg);
return SearchInfo(1, 1, false, pos.turn ? MAX : MIN, moves.size(), 0, 0, {best_move}, 0, 0, true);
}

SearchInfo result;
float alpha = MIN, beta = MAX;
const double start = get_time();
Expand Down
4 changes: 3 additions & 1 deletion src/uci.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "eval.hpp"
#include "perft.hpp"
#include "hash.hpp"
#include "endgame.hpp"

using std::cin;
using std::cout;
Expand Down Expand Up @@ -259,6 +260,7 @@ int loop() {
vector<string> parts = split(cmd, " ");
perft_push(pos, std::stoi(parts[1]));
}
else if (cmd == "eg") cout << Endgame::eg_type(pos) << endl;

else if (cmd == "ucinewgame") {
pos = parse_pos("position startpos");
Expand All @@ -268,7 +270,7 @@ int loop() {
else if (startswith(cmd, "go")) {
vector<string> parts = split(cmd, " ");
if (parts.size() > 1 && parts[1] == "perft") perft(options, pos, std::stoi(parts[2]));
else if (!searching) {
else {
searching = true;
std::thread(go, options, pos, parts, prev_eval, std::ref(searching)).detach();
}
Expand Down

0 comments on commit a03cfe8

Please sign in to comment.