完美玩Wythoff的Nim


16

您的目标是为Wythoff的Nim游戏编写一个完美的玩家。

威索夫的尼姆规则

Wythoff的Nim是确定性的两人游戏,有两堆相同的计数器。玩家轮流进行以下操作之一:

  1. 从第一堆中删除一个或多个计数器
  2. 从第二堆中删除一个或多个计数器
  3. 从第一堆和第二堆中删除相同数量的计数器(一个或多个)。

当然,筹码不能为负,但可以为0。无论哪个玩家移除最后一个计数器,整体都会获胜。

对于更注重几何的人,这里是您可以在此applet上玩的等效游戏。单个女王从四分之一无限棋盘的某个正方形开始,该正方形的角在左下角。玩家交替移动皇后,其移动方式类似于国际象棋皇后,但仅限于三个方向:

  1. 剩下
  2. 斜向左下

将女王移到角落的人都将获胜。

将女王的坐标(带有角(0,0))与各个桩的大小相关联,很容易看出两个游戏是相同的。

完美发挥

(如果您熟悉完美比赛和获胜手法的概念,则可以跳过此步骤。)

由于Wythoff的Nim是一种有限的确定性游戏,因此它具有完美玩法的概念。完美的玩家是一种始终会从理论上获胜的位置获胜的策略,这意味着存在一种能够确保获胜的策略的位置。

作为获胜策略,只要将刚移动的玩家始终移动到理论获胜位置即可,因此该玩家不会继续前进。这些获胜职位中的第一个(也称为冷职位)是(0,0), (1,2), (2,1), (3,5), (5,3)。有关为Wythoff的Nim寻找获胜策略的算法的解释以及生成获胜位置的公式,请参阅Wikipedia文章

计划要求

编写程序或函数时,将一个位置作为输入,并以该位置之后的位置形式输出获胜的位置。最少的字节数获胜。

如果不存在获胜举动,即该头寸是理论上的亏损,则您的程序应表明是这样,否则将被没收。

您的程序必须在合理的时间内运行。因此,指数递归游戏树搜索将无法满足要求。如果您想对策略进行预计算和硬编码,那很好。

输入值

一对(i,j)非负数,代表堆大小,每个最多为99。它可以是两个数字,一个元组,一个列表或您喜欢的任何容器。

输出量

移动后打印或输出位置,再次以两个数字或一个容器的形式输出。这必须是合法转移到获胜职位。如果有多个这样的举动,那么任何一个都可以,但是只有一个。

如果没有获胜的举动,则必须在输出中指出。只要不是合法位置,并且对于每一个丢失的输入都是相同的,则任何输出FalseNone,,0或)(-1,-1)都可以。

运行示例

f(5,0)   = (0,0)
f(2,2)   = (1,2)   # Or (2,1) or (0,0) 
f(1,2)   = False
f(13,9)  = (13,8)  # Or (10,6)
f(10,6)  = False
f(5,10)  = (5,3)
f(25,30) = (8,13)    

2
+1,部分是因为我喜欢四分之一无限的想法。
级圣河

定义“合理的时间”。(100,50)的几秒钟是否合理?
John Dvorak 2014年

哦。等待。输入的界限是... 30 ??? 有点低,不是吗?
约翰·德沃夏克

@JanDvorak是的,它可能允许廉价的快捷方式。更改为99-我认为足够了吗?在发布后编辑规格的歉意。
xnor

@PeterTaylor谢谢,固定。
xnor

Answers:


6

Haskell,167165个字符

c p=o p:c(o p:p)
o p=[(a,b)|a<-[0..],b<-[0..a],(a,b)?p==[]]!!0
(a,b)?p=[y|y@(c,d)<-p,c==a||c==b||d==b||a+d==b+c]
f x@(a,b)|a<b=f(b,a)|x?c[]!!0==x=(0,-1)|1>0=x?c[]!!0

该算法效率不高,但对于输入<100的输入,它仍在一秒钟内运行(尽管不是在交互式控制台中)。

说明:

c p=o p:c(o p:p)

给定一组先前的寒冷位置的寒冷位置列表是一个寒冷位置,其后为给出该位置和先前的寒冷位置列表(效率低:此位置生成两次)

o p=[(a,b)|a<-[0..],b<-[0..a],(a,b)?p==[]]!!0

一个寒冷的位置是第一对,因此该对没有可到达的寒冷位置(效率低下:我们应该从前一对去搜索)

(a,b)?p=[y|y@(c,d)<-p,c==a||c==b||d==b||a+d==b+c]

一对中可到达的位置是这样的,使得它们的第一个元素匹配,第二个元素匹配,它们的差异匹配,或者删除之前的较小堆是删除之后的较大堆。

f x@(a,b)|a<b=f(b,a)
         |x?c[]!!0==x=(0,-1)
         |1>0=x?c[]!!0

(主要方法)如果堆顺序错误,请交换它们。否则,如果从该位置可到达的第一个低温位置是该位置本身,则指示失败(理想情况下,将返回Maybe (Int,Int))。否则返回该空头位置(效率低下:一对被查询两次。更糟糕的是,该空头位置列表生成了两次)


似乎“ 因此,指数递归游戏树搜索将不足以满足 ”是乐观的,因为您所描述的听起来完全像那样。
彼得·泰勒

@PeterTaylor这是O(n ^ 4)。每个冷对花费O(n ^ 3)的时间来查找,并且其中有O(n)个。优化生成将使其达到O(n ^ 2)(如果我正确阅读了序列)。指数时间算法会慢得多。我应该运行一些测试吗?
John Dvorak 2014年

