尼姆游戏(英语:Nim),又译为拈,是一种两个人玩的回合制数学战略游戏。游戏者轮流从几排棋子(或者任何道具)中选择一排,再由这一排中取走一个或者多个,依规则不同,拿走最后一个的可能是输家,也有可能是赢家。当指定相应数量时,一堆这样的棋子称作一个尼姆堆。古代就有许多尼姆游戏的变体。最早欧洲有关尼姆游戏的参考资料是在16世纪,目前使用的名称是由哈佛大学的Charles L. Bouton命名,他也在1901年提出了此游戏的完整理论,不过没有说明名称的由来。
尼姆游戏最常见的玩法是拿到最后一个棋子的人输(misère game)。尼姆游戏也可以改为拿到最后一个棋子的人赢(normal play)。大部分类似的游戏都是最后一个棋子的人赢,不过这不是尼姆游戏最常见的玩法。不论哪一种玩法,只要刚好剩下一排的棋子是二个或二个以上(其他排可能没有棋子,或是只有一个),下一个游戏者可以轻易的获胜。下一个游戏者可以将数量最多的这排棋子全部拿走或只留一个。剩下的各排都只有一个棋子。若是misère版本,下一个游戏者下完之后,只要留下奇数排就会胜利,若是normal版本,下一个游戏者下完之后,只要留下偶数排就会胜利。
normal版本的尼姆游戏(也就是尼姆数糸统)是斯普莱格–格隆第定理的基础,其中提到在normal版本中,每一个normal版本的无偏博弈(从任何一个局势出发,双方可以采取完全相同的行动,也就是说棋盘上没有颜色的区分)都等价于一个特定大小的尼姆堆。所有的normal版本的无偏博弈都可以给与尼姆值,但misère版本的就不一定。只有温驯游戏(英语:Genus theory)才能用misère版本尼姆的策略来进行。尼姆游戏是一种特殊的偏序游戏(英语:poset game),其中的偏序关系包括了不交集的全序关系(堆)。三排棋子尼姆游戏的演进图和Ulam-Warburton自动机(英语:Ulam-Warburton automaton)演进图的三个分支相同。
normal版本是由二位游戏者一起玩,有三排棋子,各排的棋子为任意正整数。二位游戏者轮流选一排棋子,拿走上面至少一个棋子,也可以拿同一排的多个棋子。normal版本的目的是要拿到最后一个棋子。misère版本的目的就是要让对方被迫拿走最后一个棋子(拿到最后一个棋子的人输)。
以下是normal版本的游戏,由爱丽丝与鲍伯二个人玩,三排棋子分别有三个、四个及五个棋子。
尼姆游戏的策略就是在拿完棋子后,使棋子个数符合以下任何一个组态,接下来再轮到时,一定可以再拿走适当数量的棋子,使棋子个数仍符合以下任何一个组态。normal版本和misere版本的差别只在最后一两步,前面都相同:
*只在normal版本有效
**只在misere版本有效
其中有出现和,是任何正整数,和可以相同。
尼姆游戏在数学上是已解游戏,针对任意排数及个数的初始状态都已有解法,而且有简单的方式可以判断哪一个游戏者会胜利,并且此游戏者要如何取子才会胜利。
这游戏理论的关键是在各排个数在二进制下XOR位操作的结果,此操作也称为是在GF(2)中的向量加法(在模数2以下的位元加法)。在组合博弈论中常称为是“尼姆和”(nim-sum),以下也使用此一名称。和的尼姆和会写成 ⊕ ,以和一般的和区别 + 。像3, 4, 5的尼姆和计算如下:
另一个等效,比较容易心算的作法,是将三排的个数分别表示为相异二次幂的和,再设法消除成对的次幂,再将最后留下的数字相加即可:
3 = 0 + 2 + 1 = 2 1 A排4 = 4 + 0 + 0 = 4 A排5 = 4 + 0 + 1 = 4 1 A排--------------------------------------------------------------------2 = 2 1和4都消去了, 剩下的是2
在normal版本(拿到最后一个棋子的赢)中,胜利的策略就是在取走棋子后,使尼姆和为0。只要取走棋子前,尼姆和不为0,一定有办法取走部分棋子使尼姆和为0。另一个游戏者无论怎么拿,取走棋子后尼姆和都不会为0。以此策略,只要在取棋子时照策略进行,一定会胜利。要找到要拿走的棋子,可以令X是原来各排棋子数的尼姆和,游戏策略是要分别计算各排棋子数和X的尼姆和,找到尼姆和比该排棋子数少的那一排,接下来就要取走这一排的棋子,使该排棋子数等于尼姆和。以上例中,原来各排棋子数的尼姆和是 = 3 ⊕ 4 ⊕ 5 = 2。A=3、B=4、C=5且X=2,因此得到
因此下一步是取走A排的棋子,使其数量变1(拿走二个棋子)。
有一个特别简单的例子,是只剩二排的情形,其策略是在个数较多的那牌拿走部分棋子,使两者数量相同。接下来对手不论怎么下,都继续使二排的数量相同,最后即可胜利。
若是玩misère版本。前面的策略都一样,只到只剩一排的棋子超过一个(二个或二个以上)时才有不同。此时的策略都是针对超过一个棋子的那排棋子取子,使留下来的每一排都只有一个棋子。接下来玩的人只能从这几排中选一排拿走。取子可能是那排全部取完,或是只剩一个,视游戏版本而定,在玩misère版本(拿到最后一个棋子的输)时,要使留下来的排数是单数(因此对方会拿到最后一个棋子),在玩normal版本游戏时,要使留下来的排数是偶数。(因此自己会拿到最后一个棋子)。
以下是棋子数分别是3, 4, 5个,在misère版本的玩法:
上述的策略可以写成程式,以下就是Python的范例:
import functoolsMISERE = 'misere'NORMAL = 'normal'def nim(heaps, game_type): """Computes next move for Nim, for both game types normal and misere. if there is a winning move: return tuple(heap_index, amount_to_remove) else: return "You will lose :(" - mid-game scenarios are the same for both game types >>> print(nim(, MISERE)) misere You will lose :( >>> print(nim(, NORMAL)) normal You will lose :( >>> print(nim(, MISERE)) misere (2, 1) >>> print(nim(, NORMAL)) normal (2, 1) - endgame scenarios change depending upon game type >>> print(nim(, MISERE)) misere You will lose :( >>> print(nim(, NORMAL)) normal (0, 1) >>> print(nim(, MISERE)) misere (0, 1) >>> print(nim(, NORMAL)) normal You will lose :( >>> print(nim(, MISERE)) misere (1, 5) >>> print(nim(, NORMAL)) normal (1, 4) """ print(game_type, heaps, end=' ') is_misere = game_type == MISERE is_near_endgame = False count_non_0_1 = sum(1 for x in heaps if x > 1) is_near_endgame = (count_non_0_1 <= 1) # nim sum will give the correct end-game move for normal play but # misere requires the last move be forced onto the opponent if is_misere and is_near_endgame: moves_left = sum(1 for x in heaps if x > 0) is_odd = (moves_left % 2 == 1) sizeof_max = max(heaps) index_of_max = heaps.index(sizeof_max) if sizeof_max == 1 and is_odd: return "You will lose :(" # reduce the game to an odd number of 1's return index_of_max, sizeof_max - int(is_odd) nim_sum = functools.reduce(lambda x, y: x ^ y, heaps) if nim_sum == 0: return "You will lose :(" # Calc which move to make for index, heap in enumerate(heaps): target_size = heap ^ nim_sum if target_size < heap: amount_to_remove = heap - target_size return index, amount_to_removeif __name__ == "__main__": import doctest doctest.testmod()