Sprague-Grundy の定理によれば、公平なゲームの各状態には、 Grundy 数と呼ばれる非負の整数を割り当てることができます。この状態で移動するプレーヤーは、この数が 0 の場合は負け、この数が 0 以外の場合は勝ちます。 .
州のグランディ数がわかっている場合、勝利戦略は常にグランディ数が 0 である州に移動することです。
一般的なゲームのある状態のグランディ数を計算するアルゴリズムは次のとおりです。
if current player can't make a valid move:
Grundy number := 0 (this player has lost)
else:
for each move in this state:
for each sub-game the game splits into after that move:
compute Grundy number of the sub-game
compute XOR of Grundy numbers of the sub-games
Grundy number := MEX of those XORs
MEX
最小除外関数です。MEX
非負の整数のセットの最小の非負の整数に等しく、このセットに属していません。
例えば:
MEX(0) = 1
MEX(0, 1) = 2
MEX(0, 2) = 1
MEX(0, 1, 2) = 3
MEX(0, 1, 3) = 2
MEX(1, 2, 3) = 0
MEX(10, 100, 1000) = 0
このゲームのこのアルゴリズムを Python 3 で単純に実装すると、次のようになります。
import functools
from itertools import count
def mex(s):
for i in count():
if i not in s:
return i
@functools.lru_cache(10000)
def sprague_grundy(n, cur=1):
if cur >= n:
return 0
move_results = {sprague_grundy(n, cur*move) for move in range(2, 9+1)}
return mex(move_results)
for i in count(1):
print(sprague_grundy(i))
多くの場合、グランディ数の一般式を理解する最も簡単な方法は、数列を見て関係に注目することです。このゲームでは、Grundy の数を実際に計算せずに、初期状態でプレイヤー A が勝つゲームのn 個の数を見るだけで、一般式を計算することができます。
しかし、ゲームの初期状態の連続したnの Grundy 数のカウントを引き続き見ることができます(0 は初期状態でプレイヤー A が負けたことを意味し、1,2,3,4 はプレイヤー A が勝ったことを意味します)。
$ python3 sprague_grundy.py | uniq -c
1 0
1 1
2 2
4 3
1 4
9 0
18 1
36 2
72 3
18 4
162 0
324 1
648 2
1296 3
324 4
2916 0
プレイヤー A にとって、負けた初期状態はすべて
![n \in \left( 9\cdot18^k .. 18^{k+1} \right ]: \textup{for} : k=0,1,2,...](https://i.stack.imgur.com/SDkEE.gif)
言い換えれば、プレイヤー A の初期状態は iff に負けています。
