関数型プログラミング言語Elixirで簡単な MiniMax 実装を作成しています。多くの完全な知識のあるゲーム(三目並べ、コネクトフォー、チェッカー、チェスなど)があるため、この実装は、これらのゲームのゲームAIを作成するためのフレームワークになる可能性があります。
ただし、私が直面している問題の1つは、ゲームの状態を関数型言語で適切に保存する方法です。これらのゲームは、主に次の操作が頻繁に行われる2次元のゲームボードを扱います。
ほとんどの関数型言語は、多要素データ構造の基本的なビルディングブロックとしてリンクリストとタプルを使用します。しかし、これらは仕事のために非常にひどく作られたようです:
ElixirにはMapデータ構造が組み込まれています(そしてHaskellにはData.Map
)要素へのO(log n)(対数)アクセスを許可します。現在、私はx, y
位置をキーとして表すタプル。
これは「うまくいく」が、このようにマップを乱用するのは間違っていると感じます。理由は正確にはわかりません2次元ゲームボードを関数型プログラミング言語で格納するためのより良い方法を探しています。
ここでは、Map
がまさに正しい基本データ構造です。なぜそれがあなたを不安にするのか分かりません。ルックアップ時間と更新時間が適切で、サイズが動的であり、派生データ構造を簡単に作成できます。例(haskell内):
filterWithKey (\k _ -> (snd k) == column) -- All pieces in given column
filterWithKey (\k _ -> (fst k) == row) -- All pieces in given row
mapKeys (\(x, y) -> (-x, y)) -- Mirror
プログラマが最初に完全な不変性でプログラミングを開始するときに把握することがしばしば難しいもう1つのことは、1つのデータ構造だけに固執する必要がないことです。通常、「真実のソース」として1つのデータ構造を選択しますが、デリバティブのデリバティブでさえ、必要な数だけデリバティブを作成でき、必要な限り同期が維持されることがわかっています。
つまり、最上位レベルでMap
を使用し、次にLists
またはArrays
に切り替えて行を分析し、次にArrays
を使用して別の方法で列にインデックスを付けることができます分析、次にパターン分析用のビットマスク、表示用のStrings
。適切に設計された関数型プログラムは、単一のデータ構造を渡しません。これらは一連のステップであり、1つのデータ構造を取り込み、次のステップに適した新しいデータ構造を生成します。
トップレベルが理解できる形式での移動で反対側に出られる限り、その間のデータをどれだけ再構築するかを気にする必要はありません。それは不変なので、トップレベルで真実のソースへのパスをたどることができます。
私は最近F#でこれを行い、1次元のリストを使用することになりました(F#では、単一リンクリストです)。実際には、O(n)リストインデクサーの速度は、人間が使用できるボードサイズのボトルネックではありません。2D配列などの他のタイプを試してみましたが、最終的には-独自の値の等価性チェックコードを作成するか、ランクとファイルをインデックスに変換して変換します。後者の方が簡単でした。最初に機能させ、後で必要に応じてデータ型を最適化します。重要なほど大きな違いを生む可能性は低いです。
結局、ボードの操作がボードのタイプと操作によって適切にカプセル化されている限り、実装はそれほど重要ではありません。たとえば、ボードを設定するためのテストの例を次に示します。
let pos r f = {Rank = r; File = f} // immutable record type
// or
let pos r f = OnBoard (r, f) // algebraic type
...
let testBoard =
Board.createEmpty ()
|> Board.setPiece p (pos 1 2)
|> ...
このコード(または呼び出しコード)では、ボードがどのように表されているかは問題ではありません。ボードは、その基盤となる構造よりも、その上の操作によって表されます。