Conteggio delle creature su una piastrellatura esagonale


18

Questa sfida ti farà contare le "creature" nel gioco a tessere Palago.

Una creatura è qualsiasi forma chiusa che può essere formata da tessere Palago di colore corrispondente in una griglia esagonale.

Il gioco Palago è composto da tessere come questa:

Piastrella Palago

Queste tessere possono essere ruotate di , o per niente e posizionate ovunque su una griglia esagonale. Ad esempio, ecco una creatura (rossa) che richiede 12 tessere.120240Dodici creature tessera.

Sfida

L'obiettivo di questa sfida è quello di scrivere un programma che accetta un numero intero ncome input e calcola il numero di creature (fino a rotazione e riflessione) che richiedono ntessere. Il programma dovrebbe essere in grado di gestire fino a n=10su TIO . Questo è , quindi vince meno byte.

Dati di esempio

I valori devono corrispondere ai dati trovati nella sezione "Conteggi e stime delle creature" del sito web del creatore . cioè

 n | output
---+-------
 1 | 0
 2 | 0
 3 | 1 
 4 | 0
 5 | 1
 6 | 1
 7 | 2
 8 | 2
 9 | 9
10 | 13
11 | 37
12 | 81

"Il programma dovrebbe essere in grado di gestire fino a n=10TIO." - se si tratta di un requisito di velocità di esecuzione, utilizzare code-challenge anziché code-golf , quest'ultimo si riferisce a un'attività di ottimizzazione dei byte puri.
Jonathan Frech,

10
Sulla base della discussione qui , sembra che sia giusto avere un requisito di velocità di esecuzione in una domanda di code-golf , purché il punteggio sia il numero di byte.
Peter Kagey,

2
+1 Proprio come una sequenza a spirale , questa sfida è facile da capire e davvero interessante da risolvere ... ma richiede un bel po 'di codice. : p
Arnauld,

1
Quindi .... stiamo solo prendendo un input e restituendo l'output dall'elenco sopra, per n tra 1 e 10? Posso semplicemente usare una tabella di ricerca?
BradC,

6
@BradC Questa è una scappatoia predefinita . L'OP ha appena affermato che le soluzioni dovrebbero essere in grado di gestire fino a su TIO . n=10
Arnauld,

Risposte:


5

JavaScript (Node.js) , 480 417 byte

-63 byte grazie a @Arnauld. Wow.

n=>(E=(x,y,d,k,h)=>V[k=[x+=1-(d%=3),y+=~d%3+1,d]]?0:(V[k]=1,h=H.find(h=>h[0]==x&h[1]==y))?(d^(t=2-h[2])?E(x,y,t)||E(x,y,h[2]*2):E(x,y,t+2)):[x,y,0],I=c=>c.map(([x,y,t])=>[x-g(0),y-g(1),t],g=p=>Math.min(...c.map(h=>h[p]))).sort(),S=e=>(V={},e=E(0,0,0))?(--n&&H.pop(H.push(e),S(),S(e[2]=1),S(e[2]=2)),++n):n-1||E[I(c=H)]||[0,0,0,++N,0,0].map(r=>E[I(c=c.map(([x,y,t])=>[-x-y,r?y:x,(r?t*2:t+1)%3]))]=1))(H=[[N=0,0,1]])&&N

Provalo online!

In primo luogo, rispetta Arnauld la cui risposta mi ha dato l'ispirazione per scavare più a fondo. Ho cercato di essere originale con i miei algoritmi, anche se ho modificato intenzionalmente parte del mio codice per utilizzare le stesse variabili di Arnauld in modo che il codice potesse essere confrontato più facilmente.

Alla ricerca di esagoni vuoti

La ricerca di creature è:

  • Inizializza l'elenco di tessere con la tessera 1 a 0,0
  • ricorsivamente:
    • Cerca un esagono vuoto necessario per completare la creatura
    • Se trovato esagono vuoto
      • Aggiungi ogni tipo di tessera 0,1,2 all'esagono vuoto e ricomincia
    • Se esagono vuoto non trovato
      • Se la creatura ha le dimensioni corrette e non è già nello zoo
        • Incrementa il numero di creature distinte trovate da una
        • Aggiungi tutte le rotazioni e i riflessi della creatura allo zoo

La ricerca di esagoni vuoti ha scoperto un'interessante simmetria. Arnauld scoprì che una delle sei direzioni poteva essere ignorata, ma in realtà tre su sei possono essere ignorate!

Ecco la direzione originale di Arnauld e il tasto piastrella:

Direzione di Arnauld e chiave delle tessere

