2

I am attempting to make a chess game from scratch and made significant progress over the past couple of months. However, I recently got stuck on a problem that I just cannot figure out how to solve.

CODE (UPDATED)

Since the source code is too large to post here, I think it is best to leave a link to my GitHub account where it can be found: https://github.com/lbragile/chessCAMO

DESCRIPTION

Chess

Contains relevant game flags and turn tracking, as well as keeps a stack of the board positions to allow a player to undo moves.

Piece

Contains the piece's:

  • square ([0,63])
  • move information (for castling and initial pawn moves)
  • type
  • color

Piece Type classes

Includes:

  • Pawn
  • Knight
  • Bishop
  • Rook
  • Queen
  • King
  • Empty (for blank squares)

These inherit from the base class (Piece)

QUESTION

With this set up, Chess and Piece are separate classes (one is not derived from the other). However, to keep track of the board positions in a stack, the Chess class uses the member variable:

stack<vector<Piece*>> board_positions;

This does in fact allow me to undo the position and even make moves afterwards. However, after running my algorithm through my test cases, I noticed that it fails the cases involving flags for check, double check, checkmate, and stalemate. When I checked with my GUI, this was confirmed. For example, making a move which causes a check, then "undoing" that move, and repeating the move again. This fails since the check flag of the previous position is not stored.

This leads to the obvious conclusion that I must store the entire Chess object at each move so that I get both the board representation and the corresponding necessary flags. I just cannot figure out how to have a member variable in the class Chess that can store Chess objects in a stack. The main reason is that Chess itself contains Piece objects in it which are polymorphic (virtual functions) and the two classes are separate.

Any suggestions or helpful tips as to how to proceed would be much appreciated.

EDIT #1

I updated the code using the suggestions provided. However, my current problem is that the board flags do not seem to update on the stack. This seems to stem from the ~Chess(). I think it is not actually destroying the object pointers but rather simply popping them. I attempted using std::move() to move the resources in makeMove(int src, int dest, istream &in) from temp_chess in the pushInfo() function of the if statement and deleting the game_info pointer in the destructor, however this doesn't seem to fix the issue.

EDIT #2

See my answer below, I solved the problem using serialization/de-serialization of the Chess object.

