Visualiza un tablero de Nim como un experto

10

Antecedentes

En el juego de Nim , los jugadores alternan quitando "piedras" de las "pilas": en cada turno, un jugador debe quitar entre una y todas las piedras de una sola pila. El objetivo de Nim es tomar la última piedra o, en la variante misere, obligar a tu oponente a hacerlo; sin embargo, resulta que las estrategias son casi idénticas.

Nim hace un divertido juego de bar. Puede usar fósforos o monedas para las "piedras", y las "pilas" generalmente están dispuestas en una línea. A continuación se muestra una configuración clásica con pilas de 1, 3, 5 y 7:

nim con fósforos

Si nunca antes has jugado a Nim, puedes probarlo antes de intentar este desafío. Aquí hay una versión llamada "Perlas antes de los cerdos" .

Estrategia

La estrategia óptima en Nim es lo suficientemente complicada como para que la mayoría de los laicos pierdan constantemente ante un experto, pero simple de describir con aritmética binaria .

Sin embargo, hacer operaciones XOR mentales binarias es difícil, por lo que afortunadamente hay una forma equivalente de visualizar la estrategia correcta que es más fácil de implementar en tiempo real, incluso cuando está borracho.

Solo hay tres pasos:

  1. Agrupe mentalmente las "piedras" en cada línea en subgrupos cuyos tamaños son potencias de 2, comenzando con el tamaño más grande posible: 8, 4, 2 y 1 son suficientes para la mayoría de los juegos.
  2. Intenta hacer coincidir cada grupo con un gemelo en otra línea, para que cada grupo tenga un par.
  3. Si esto no es posible, elimine las "piedras" no emparejadas de una sola línea (esto siempre será posible; consulte el enlace de Wikipedia para saber por qué) para que el paso 2. sea posible.

O, dicho de otra manera: "Quite algunas piedras de una pila de tal manera que si luego agrupa las pilas en poderes de 2, todos los grupos pueden ser emparejados con un grupo en otra pila". Con la advertencia de que no puedes dividir potencias mayores de 2 en unidades más pequeñas, por ejemplo, no puedes agrupar una línea con 8 piedras en dos grupos de 4.

Por ejemplo, así es como visualizarías el tablero de arriba:

fósforos equilibrados

Este tablero está perfectamente equilibrado, por lo que querrás que tu oponente se mueva primero.

El reto

Dada una lista de enteros positivos que representan el tamaño de las "pilas" de Nim, devuelva una visualización en texto plano del tablero de Nim como lo ve un experto.

Lo que constituye una visualización válida se explica mejor con un ejemplo, pero debe:

  1. Asigne un carácter distinto a cada "subgrupo de potencia de 2" y su par (los subgrupos no emparejados no califican), y use ese carácter para representar las "piedras" tanto en el subgrupo como en el par.
  2. Representar cualquier "piedras" no apareados (es decir, los que un experto eliminaría al jugar normales - no misere - Nim) usando un guión: -.

Habrá múltiples formas de lograr una visualización válida, y todas son válidas. Analicemos algunos casos de prueba:

Casos de prueba

Entrada: 1, 3, 5, 7

Salida posible 1:

A
BBA
CCCCD
CCCCBBD

Opcionalmente, puede incluir espacios entre los caracteres, así como líneas en blanco entre las filas:

Salida posible 2:

A

B B A

C C C C D

C C C C B B D

Entrada: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

El orden y la elección de los personajes pueden ser lo que quieras:

Salida posible 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

Los símbolos Unicode también están bien:

Salida posible 2:

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

Entrada: 7

De las reglas se deduce que cualquier "pila única" debe eliminarse por completo.

Salida posible 1:

-------

Salida posible 2:

- - - - - - -

Entrada: 5, 5

Salida posible:

A A A A B
A A A A B

Reglas Adicionales

  • Este es el código de golf con reglas estándar. El código más corto gana.
  • La entrada es flexible y puede tomarse en cualquier forma de lista que le resulte conveniente.
  • La salida también es flexible, como lo ilustran los ejemplos anteriores. Se permitirán variaciones más razonables. Pregunta si no estás seguro de algo.
