How to instantiate abstract child class with parent class

Viewed 103

I'm trying to make a Chess game, and I'm having difficulties with creating the objects. My thought process went something like this:

Create a game board with 64 tiles, the tiles would have their own position and a pointer to a piece, thus I would be able to "easily" load in and unload a piece.

Tile class:

class Tile {
private:
    int x;
    int y;
    Piece* piece;
public:
    Tile(int x, int y) {
        this->x = x;
        this->y = y;
        piece = nullptr;
    }
    void loadTile(Piece piece) {
        this->piece = new Piece(piece); //this gives an error
    }
    void unloadTile() {
        delete this->piece;
    }
};

The individual pieces would then inherit from a parent class (below you can see a dumbed-down version). Inherited pieces would all have the same constructor and they would only differ in the way they calculate the possible moves. This, in my mind, is the best scenario to use a virtual function.

Piece and pawn class:

class Piece {
protected:
    int x;
    int y;
public:
    Piece(int x, int y) {
        this->x = x;
        this->y = y;
    }
    virtual vector<int> returnPossibleMoves() = 0;
};

class Pawn : public Piece {
public:
    using Piece::Piece;
    vector<int> returnPossibleMoves() {
        vector<int> moves;
        moves.push_back(10); //dont think about this too much
        return moves;
    }
};

And here is the problem - the loadTile() function cannot instantiate the piece object because it is abstract.

I can see that my code may not work because I try to instantiate Piece with Pawn, but I don't really know how I would make it work, or what the workaround for this is. Hopefully you will be able to see what I'm trying to go for.

2 Answers

To strictly answer the question: you cannot create instances of abstract classes. That's why new Piece is not allowed. You would have to create an instance of a derived type that is not abstract, such as Pawn, and assign the piece pointer to point to that:

void Tile::loadTile() {
    this->piece = new Pawn; //this is allowed
}

There are clearly some design changes that you'll need to make with this in mind, some of which have been mentioned in the comments on your question.

The Tile don't know which Piece type to instantiate, this is the fundamental problem.

What about something like this? (Disclaim, I just implemented some ideas, the code need probably lot of improvements until to get to sufficient quality)

#include <array>
#include <cassert>
#include <memory>
#include <vector>

using namespace std;

class Piece;
using CPiecePtr = std::shared_ptr<const Piece>;

enum class PieceType
{
    Pawn
};

class Pos
{
    int m_x=0;
    int m_y=0;
public:
    Pos()=default;
    Pos(const Pos&)=default;
    Pos& operator=(const Pos&)=default;
    Pos( int x, int y): m_x(x), m_y(y)
    {
        assert(x>=0 && x<8 && y>=0 && y<8);
    }
    int x() const { return m_x; }
    int y() const { return m_y; }
};

class Move
{
    Pos m_origin;
    Pos m_destination;
public:
    Move()=default;
    Move(const Move&)=default;
    Move& operator=(const Move&)=default;
    Move( const Pos& orig, const Pos& dest): m_origin(orig), m_destination(dest){}
    
    const Pos& getDestination() const { return m_destination; }
    const Pos& getOrigin() const { return m_origin; }
};
using MoveSet = std::vector<Move>;

class Tile 
{
private:
    CPiecePtr m_piece;
public:
    void loadTile(CPiecePtr piece)
    {
        m_piece = piece;
    }
    void unloadTile() 
    {
        m_piece = nullptr;
    }
    
    void setPiece(CPiecePtr piece) // this is more generic than previous two functions
    {
        m_piece = piece;
    }
    
    CPiecePtr getPiece() const 
    {
        return m_piece;
    }
};

class Piece 
{
    PieceType m_type;
public:
    virtual MoveSet returnPossibleMoves(const Pos&) const = 0;
    Piece(): m_type(PieceType::Pawn){}
    
    PieceType getType() const { return m_type; }
};

class Pawn : public Piece 
{
public:
    MoveSet returnPossibleMoves(const Pos& pos) const override
    {
        MoveSet moves;
        moves.push_back(Move(pos, Pos(pos.x(), pos.y()+1)));
        //...
        
        //TODO how to manage special moves? King-rook, replace pawn at end line...
        return moves;
    }
};

class Chess
{
private:
    std::array<std::array<Tile,8>,8> m_board;
    std::vector<CPiecePtr> m_pieces;
public:
    Chess()
    {
        m_pieces.push_back( std::make_shared<const Pawn>());
        //...
        
        setPieceAt(Pos(0,1), m_pieces[0]);
    }
    
    CPiecePtr getPieceAt( const Pos& pos) const
    {
        return m_board[pos.x()][pos.y()].getPiece();
    }
    
    void setPieceAt( const Pos& pos, CPiecePtr piece)
    {
        return m_board[pos.x()][pos.y()].setPiece(piece);
    }
    
    // example:
    MoveSet getMoveSetForPos( const Pos& pos)
    {
        const auto& piecePtr = getPieceAt(pos);
        if (nullptr != piecePtr)
        {
            return piecePtr->returnPossibleMoves(pos);
        }
        return {};
    }
    
    void movePiece( const Move& move)
    {
        const auto& prevPiece = getPieceAt(move.getOrigin());
        const auto& nextPiece = getPieceAt(move.getDestination());
        assert(prevPiece && !nextPiece);
        
        setPieceAt(move.getDestination(), prevPiece);
        setPieceAt(move.getOrigin(), nullptr);
    }
};

int main()
{
    Chess chess;
   
    const auto& moves = chess.getMoveSetForPos(Pos(0,1));
    if (moves.size()>0)
    {
       chess.movePiece(moves[0]);
    }
    
    assert( chess.getPieceAt(Pos(0,2))->getType() == PieceType::Pawn);
    
    return 0;
}

EDIT: I was not very proud of the answer, so I edited the code to make it compile. However, a fully working Chess is more complex than that, I leave how to manage king-rook and other special moves to the reader.

Related