棋子层次结构设计:继承与类型字段

发布于 2024-10-09 13:20:55 字数 501 浏览 10 评论 0原文

我有一个片段的基类

 class piece;

和一个包含派生对象的数组

piece* board[8][8];

优点,通过虚拟函数进行干净的设计。缺点是,如果我必须在板上找到一块或比较一块,我必须恢复到动态铸造(或 typeid)。它很丑陋,并且在发出数百万个请求时可能会消耗性能。

另一方面,如果我创建一个单件类的数组,它有一个用于识别件的类型字段,我就不会遇到这个问题(而且它应该更快),但我必须制作超级丑陋的 switch 语句。我想,由于部件的数量是有限的,而且我不认为自己会做那么多的开关,所以这最终可能是一个更好的选择,你觉得呢?

这是为了好玩(所以没有位板)。

阅读一些答案,我认为仅使用类型字段进行运算符重载(==!=,...)可以带来两个词的最佳效果。

boost::variant 看起来也很有趣。

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).

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

木落 2024-10-16 13:20:55

或者,如果您的类集有限 - 即您知道数量,请使用变体和访问者。例如, boost::variant 而棋盘就是由这种类型的 2D 数组组成的。现在要进行询问,您可以使用访客...

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...

情感失落者 2024-10-16 13:20:55

我会选择类层次结构。

为了查找作品,您可以为每种作品类型保留一个单独的列表。这样您就知道在哪里寻找每种单品类型。

为了进行比较,您也可以依赖虚拟方法。

另一种方法是使用组件架构(如下所述:http://cowboyprogramming .com/2007/01/05/evolve-your-heirachy/),但我认为这对于一个你清楚地知道类型并且知道这些类型不会很快改变的国际象棋游戏来说太多了:)。

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 :).

ぃ双果 2024-10-16 13:20:55

我会使用层次结构,如果我想知道类型(为什么?)有一个标识类型的虚拟方法。

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

决绝 2024-10-16 13:20:55

我从来没有写过国际象棋程序,但我猜最常见的操作是这样的:

  • 显示/打印棋盘
  • 获取每个棋子的可能移动集
  • 总结棋盘所有棋子的值,也许总结一些取决于棋子的“位置值”(车在开放线上,诸如此类)

此外,某些棋子具有“状态”(国王只能在之前没有移动过的情况下才能城堡,兵可以攻击顺便说一下,如果另一个棋子刚刚移动了两个方格),则仅适用于一种棋子。

这一切都让我尖叫着阶级等级制度。 (假设您不需要位板性能)

另一方面,您不太可能需要添加新的块类型,或者您将能够单独重复使用其中一个块类型。即可扩展性和模块化并不是真正的问题。因此,如果您发现算法的某些重要部分实际上应该位于一个位置,但分散在多个片段类中 - 请使用 switch 语句。只需在返回 PieceType 枚举的 Piece 类中添加一个抽象方法并打开它即可。

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.

梦里兽 2024-10-16 13:20:55

您不能同时担心性能和代码的乐趣:)

考虑使用“半字节板”(或至少是字节板)而不是位板,其中每个半字节代表一种类型。每个半字节也是在该块类型上操作的单例对象表中的索引。

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

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
计㈡愣 2024-10-16 13:20:55

“超级难看的 switch 语句”是正确的技术。它并不难看。这就是所谓的函数式编程。

继承是完全错误的技术。每个棋子都以不同的方式移动,具有不同的图形和其他属性。没有什么共同点。棋子并不是抽象的。它们是离散对象的具体集合。

你必须通过统一来使一些东西变得通用:创建所谓的求和类型。
在 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)
| ....

在 C++ 中没有正确的求和类型,您可以使用它来代替:

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

它更笨拙。向 C 和 C++ 委员会投诉。但要使用正确的概念。总和类型(可区分联合、变体)是统一一组离散的具体类型的方式。类和继承用于表示抽象并提供其实现。

国际象棋没有什么抽象的。这都是关于组合的。这不是不同技术的优缺点的问题:而是使用正确的技术的问题。

[顺便说一句:是的,你可以尝试 boost 变体,但我不能推荐它用于此应用程序,因为这些片段没有关联数据,枚举是完美的]

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]

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文