Immagina di iniziare dalla tessera A di tipo 1 nel punto blu. Sembra che dobbiamo ricorrere in d = 0 e d = 5. Comunque, qualunque tessera sia posizionata in d = 0, avrà sicuramente un'uscita in d = 4, che visiterà lo stesso esagono che esce dalla tessera A in d = 5. Questa è la scoperta di Arnauld, ed è ciò che mi ha fatto pensare.

Notare che:

  • Ogni riquadro che ha un'uscita in d = 0 ha un'uscita in d = 5
  • Ogni tessera che ha un'uscita in d = 2 ha un'uscita in d = 1
  • Ogni tessera che ha un'uscita in d = 4 ha un'uscita in d = 3

  • Ogni riquadro che può essere inserito da d = 0 ha un'uscita in d = 4

  • Ogni riquadro che può essere inserito da d = 2 ha un'uscita in d = 0
  • Ogni riquadro che può essere inserito da d = 4 ha un'uscita in d = 2

Ciò significa che dobbiamo solo considerare le direzioni 0,2,4. Qualsiasi uscita nelle direzioni 1,3,5 può essere ignorata perché gli esagoni raggiungibili nelle direzioni 1,3,5 possono invece essere raggiunti da un esagono adiacente usando le direzioni 0,2 o 4.

Quant'è fico!?

Indicazioni rietichettate

Quindi ho rietichettato le direzioni e le tessere in questo modo (immagine di Arnauld modificata):

Indicazioni semplificate

Ora abbiamo la seguente relazione tra tessere, voci ed uscite:

    |  t=0  |  t=1  |  t=2
----+-------+-------+-------
d=0 |  0,2  |  1,2  |    2
d=1 |  0,2  |    0  |  0,1
d=2 |    1  |  1,2  |  0,1

Quindi le uscite sono: d + t == 2? (4-t)% 3: 2-t e 2 * t% 3

Rotazioni e riflessioni esagonali

Per rotazioni e riflessioni, ho deciso di provare le coordinate assiali esagonali x, y invece delle coordinate del cubo x, y, z.

-1,2   0,2   1,2   2,2
    0,1   1,1   2,1
 0,0   1,0   2,0   3,0

In questo sistema, la rotazione e la riflessione erano più semplici di quanto mi aspettassi:

120 Rotation:   x=-x-y   y=x   t=(t+1)%3
Reflection:     x=-x-y   y=y   t=(t*2)%3

Per ottenere tutte le combinazioni che ho eseguito: marciume, marciume, marciume, riflesso, marciume, marciume

Codice (480 byte originale)

f=n=>(
    // H:list of filled hexes [x,y,tile] during search for a complete creature
    // N:number of distinct creatures of size n
    // B:record of all orientations of all creatures already found
    H=[[0,0,1]],N=0,B={},

// E: find an empty hex required to complete creature starting in direction d from x,y
    E=(x,y,d,k,h)=>(
        x+=1-d,
        y+=1-(d+1)%3,
        // V: list of visited hexes during this search in E
        V[k=[x,y,d]] ? 
            0
        : (V[k]=1, h=H.find(h=>h[0]==x&&h[1]==y)) ? 
            // this hex is filled, so continue search in 1 or 2 directions
            (d==2-h[2] ? E(x,y,(4-h[2])%3) : (E(x,y,2-h[2]) || E(x,y,h[2]*2%3))) 
        : [x,y,0] // return the empty hex 
    ),

    // I: construct unique identifier for creature c by moving it so x>=0 and y>=0
    I=c=>(
        M=[0,1].map(p=>Math.min(...c.map(h=>h[p]))),
        c.map(([x,y,t])=>[x-M[0],y-M[1],t]).sort()
    ),

    // A: add complete creature c to B
    A=c=>{
        n==1&&!B[I(c)]&&(
            // creature is correct size and is not already in B
            N++,
            [0,0,0,1,0,0].map(
                // Add all rotations and reflections of creature into B
                // '0' marks a rotation, '1' marks a (vertical) reflection
                // rotation:   x=-x-y   y=x   t=(t+1)%3
                // reflection: x=-x-y   y=y   t=(t*2)%3
                r=>B[I(c=c.map(([x,y,t])=>[-x-y,r?y:x,(r?t*2:t+1)%3]))]=1)          
        )
    },

    // S: recursively search for complete creatures starting with hexes H
    S=e=>{
        V={};
        (e=E(0,0,0)) ?
            // e is a required empty hex, so try filling it with tiles 0,1,2
            (--n && (H.push(e),S(),S(e[2]=1),S(e[2]=2),H.pop()), ++n)
        : A(H) // creature is complete, so add it to B
    },

    S(),
    N
)

Codice (Arnauld 417 byte)

