Gioca perfettamente a Nyth di Wythoff


16

Il tuo obiettivo è quello di scrivere un giocatore perfetto per il gioco di Wythoff's Nim .

Regole di Wythoff's Nim

Wythoff's Nim è un gioco deterministico per due giocatori giocato con due pile di segnalini identici. I giocatori alternano i turni, in cui svolgono una di queste:

  1. Rimuovi uno o più segnalini dalla prima pila
  2. Rimuovi uno o più segnalini dalla seconda pila
  3. Rimuovi un numero uguale di segnalini (uno o più), sia dalla prima pila che dalla seconda pila.

Ovviamente, le pile non possono diventare negative, ma possono a 0. Qualunque giocatore rimuova l'ultimo segnalino in generale vince la partita.

Per i più geometrici, ecco una formulazione equivalente del gioco che puoi giocare su questa applet . Una sola regina inizia su un quadrato di una scacchiera di un quarto di infinito il cui angolo è in basso a sinistra. I giocatori si alternano muovendo la regina, che si muove come una regina degli scacchi ma limitata a tre direzioni:

  1. Giù
  2. Sinistra
  3. Diagonalmente in basso e a sinistra

Chi muove la regina in un angolo vince.

Associando le coordinate della regina (con l'angolo (0,0)) alle dimensioni delle rispettive pile, è facile vedere che entrambi i giochi sono uguali.

Gioco perfetto

(Puoi saltare questo se hai familiarità con le nozioni di gioco perfetto e mosse vincenti.)

Dato che il Nim di Wythoff è un gioco finito e deterministico, ha una nozione di gioco perfetto . Un giocatore perfetto è una strategia che vincerà sempre da una posizione teoricamente conquistata, ovvero una posizione in cui esiste una strategia che garantisce una vittoria.

Per essere una strategia vincente, è sufficiente spostarsi per spostarsi sempre in una posizione di vincita teorica per il giocatore che si è appena mosso, e quindi il giocatore non va avanti. Le prime posizioni vincenti (chiamate anche posizioni fredde ) sono (0,0), (1,2), (2,1), (3,5), (5,3). Vedi l' articolo di Wikipedia per una spiegazione di un algoritmo per trovare una strategia vincente per il Nim di Wythoff, nonché una formula per generare posizioni vincenti.

Requisiti del programma

Scrivere un programma o una funzione prende una posizione come input ed emette una mossa vincente nella forma della posizione dopo quella mossa. Vince il minor numero di byte.

Se non esiste alcuna mossa vincente, ovvero la posizione è una perdita teorica, il programma dovrebbe indicarlo e rinunciare.

Il programma deve essere eseguito entro un periodo di tempo ragionevole. Pertanto, una ricerca esponenziale ricorsiva dell'albero del gioco non sarà sufficiente. Se vuoi precompilare e codificare una strategia, va bene.

Ingresso

Una coppia (i,j)di numeri non negativi che rappresentano le dimensioni della pila, ciascuno al massimo 99. Possono essere due numeri, una tupla, un elenco o qualunque contenitore preferiate.

Produzione

Stampa o stampa la posizione dopo lo spostamento, sempre come due numeri o un suo contenitore. Questa deve essere una mossa legale verso una posizione vincente. Se ci sono più di queste mosse, ognuna va bene, ma solo una.

Se non ci sono mosse vincenti, è necessario indicarlo nell'output. Qualsiasi output come False, None, 0, o (-1,-1)farà, fintanto che non è una posizione giuridica, ed è lo stesso per ogni ingresso perdente.

L'esempio funziona

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, in parte perché mi piace l'idea di un quarto di infinito.
Level River,

definire "ragionevole lasso di tempo". Diversi secondi per (100,50) sono un periodo di tempo ragionevole?
John Dvorak,

Oh. Aspettare. l'ingresso è limitato da ... 30 ??? È un po 'basso, no?
John Dvorak,

@JanDvorak Hai ragione, potrebbe consentire scorciatoie economiche. Modificato in 99 - penso che sia sufficiente? Ci scusiamo per la modifica delle specifiche dopo la pubblicazione.
xnor

@PeterTaylor Grazie, risolto.
xnor

Risposte:


6

Haskell, 167 165 caratteri

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

L'algoritmo è inefficiente, ma funziona ancora entro un secondo (anche se non nella console interattiva) per input <100.

Spiegazione:

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

L'elenco delle posizioni fredde dato un insieme di posizioni fredde precedenti è una posizione fredda seguita dall'elenco delle posizioni fredde data questa posizione e le precedenti (Inefficienza: questa posizione viene generata due volte)

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

Una posizione fredda è la prima coppia in modo tale che non ci siano posizioni fredde raggiungibili da quella coppia (Inefficienza: dovremmo invece cercare dalla coppia precedente)

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

Le posizioni raggiungibili da una coppia sono quelle in modo tale che i loro primi elementi corrispondano, i loro secondi elementi corrispondano, le loro differenze corrispondano o l'heap più piccolo prima della rimozione è l'heap più grande dopo la rimozione.

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

(metodo principale) Se gli heap sono nell'ordine sbagliato, scambiarli. Altrimenti, se la prima posizione fredda raggiungibile da una posizione è la posizione stessa, indicare un fallimento (idealmente, si dovrebbe Maybe (Int,Int)invece tornare ). Altrimenti restituisce quella posizione fredda (Inefficienza: detta coppia viene cercata due volte. Peggio ancora, l'elenco delle posizioni fredde viene generato due volte)