没关系,我相信你。
彼得·泰勒

您可以删除x@x@(a,b)?p=...
傲haskeller

不确定如何到达那里。修复,谢谢。
约翰·德沃夏克

5

GolfScript(63 57字节)

{\]zip{~-}%0|$(!*,1=}+1,4*{..,,^[(.@,2/+.2$]+}38*2/?0]0=`

期望来自stdin的输入形式为[a b]0如果输出为亏损形式,则将输出保留在stdout上。在线演示

总览

,4*{..,,^[(.@,2/+.2$]+}38*2/

使用Beatty sequence属性来计算一个寒冷的位置列表(包括[b a]每个寒冷的位置的翻转版本[a b])。

然后?搜索满足以下条件的第一个寒冷位置:

{\]zip{~-}%0|$(!*,1=}+

这基本上检查该位置是从输入位置到达通过计算矢量差,然后验证它要么[0 x][x 0][x x]对于一些x > 0。IMO该测试是巧位:0|$力任何阵列中的那些格式之一到表单[0 x]而映射[0 0][0][a b]其中既不a也不b0一个三元件阵列,和[-x 0][-x -x][-x 0]。然后(!*,1=检查我们是否有[0 x]

最后0]0=`,进行后备情况和格式输出。


4

粘稠度 57 58 61 62

K1.618Jm,s*dKs*d*KKU39M<smf|}TJ}_TJm,-Ghb-Hebt^,0hk2U99 1

在线尝试。

与其他答案非常相似,但该维基百科页面上没有进行其他操作;)不可思议的数字39是值<99

定义一个g可以调用的函数,例如g 30 25。退货[]失败,[(13,8)]成功。

说明

K1.618                            : K=phi (close enough for first 39 values)
      Jm,s*dKs*d*KKU39            : J=cold positions with the form (small,big)
M<s                              1: Define g(G,H) to return this slice: [:1] of the list below 
   mf|}TJ}_TJ                     : map(filter: T or reversed(T) in J, where T is each member of..
             m,-Ghb-Hebt^,0hk2    : [(G H) - (0 x+1),(x+1 0) and (x+1 x+1)]
                              U99 : for each x from 0 - 98

s强制转换为int-保留几个字符/____1rZ39可以U39用一元范围替换为。同样,您可以替换r99)U99
isaacg 2014年

@isaacg谢谢!我完全忘记了U。我应该真正解释一下:P
FryAmTheEggman

@isaacg关于Pyth的想法,我认为@如果第二个参数现在是列表,则可以使执行集交集。自a更改以来,它有点尴尬:P
FryAmTheEggman 2014年

那是个好主意-我已经实现了。我还更改了交集代码,以实现一些以前不可能的技巧,包括采用两个列表列表的交集。
isaacg 2014年

2

Javascript ES6-280字节

缩小的

r=x=>~~(x*1.618);g=(y,x)=>y(x)?g(y,x+1):x;s=A=>A?[A[1],A[0]]:A;f=(a,b)=>j([a,b])||j([a,b],1);j=(A,F)=>l(A,F)||s(l(s(A),F));l=(A,F)=>([a,b]=A,c=(F&&a+b>=r(b)&&(e=g(x=>a+b-2*x-r(b-x),0))?[a-e,b-e]:(e=g(x=>r(a+x)-2*a-x,0))+a<b?[a,a+e]:(e=r(b)-b)<a?[e,b]:0),c&&r(c[1]-c[0])==c[0]?c:0)

展开式

r = x => ~~(x*1.618);
g = (y,x) => y(x) ? g(y,x+1) : x;
s = A =>A ? [A[1],A[0]] : A;
f = (a,b) => j([a,b]) || j([a,b],1);
j = (A,F) => l(A,F) || s(l(s(A),F));
l = (A,F) => (
    [a,b] = A,
    c = (
        F && a+b >= r(b) && (e = g( x => a+b - 2*x - r(b - x), 0 )) ? [a-e,b-e] :
        (e = g( x => r(a+x) - 2*a - x, 0)) + a < b ? [a,a+e] :
        (e = r(b) - b) < a ? [e,b] : 0
    ),
    c && r(c[1] - c[0]) == c[0] ? c : 0
);

尼斯和快速的算法。以O(n)运行,但如果没有字节节省循环,它将以恒定的时间运行。该循环是作为递归增量器实现的,因此脚本最终将因数百或数千个n的递归错误而失败。使用泰勒先生提到的相同的Beatty序列属性,但是它不计算序列,而只是逐步上乘正确的术语。

可以在所有测试输入上正常运行,并且在我测试过的其他输入上都可以正常运行。

要调用的函数是f。如果成功则返回一个数组,如果0放弃则返回一个。


等一下,输出数组可以吗?
约翰·德沃夏克

@JanDvorak:xnor在有效输出列表中列出了一个元组,因此我想到了。也许他可以澄清这个问题。无论如何,这都是微不足道的解决方案。
COTO

一对的数组或单例数组都可以;不是多个获胜举动。
xnor

1

Perl 5-109(带有2个标记)

#!perl -pl
for$a(@v=0..99){for$b(@v){$c=$a;$d=$b;${$:="$a $b"}||
map{$$_||=$:for++$c.$".++$d,"$a $d","$c $b"}@v}}$_=$$_

用法:

$ perl wyt.pl <<<'3 5'

$ perl wyt.pl <<<'4 5'
1 2

只需为每个可能的输入计算解决方案,然后进行一次查找即可。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.