尼姆游戲是一種兩個人玩的回合制數學戰略游戲。游戲者輪流從一堆棋子(一共有好幾堆,一次只能從其中一堆拿。)(或者任何道具)中取走一個或者多個,最后不能再取的就是輸家。當指定相應數量時,一堆這樣的棋子稱作一個尼姆堆。
本文中的尼姆游戲是傳統尼姆游戲的一個變形,即:只有一堆棋子,每次從尼姆堆中拿走的棋子數量不能超過尼姆堆中棋子數量的一半,但至少取走一個,最后不能再取的就是輸家。
首先用Python實現這個游戲:
from random import randint def nimu(n): while n > 1 : # 玩家走 print("\nNow it's your turn, and we have {0} left.".format(n)) # 確保玩家輸入的是合法整數值 while True : try: num = int(input("How many would you to take:")) assert 1<= num <= n//2 break except: print("\nThe number you can take must between 1 and {0}".format(n//2)) n -= num if n == 1: return "You lose!" # 機器人隨意拿走一些 n -= randint(1,n//2) else: return "You win!" print(nimu(randint(1, 1000)))
上述代碼初值由計算機從1-1000中隨機產生。
運行結果如下圖:

下表從數列角度逆推游戲的機理,發現一個機器人必勝模式:

分析數列[2,5,11,23, ……],得出其表達式為(3*2n-2-1),只要玩家出現任意一步尼姆堆中數量為該數值時,就可確保機器人必勝。
進一步對機器人必勝模式進行分析:

在玩家做出選擇后,要根據尼姆堆中剩余元素個數N(3*2**n <= N <= 3*2**(n+1)-2)確定機器人應選擇的值:首先確定其所在范圍的n值,n=math.floor(log2(N/3));爾后,根據n值確定機器人應從尼姆堆中拿走的數量為N-(3*2**n-1)。
利用Python來實現這個必勝機器人,代碼如下:
from math import log2, floor from random import randint def answer_n(x): return floor(log2(x/3)) def dead_num(n): # 定義根據任意元素數確定機器人所需要選擇的數值,以使玩家必輸 if log2((n+1)/3)%1 == 0: # 無法得到使玩家必輸的數值,則在規定范圍內隨機使用一個數值 return randint(1,n//2) else: # 按玩家必輸模式計算出機器人應拿走的元素數量 return n-(3*2**answer_n(n)-1) def nimu(n): while n > 1 : # 玩家走 print("\nNow it's your turn, and we have {0} left.".format(n)) # 確保玩家輸入的是合法整數值 while True : try: num = int(input("How many would you to take:")) assert 1<= num <= n//2 break except: print("\nThe number you can take must between 1 and {0}".format(n//2)) n -= num if n == 1: return "You lose!" # 機器人拿走使玩家進入必輸模式的數值 n -= dead_num(n) else: return "You win!" print(nimu(randint(1, 1000)))
其運行結果如下:
結果一

結果二

從運行結果可以看出,機器人每次都會拿走一定的值使玩家選擇時尼姆堆中的元素數量為其必輸值,所以只要玩家一步錯,就會陷入萬劫不復。
但是也有的玩家在先手的情況下步步為營,不給機器人任何破綻,這樣機器人也不能贏,例如:

當然我們可以給玩家一個時間限定,在有限的時間內,如果尼姆堆中元素數量足夠大,人類玩家難以通過手算或心算方式得出讓機器人必輸的取值,所以機器人的計算速度使得其在比賽中占有極大優勢。
最終,我們用Python實現了一個尼姆游戲“無敵”機器人(之所以打個引號,是因為在你“先手”&“掌握規則”&“能在規定時間內計算出應該拿走的數量”時,你也可以打敗機器人)
PS:初學Python語言,格式或者算法上有什么問題還請大牛指教!
