私は、自殺チェスと敗者チェスを通常のチェスと一緒にプレイするチェスバリアントエンジンを持っています。時間が経つにつれて、エンジンにさらにバリエーションを追加する可能性があります。エンジンは、OOP を適切に使用して C++ で完全に実装されています。私の質問は、そのようなバリアント エンジンの設計に関するものです。
当初、このプロジェクトは自殺専用のエンジンとして開始されましたが、時間の経過とともに他のフレーバーを追加しました。新しいバリアントを追加するために、まず C++ でポリモーフィズムを使用して実験しました。たとえば、MoveGenerator
抽象クラスには 2 つのサブクラスがSuicideMoveGenerator
ありNormalMoveGenerator
、ユーザーが選択したゲームの種類に応じて、ファクトリが適切なサブクラスをインスタンス化します。しかし、これははるかに遅いことがわかりました。明らかに、仮想関数を含むクラスをインスタンス化することと、タイトなループで仮想関数を呼び出すことはどちらも非常に非効率的だからです。
しかし、コードを最大限に再利用してさまざまなバリアントのロジックを分離するために、テンプレートの特殊化を伴う C++ テンプレートを使用することに気がつきました。ゲームのタイプを選択すると、基本的にゲームの最後までそれを使い続けるため、コンテキストでは動的リンクは実際には必要ないため、これも非常に論理的に思えました。C++ テンプレートの特殊化はまさにこれを提供します - 静的ポリモーフィズム。テンプレート パラメータは、 または または のいずれSUICIDE
かLOSERS
ですNORMAL
。
enum GameType { LOSERS, NORMAL, SUICIDE };
したがって、ユーザーがゲームの種類を選択すると、適切なゲーム オブジェクトがインスタンス化され、そこから呼び出されるすべてのものが適切にテンプレート化されます。たとえば、ユーザーが自殺チェスを選択した場合、次のように言いましょう。
ComputerPlayer<SUICIDE>
オブジェクトはインスタンス化され、そのインスタンス化は基本的に制御フロー全体に静的にリンクされます。の関数はなどでComputerPlayer<SUICIDE>
動作しますがMoveGenerator<SUICIDE>
、Board<SUICIDE>
対応する関数NORMAL
は適切に動作します。
全体として、これにより、最初に適切なテンプレート化特殊クラスをインスタンス化でき、他のif
条件がどこにもないため、全体が完全に機能します。最高のことは、パフォーマンスの低下がまったくないことです。
ただし、このアプローチの主な欠点は、テンプレートを使用するとコードが読みにくくなることです。また、テンプレートの特殊化を適切に処理しないと、重大なバグが発生する可能性があります。
他のバリアント エンジンの作成者は通常、ロジックを分離するために (コードを適切に再利用して) 何をしているのだろうか?? C++ テンプレート プログラミングが非常に適していることがわかりましたが、他にもっと良いものがあれば、喜んで受け入れます。特に、HG Muller 博士による Fairymax エンジンをチェックしましたが、ゲームのルールを定義するために構成ファイルを使用します。私のバリアントの多くは異なる拡張機能を持っており、構成ファイルのレベルまで一般化することでエンジンが強力に成長しない可能性があるため、私はそれをしたくありません。別の人気のあるエンジン Sjeng は、if
いたるところに条件が散らばっており、個人的には良い設計ではないと感じています。
新しいデザインの洞察は非常に役に立ちます。