diff --git a/Development/Mate In One Move/.gitignore b/Development/Mate In One Move/.gitignore new file mode 100644 index 0000000..675b5a9 --- /dev/null +++ b/Development/Mate In One Move/.gitignore @@ -0,0 +1,52 @@ +#cmake + +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +#C++ + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +# name of executable if no extention + +build/ + diff --git a/Development/Mate In One Move/.vscode/settings.json b/Development/Mate In One Move/.vscode/settings.json new file mode 100644 index 0000000..dbaf2db --- /dev/null +++ b/Development/Mate In One Move/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" +} \ No newline at end of file diff --git a/Development/Mate In One Move/CMakeLists.txt b/Development/Mate In One Move/CMakeLists.txt new file mode 100644 index 0000000..e1ee0f9 --- /dev/null +++ b/Development/Mate In One Move/CMakeLists.txt @@ -0,0 +1,94 @@ +cmake_minimum_required( VERSION 3.26 ) + +project( Mate_In_One_Move ) + +set( MAIN_PROGRAM MIOM ) + +# Set compiler flags +set( CMAKE_CXX_STANDARD 20 ) +set( CMAKE_CXX_EXTENSIONS OFF ) + +# use mold linker +set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=mold") +set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=mold") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=mold") + +set(SOURCE + src/Solver.cpp + main.cpp + ) + + +add_executable( ${MAIN_PROGRAM} ${SOURCE} ) + +target_compile_options( ${MAIN_PROGRAM} + PRIVATE + -Wall + -Werror + -Wextra + -pedantic + -pedantic-errors +) + +target_include_directories( ${MAIN_PROGRAM} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) +target_include_directories( ${MAIN_PROGRAM} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" ) + + +set( TEST_SUITE MIOM_Tests_Suite ) + +include(CTest) +enable_testing() + +find_package( GTest REQUIRED ) + +set( TEST_SOURCE + src/Solver.cpp + tests/mate_in_one_move.t.cpp +) + +add_executable( ${TEST_SUITE} ${TEST_SOURCE} ) + +target_compile_options( ${TEST_SUITE} + PRIVATE + -Wall + -Wextra + -pedantic + -pedantic-errors + -pg + -ftest-coverage + -fsanitize=address + -fsanitize=undefined + -fsanitize=shift + -fsanitize=shift-exponent + -fsanitize=shift-base + -fsanitize=integer-divide-by-zero + -fsanitize=vla-bound + -fsanitize=null + -fsanitize=return + -fsanitize=signed-integer-overflow + -fsanitize=bounds + -fsanitize=bounds-strict + -fsanitize=alignment + -fsanitize=object-size + -fsanitize=float-divide-by-zero + -fsanitize=float-cast-overflow + -fsanitize=enum + -fsanitize=pointer-overflow + -fsanitize-address-use-after-scope + -fstack-protector-all +) + +target_link_options(${TEST_SUITE} + PRIVATE + -fsanitize=undefined + -fsanitize=address +) + +target_link_libraries( ${TEST_SUITE} GTest::gtest_main ) +target_link_libraries( ${TEST_SUITE} GTest::gmock_main) + +target_include_directories( ${TEST_SUITE} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) +target_include_directories( ${TEST_SUITE} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" ) + +include( GoogleTest ) +gtest_discover_tests( ${TEST_SUITE} ) \ No newline at end of file diff --git a/Development/Mate In One Move/Problem Description.odt b/Development/Mate In One Move/Problem Description.odt new file mode 100644 index 0000000..24cc015 Binary files /dev/null and b/Development/Mate In One Move/Problem Description.odt differ diff --git a/Development/Mate In One Move/Problem Description.pdf b/Development/Mate In One Move/Problem Description.pdf new file mode 100644 index 0000000..b8dcd2c Binary files /dev/null and b/Development/Mate In One Move/Problem Description.pdf differ diff --git a/Development/Mate In One Move/include/Solver.h b/Development/Mate In One Move/include/Solver.h new file mode 100644 index 0000000..251a9d9 --- /dev/null +++ b/Development/Mate In One Move/include/Solver.h @@ -0,0 +1,91 @@ +/** + * @file Solver.h + * @author William Weston + * @brief Class to solve "Mate in One" Problem + * @version 0.1 + * @date 2023-06-09 + * + * @copyright Copyright (c) 2023 + * + */ +#ifndef WJTW_SOLVER_H_2023_06_09_INCLUDE +#define WJTW_SOLVER_H_2023_06_09_INCLUDE + +#include +#include +#include +#include + +using Board = std::vector; // [row][col] +using Cell = std::pair; // row by col +using Direction = std::pair; +using Directions = std::vector; + +struct Move +{ + using Cell = std::pair; // row by col + + Cell from = { -1, -1 }; // returing a default constructed move indicates no "mate in one move" could be found + Cell to = { -1, -1 }; +}; + + auto operator<<( std::ostream&, Move const& ) -> std::ostream&; +constexpr auto operator==( Move const& lhs, Move const& rhs ) noexcept -> bool; + auto output_format( Move const& move ) -> std::string; + +class Solver final +{ +public: + auto load_board( std::istream& in ) -> bool; + auto solve() -> Move; + auto print_board() const -> void; + +private: + using Board = std::vector; // [row][col] + + Board board_ = {}; + + auto find_solution( std::vector const& white_moves ) -> Move; + auto is_checkmate( Move const& white ) -> bool; +}; + + +constexpr auto Empty = '.'; +constexpr auto Unlimited = 16; // max distance a piece can travel + +auto const Cross = Directions{ { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } }; +auto const Diagonal = Directions{ { 1, 1 }, { -1, 1 }, { 1, -1 }, { -1, -1 } }; +auto const Knight = Directions{ { 1, 2 }, { 1, -2 }, { 2, 1 }, { 2, -1 }, { -1, 2 }, { -1, -2 }, { -2, 1 }, { -2, -1 } }; +auto const All_Moves = Directions{ { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, { 1, 1 }, { -1, 1 }, { 1, -1 }, { -1, -1 } }; + + +enum class Side +{ + white, + black, +}; + +auto generate_moves( Side side, Board const& board ) -> std::vector; +auto generate_moves( Cell const& cell, Board const& board ) -> std::vector; +auto pawn_moves( Cell const& cell, Board const& board ) -> std::vector; +auto move_generator( Cell const&, Board const&, Directions const&, int ) -> std::vector; +auto do_move( Move const& move, Board const& board ) -> Board; +auto do_castling_move( Move const& move, Board const& board ) -> Board; + +auto can_escape( Move const& black, Board const& board ) -> bool; +constexpr auto is_white( unsigned char ch ) noexcept -> bool; +constexpr auto is_castled( Cell cell, char piece ) noexcept -> bool; +constexpr auto is_pawn( char piece ) noexcept -> bool; +constexpr auto is_knight_move( Move const& move ) noexcept -> bool; +constexpr auto is_king_taken( Move const&, Board const&, Side ) -> bool; +constexpr auto is_valid( Move const& move, Board const& board ) noexcept -> bool; + +auto is_in_check( Side side, Board const& board ) -> bool; +auto print_board( Board const& board ) -> void; + +// ------------------------------------------------------------------------------------------------------------------- + + +#include "Solver.inl" + +#endif \ No newline at end of file diff --git a/Development/Mate In One Move/include/Solver.inl b/Development/Mate In One Move/include/Solver.inl new file mode 100644 index 0000000..d2098e8 --- /dev/null +++ b/Development/Mate In One Move/include/Solver.inl @@ -0,0 +1,112 @@ +/** + * @file Solver.inl + * @author William Weston + * @brief Inline functions from Solver.h + * @version 0.1 + * @date 2023-06-12 + * + * @copyright Copyright (c) 2023 + * + */ + +#ifndef WJTW_SOLVER_INL_2023_06_12_INCLUDE +#define WJTW_SOLVER_INL_2023_06_12_INCLUDE + +#include // isupper +#include + +constexpr auto +operator==( Move const& lhs, Move const& rhs ) noexcept -> bool +{ + return lhs.from.first == rhs.from.first && lhs.from.second == rhs.from.second && + lhs.to.first == rhs.to.first && lhs.to.second == rhs.to.second; +} + + +constexpr auto +is_white( unsigned char ch ) noexcept -> bool +{ + return std::isupper( ch ); +} + +constexpr auto +is_pawn( char piece ) noexcept -> bool +{ + return ( piece == 'p' || piece =='P' ); +} + +constexpr auto +is_castled( Cell cell, char piece ) noexcept -> bool +{ + if ( !is_pawn( piece ) ) // only pawns can be castled + return false; + + [[maybe_unused]] auto const& [row, dummy] = cell; + auto const isWhite = is_white( piece ); + + return ( ( isWhite && row == 0 ) || ( !isWhite && row == 7 ) ); +} + + +constexpr auto +is_knight_move( Move const& move ) noexcept -> bool +{ + auto const& [from_row, from_col] = move.from; + auto const& [to_row, to_col] = move.to; + + for ( auto const& direction : Knight ) + { + auto const& [row_dir, col_dir] = direction; + auto const dest_row = from_row + row_dir; + auto const dest_col = from_col + col_dir; + + return ( ( dest_row == to_row ) && ( dest_col == to_col ) ); + } + + return false; +} + +/** + * @brief + * + * @param move + * @param board + * @param side the side ( black/white ) whose piece the Move represents + * @return true + * @return false + */ +constexpr auto +is_king_taken( Move const& move, Board const& board, Side side ) -> bool +{ + auto const& [to_row, to_col] = move.to; + auto const& dest_piece = board[to_row][to_col]; + + switch ( side ) + { + case Side::white: + return ( dest_piece == 'k' ); + case Side::black: + return ( dest_piece == 'K' ); + default: + throw std::runtime_error( "Error: Unknown Side" ); + } +} + +constexpr auto +is_valid( Move const& move, Board const& board ) noexcept -> bool +{ + auto const& [from_row, from_col] = move.from; + auto const& [to_row, to_col] = move.to; + + // out of bounds + if ( to_row < 0 || to_row > 7 || to_col < 0 || to_col > 7 ) + return false; + + auto const& piece = board[from_row][from_col]; + auto const& destination_piece = board[to_row][to_col]; + + return ( ( destination_piece == Empty ) || // empty square + ( is_white( piece ) != is_white( destination_piece ) ) ); // the two pieces are not of the same side +} + +#endif // WJTW_SOLVER_INL_2023_06_12_INCLUDE \ No newline at end of file diff --git a/Development/Mate In One Move/input.txt b/Development/Mate In One Move/input.txt new file mode 100644 index 0000000..f398cda --- /dev/null +++ b/Development/Mate In One Move/input.txt @@ -0,0 +1,48 @@ +rn...q.b +pb..pPkp +.p...... +..ppN..p +...P.... +..NB.... +PPPQ.PP. +..KR.... +..kr...r +p..n..pp +.p.Bnp.. +....p... +........ +........ +PPP..PPP +...RKB.R +rnbq..kr +.p.n..pp +p...p... +...pP... +......Q. +B.PB.... +P.P..PPP +R....RK. +r.bqkb.r +pp.npppp +.....n.. +.....N.. +...PN... +........ +PPPBQPPP +R...KB.R +........ +.....p.. +...p.... +b...Q.K. +k.nq.... +p..NR..r +..P..P.. +R..Bn... +.rbq..r. +p.Pk.K.b +.P.bnp.. +..n....p +........ +........ +....p... +...R.... diff --git a/Development/Mate In One Move/main.cpp b/Development/Mate In One Move/main.cpp new file mode 100644 index 0000000..8217421 --- /dev/null +++ b/Development/Mate In One Move/main.cpp @@ -0,0 +1,33 @@ +/** + * @file main.cpp + * @author William Weston + * @brief Mate in One Move Solver + * @version 0.1 + * @date 2023-06-09 + * + * @copyright Copyright (c) 2023 + * + */ + +#include "Solver.h" + +#include +#include + +// ------------------------------------------------------------------------------------------------ + +auto main() -> int +{ + auto solver = Solver(); + + while ( solver.load_board( std::cin ) ) + { + auto solution = solver.solve( ); + std::cout << output_format( solution ) << "\n"; + } + + return EXIT_SUCCESS; +} + +// ------------------------------------------------------------------------------------------------ + diff --git a/Development/Mate In One Move/src/Solver.cpp b/Development/Mate In One Move/src/Solver.cpp new file mode 100644 index 0000000..5554ab4 --- /dev/null +++ b/Development/Mate In One Move/src/Solver.cpp @@ -0,0 +1,376 @@ +/** + * @file Solver.cpp + * @author William Weston + * @brief Class to solve "Mate in One" Problem + * @version 0.1 + * @date 2023-06-09 + * + * @copyright Copyright (c) 2023 + * + */ + +#include "Solver.h" + +#include +#include +#include +#include +#include +#include + + + + + + +// --------------------------------------------- Move ---------------------------------------------- + +auto +operator<<( std::ostream& out, Move const& mv ) -> std::ostream& +{ + out << '[' << mv.from.first << ',' << mv.from.second << "] -> [" << mv.to.first << ',' << mv.to.second << ']'; + return out; +} + +auto +output_format( Move const& move ) -> std::string +{ + if ( move == Move() ) + return "no solution"; + + auto const& from = move.from; + auto const from_file = 'a' + from.second; + auto const from_rank = 8 - from.first; + + auto const& to = move.to; + auto const to_file = 'a' + to.second; + auto const to_rank = 8 - to.first; + + auto ret = std::string(); + + ret.push_back( from_file ); + ret += std::to_string( from_rank ); + ret.push_back( to_file ); + ret += std::to_string( to_rank ); + + return ret; +} + +// --------------------------------------------- Solver ------------------------------------------- + +auto +Solver::load_board( std::istream& in ) -> bool +{ + board_.clear(); // clear the previous board + + // no input validation required + auto line = std::string(); + + for ( auto i = 0; i < 8; ++i ) + { + std::getline( in, line ); + if ( line.size() ) + board_.push_back( line ); + } + + return ( board_.size() == 8 ); // return false when no more valid boards can be loading from the stream +} + +auto +Solver::solve() -> Move +{ + // generate all moves for white + auto const white_moves = generate_moves( Side::white, board_ ); + + return find_solution( white_moves ); +} + +auto +Solver::print_board() const -> void +{ + ::print_board( board_ ); +} + + +// ------------------------------------------------------------------------------------------------ + +/** + * @brief + * + * @param white_moves + * @return Move + * + * returing a default constructed move indicates no "mate in one move" could be found + */ +auto +Solver::find_solution( std::vector const& white_moves ) -> Move +{ + // check if a move results in checkmate + for ( auto const& move : white_moves ) + if ( is_checkmate( move ) ) + return move; + + return Move{}; // no solution +} + +auto +Solver::is_checkmate( Move const& white_move ) -> bool +{ + // for each possible white move generate all possible black moves + // if all black moves result in check mate for a specific white move, then return true + + auto const tmp_board = do_move( white_move, board_ ); + + if ( !is_in_check( Side::black, tmp_board ) ) + return false; + + auto const black_moves = generate_moves( Side::black, tmp_board ); + + for ( auto const& black_move : black_moves ) + if ( can_escape( black_move, tmp_board ) ) + return false; // no need to process any more black moves + + return true; +} + + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ + + +auto +generate_moves( Side side, Board const& board ) -> std::vector +{ + auto is_side = ( side == Side::white ) + ? [] ( unsigned char ch ) { return std::isupper( ch ); } // white's pieces are capital case + : [] ( unsigned char ch ) { return std::islower( ch ); }; // black's pieces are lowercase + + auto moves = std::vector(); + + for ( auto row = 0u; row < 8u; ++row ) + { + for ( auto col = 0u; col < 8u; ++col ) + { + if ( is_side( board[row][col] ) ) // is it our piece? + { + auto tmp = generate_moves( { row, col }, board ); + moves.insert( moves.end(), + std::make_move_iterator( tmp.begin() ), + std::make_move_iterator( tmp.end() ) ); + } + } + } + + return moves; +} + +auto +generate_moves( Cell const& cell, Board const& board ) -> std::vector +{ + auto const& [row, col] = cell; + + auto const piece = []( unsigned char ch ) { return ( ch != '.' ) ? std::toupper( ch ) : '.'; }( board[row][col] ); + + // if a pawn is castled add moves for both 'Q' and 'N" + switch ( piece ) + { + case 'P': + return pawn_moves( cell, board ); + case 'N': + return move_generator( cell, board, Knight, 1 ); + case 'B': + return move_generator( cell, board, Diagonal, Unlimited ); + case 'R': + return move_generator( cell, board, Cross, Unlimited ); + case 'Q': + return move_generator( cell, board, All_Moves, Unlimited ); + case 'K': + return move_generator( cell, board, All_Moves, 1 ); + default: + throw std::runtime_error( "Error: Unknown Piece" ); + } + + return {}; +} + +auto +move_generator( Cell const& cell, Board const& board, Directions const& directions, int distance ) -> std::vector +{ + auto moves = std::vector(); + + for ( auto const& [row, col] = cell; auto const& direction : directions ) + { + for ( auto dis = 1; dis <= distance; ++dis ) + { + auto const& [row_dir, col_dir] = direction; + auto const dest = std::make_pair( row + row_dir * dis, col + col_dir * dis ); + auto tmp_move = Move( cell, dest ); + + if ( is_valid( tmp_move, board ) ) + { + moves.push_back( std::move( tmp_move ) ); + + if ( auto const& target = board[dest.first][dest.second]; target != Empty ) + break; // if we took a piece we can't go any farther in that direction ( the opponents piece is blocking us ) + } + else + break; // a piece is in our way or edge of board; + } + } + + return moves; +} + +auto +pawn_moves( Cell const& cell, Board const& board ) -> std::vector +{ + auto const& [row, col] = cell; + auto const& piece = board[row][col]; + auto const isWhite = is_white( piece ); + + // Special Case: Pawn is castled, add moves for both 'Q' and 'N' + if ( is_castled( cell, piece) ) + { + auto queen = move_generator( cell, board, All_Moves, Unlimited ); + auto knight = move_generator( cell, board, Knight, 1 ); + + queen.insert( queen.end(), std::make_move_iterator( knight.begin() ), std::make_move_iterator( knight.end() ) ); + return queen; + } + + // All other cases: + auto moves = std::vector(); + + // pawn going forward + auto const dir = isWhite ? -1 : 1; + + auto tmp_move = Move( cell, std::make_pair( row + dir, col ) ); + if ( is_valid( tmp_move, board ) ) + { + if ( board[row + dir][col] == Empty ) // pawn can only move forward to an empty cell + moves.push_back( std::move( tmp_move ) ); + } + + if ( ( isWhite && row == 6 ) || ( !isWhite && row == 1 ) ) // pawn in intial square, can move two squares + { + tmp_move = Move( cell, std::make_pair( row + 2 * dir, col ) ); + if ( is_valid( tmp_move, board ) ) + if ( board[row + dir][col] == Empty && board[row + 2 * dir][col] == Empty ) + moves.push_back( std::move( tmp_move ) ); + } + + // pawns can move diagonally one square to attack + auto const top_row = row + dir; + auto const top_left_col = col - 1; + auto const top_right_col = col + 1; + auto const& top_left = board[top_row][top_left_col]; + auto const& top_right = board[top_row][top_right_col]; + + if ( top_left != Empty && ( isWhite != is_white( top_left ) ) ) + { + tmp_move = Move( cell, std::make_pair( top_row, top_left_col ) ); + if ( is_valid( tmp_move, board ) ) + moves.push_back( std::move( tmp_move ) ); + } + + if ( top_right != Empty && ( isWhite != is_white( top_right ) ) ) + { + tmp_move = Move( cell, std::make_pair( top_row, top_right_col ) ); + if ( is_valid( tmp_move, board ) ) + moves.push_back( std::move( tmp_move ) ); + } + + return moves; +} + +auto +do_move( Move const& move, Board const& board ) -> Board +{ + auto const& [from_row, from_col] = move.from; + auto const& [to_row, to_col] = move.to; + auto const& piece = board[from_row][from_col]; + + // handle special case of pawn being castled + if ( is_castled( move.from, piece ) ) + return do_castling_move( move, board ); + + // all other moves handled here + auto new_board = board; + + new_board[from_row][from_col] = Empty; // now empty + new_board[to_row][to_col] = piece; + + return new_board; +} + +auto +do_castling_move( Move const& move, Board const& board ) -> Board +{ + auto const& [from_row, from_col] = move.from; + auto const& [to_row, to_col] = move.to; + auto const& piece = board[from_row][from_col]; + auto const isWhite = is_white( piece ); + auto new_board = board; + + new_board[from_row][from_col] = Empty; // now empty + + if ( is_knight_move( move ) ) + new_board[to_row][to_col] = ( isWhite ) ? 'N' : 'n'; + + else // queen move + new_board[to_row][to_col] = ( isWhite ) ? 'Q' : 'q'; + + return new_board; +} + +auto +can_escape( Move const& black, Board const& board ) -> bool +{ + auto const tmp_board = do_move( black, board ); + + // need to check if white king is taken + if ( is_king_taken( black, board, Side::black) ) + return true; // black can escape by taking white's king + + // generate white moves and check if any of them result in a black king killed + auto const white_moves = generate_moves( Side::white, tmp_board ); + + for ( auto const& move : white_moves ) // if any of white's potential next moves can take the black king, black's move + { // doesn't result in his escape + if ( is_king_taken( move, tmp_board, Side::white ) ) + return false; + } + + return true; +} + + +auto +print_board( Board const& board ) -> void +{ + std::cout << '\n' << " 0 1 2 3 4 5 6 7 \n"; + for ( auto row = 0; auto const& line : board ) + { + std::cout << row++ << ' '; + for ( auto const& piece : line ) + std::cout << ' ' << piece << ' '; + + std::cout << '\n'; + } +} + +auto +is_in_check( Side side, Board const& board ) -> bool +{ + auto const opponent = ( side == Side::white ) ? Side::black : Side::white; + auto const moves = generate_moves( opponent, board ); + + for ( auto move : moves ) + { + if ( is_king_taken( move, board, opponent ) ) + return true; + } + + return false; +} +// ------------------------------------------------------------------------------------------------ diff --git a/Development/Mate In One Move/tests/mate_in_one_move.t.cpp b/Development/Mate In One Move/tests/mate_in_one_move.t.cpp new file mode 100644 index 0000000..9b3f338 --- /dev/null +++ b/Development/Mate In One Move/tests/mate_in_one_move.t.cpp @@ -0,0 +1,588 @@ +/** + * @file mate_in_one_move.t.cpp + * @author William Weston + * @brief + * @version 0.1 + * @date 2023-06-12 + * + * @copyright Copyright (c) 2023 + * + */ + + +#include "Solver.h" + + +#include +#include + +#include +#include +#include + + + + +TEST( Tests_MateInOneMove, generate_moves_for_cell ) +{ + + auto board = Board{ "........", + "........", + "........", + "........", + "........", + "........", + "....P...", + "........" + }; + + + auto results = generate_moves( Cell{ 6, 4 }, board ); + + + EXPECT_THAT( results, testing::UnorderedElementsAre( Move{ { 6, 4 }, { 5, 4 } }, + Move{ { 6, 4 }, { 4, 4 } } + ) ); +} + + +TEST( Tests_MateInOneMove, Solver_Sample_Input1 ) +{ + using namespace std::literals; + + auto board = std::string{ + "rn...q.b\n" + "pb..pPkp\n" + ".p......\n" + "..ppN..p\n" + "...P....\n" + "..NB....\n" + "PPPQ.PP.\n" + "..KR....\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "d2g5"s ); +} + +TEST( Tests_MateInOneMove, Solver_Sample_Input2 ) +{ + using namespace std::literals; + + auto board = std::string{ + "..kr...r\n" + "p..n..pp\n" + ".p.Bnp..\n" + "....p...\n" + "........\n" + "........\n" + "PPP..PPP\n" + "...RKB.R\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "f1a6"s ); +} + +TEST( Tests_MateInOneMove, Solver_Sample_Input3 ) +{ + using namespace std::literals; + + auto board = std::string{ + "rnbq..kr\n" + ".p.n..pp\n" + "p...p...\n" + "...pP...\n" + "......Q.\n" + "B.PB....\n" + "P.P..PPP\n" + "R....RK.\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "g4e6"s ); +} + +TEST( Tests_MateInOneMove, Solver_Sample_Input4 ) +{ + using namespace std::literals; + + auto board = std::string{ + "r.bqkb.r\n" + "pp.npppp\n" + ".....n..\n" + ".....N..\n" + "...PN...\n" + "........\n" + "PPPBQPPP\n" + "R...KB.R\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "e4d6"s ); +} + +TEST( Tests_MateInOneMove, Solver_Sample_Input5 ) +{ + using namespace std::literals; + + auto board = std::string{ + "........\n" + ".....p..\n" + "...p....\n" + "b...Q.K.\n" + "k.nq....\n" + "p..NR..r\n" + "..P..P..\n" + "R..Bn...\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "e5e8"s ); +} + +TEST( Tests_MateInOneMove, Solver_Sample_Input6 ) +{ + using namespace std::literals; + + auto board = std::string{ + ".rbq..r.\n" + "p.Pk.K.b\n" + ".P.bnp..\n" + "..n....p\n" + "........\n" + "........\n" + "....p...\n" + "...R....\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "c7b8"s ); +} + +// https://korpalskichess.com/?page_id=2013#1 +TEST( Tests_MateInOneMove, RookAndKing ) +{ + using namespace std::literals; + + auto board = std::string{ + ".k......\n" + "......R.\n" + ".K......\n" + "........\n" + "........\n" + "........\n" + "........\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "g7g8"s ); +} + +// https://korpalskichess.com/?page_id=2025#1 +TEST( Tests_MateInOneMove, TwoRooksAndKing ) +{ + using namespace std::literals; + + auto board = std::string{ + "........\n" + "........\n" + "........\n" + "...K....\n" + ".......k\n" + "........\n" + "......R.\n" + ".....R..\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "f1h1"s ); +} + +// https://korpalskichess.com/?page_id=6954#1 +TEST( Tests_MateInOneMove, KingQueenAndPawns ) +{ + using namespace std::literals; + + auto board = std::string{ + "........\n" + "........\n" + "........\n" + ".p.Q....\n" + "k...K...\n" + "..P.....\n" + "........\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "d5a2"s ); +} + +// https://korpalskichess.com/?page_id=3316#1 +TEST( Tests_MateInOneMove, KingAndPawns ) +{ + using namespace std::literals; + + auto board = std::string{ + "....k...\n" + "....P...\n" + "....KP..\n" + ".......p\n" + "........\n" + "........\n" + "........\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "f6f7"s ); +} + +// https://korpalskichess.com/?page_id=7004#1 +TEST( Tests_MateInOneMove, KingAndFourBishops ) +{ + using namespace std::literals; + + auto board = std::string{ + ".k......\n" + ".....K..\n" + ".....B..\n" + "..BB.B..\n" + "........\n" + "........\n" + "........\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "f6e5"s ); +} + + +// https://korpalskichess.com/?page_id=7006#1 +TEST( Tests_MateInOneMove, KingAndTwoKnights ) +{ + using namespace std::literals; + + auto board = std::string{ + ".......k\n" + "........\n" + ".....NKN\n" + "........\n" + "........\n" + "........\n" + "........\n" + "....q...\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "h6f7"s ); +} + +// https://korpalskichess.com/?page_id=7010#1 +TEST( Tests_MateInOneMove, KingBishopKnight ) +{ + using namespace std::literals; + + auto board = std::string{ + ".......k\n" + "........\n" + "......KN\n" + "..B.....\n" + "........\n" + "........\n" + "........\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "c5d4"s ); +} + +// https://korpalskichess.com/?page_id=3674#1 +TEST( Tests_MateInOneMove, KingQueenRook ) +{ + using namespace std::literals; + + auto board = std::string{ + "........\n" + "........\n" + "...K...k\n" + "......R.\n" + ".....Q..\n" + "........\n" + "........\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "f4h4"s ); +} + +// https://korpalskichess.com/?page_id=6863#1 +TEST( Tests_MateInOneMove, KingTwoQueens ) +{ + using namespace std::literals; + + auto board = std::string{ + "......Q.\n" + "........\n" + "...Q....\n" + "........\n" + "kp......\n" + "........\n" + "..K.....\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "d6a6"s ); +} + + +// https://korpalskichess.com/?page_id=3056#1 +TEST( Tests_MateInOneMove, KingRookBishop ) +{ + using namespace std::literals; + + auto board = std::string{ + "........\n" + "k.B.....\n" + "..K.....\n" + "........\n" + "........\n" + "........\n" + ".......R\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "h2a2"s ); +} + + +// https://korpalskichess.com/?page_id=3041#1 +TEST( Tests_MateInOneMove, KingQueenBishop ) +{ + using namespace std::literals; + + auto board = std::string{ + ".Q......\n" + "..K.....\n" + "........\n" + "........\n" + "k.......\n" + "........\n" + "........\n" + "....B...\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "b8b4"s ); +} + +// https://korpalskichess.com/?page_id=3062#1 +TEST( Tests_MateInOneMove, KingQueenKnight ) +{ + // Failure of this test indicates wrong move was chosen because stalemate was not taken into account e4c3 + // Stalemate is a situation in chess where the player whose turn it is to move is not in check and has no legal move. + using namespace std::literals; + + auto board = std::string{ + "........\n" + ".K......\n" + "........\n" + "........\n" + "....N...\n" + "......Q.\n" + "........\n" + ".....k..\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "g3f2"s ); +} + + + +// https://korpalskichess.com/?page_id=3049#1 +TEST( Tests_MateInOneMove, KingRookKnight ) +{ + using namespace std::literals; + + auto board = std::string{ + "......k.\n" + ".....R..\n" + "....N.K.\n" + "........\n" + "........\n" + "........\n" + "........\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "f7f8"s ); // wrong solution f7h7 +} + + +TEST( Tests_MateInOneMove, NoSolution ) +{ + using namespace std::literals; + + auto board = std::string{ + "........\n" + "........\n" + "........\n" + "........\n" + "........\n" + "........\n" + "........\n" + "........\n" + }; + + auto input_stream = std::istringstream( board ); + auto solver = Solver(); + + solver.load_board( input_stream ); + + auto move = solver.solve(); + auto solution = output_format( move ); + + EXPECT_EQ( solution, "no solution"s ); +} diff --git a/mateinone.cpp b/mateinone.cpp new file mode 100644 index 0000000..c78bef0 --- /dev/null +++ b/mateinone.cpp @@ -0,0 +1,531 @@ +/** + * @file mateinone.cpp + * @author William Weston + * @brief Mate in One Move Problem From Kattis + * @version 0.1 + * @date 2023-06-09 + * + * @copyright Copyright (c) 2023 + * + */ + + +#include +#include // EXIT_SUCCESS +#include +#include +#include +#include +#include +#include + +using Board = std::vector; // [row][col] +using Cell = std::pair; // row by col +using Direction = std::pair; +using Directions = std::vector; + +constexpr auto Empty = '.'; +constexpr auto Unlimited = 16; // max distance a piece can travel + +auto const Cross = Directions{ { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 } }; +auto const Diagonal = Directions{ { 1, 1 }, { -1, 1 }, { 1, -1 }, { -1, -1 } }; +auto const Knight = Directions{ { 1, 2 }, { 1, -2 }, { 2, 1 }, { 2, -1 }, { -1, 2 }, { -1, -2 }, { -2, 1 }, { -2, -1 } }; +auto const All_Moves = Directions{ { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, { 1, 1 }, { -1, 1 }, { 1, -1 }, { -1, -1 } }; + + +struct Move +{ + using Cell = std::pair; // row by col + + Cell from = { -1, -1 }; // returing a default constructed move indicates no "mate in one move" could be found + Cell to = { -1, -1 }; +}; + + auto operator<<( std::ostream&, Move const& ) -> std::ostream&; +constexpr auto operator==( Move const& lhs, Move const& rhs ) noexcept -> bool; + + +class Solver final +{ +public: + auto load_board( std::istream& in ) -> bool; + auto solve() -> Move; + +private: + using Board = std::vector; // [row][col] + + Board board_ = {}; + + auto find_solution( std::vector const& white_moves ) -> Move; + auto is_checkmate( Move const& white ) -> bool; +}; + + +enum class Side +{ + white, + black, +}; + +// ------------------------------------------------------------------------------------------------ + +auto generate_moves( Side side, Board const& board ) -> std::vector; +auto generate_moves( Cell const& cell, Board const& board ) -> std::vector; +auto pawn_moves( Cell const& cell, Board const& board ) -> std::vector; +auto move_generator( Cell const&, Board const&, Directions const&, int ) -> std::vector; +auto do_move( Move const& move, Board const& board ) -> Board; +auto do_castling_move( Move const& move, Board const& board ) -> Board; + +auto can_escape( Move const& black, Board const& board ) -> bool; +auto is_white( unsigned char ch ) noexcept -> bool; +auto is_castled( Cell cell, char piece ) noexcept -> bool; +auto is_pawn( char piece ) noexcept -> bool; +auto is_knight_move( Move const& move ) noexcept -> bool; +auto is_king_taken( Move const&, Board const&, Side ) -> bool; +auto is_valid( Move const& move, Board const& board ) noexcept -> bool; +auto is_in_check( Side side, Board const& board ) -> bool; + +auto output_format( Move const& move ) -> std::string; + +// ------------------------------------------------------------------------------------------------ + +auto main() -> int +{ + auto solver = Solver(); + + while ( solver.load_board( std::cin ) ) + { + auto solution = solver.solve( ); + std::cout << output_format( solution ) << "\n"; + } + + return EXIT_SUCCESS; +} + + +auto +output_format( Move const& move ) -> std::string +{ + if ( move == Move() ) + return "no solution"; + + auto const& from = move.from; + auto const from_file = 'a' + from.second; + auto const from_rank = 8 - from.first; + + auto const& to = move.to; + auto const to_file = 'a' + to.second; + auto const to_rank = 8 - to.first; + + auto ret = std::string(); + + ret.push_back( from_file ); + ret += std::to_string( from_rank ); + ret.push_back( to_file ); + ret += std::to_string( to_rank ); + + return ret; +} + + +// ------------------------------------------------------------------------------------------------ + + +constexpr auto +operator==( Move const& lhs, Move const& rhs ) noexcept -> bool +{ + return lhs.from.first == rhs.from.first && lhs.from.second == rhs.from.second && + lhs.to.first == rhs.to.first && lhs.to.second == rhs.to.second; +} + + +auto +operator<<( std::ostream& out, Move const& mv ) -> std::ostream& +{ + out << '[' << mv.from.first << ',' << mv.from.second << "] -> [" << mv.to.first << ',' << mv.to.second << ']'; + return out; +} + + +auto +is_white( unsigned char ch ) noexcept -> bool +{ + return std::isupper( ch ); +} + +auto +is_pawn( char piece ) noexcept -> bool +{ + return ( piece == 'p' || piece =='P' ); +} + +auto +is_castled( Cell cell, char piece ) noexcept -> bool +{ + if ( !is_pawn( piece ) ) // only pawns can be castled + return false; + + [[maybe_unused]] auto const& [row, dummy] = cell; + auto const isWhite = is_white( piece ); + + return ( ( isWhite && row == 0 ) || ( !isWhite && row == 7 ) ); +} + + +auto +is_knight_move( Move const& move ) noexcept -> bool +{ + auto const& [from_row, from_col] = move.from; + auto const& [to_row, to_col] = move.to; + + for ( auto const& direction : Knight ) + { + auto const& [row_dir, col_dir] = direction; + auto const dest_row = from_row + row_dir; + auto const dest_col = from_col + col_dir; + + return ( ( dest_row == to_row ) && ( dest_col == to_col ) ); + } + + return false; +} + +/** + * @brief + * + * @param move + * @param board + * @param side the side ( black/white ) whose piece the Move represents + * @return true + * @return false + */ +auto +is_king_taken( Move const& move, Board const& board, Side side ) -> bool +{ + auto const& [to_row, to_col] = move.to; + auto const& dest_piece = board[to_row][to_col]; + + switch ( side ) + { + case Side::white: + return ( dest_piece == 'k' ); + case Side::black: + return ( dest_piece == 'K' ); + default: + throw std::runtime_error( "Error: Unknown Side" ); + } +} + +auto +is_valid( Move const& move, Board const& board ) noexcept -> bool +{ + auto const& [from_row, from_col] = move.from; + auto const& [to_row, to_col] = move.to; + + // out of bounds + if ( to_row < 0 || to_row > 7 || to_col < 0 || to_col > 7 ) + return false; + + auto const& piece = board[from_row][from_col]; + auto const& destination_piece = board[to_row][to_col]; + + return ( ( destination_piece == Empty ) || // empty square + ( is_white( piece ) != is_white( destination_piece ) ) ); // the two pieces are not of the same side +} + + + +// --------------------------------------------- Solver ------------------------------------------- + +auto +Solver::load_board( std::istream& in ) -> bool +{ + board_.clear(); // clear the previous board + + // no input validation required + auto line = std::string(); + + for ( auto i = 0; i < 8; ++i ) + { + std::getline( in, line ); + if ( line.size() ) + board_.push_back( line ); + } + + return ( board_.size() == 8 ); // return false when no more valid boards can be loading from the stream +} + +auto +Solver::solve() -> Move +{ + // generate all moves for white + auto const white_moves = generate_moves( Side::white, board_ ); + + return find_solution( white_moves ); +} + + +// ------------------------------------------------------------------------------------------------ + + +auto +Solver::find_solution( std::vector const& white_moves ) -> Move +{ + // check if a move results in checkmate + for ( auto const& move : white_moves ) + if ( is_checkmate( move ) ) + return move; + + return Move{}; // no solution +} + +auto +Solver::is_checkmate( Move const& white_move ) -> bool +{ + // for each possible white move generate all possible black moves + // if all black moves result in check mate for a specific white move, then return true + + auto const tmp_board = do_move( white_move, board_ ); + + if ( !is_in_check( Side::black, tmp_board ) ) + return false; + + auto const black_moves = generate_moves( Side::black, tmp_board ); + + for ( auto const& black_move : black_moves ) + if ( can_escape( black_move, tmp_board ) ) + return false; // no need to process any more black moves + + return true; +} + + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ + + +auto +generate_moves( Side side, Board const& board ) -> std::vector +{ + auto is_side = ( side == Side::white ) + ? [] ( unsigned char ch ) { return std::isupper( ch ); } // white's pieces are capital case + : [] ( unsigned char ch ) { return std::islower( ch ); }; // black's pieces are lowercase + + auto moves = std::vector(); + + for ( auto row = 0u; row < 8u; ++row ) + { + for ( auto col = 0u; col < 8u; ++col ) + { + if ( is_side( board[row][col] ) ) // is it our piece? + { + auto tmp = generate_moves( { row, col }, board ); + moves.insert( moves.end(), + std::make_move_iterator( tmp.begin() ), + std::make_move_iterator( tmp.end() ) ); + } + } + } + + return moves; +} + +auto +generate_moves( Cell const& cell, Board const& board ) -> std::vector +{ + auto const& [row, col] = cell; + + auto const piece = []( unsigned char ch ) { return ( ch != '.' ) ? std::toupper( ch ) : '.'; }( board[row][col] ); + + // if a pawn is castled add moves for both 'Q' and 'N" + switch ( piece ) + { + case 'P': + return pawn_moves( cell, board ); + case 'N': + return move_generator( cell, board, Knight, 1 ); + case 'B': + return move_generator( cell, board, Diagonal, Unlimited ); + case 'R': + return move_generator( cell, board, Cross, Unlimited ); + case 'Q': + return move_generator( cell, board, All_Moves, Unlimited ); + case 'K': + return move_generator( cell, board, All_Moves, 1 ); + default: + throw std::runtime_error( "Error: Unknown Piece" ); + } + + return {}; +} + +auto +move_generator( Cell const& cell, Board const& board, Directions const& directions, int distance ) -> std::vector +{ + auto moves = std::vector(); + + auto const& [row, col] = cell; + for ( auto const& direction : directions ) + { + for ( auto dis = 1; dis <= distance; ++dis ) + { + auto const& [row_dir, col_dir] = direction; + auto const dest = std::make_pair( row + row_dir * dis, col + col_dir * dis ); + auto tmp_move = Move{ cell, dest }; + + if ( is_valid( tmp_move, board ) ) + { + moves.push_back( std::move( tmp_move ) ); + + if ( auto const& target = board[dest.first][dest.second]; target != Empty ) + break; // if we took a piece we can't go any farther in that direction ( the opponents piece is blocking us ) + } + else + break; // a piece is in our way or edge of board; + } + } + + return moves; +} + +auto +pawn_moves( Cell const& cell, Board const& board ) -> std::vector +{ + auto const& [row, col] = cell; + auto const& piece = board[row][col]; + auto const isWhite = is_white( piece ); + + // Special Case: Pawn is castled, add moves for both 'Q' and 'N' + if ( is_castled( cell, piece) ) + { + auto queen = move_generator( cell, board, All_Moves, Unlimited ); + auto knight = move_generator( cell, board, Knight, 1 ); + + queen.insert( queen.end(), std::make_move_iterator( knight.begin() ), std::make_move_iterator( knight.end() ) ); + return queen; + } + + // All other cases: + auto moves = std::vector(); + + // pawn going forward + auto const dir = isWhite ? -1 : 1; + + auto tmp_move = Move{ cell, std::make_pair( row + dir, col ) }; + if ( is_valid( tmp_move, board ) ) + { + if ( board[row + dir][col] == Empty ) // pawn can only move forward to an empty cell + moves.push_back( std::move( tmp_move ) ); + } + + if ( ( isWhite && row == 6 ) || ( !isWhite && row == 1 ) ) // pawn in intial square, can move two squares + { + tmp_move = Move{ cell, std::make_pair( row + 2 * dir, col ) }; + if ( is_valid( tmp_move, board ) ) + if ( board[row + dir][col] == Empty && board[row + 2 * dir][col] == Empty ) + moves.push_back( std::move( tmp_move ) ); + } + + // pawns can move diagonally one square to attack + auto const top_row = row + dir; + auto const top_left_col = col - 1; + auto const top_right_col = col + 1; + auto const& top_left = board[top_row][top_left_col]; + auto const& top_right = board[top_row][top_right_col]; + + if ( top_left != Empty && ( isWhite != is_white( top_left ) ) ) + { + tmp_move = Move{ cell, std::make_pair( top_row, top_left_col ) }; + if ( is_valid( tmp_move, board ) ) + moves.push_back( std::move( tmp_move ) ); + } + + if ( top_right != Empty && ( isWhite != is_white( top_right ) ) ) + { + tmp_move = Move{ cell, std::make_pair( top_row, top_right_col ) }; + if ( is_valid( tmp_move, board ) ) + moves.push_back( std::move( tmp_move ) ); + } + + return moves; +} + +auto +do_move( Move const& move, Board const& board ) -> Board +{ + auto const& [from_row, from_col] = move.from; + auto const& [to_row, to_col] = move.to; + auto const& piece = board[from_row][from_col]; + + // handle special case of pawn being castled + if ( is_castled( move.from, piece ) ) + return do_castling_move( move, board ); + + // all other moves handled here + auto new_board = board; + + new_board[from_row][from_col] = Empty; // now empty + new_board[to_row][to_col] = piece; + + return new_board; +} + +auto +do_castling_move( Move const& move, Board const& board ) -> Board +{ + auto const& [from_row, from_col] = move.from; + auto const& [to_row, to_col] = move.to; + auto const& piece = board[from_row][from_col]; + auto const isWhite = is_white( piece ); + auto new_board = board; + + new_board[from_row][from_col] = Empty; // now empty + + if ( is_knight_move( move ) ) + new_board[to_row][to_col] = ( isWhite ) ? 'N' : 'n'; + + else // queen move + new_board[to_row][to_col] = ( isWhite ) ? 'Q' : 'q'; + + return new_board; +} + +auto +can_escape( Move const& black, Board const& board ) -> bool +{ + auto const tmp_board = do_move( black, board ); + + // need to check if white king is taken + if ( is_king_taken( black, board, Side::black) ) + return true; // black can escape by taking white's king + + // generate white moves and check if any of them result in a black king killed + auto const white_moves = generate_moves( Side::white, tmp_board ); + + for ( auto const& move : white_moves ) // if any of white's potential next moves can take the black king, black's move + { // doesn't result in his escape + if ( is_king_taken( move, tmp_board, Side::white ) ) + return false; + } + + return true; +} + + + +auto +is_in_check( Side side, Board const& board ) -> bool +{ + auto const opponent = ( side == Side::white ) ? Side::black : Side::white; + auto const moves = generate_moves( opponent, board ); + + for ( auto move : moves ) + { + if ( is_king_taken( move, board, opponent ) ) + return true; + } + + return false; +} + +// ------------------------------------------------------------------------------------------------