像专家一样可视化Nim板


10

背景

Nim游戏中,玩家交替从“桩”中移除“石头”:每回合,玩家必须从单个桩中移除一个或所有宝石。Nim的目标是夺走最后一块石头,或者在惨败的情况下,迫使对手这样做-但是,事实证明,策略几乎是相同的。

Nim制作了有趣的酒吧游戏。您可以将火柴或硬币用于“石头”,并且通常将“桩”排列成一行。下面是一个经典的设置,其中包含1、3、5和7堆:

尼姆用火柴棍

如果您以前从未玩过Nim,则可以在尝试此挑战之前尝试一下。这是一个称为“猪前珍珠”版本

战略

Nim中的最佳策略非常棘手,以至于大多数非专业人士始终输给专家,但是用二进制算术即可轻松描述。

但是,进行心理二进制XOR操作非常困难,因此幸运的是,有一种等效的方法可以可视化正确的策略,即使在醉酒的情况下,也更容易实时实施。

只有三个步骤:

  1. 将每行中的“石头”分为大小为2的幂的子组,从可能的最大大小开始:8、4、2和1就足以用于大多数游戏。
  2. 尝试使每个组与另一行中的双胞胎匹配,以便每个组都有一对。
  3. 如果无法做到这一点,请从单行中删除未配对的“石头”(这将始终是可能的-请参阅Wikipedia链接以了解原因),从而使步骤2成为可能。

或者说另一种方式:“从单个桩中取出一些石头,这样,如果您将这些桩归为2的幂,则所有组都可以与其他桩中的一组配对。” 需要注意的是,您不能将2的大数分解为较小的小数,例如,您不能将包含8个宝石的线分为4组。

例如,这是您可视化上面的面板的方式:

平衡火柴

该板完全平衡,因此您希望您的对手先移动。

挑战

给定代表Nim“桩”大小的正整数列表,返回专家看到的Nim板纯文本可视化。

通过示例可以最好地解释什么构成有效的可视化,但是您必须:

  1. 给每个“ 2的幂次幂”子组及其对分配一个不同的字符(未配对的子组不合格),并使用该字符表示子组和对中的“石头”。
  2. 代表任何配对的“石头”(即那些当打正常的专家将消除-不MISERE - NIM)使用连字符:-

有多种方法可以实现有效的可视化,并且所有方法都是有效的。让我们研究一些测试用例:

测试用例

输入:1、3、5、7

可能的输出1:

A
BBA
CCCCD
CCCCBBD

您可以选择在字符之间包括空格,在行之间包括空白行:

可能的输出2:

A

B B A

C C C C D

C C C C B B D

输入:1、2、3、4、5、6、7、8、9、10

字符的顺序和选择可以随心所欲:

可能的输出1:

G
E E
E E G
C C C C
C C C C F
B B B B D D
B B B B D D F
H H I - - - - -
A A A A A A A A I
A A A A A A A A H H

Unicode符号也可以:

可能的输出2:

◎
◈  ◈
◈  ◈  ◎
△  △  △  △
△  △  △  △  ◉
◐  ◐  ◐  ◐  ◀  ◀
◐  ◐  ◐  ◐  ◀  ◀  ◉
▽  ▽  ◒  -  -  -  -  -
▥  ▥  ▥  ▥  ▥  ▥  ▥  ▥  ◒ 
▥  ▥  ▥  ▥  ▥  ▥  ▥  ▥  ▽  ▽  

输入7

从规则中得出结论,必须完全去除任何“单堆”。

可能的输出1:

-------

可能的输出2:

- - - - - - -

输入:5、5

可能的输出:

A A A A B
A A A A B

附加规则

  • 这是具有标准规则的代码高尔夫球。最短的代码胜出。
  • 输入是灵活的,并且可以采用您喜欢的任何列表形式进行输入。
  • 如上面的示例所示,输出也很灵活。允许最合理的变化。询问您是否不确定某些事情。

1
每个堆可以容纳多少块石头,或者可视化需要多少个不同的字符?(在极端情况下,例如,如果需要的字符数大于可打印的ASCII字符数,或者需要超过255个不同的字符怎么办?)
Doorknob

@Doorknob您可以假设不会发生。您甚至可以假设字母表中的字母足以满足任何输入要求。
约拿(Jonah)

@Jonah这将是第二个测试用例的有效输出吗?["H","EE","EEH","CCCC","CCCCI","DDDDFF","DDDDFFI","AAAAAAAA","AAAAAAAA-","----------"]
ngn

1
@ouurous我认为简单的答案是。从技术上讲AAAABBBB实际上是无效的,ABB但实际上不是无效的,但是它会使输出的可读性降低,因此我认为最好只在一条直线上减小一条明确的规则。
约拿(Jonah)

1
@JonathanAllan是的,我依靠所有三个步骤必须同时发生的逻辑。因此,如果执行步骤1和2但不能执行步骤3,则必须将解决方案调整为步骤1和2。我认为这很令人困惑。我在下面添加了您的描述。
约拿(Jonah)

Answers:


2

红宝石,169个 164 148字节的

->a{s=eval a*?^
c=?@
m={}
a.map{|x|z=x-(x^s);[$><<?-*z,x-=z,s=0]if z>0
n=1
eval'x&1>0?[$><<(m[n]||c.next!)*n,m[n]=!m[n]&&c*1]:0;n*=2;x/=2;'*x
puts}}

在线尝试!