Sembra che " Quindi, una ricerca esponenziale ricorsiva dell'albero del gioco non basterà " era ottimista, perché ciò che descrivi suona esattamente così.
Peter Taylor,

@PeterTaylor questo è O (n ^ 4). Ogni coppia fredda impiega O (n ^ 3) tempo per trovare e ce ne sono O (n). L'ottimizzazione della generazione la porterebbe su O (n ^ 2) (se leggessi correttamente la sequenza). Un algoritmo a tempo esponenziale sarebbe molto più lento. Devo eseguire alcuni test?
John Dvorak,

Va bene, ti credo.
Peter Taylor,

è possibile rimuovere il x@dax@(a,b)?p=...
haskeller orgogliosi

Non sono sicuro di come ci sia arrivato. Riparando, grazie.
John Dvorak,

5

GolfScript ( 63 57 byte)

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

Si aspetta input da stdin nel modulo [a b]e lascia l'output su stdout in quel modulo o 0se si tratta di una posizione perdente. Demo online

Panoramica

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

calcola un elenco di posizioni fredde (inclusa la versione capovolta [b a]per ciascuna posizione fredda [a b]) usando la proprietà della sequenza Beatty .

Quindi ?cerca la prima posizione fredda soddisfacendo il blocco creato da

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

che fondamentalmente verifica che la posizione è raggiungibile dalla posizione di ingresso calcolando la differenza vettoriale e quindi verificare che è sia [0 x], [x 0]o [x x]per alcuni x > 0. L'IMO che test è il bit più intelligente: 0|$forza qualsiasi array in uno di quei formati nel modulo [0 x]durante la mappatura [0 0]su [0], [a b]dove abè né 0su un array a tre elementi [-x 0]eo [-x -x]su [-x 0]. Quindi (!*,1=controlla che abbiamo [0 x].

Finalmente 0]0=`esegue il caso di fallback e la formattazione per l'output.


4

Pyth 57 58 61 62

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

Provalo online.

Abbastanza simile ad altre risposte, ma quella pagina di Wikipedia non ha dato molto altro da fare;) Il numero magico 39è il numero di posizioni fredde con valori < 99.

Definisce una funzione gche è possibile chiamare come g 30 25. Restituisce []per errore, [(13,8)]in caso di successo.

Spiegazione

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

sviene lanciato su int - salva alcuni personaggi /____1. rZ39può essere sostituito da U39, utilizzando l'intervallo unario. Allo stesso modo, è possibile sostituire r99)con U99.
isaacg,

@isaacg Grazie! Mi sono completamente dimenticato U. Dovrei davvero aggiornare la spiegazione: P
FryAmTheEggman

@isaacg Solo un pensiero su Pyth, penso che potresti far @eseguire intersezione di set se il suo secondo argomento è un elenco ora. È un po 'goffamente escluso da quando è astato cambiato: P
FryAmTheEggman

Questa è una buona idea: l'ho implementata. Ho anche modificato il codice di intersezione per consentire alcuni trucchi che prima non erano possibili, incluso prendere l'intersezione di due elenchi di elenchi.
Isaacg,

2

Javascript ES6 - 280 byte

minified

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)

allargato

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
);

Algoritmo piacevole e veloce. Funziona in O (n), ma verrebbe eseguito a tempo costante se non fosse per un loop salvaspazio. Il ciclo è implementato come un incrementatore ricorsivo, quindi lo script alla fine fallirà con un errore di ricorsione per n tra centinaia o migliaia. Utilizza la stessa proprietà della sequenza Beatty menzionata da Mr. Taylor, ma anziché calcolare la sequenza, passa semplicemente al termine o ai termini corretti.

Funziona correttamente su tutti gli input di test e su molte dozzine oltre a quelle che ho testato.

La funzione da invocare è f. Restituisce una matrice di successo e di 0rinuncia.


Aspetta, l'output di un array è ok?
John Dvorak,

@JanDvorak: xnor ha una tupla elencata nell'elenco di output validi, quindi l'ho immaginato. Forse può chiarire la questione. È una soluzione banale, in ogni caso.
COTO,

Un array o un array singleton della coppia va bene; non lo sono più mosse vincenti.
xnor

1

Perl 5 - 109 (con 2 bandiere)

#!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}}$_=$$_

Uso:

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

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

Calcola semplicemente la soluzione per ogni possibile input, quindi esegue una sola ricerca.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.