Arnauld ha gentilmente presentato un salvataggio di 63 byte che ha usato trucchi che mi hanno impiegato un po 'di tempo per avvolgermi la testa. Dal momento che ha molte modifiche interessanti, ho pensato di mettere il suo codice di seguito (ho aggiunto i miei commenti) in modo che possa essere contrastato con la mia versione.

f=n=>(
    // E:find an empty hex required to complete creature starting in direction d from x,y
    E=(x,y,d,k,h)=>
      V[k=[x+=1-(d%=3),y+=~d%3+1,d]] ?
        0
      :(V[k]=1,h=H.find(h=>h[0]==x&h[1]==y)) ?
        (d^(t=2-h[2]) ? E(x,y,t) || E(x,y,h[2]*2) : E(x,y,t+2))
      :[x,y,0],

    // I: construct unique identifier for creature c by moving it so x>=0 and y>=0
    I=c=>c.map(([x,y,t])=>[x-g(0),y-g(1),t],g=p=>Math.min(...c.map(h=>h[p]))).sort(),

    // S: recursively search for complete creatures starting with hexes H
    S=e=>
      (V={},e=E(0,0,0)) ?
        (--n&&H.pop(H.push(e),S(),S(e[2]=1),S(e[2]=2)),++n)
      :n-1
        ||E[I(c=H)] 
        // creature is the correct size and has not been seen before
        // so record all rotations and reflections of creature in E[]
        ||[0,0,0,++N,0,0].map(r=>E[I(c=c.map(([x,y,t])=>[-x-y,r?y:x,(r?t*2:t+1)%3]))]=1)
)
// This wonderfully confusing syntax initializes globals and calls S()
(H=[[N=0,0,1]]) && N

Bella visione delle indicazioni! E penso che questo possa essere giocato al di sotto delle dimensioni della mia risposta.
Arnauld,


@Arnauld È fantastico! Ho una grande giornata di lavoro davanti a me adesso, ma non vedo l'ora di dare un'occhiata domani. Grazie.
John Rees,

20

JavaScript (Node.js) ,  578 ... 433  431 byte

f=(n,T=[B=[N=0,0,0,1,1]])=>!n||T.some(([x,y,q,m])=>B.some((p,d)=>m>>d&1&&((p=x+~-s[d],q=y+~-s[d+2],t=T.find(([X,Y])=>X==p&Y==q))?(q=t[3])&(p=D[d*3+t[4]])^p?t[f(n,T,t[3]|=p),3]=q:0:[0,1,2].map(t=>f(n-1,[...T,[p,q,-p-q,D[d*3+t],t]])))),s="2100122",D=Buffer("160).(9!'8 &$<%"))|n>1||[0,1,2,1,2,0].some((_,d,A)=>B[k=T.map(a=>[(h=n=>Math.min(...T.map(R=a=>a[A[(d+n)%6]]))-R(a))(0),h(3),(x=(a[4]+d*2)%3,d>2)*x?3-x:x]).sort()])?N:B[k]=++N

n=1n=13

Come?

Indicazioni e tessere

Usiamo i seguenti codici per la bussola a 6 direzioni e le piastrelle:

indicazioni e tessere

Partiamo dal presupposto che la creatura è blu.

Connessioni

Abbiamo bisogno di una tabella per sapere quali parti della creatura devono essere collegate ad altre tessere quando entriamo in una determinata tessera in una data direzione:

     |  T=0  |  T=1  |  T=2
-----+-------+-------+-------
 d=0 | 0,4,5 | 1,2,4 |   4
 d=1 | 0,3,5 | 1,2,3 |   3
 d=2 | 0,3,4 |   0   | 0,1,2
 d=3 | 3,4,5 |   5   | 1,2,5
 d=4 |   2   | 2,3,4 | 0,2,5
 d=5 |   1   | 1,3,4 | 0,1,5

Esempio:

15134

connessioni

5

     |  T=0  |  T=1  |  T=2
-----+-------+-------+-------
 d=0 |  0,4  | 1,2,4 |   4
 d=1 |  0,3  | 1,2,3 |   3
 d=2 | 0,3,4 |   0   | 0,1,2
 d=3 |  3,4  |   -   |  1,2
 d=4 |   2   | 2,3,4 |  0,2

+32

     |  T=0  |  T=1  |  T=2              |  T=0  |  T=1  |  T=2
-----+-------+-------+-------       -----+-------+-------+-------
 d=0 |   17  |   22  |   16          d=0 |  "1"  |  "6"  |  "0"
 d=1 |    9  |   14  |    8          d=1 |  ")"  |  "."  |  "("
 d=2 |   25  |    1  |    7    -->   d=2 |  "9"  |  "!"  |  "'"
 d=3 |   24  |    0  |    6          d=3 |  "8"  |  " "  |  "&"
 d=4 |    4  |   28  |    5          d=4 |  "$"  |  "<"  |  "%"