lbragile
  • 7,549
  • 3
  • 27
  • 64
  • 3
    You just need to create another class or structure that holds positions and flags together. Then when stack pops positions and flags will roll back. – Slava Jun 29 '20 at 00:59
  • 1
    @Slava Thanks for the quick response! If I understand correctly, lets say I have a GAME class, its member variables will be `stack> board_positions` and `stack> board_flags`. Then the public member functions will manipulate these (rather than in Chess - for `board_positions`)? If so, I am thinking that GAME will also be completely separate from Chess and Piece classes? – lbragile Jun 29 '20 at 01:06
  • You could use the [memento pattern](https://en.wikipedia.org/wiki/Memento_pattern) which means adding a polymorphic object that reverts any actions to a given state (in this case it could be `std::function` for example, which would be a function you can pass a game state to, and it would revert the last action). You could also just have a stack of `Chess` objects. Copying the whole state makes reverting trivial -- you just pop the top state and replace the current one with that. It requires more memory, but this may not be an issue. – cdhowie Jun 29 '20 at 01:27
  • @lbragile -- You should think how to restore the state of the game at any point, not just "undo". If you started with that in mind, doing "undo" would be easier -- just have a stack of saved game states. For example, let's say you want to save the game for later. How do you restore a saved game? – PaulMcKenzie Jun 29 '20 at 01:31
  • @PaulMcKenzie I am afraid I do not see the point you were making, do you mind elaborating? I think being able to undo, allows you to revert to any position. Unless you mean letting the user enter a board state number and reverting to that directly, which could be an option, however I never saw that in online chess sites. – lbragile Jun 29 '20 at 02:03
  • @cdhowie thanks for the suggestion, I will take a look at memento pattern. Regarding making stack, I tried this and it compiles fine, but when I run it through the test cases, it freezes and I had no luck trying to fix this. – lbragile Jun 29 '20 at 02:50
  • Make a board snapshot (including flags) after each move. Revert to previous snapshot when a user wants to undo. I guess that's what @PaulMcKenzie means. – Louis Go Jun 29 '20 at 03:09
  • Note: holding raw pointers might cause memory leak, you might want to use `shared_ptr` . – Louis Go Jun 29 '20 at 03:16
  • @lbragile -- The probable reason why `std::stack` freezes is that `Chess` does not have correct copy-semantics. Where is the user-defined `operator=` for `Chess`? If you wrote a simple `main` program: `int main() { Chess c1(...); Chess c2(...); ... c2 = c1;}`, don't be surprised if things don't work out too well when `main` exits. One of the requirements of placing value types in the STL containers is that the type needs to be copyable without any issues. Obviously your `Chess` class violates the "safely-copyable" requirements. – PaulMcKenzie Jun 29 '20 at 04:14
  • 3
    No, that's a bad idea to have to parallel vectors. I mean `struct board { std::vector positions; std::vector flags; };` and then `std::stack board_stack;` – Slava Jun 29 '20 at 04:18
  • 3
    @lbragile `Chess & operator =(const Chess & object) = default;`. No. You need to actually implement this. You can easily do that by using [copy / swap](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom). In addition, you need to follow the [rule of 3](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). What you did is implement two out of those three functions, and hoped that the lack of a user-defined assignment operator wouldn't trip you up. – PaulMcKenzie Jun 29 '20 at 04:24
  • Thanks everyone, I took each of your suggestions and made edits to my code (see bounty). However, my current problem is that the flags seem to not update at each stack layer as they are always 0. Any suggestions would be much appreciated. – lbragile Jul 01 '20 at 02:41
  • 3
    Bounty or not - if the question is to have any value to future readers you should make a [mcve] and put it in the actual question. – Ted Lyngmo Jul 01 '20 at 10:30
  • @PaulMcKenzie I read over your comments again, and it seems like you are hinting at Serialization/De-serialization of the object. That is, saving it to a file and reading it back at any point according to the number of moves made. Is that correct? If so, I would have to delete the `Chess` object prior to de-serializing it, as I am using dynamically allocated raw pointers, right? – lbragile Jul 02 '20 at 05:06

1 Answers1

2

I managed to solve the problem by serializing/de-serializing the Chess object's private members to a file.

The way I did this was by making a separate folder which stores these files and added a move counting private member to Chess. Then I added the serializing functionality using the overloaded extraction and insertion operators:

chess.h

class Chess
{
public:
    // previous functions
    
    // Accessor/mutator functions for number of moves made
    int getNumMoves() { return num_moves;}
    void setNumMoves(int num_moves) {this->num_moves = num_moves;}        

    // to serialize (write the object to a file)
    friend ostream & operator << (ostream &out, const Chess &chess_object);

    // to de-serialize (read in the object from a file)
    friend istream & operator >> (istream &in, Chess &chess_object);

private:
    // previous members
    
    int num_moves;
};

chess.cpp

// puts each member field on a new line
ostream & operator << (ostream &out, const Chess &chess_object)
{
    for(const auto & elem : chess_object.getBoard())
        out << elem->getPieceType() << endl << elem->getPieceSquare() << endl << elem->getPieceColor() << endl
            << elem->getPieceMoveInfo() << endl << elem->getEnPassantLeft() << endl << elem->getEnPassantRight() << endl;

    out << chess_object.getCheck() << endl << chess_object.getDoubleCheck() << endl << chess_object.getCheckmate() << endl << chess_object.getStalemate() << endl;

    out << chess_object.getTurn() << endl;

    return out;
}


istream & operator >> (istream &in, Chess &chess_object)
{
    // delete the allocated memory and restore new data
    vector<Piece*> board = chess_object.getBoard();
    for(unsigned int i = 0; i < board.size(); i++)
        delete board[i];
    // board.clear();

    string input;
    for(auto & elem : board)
    {
        in >> input;
        switch(input[0] - '0')
        {
            case 0:
                elem = new Pawn(0, PAWN, NEUTRAL);
                break;
            case 1:
                elem = new Knight(0, KNIGHT, NEUTRAL);
                break;
            case 2:
                elem = new Bishop(0, BISHOP, NEUTRAL);
                break;
            case 3:
                elem = new Rook(0, ROOK, NEUTRAL);
                break;
            case 4:
                elem = new Queen(0, QUEEN, NEUTRAL);
                break;
            case 5:
                elem = new King(0, KING, NEUTRAL);
                break;
            default:
                elem = new Empty(0, EMPTY, NEUTRAL);    
        }

        in >> input;
        elem->setPieceSquare(stoi(input));
        in >> input;
        elem->setPieceColor((pieceColor) (input[0] - '0'));
        in >> input;
        elem->setPieceMoveInfo(input == "1");
        in >> input;
        elem->setEnPassantLeft(input == "1");
        in >> input;
        elem->setEnPassantRight(input == "1");
    }

    chess_object.setBoard(board);

    in >> input;
    chess_object.setCheck(input == "1");
    in >> input;
    chess_object.setDoubleCheck(input == "1");
    in >> input;
    chess_object.setCheckmate(input == "1");
    in >> input;
    chess_object.setStalemate(input == "1");

    in >> input;
    chess_object.setTurn((pieceColor) (input[0] - '0'));

    return in;
}

Thank you to all for the help you provided!

lbragile
  • 7,549
  • 3
  • 27
  • 64