Jonás
fuente
1
¿Existe un límite para la cantidad de piedras que puede contener cada pila, o para cuántos caracteres distintos serán necesarios para la visualización? (En el caso extremo, ¿qué pasa si, por ejemplo, se necesita más del número de caracteres ASCII imprimibles o más de 255 caracteres distintos?)
Pomo de la puerta
@Doorknob Puede suponer que eso no sucederá. Incluso podría suponer que las letras del alfabeto serán suficientes para cualquier entrada.
Jonás
@ Jonás, ¿sería una salida válida para el segundo caso de prueba? ["H","EE","EEH","CCCC","CCCCI","DDDDFF","DDDDFFI","AAAAAAAA","AAAAAAAA-","----------"]
ngn
1
@ Οurous Creo que la respuesta simple sí. Técnicamente, en AAAABBBBrealidad no es válido, y ABBno lo es, pero hace que la salida sea menos legible, así que creo que hacer que disminuir dentro de una línea sea una regla explícita es lo mejor.
Jonás
1
@ JonathanAllan Sí, estoy confiando en la lógica de que los 3 pasos deben ocurrir simultáneamente. Entonces, si realiza los pasos 1 y 2 pero no puede realizar el paso 3, debe ajustar su solución a los pasos 1 y 2. Lo que puedo ver que es confuso. He añadido tu descripción a continuación.
Jonás

Respuestas:

2

Ruby, 169 164 148 bytes

->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}}

Pruébalo en línea!

Primero, inicializamos

  • el nim-sum con s=eval a*?^(que es más corto que a.reduce:^)
  • la variable c, que almacena el primer carácter único no utilizado
  • Un mapa mque asigna potencias de dos longitudes a los caracteres utilizados para representarlos

Luego, recorriendo cada pila, ejecutamos lo siguiente:

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

Según la estrategia de Wikipedia , si la pila XOR de nim-sum es menor que la pila , debemos eliminar las piedras de esa pila de modo que su longitud se convierta en la pila XOR de nim-sum . Al almacenar la diferencia en la variable z, podemos probar para ver si esta diferencia es positiva, y si es así 1.) imprima tantos guiones, 2.) reste del montón y 3.) establezca la variable nim-sum en cero para evitar más remoción de cálculos.

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

Ahora "recorremos" cada bit y hacemos un seguimiento de sus valores dividiendo repetidamente xpor 2y multiplicando el acumulador npor 2. El ciclo es en realidad una cadena evaluada xveces, que es mucho mayor que las log2(x)veces que es necesario, pero no se hace daño (aparte de la ineficiencia). Para cada bit, ejecutamos lo siguiente si el bit es 1 ( x&1>0):

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

Imprimir un personaje nveces. Si ya imprimimos un grupo no emparejado de tantas piedras, use ese carácter; de lo contrario, use el siguiente carácter no utilizado (avanzando cen el lugar debido a !).

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

Si m[n]existió (es decir, acabamos de completar un par), entonces m[n]se restablece. De lo contrario, acabamos de comenzar un nuevo par, así que establecemos m[n]el carácter que usamos ( *1es una forma corta de hacer una copia c).

Pomo de la puerta
fuente
4

Python 2 , 150 196 206 bytes

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

Pruébalo en línea!

TFeld
fuente
No creo que esto funcione 4, 9, 10.
Neil
@Neil Nice catch, debería arreglarse ahora
TFeld
1
Lo siento, logré engañarlo nuevamente, esta vez con 14, 21, 35.
Neil
También falla para [1, 3, 4, 5], donde debería eliminar todo el segundo montón.
Pomo de la puerta
@Pomo de la puerta, ¿es necesario? Las reglas dicenThere will be multiple ways to achieve a valid visualization, and all are valid
TFeld
1

JavaScript (ES6), 215 bytes

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>

Solo visualiza hasta 36 personajes diferentes. Me alivia que esto funcione 1, 3, 4, 5.

Neil
fuente
Muy agradable. Es divertido jugar con él en tiempo real.
Jonás
1

Limpio , 454 bytes

todavía jugando al golf

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)

Pruébalo en línea!

Define la función $ :: [Int] -> String, toma los tamaños de pila y devuelve una cadena donde -denotan piedras a eliminar, y los grupos están representados por caracteres ASCII ascendentes -. Si se necesitan suficientes grupos, los caracteres finalmente se reintegrarán, y debido a foldrque requiere más de un gigabyte de memoria para ejecutar el segundo caso de prueba.

Versión con sangría de la comprensión gigante:

$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
    ]
]
Οurous
fuente
Por curiosidad, Clean parece similar a Haskell ... ¿cuáles son sus ventajas sobre Haskell?
Jonás
1
@ Jonás Es bastante similar, sí. Fuera de mi cabeza, tiene una manipulación de tipo de nivel inferior, IL / Assembly en línea, y la interoperabilidad con C se logra más fácilmente que con Haskell. Sin embargo, para uso real, debido a la oscuridad y la naturaleza experimental / académica de Clean, recomendaría Haskell (que también tiene más soporte en términos de bibliotecas e información de referencia). Simplemente me gusta que Clean sea todo.
Οurous