Chess piece hierarchy design: inheritance vs type fields

谁说我不能喝 提交于 2019-12-04 20:53:07

问题


I have a base class for pieces

 class piece;

and an array containing derived objects

piece* board[8][8];

Advantage, clean design through virtual functions. Disadvantage, if I have to find a piece in the board or compare a piece I have to revert to dynamic casting (or typeid). It’s ugly and could be a performance hog when making millions of requests.

In the other hand, if I make an array of a single piece class, that has a type field for identifying pieces, I don’t have this problem (and it should be faster) but I have to make super ugly switch statements. I guess that since the number of pieces is finite and I don’t see myself making that many of switches, this could be in the end a better choice, what do you think?

This is for fun (so no bitboard).

EDIT 1

Reading some answers, I think using type fields only for operator overloading (==,!=...) could bring the best of both words.

The boost::variant looks very interesting too.


回答1:


I would go with the class hierarchy.

For finding a piece you can keep a separeted list for each piece type. So you know where to look for each piece type.

For comparison you can rely on virtual methods too.

Another aproach is to use a component architecture (like described here: http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/), but I think it is too much for a chess game where you clealy know the types and know that those types will not change soon :).




回答2:


Alternatively, if your set of classes is limited - i.e. you know the number, use a variant and visitors. For example, boost::variant<king, queen, bishop, knight ...> And the board is made up of a 2D array of this type. Now to interrogate, you can use visitors...




回答3:


I would go with the hierarchy and if I want to know the type (why?) have a virtual method which identifies the type.




回答4:


I never wrote a chess program, but I'd guess the most common operations would be things like:

  • display/print the board
  • get the set of possible moves for each piece
  • sum up the values of all pieces for a board, maybe sum up some kind of "position value" that depends on the piece (rook on an open line, things like that)

Additionally, some of the pieces have "state" (a king can only castle if it hasn't moved before, a pawn can strike in passing if the other pawn just moved two squares) that only apply to one kind of piece.

That all screams class hierarchy to me. (Assuming you don't need bitboard-performance)

On the other hand, it's unlikely that you will ever have to add new piece types or that you will ever be able to re-use one of the piece types in separation. i.e. extensibility and modularity is not really an issue. So if you find that some important part of your algorithm that should really be in one place is scattered over multiple piece classes - use a switch statement. Just add an abstract method tp the Piece class that returns a PieceType enum and switch on that.




回答5:


You can't worry about performance and code for fun at the same time :)

Consider having "nibbleboard" (or at least byteboard) instead of bitboard, where each nibble represents one piece type. Each nibble is also index in the table of singleton objects that operate on that piece type.

class Empty : public Piece {};
class Rook : public Piece {};
...

const int wrook = 1;
...
const int bpawn = 12;

Piece* Operator[13] = {new Empty(), new Rook(), ..., new Pawn()};

byte table[64] = {
    wrook, wbishop, wknight, wking, wqueen, wknight, wbishop, wrook,
    wpawn, wpawn, wpawn, wpawn, wpawn, wpawn, wpawn, wpawn, 
    0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 
    bpawn, bpawn, bpawn, bpawn, bpawn, bpawn, bpawn, bpawn, 
    brook, bbishop, bknight, bking, bqueen, bknight, bbishop, brook};

// Given some position and some operation DoSomething we would have this:
Operator[table[position]]->DoSomething(table, position, <other parameters>);

// Possible return value of DoSomething might be new table



回答6:


The "super ugly switch statement" is the correct technique. It isn't ugly. It's called functional programming.

Inheritance is completely the wrong technique. Each of the pieces moves in a different way, has a different graphic, and other properties. There's nothing common. Chess pieces are not abstract. They're a concrete collection of discrete objects.

You have to make something common by unification: creating what is called a sum type. In Ocaml:

type shape = Pawn | Rook | Knight | Bishop | Queen | King
type color = Black | White
type piece = shape * color
type pos = { row:int;  col:int }

let check_white_move piece board from to = match piece with
| Pawn -> on_board to && (from.row = 2 && to.row = 4 or to.row = from.row + 1)
| ....

In C++ there is no proper sum type, you can use instead:

enum shape { pawn, rook, knight, bishop, queen, king};
..
bool check_white_move (..) { switch piece {
 case pawn: ...

It's more clumsy. Complain to the C and C++ committees. But use the right concept. Sum types (discriminated unions, variants) are the way to unify a discrete set of concrete types. Classes and inheritance are used for representing abstractions and providing implementations thereof.

There's nothing abstract about chess. It's all about combinations. This is not a question of advantages and disadvantages of different techniques: it's about using the correct technique.

[BTW: yea, you can try boost variant though I can't recommend it for this application since the pieces have no associated data an enum is perfect]



来源:https://stackoverflow.com/questions/4570389/chess-piece-hierarchy-design-inheritance-vs-type-fields

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!