inspired by Brian Bucklew's talk on Roguelike architecture
What Bucklew has done is take all the parameters and methods that we normally enshrine in an interface (thus defining the mini language that client objects use to communicate with their service objects) and he's shoved them all into an event. So now the only thing in the interfaces between objects is the one method that accepts events.
Yet here you are with IsOwnedByPlayerOne()
. That's not an event. But you don't have to follow his method.
Ultimately I'd like to be able to express each behavioural component of Chess as a small, encapsulated component of a chess piece and swap these out at a whim to quickly create pieces with new behaviours.
You can do this even in the old school OOP languages, without doing construction in xml or writing your own class loader. Rather then reach for inheritance every time, instead learn to prefer composition and delegation.
Let me tell you how I made my queen in my chess tournament. She was just a piece like any other. But when it came time to generate her moves she couldn't be bothered with it. She just asked the rook and the bishop what they could do if they were in her place.
I didn't do this by inheriting the Rook or Bishop classes. I composed my queen with them. They were injected into her when she was born. Her knowledge of them was controlled. She didn't even know which was which. She just knew that she had a team of pieces that could decide her move list. She delegated the work to them. All she did was turn their lists into one list.
This meant the Elephant and Hawk Seirawan chess pieces could be added to the game just by injecting the knight.
The issue I've come to is this: a Component is very much a low-level type, but they need awareness of the world around them which can only be achieved through access to higher-level types.
This is a different issue. Conceptually it's a bit hard. It reminds me of relativity. Let me ask you this: do you know where you are? How do you know? If I drugged you and took you and your bed and floated you out into a lake how would you know where you are? Something has to tell you that you're floating in a lake.
That's how I solved this problem. I told the pieces where they were. That way they don't have to remember and they don't have to ask.
To display a piece:
board[rank, file].display(rank, file);
To generate a pieces legal moves:
moves.addAll( board[rank, file].listMoves(board, rank, file) );
This follows a principle that goes by a few names but I like to call it "Tell, don't ask".
The design allowed the using code to deal with pieces polymorphically (the good part of OOP) and functionally. Which meant it could just loop the board, which was a simple 2D array of piece references and some game state. Needed to add a null object piece that displayed nothing, offered no moves, that anyone could capture, and that didn't block movement.
For example, say I send a "IsLegalMove" message to a Rook Piece, asking whether it can legally move from a1 to a6. It would need to check whether there are any other Pieces between a1 and a6, which requires access to the Board. But the Board itself contains the Piece which in turn contains the Component, so conceptually the design is getting a bit loopy.
Don't send "IsLegalMove" messages. Send
newMoveList = board("a2").filterMoves(board, rooksMoveList)
And let a2 prune the illegal moves for you with it's amazing ability to know whether or not it is friend, foe, or blank. You will do this not only for the a2 square but all the squares between a1 and a6. You do this for every move the rook could make. In addition to making the programming easy this ensures a rook surrounded with friendly pieces or board edges isn't eating up time pointlessly.
I could give either Piece or Component access to Board as a field. Or I could add Board as a parameter to IComponent``'s Handle() method. I don't feel great about either of these solutions but can't put my finger on why. Which one would be better aligned with quality software principles? Or are there others you'd recommend?
Feeling good about an approach only comes with time. Doesn't mean it's right. Just that you've gotten comfortable with it. After decades at this I'm very comfortable minimizing what knows about what. If a piece doesn't have to know where it is when I'm not using it, why make it remember? Let the board do that.