Una volta appiattito, questo dà:

D = Buffer("160).(9!'8 &$<%")

Coordinate

X+y+z=0

coordinate del cubo

Crediti: www.redblobgames.com

Semplifica l'elaborazione di rotazioni e riflessioni nella fase finale dell'algoritmo.

Codifica delle tessere

I riquadri sono memorizzati in un elenco, senza un ordine specifico. Ciò significa che non dobbiamo preoccuparci di alcuna allocazione 2D dinamica e possiamo iterare facilmente sui riquadri esistenti. Il rovescio della medaglia è che, date le coordinate specifiche, abbiamo bisogno find()del riquadro corrispondente nell'elenco.

(X,y,z,m,t)

  • (X,y,z)
  • m
  • t012

Algoritmo

1(0,0,0)0

tessera iniziale

Pertanto, questa piastrella è codificata come [0,0,0,1,1].

Ad ogni iterazione, cerchiamo:

  • Piastrelle con connessioni mancanti: in questo caso, successivamente cerchiamo di completare la connessione con ogni tipo di piastrella.

  • Piastrelle che sono già connesse ma per le quali è necessario aggiungere nuove connessioni perché sono state raggiunte in una direzione diversa: in questo caso, aggiorniamo la maschera di direzione (con un OR bit a bit) e forziamo una nuova iterazione.

Se tutte le connessioni sono valide e abbiamo raggiunto il numero richiesto di tessere, dobbiamo ancora verificare se si tratta di una nuova creatura o solo di una versione modificata di una esistente:

  1. Applichiamo le seguenti trasformazioni:

    • Eseguiamo le 3 rotazioni possibili sostituendo (X,y) con (X,y)(y,z)(z,X)

    • Eseguiamo le 3 riflessioni corrispondenti sostituendo (X,y) con (y,X) ,(z,y)(X,z)

  2. (0,0)

  3. Ordiniamo le tessere secondo le loro coordinate e tipi. (Questo tipo viene elaborato in ordine lessicografico, il che va bene.)

  4. Infine, forziamo l'elenco risultante in una stringa di chiavi che può essere confrontata con le altre chiavi.

  5. Interrompiamo non appena viene abbinata una chiave nota o memorizziamo la nuova chiave e incrementiamo il risultato finale se nessuna delle trasformazioni porta a una chiave nota.

Commentate

f = (n, T = [B = [N = 0, 0, 0, 1, 1]]) =>
  // abort if n = 0
  !n ||
  // for each tile in T
  T.some(([x, y, q, m]) =>
    // for d = 0 to d = 4
    B.some((p, d) =>
      // if this tile requires a connection in this direction
      m >> d & 1 && (
        // look for a connected tile t at the corresponding position (p, q)
        (
          p = x + ~-s[d],
          q = y + ~-s[d + 2],
          t = T.find(([X, Y]) => X == p & Y == q)
        ) ?
          // if t exists, make sure that its direction mask is up-to-date
          (q = t[3]) & (p = D[d * 3 + t[4]]) ^ p ?
            // if it's not, update it and force a new iteration
            t[f(n, T, t[3] |= p), 3] = q
          :
            0
        :
          // if t does not exist, try each type of tile at this position
          [0, 1, 2].map(t => f(n - 1, [...T, [p, q, -p - q, D[d * 3 + t], t]]))
      )
    ),
    // s is used to apply (dx, dy)
    s = "2100122",
    // D holds the direction masks for the connections
    D = Buffer("160).(9!'8 &$<%")
  ) |
  // stop here if the above some() was truthy or we have more tiles to add
  n > 1 ||
  // otherwise, apply the transformations
  [0, 1, 2, 1, 2, 0].some((_, d, A) =>
    B[
      // compute the key k
      k =
        // by generating the updated tuples [x, y, type] and sorting them
        T.map(a =>
          [
            // transform the 1st coordinate
            (h = n => Math.min(...T.map(R = a => a[A[(d + n) % 6]])) - R(a))(0),
            // transform the 2nd coordinate
            h(3),
            // update the type
            (x = (a[4] + d * 2) % 3, d > 2) * x ? 3 - x : x
          ]
        ).sort()
    ]
  ) ?
    // if the key was found, just return N
    N
  :
    // if this is a new creature, store its key and increment N
    B[k] = ++N

Adoro questa risposta. Mi ha fatto eccitare per provarlo durante il fine settimana!
John Rees,

Sto per pubblicare una risposta che spero possa trovare interessante. Andrebbe bene usare una delle tue immagini per aiutare la mia spiegazione? Ti accrediterò ovviamente.
John Rees,

@JohnRees Certo, nessun problema.
Arnauld,
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.