diff --git a/main.cpp b/main.cpp index 29e395645fc99d45e510c4c884b1e06e7c5becbc..9f9cf3384ce45f561f2f11fd856999dfc67e778f 100644 --- a/main.cpp +++ b/main.cpp @@ -45,6 +45,7 @@ int main() Input::update(); if(bRedraw){ + //Draw the Main Menu text Interface::setCursorPosition({8,2}); Interface::printUnicode(L"Chess++"); Interface::setCursorPosition({7,3}); @@ -53,12 +54,12 @@ int main() Interface::printUnicode(L"Would you like to"); Interface::setCursorPosition({7,8}); Interface::printUnicode(L"enable AI?"); - Interface::setCursorPosition({1,10}); - Interface::printUnicode(L"WARNING: AI may crash"); - Interface::setCursorPosition({5,13}); + Interface::setCursorPosition({5,11}); + //save the current Text Attributes unsigned short textAttribs = Interface::getTextAttribute(); + //if the AI is enabled, show it in the Menu if(AI){ Interface::setTextAttribute(BACKGROUND_GREEN); }else{ @@ -66,10 +67,12 @@ int main() } Interface::printUnicode(L"Enable"); + //reset the Text Attributes to the previous Attribs, for the space inbetween the 2 Options Interface::setTextAttribute(textAttribs); Interface::printUnicode(L" "); + //if the AI is not enabled, show that in the Menu if(!AI){ Interface::setTextAttribute(BACKGROUND_RED); }else{ @@ -77,37 +80,40 @@ int main() } Interface::printUnicode(L"Disable"); + //reset the Text Attributes to the previous Attribs, and print the controls Interface::setTextAttribute(textAttribs); - Interface::setCursorPosition({6,14}); + Interface::setCursorPosition({6,12}); Interface::printUnicode(L"[←A] [D→]"); Interface::setCursorPosition({0,17}); Interface::printUnicode(L"[ESC] Quit\n[Enter] Confirm"); + //dont redraw unless something happened bRedraw = false; } + //get Human Input + //if A or Left was pressed, enable the AI if( Input::isPressed(Input::KeyCode::A) || Input::isPressed(VK_LEFT) ){ AI = true; bRedraw = true; } + + //if D or Right was pressed, disable the AI if( Input::isPressed(Input::KeyCode::D) || Input::isPressed(VK_RIGHT) ){ AI = false; bRedraw = true; } + //if Enter was pressed, confirm and start the game if( Input::isPressed(VK_RETURN) ){ Interface::clearScreen(); -/* if(AI){ - aiRootNode = new Chess::AI::Move_Node(cBoard.getPieces()); - } -*/ bRedraw = true; break; } - //Quit the game + //Quit the game if Esc was pressed or held or anything if(Input::isPressed(VK_ESCAPE) || Input::isHeld(VK_ESCAPE)){ exit(0); } @@ -163,6 +169,7 @@ int main() //do best move if(!cBoard.do_move(cBoard.get(aiMove->m_Position), aiMove->m_Target)){ + //if the AI already stalled a few times, print out a Stalled message if(aiStallCounter == 2){ Interface::setCursorPosition({0,0}); Interface::printUnicode(L"AI STALLED!!"); @@ -173,6 +180,7 @@ int main() aiRootNode = new Chess::AI::Move_Node(cBoard.getPieces()); }else{ + //if the ai didn't stall, reset the stall counter and do the move aiRootNode->do_move(aiMove); aiStallCounter = 0; } diff --git a/src/chess/ai/move_node.cpp b/src/chess/ai/move_node.cpp index e4f3028d44fd8620ae2a8a52a051df2687a81bf8..6bb69348d2618bd4bc1d8440f3fdf247c12a7770 100644 --- a/src/chess/ai/move_node.cpp +++ b/src/chess/ai/move_node.cpp @@ -12,8 +12,10 @@ namespace AI{ //predeclare evaluateTree void evaluateTree(Move_Node* root, bool first = true); + //New Move_Node from State and Parent node Move_Node::Move_Node(const State& state, Move_Node* parent) :m_Parent(parent), m_State(new State(state, state.m_Board.getActivePlayer(), &state.m_Move)){ + //generates the next possible moves in a tree structure generateMoves(); } @@ -23,28 +25,39 @@ namespace AI{ if(m_Children.size() > 0) m_Children.clear(); + //save the best possible state's value double best_eval = 0; + + //iterate over all the pieces on the current board/virtual board for(int piece_index = 0; piece_index < 64; piece_index++){ + + //iterate over all possible places on the board to check if there is a valid move for(int target_index = 0; target_index < 64; target_index++){ + + //create a unique pointer to automatically clear up memory when the current iteration is over std::unique_ptr<Board> virtualBoard = make_unique<Board>(m_State->m_Board.getPieces(),m_State->m_Board.getActivePlayer()); + //create Position objects for the current Piece's place and the Target Place Position from = {(int8_t)(piece_index%8), (int8_t)(piece_index/8)}; Position target(target_index%8, target_index/8); + //get the current piece. can be nullptr when there is no piece Piece* currentPiece = virtualBoard.get()->get(from); + //if there is no piece there will not be a possible move if(!currentPiece) continue; + //if the current piece does not belong to the currently active player, there wont be a move possible if(currentPiece->getTeam() != m_State->m_Board.getActivePlayer()) continue; - + //if there is a move possible from the current piece to the target, then do it on the virtual board if(virtualBoard.get()->do_move(currentPiece, target)){ + //create a Move instance to save the done move. Move* step = new Move(currentPiece, from, target); //generate a new State from the state of the virtual Board -// std::unique_ptr<State> newState = make_unique<State>(virtualBoard->getPieces(), m_State->m_Board.getActivePlayer()); State* newState = new State(virtualBoard->getPieces(), m_State->m_Board.getActivePlayer(), step); //set initial evaluation @@ -63,7 +76,8 @@ namespace AI{ //if the Simulation is not at its deepest part yet, recurse if(m_sSimulationDepth > 0){ - //create a new Move_Node + //create a new Move_Node and push it onto the stack of moves that are possible after the current move. + //This also recursively calls this function, letting the new child generate its child nodes. Move_Node* newNode = new Move_Node(*newState,this); m_Children.push_back(newNode); } @@ -89,20 +103,29 @@ namespace AI{ //if this was the first iteration, then evaluate EVERY child instead of just the best one. //keep the current Evaluation and afterwards sort the Children again if(first){ + //save a copy of the root eval double eval = root->m_State->m_Evaluation; + //for every child of the root, evaluate this function. + //this will lead to the child nodes of the root having the value of the best move + //calculated into their eval. for(Move_Node* child : root->m_Children){ evaluateTree(child,false); } + //let the root keep its original eval. only works for root. children dont do this root->m_State->m_Evaluation = eval; + //sort the child nodes by their evaluated values. std::sort(root->m_Children.begin(), root->m_Children.end(),[](Move_Node* a, Move_Node* b){ return a->m_State->m_Evaluation < b->m_State->m_Evaluation; }); } else { + //if this is not the first element //follow the best children in each branch + //then, from the bottom up, subtract the Evaluated Values from the parent + //because the parents will always be for the oposing player if(root->m_Children.size()>0) evaluateTree(root->m_Children.front(), false); if(root->m_Parent) @@ -110,20 +133,31 @@ namespace AI{ } } + //returns the best possible Move, according to the evaluation const Move* Move_Node::getBestMove() { + //check if there is even children, + //if so then return the best childs move if(m_Children.size()>0) return &m_Children.front()->m_State->m_Move; + //else return an empty move return new Move(nullptr, Position(), Position()); } + //do a move in the AI... + //tries to make the coding a little faster bool Move_Node::do_move(const Move* move){ + //check if the move is somewhere in the children. + //if it is, then set the root node to the child and regenerate it's children for(auto child : m_Children) if(child->m_State->m_Move == *move){ -// child->m_State->setActivePlayer(m_State->m_Board.getActivePlayer()==Color::eWhite?Color::eBlack:Color::eWhite); + //regenerate the Moves for the Child child->generateMoves(); + //set the root node (this) to the child *this = *child; return true; } + //if there was no such move, return false after regenerating all the moves + generateMoves(); return false; } diff --git a/src/chess/ai/state.cpp b/src/chess/ai/state.cpp index 7b72028863540a8640eb7e08fa848cc2be624df8..8998525ffda90ab3e6d57f337a588665199a6166 100644 --- a/src/chess/ai/state.cpp +++ b/src/chess/ai/state.cpp @@ -64,49 +64,45 @@ constexpr std::array<PieceValue,6> PiecePositionFactor = {{{ }}}; State::State(const Board_Data& data, Chess::Color player) - :m_Board(data), m_Move(nullptr, Position(), Position()){ - //set the active Player of the Board - m_Board.m_ActivePlayer = player; - + :m_Board(data, player), m_Move(nullptr, Position(), Position()){ //evaluate the State of the board reevaluateState(); } - State::State(const State& previous, Color player) - :m_Board(previous.m_Board.m_Pieces), m_Move(nullptr, Position(), Position()){ - m_Board.m_ActivePlayer = player; - reevaluateState(); - } - State::State(const State& previous, Color player, const Move* move) - :m_Board(previous.m_Board.m_Pieces), m_Move(*move){ - m_Board.m_ActivePlayer = player; + :m_Board(previous.m_Board.getPieces(),player), m_Move(*move){ + //evaluate the State of the virtual board reevaluateState(); } - State::State(const State& previous) - :m_Board(previous.m_Board.m_Pieces), m_Move(nullptr, Position(), Position()){ - m_Board.m_ActivePlayer = m_Board.getActivePlayer()==Color::eBlack?Color::eWhite:Color::eBlack; - } - void State::reevaluateState(){ //reset evaluation m_Evaluation = 0.0; //iterate over every piece on the board for(auto piece : m_Board.getPieces()){ + + //if there is no piece, continue untill there is a piece if(!piece) continue; + //get the Base Value of the Type of Piece double dBaseValue = PieceTypeValue[piece->getType()]; + + //get the Position Factor for the Piece Type at the Position of the Piece double dPositionFactor; + if(piece->getTeam() == Color::eWhite){ + //if the Piece is White, dont mirror the Factors dPositionFactor = PiecePositionFactor[piece->getType()][piece->indexFromPosition()]; } else { + //if the Piece is Black the factors have to be mirrored, since the black pieces are mirrored to the white ones dPositionFactor = PiecePositionFactor[piece->getType()][piece->getPosition().x + (8 - piece->getPosition().y) * 8]; } + //if the Piece belongs to the currently active Player, add the Evaluation to the overall evaluation, + //else the Position is better for the other Party, so subtract it. if(piece->getTeam() == m_Board.getActivePlayer()) m_Evaluation += dBaseValue + (dBaseValue * dPositionFactor); else @@ -114,8 +110,4 @@ constexpr std::array<PieceValue,6> PiecePositionFactor = {{{ } } - void State::setActivePlayer(Chess::Color player){ - m_Board.m_ActivePlayer = player; - } - }} diff --git a/src/chess/ai/state.h b/src/chess/ai/state.h index b9be9a5d685d849d55e07fbdad72206bc9181f57..9e0cc6a3e914c96b7b1824b39d7b98d9c0cc1f45 100644 --- a/src/chess/ai/state.h +++ b/src/chess/ai/state.h @@ -13,9 +13,7 @@ namespace AI{ struct State{ public: State(const Board_Data& state, Color player = Color::eBlack); - State(const State& previous, Color player); State(const State& previous, Color player, const Move* move); - State(const State& previous); Board m_Board; Move m_Move; @@ -24,7 +22,6 @@ namespace AI{ void reevaluateState(); - void setActivePlayer(Color); }; }} diff --git a/src/chess/board.cpp b/src/chess/board.cpp index 064ef60ca31b9efd5a669388b8bc96f0bbca1a90..4fb6bffb2be5a7157be632308a6ecda677ea1000 100644 --- a/src/chess/board.cpp +++ b/src/chess/board.cpp @@ -209,8 +209,12 @@ namespace Chess { if(piece->m_Identifier == Identifier::ePawn) if((target.y == 7 && piece->getTeam() == Color::eWhite) || (target.y == 0 && piece->getTeam() == Color::eBlack)){ + //if it did, turn it into a queen at that position (by adding a new Queen). add(new Queen(piece->getTeam(), target)); + //and removing the old Pawn set(piece->m_Position, nullptr); + + //set the board so that its the enemys turn now and return true if(m_ActivePlayer == Color::eBlack) m_ActivePlayer = Color::eWhite; else @@ -223,6 +227,7 @@ namespace Chess { set(piece->m_Position, nullptr); piece->m_Position = target; + //set the board so that its the enemys turn now and return true if(m_ActivePlayer == Color::eBlack) m_ActivePlayer = Color::eWhite; else @@ -230,6 +235,8 @@ namespace Chess { return true; } + + //the move was invalid. dont do anything and return false return false; } @@ -238,16 +245,20 @@ namespace Chess { for(Piece* piece:m_Pieces){ //check if the piece exists if(piece){ + //set the enemy Color to be the current color temporarily Color eTeam = piece->getTeam(); + //flip it around if(eTeam == eWhite) eTeam = eBlack; else eTeam = eWhite; + //ckeck if a move on the enemy King is possible. if yes, its a check if(piece->move(m_Kings[eTeam]->m_Position,m_Pieces)) return true; } } + //if no piece can strike the enemy king, its not a check return false; } } diff --git a/src/chess/board.h b/src/chess/board.h index 26d9a5448cf089c8632b1c0b7273e4e38faa058e..51d290b0094e61c2915ee6c1154ea444223f8ca3 100644 --- a/src/chess/board.h +++ b/src/chess/board.h @@ -11,14 +11,8 @@ namespace Chess{ - namespace AI{ - struct State; - } - //Board class tracks the entire game class Board{ - friend AI::State; - //every field on the board Board_Data m_Pieces; @@ -57,7 +51,7 @@ namespace Chess{ Piece* get(Position pos) const; //get all the pieces - inline Board_Data getPieces(){ return m_Pieces; } + inline Board_Data getPieces() const { return m_Pieces; } //get the currently active player inline Color getActivePlayer() const { return m_ActivePlayer; } diff --git a/src/chess/common.h b/src/chess/common.h index e02157a2230577988ba24ccf6afd54b3871800d9..a111109ef7b66839656d495ffbccd2c40d93465d 100644 --- a/src/chess/common.h +++ b/src/chess/common.h @@ -6,12 +6,19 @@ namespace Chess { +#if _MSC_VER && !__INTEL_COMPILER + template<typename T, typename ... Arguments> + std::unique_ptr<T> make_unique(Arguments&& ... args){ + return std::make_unique<T>(std::forward<Arguments>(args)...); + } +#elif defined(__MINGW32__) //Apparently QT Creator does not suport std::make_unique... //So here is an implementation of std::make_unique in the Chess namespace template<typename T, typename ... Arguments> std::unique_ptr<T> make_unique(Arguments&&... args){ return std::unique_ptr<T>(new T(std::forward<Arguments>(args)...)); } +#endif //the Enum corresponding to the different Pieces enum Identifier{ diff --git a/src/chess/pieces/bishop.cpp b/src/chess/pieces/bishop.cpp index adfd20dffb657f298f638991e56854917afa2554..ce6aad10486d4176221fbfd981a0d087ed418dd3 100644 --- a/src/chess/pieces/bishop.cpp +++ b/src/chess/pieces/bishop.cpp @@ -30,6 +30,7 @@ namespace Chess { if(strike && !enemy) return false; + //if the movement is Diagonal, check if the Diagonal Movement is valid and return the answer if(movement.x*movement.x == movement.y*movement.y){ return checkDiagonal(m_Position, pieces, to); } diff --git a/src/chess/pieces/king.cpp b/src/chess/pieces/king.cpp index fcfe6a014666ca13eddd267c7d20261975f60489..0d480c0416ba4d20829034573a8170973181b21c 100644 --- a/src/chess/pieces/king.cpp +++ b/src/chess/pieces/king.cpp @@ -30,8 +30,10 @@ namespace Chess { if(strike && !enemy) return false; + //check if the movement is 1 in one direction and 2 in the other, then return true if((movement.x*movement.x == 1 || movement.x == 0) && (movement.y*movement.y == 1 || movement.y == 0)) return true; + return false; } }