首先,我们初始化

  • 的nim-sum s=eval a*?^(小于a.reduce:^
  • 变量c,存储第一个未使用的唯一字符
  • 地图m该幂的两个长度映射到字符用于表示它们

然后,遍历每个堆,运行以下命令:

z=x-(x^s);[$><<?-*z,x-=z,s=0]if z>0

维基百科的策略,如果NIM-和XOR堆小于一堆,我们应该从一堆使得它的长度变成结石取出NIM-和XOR桩。通过将差异存储在变量中z,我们可以测试该差异是否为正,如果是,则1.)打印很多破折号,2.)从堆中减去它,并3.)将nim-sum变量设置为零以防止进一步去除结石。

n=1
eval'[...];n*=2;x/=2;'*x

现在,我们“循环”了每一位,并通过反复将跟踪它们的值x2和累加器乘以n通过2。循环实际上是一个字符串计算的x时间,该时间远大于log2(x)所需的时间,但是没有造成任何伤害(除了效率低下)。对于位,如果位是1(x&1>0),则运行以下命令:

$><<(m[n]||c.next!)*n

打印一个字符n时间。如果我们已经打印了这么多石头的未配对组,请使用该字符。否则,请使用下一个未使用的字符(c由于导致就位前进!)。

m[n]=!m[n]&&c*1

如果m[n]存在(即我们刚刚完成了一对配对),则将m[n]其重置。否则,我们将开始一对新配对,因此将m[n]其设置为我们使用的字符(*1是复制的一种简便方法c)。


4

Python 2中150个 196 206字节

def f(p):
 c=48;s=[l*'.'for l in p];m=2**len(bin(l))
 while m:
  if sum(m*'.'in l for l in s)>1:
   for i,l in enumerate(s):s[i]=l.replace('.'*m,chr(c)*m,`s`.count(chr(c)*m)<2)
   c+=1
  else:m/=2
 return s

在线尝试!


我认为这不适合4, 9, 10
尼尔

@Neil不错的收获,应该立即解决
TFeld

1
抱歉,这次我又设法弄糊涂了14, 21, 35
尼尔

对于[1、3、4、5],它也会失败,应该将整个第二个桩移走。
Doorknob

@Doorknob,这是必需的吗?规则说There will be multiple ways to achieve a valid visualization, and all are valid
TFeld

1

JavaScript(ES6),215字节

f=
(a,c=0,x=eval(a.join`^`),i=a.findIndex(e=>(e^x)<e),b=a.map(_=>``),g=e=>(d=e&-e)&&a.map((e,i)=>e&d&&(a[i]-=d,b[i]=(c++>>1).toString(36).repeat(d)+b[i]))&&g(e-d))=>g(eval(a.join`|`),b[i]='-'.repeat(a[i]-(a[i]^=x)))||b
<textarea oninput=o.textContent=/\d/.test(this.value)?f(this.value.match(/\d+/g)).join`\n`:``></textarea><pre id=o>

最多只能显示36个不同的字符。我为此感到宽慰1, 3, 4, 5


非常好。实时玩起来很有趣。
约拿(Jonah)

1

干净,454字节

还在打高尔夫球

import StdEnv,Text,Data.List
$p=join"\n"[{#toChar c+'-'\\c<-e}\\e<-[take i(e++[0,0..])\\e<-r[[~c\\c<-reverse e,_<-[1..c]]\\e<-hd[q\\q<-foldr(\h t=[[a:b]\\a<-h,b<-t])[[]][[c\\c<-subsequences(takeWhile((>=)k)(iterate((*)2)1))|sum c<=k]\\k<-p]|sum[1\\a<-q&b<-p|sum a<>b]<2&&foldr(bitxor)0(flatten q)==0]]1&i<-p]]
r[]_=[]
r[h:t]n|all((<)0)h=[h:r t n]
#[m:_]=removeDup[e\\e<-h|e<0]
#(a,[x:b])=span(all((<>)m))t
=r([[if(e==m)n e\\e<-k]\\k<-[h:a]++[x]]++b)(n+1)

在线尝试!

定义函数$ :: [Int] -> String,获取桩的大小并返回一个字符串,其中-表示要去除的石头,并且组由从升序的ASCII字符表示-。如果需要足够的组,则字符最终将回绕,foldr因此,运行第二个测试用例将需要超过1 GB的内存。

缩进版的巨人理解:

$p=join"\n"[
    {#
        toChar c+'-'
        \\c<-j
    }
    \\j<-[
        take i(e++[0,0..])
        \\e<-r[
            [
                ~c
                \\c<-reverse e
                ,_<-[1..c]
            ]
            \\e<-hd[
                q
                \\q<-foldr(\h t=[
                    [a:b]
                    \\a<-h
                    ,b<-t
                ])[[]][
                    [
                        c
                        \\c<-subsequences(takeWhile((>=)k)(iterate((*)2)1))
                        |sum c<=k
                    ]
                    \\k<-p
                ]
                |sum[
                    1
                    \\a<-q
                    &b<-p
                    |sum a<>b
                ]<2&&foldr(bitxor)0(flatten q)==0
            ]
        ]1
        &i<-p
    ]
]

只是好奇,Clean看起来与haskell类似...与Haskell相比,它的优点是什么?
约拿(Jonah)

1
@Jonah非常相似,是的。比起Haskell,它具有较低级别的类型操纵,内联IL /汇编以及与C的互操作,这些使我无比轻松。但是,由于Clean的晦涩和实验/学术性质,在实际使用中,我建议使用Haskell(在库和参考信息方面也有更多支持)。我只是碰巧喜欢Clean就是全部。
18
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.