Come potrei implementare qualcosa come la griglia di creazione di Minecraft?


55

Il sistema di crafting in Minecraft utilizza una griglia 2x2 o 3x3. Metti gli ingredienti sulla griglia e, se metti gli ingredienti giusti nel modello giusto, attiverà la ricetta.

Alcuni punti interessanti sul design:

  • Alcune ricette possono scambiare determinati ingredienti con altri. Ad esempio, un plettro utilizza bastoncini per il manico e può utilizzare assi di legno, ciottoli, lingotti di ferro, lingotti d'oro o gemme di diamanti per la testa.
  • Ciò che conta è la posizione relativa all'interno del modello, non la posizione assoluta sulla griglia. Cioè, puoi fabbricare una torcia posizionando lo stick e il carbone (o carbone) nel modello corretto in una qualsiasi delle sei posizioni sulla griglia 3x3.
  • I motivi possono essere capovolti orizzontalmente.

Potrei pensarci troppo, ma questo sembra un interessante problema di ricerca / riduzione del set. Quindi, come funziona (o potrebbe) funzionare, dal punto di vista algoritmico?


6
Domanda fantastica! Sono molto interessata! Ho già delle idee ma le condividerò non appena torno a casa.
jcora,

In realtà sto cercando di scrivere una copia di Minecraft "il più vicino possibile". Ho già scritto tutte le cose dell'inventario e del gestore delle ricette e devo dire che funziona molto bene. Sono soddisfatto del codice pulito. Tuttavia, non è stato facile scriverlo. Ho scritto circa 10 lezioni per far funzionare tutte le ricette, le griglie, l'inventario, ecc. Quando trovo un po 'di tempo, scrivo una risposta.
Martijn Courteaux,

Dovresti vedere come Minecraft codifica le loro ricette di creazione.
Bradman175,

Risposte:


14

Un'altra soluzione è utilizzare un albero piuttosto complicato. I nodi del ramo nel tuo albero verrebbero creati ripetendo la ricetta (usando ancora una volta for (y) { for (x) }); questa è la struttura ad albero standard di magazzino. Il tuo nodo finale conterrebbe una struttura aggiuntiva ( Dictionary/ HashMap) che mappa le dimensioni alle ricette.

In sostanza quello che stai cercando è questo:

Albero delle ricette

I nodi neri sono i tuoi rami che indicano il tipo di elemento - quelli rossi sono le tue foglie (terminatori) che ti consentono di differenziare dimensioni / orientamento.

Per cercare su questo albero, dovresti prima trovare il rettangolo di selezione (come indicato nella mia prima risposta ) e quindi scorrere i nodi nello stesso ordine che attraversa l'albero mentre procedi. Infine, dovresti semplicemente cercare la dimensione nel tuo Dictionaryo HashMape otterrai il risultato della ricetta.

Solo per i calci sono andato e implementato questo - che probabilmente chiarirà la mia risposta. Inoltre : mi rendo conto che questa è una risposta diversa - e giustamente: è una soluzione diversa.


Molto semplice. Mi piace.
David Eyk,

Accetterò questo, perché mi piacciono gli alberi, gli hash e i diagrammi che tagliano il nocciolo della questione. Uno sguardo al diagramma e ho capito il tuo algoritmo quasi immediatamente.
David Eyk,

23

Devi ricordare che Minecraft utilizza solo una serie molto piccola di possibili ricette, quindi non è necessario nulla di così intelligente.

Detto ciò, ciò che farei è trovare la griglia più piccola adatta (ovvero ignorare righe e colonne vuote per scoprire se si tratta di un 2x2 o 3x3 o 2x3 (porta)). Quindi scorrere l'elenco delle ricette con quella dimensione, semplicemente controllando se il tipo di elemento è lo stesso (vale a dire, nei peggiori confronti di 9 numeri interi in Minecraft poiché utilizza un ID di tipo intero per elementi e blocchi) e fermarsi quando si trova una corrispondenza.

In questo modo anche la posizione relativa degli oggetti è irrilevante (puoi posizionare una torcia in qualsiasi punto della griglia di lavorazione e funzionerà perché la vede come una scatola 1x2, non una scatola 3x3 che è per lo più vuota).

Se hai una grande quantità di ricette in modo che fare una ricerca lineare attraverso le possibili corrispondenze impieghi troppo tempo sarebbe possibile ordinare l'elenco e fare una ricerca binaria (O (log (N)) vs O (N)). Ciò comporterebbe un lavoro extra nella creazione dell'elenco, ma ciò può essere fatto all'avvio una volta e conservato in memoria successivamente.

Anche un'ultima cosa, per consentire di capovolgere la ricetta in orizzontale più semplice, sarebbe semplicemente aggiungere la versione speculare all'elenco.

Se si desidera farlo senza aggiungere una seconda ricetta, è possibile verificare se la ricetta di input ha un elemento in [0,0] con ID superiore rispetto a [0,2] (o [0,1] per 2x2, nessun controllo necessario per 1x2 e in tal caso rispecchiarlo, altrimenti continua a controllare la riga successiva fino a quando non hai raggiunto la fine. Usando questo devi anche assicurarti che le ricette vengano aggiunte nella rotazione corretta.


1
Mi chiedevo se il piccolo numero di ricette in Minecraft non potesse prestarsi a una ricerca lineare.
David Eyk,

1
Lo fa, ed è sostanzialmente quello che ho scritto sopra (con possibile ottimizzazione della ricerca binaria). Tuttavia, ciò lascia alcune opzioni per la realizzazione di torce che puoi adattare praticamente ovunque. Ottenendo innanzitutto le dimensioni della ricetta non è necessario aggiungere 6 ricette per torce e limitare la ricerca a quelle della dimensione corretta in un solo passaggio. - Quindi sostanzialmente le opzioni per il tuo punto 2 sono raggruppare per dimensione, spostare la ricetta in un angolo o aggiungere più ricette duplicate.
Elva,

15

Vedere se una determinata configurazione di griglia corrisponde a una determinata ricetta è semplice se si codifica la griglia 3x3 come stringa e si utilizza una corrispondenza di espressioni regolari . Accelerare lo sguardo è una questione diversa, di cui parlerò alla fine. Continua a leggere per ulteriori informazioni.

Passaggio 1) Codifica la griglia come stringa

Basta dare un carattere char ad ogni tipo di cella e concatenare tutto fianco a fianco in questo ordine:

123
456 => 123456789
789

E come esempio più concreto, considera la ricetta del bastone, dove W sta per legno ed E è una cella vuota (potresti semplicemente usare un carattere vuoto ''):

EEE
WEE => EEEWEEWEE
WEE

Passaggio 2) Abbina la ricetta usando l'espressione regolare (o String.Contains con un po 'di elaborazione sui dati)

Continuando dall'esempio sopra, anche se spostiamo la formazione in giro, c'è ancora un motivo nella stringa (WEEW imbottito da E su entrambi i lati):

EEW
EEW => EEWEEWEEE
EEE

Quindi, indipendentemente da dove sposti il ​​bastoncino, corrisponderà comunque alla seguente espressione regolare: /^E*WEEWE*$/

Le espressioni regolari ti consentono anche di eseguire il comportamento condizionale che hai citato. Ad esempio (ricetta preparata), se si desidera che un piccone fatto di ferro o pietra dia lo stesso risultato, ovvero:

III    SSS
EWE or EWE
EWE    EWE

È possibile combinare entrambi nell'espressione regolare: /^(III)|(SSS)EWEEWE$/

I flip orizzontali possono anche essere aggiunti altrettanto facilmente (usando anche l'operatore |).

Modifica: in ogni caso, la parte regex non è strettamente necessaria. È solo un modo per incapsulare il problema in una singola espressione Ma per il problema della posizione variabile potresti anche tagliare la stringa della griglia di qualsiasi spazio di riempimento (o E in questo esempio) e fare un String.Contains (). E per il problema degli ingredienti multipli o le ricette speculari, potresti semplicemente gestirle tutte come ricette multiple (cioè separate) con lo stesso risultato.

Passaggio 3) Accelerare la ricerca

Per quanto riguarda la riduzione della ricerca, dovrai creare una struttura di dati per raggruppare le ricette e aiutare con la ricerca. Trattare la griglia come stringa presenta anche alcuni vantaggi :

  1. È possibile definire la "lunghezza" di una ricetta come la distanza tra il primo carattere non vuoto e l'ultimo carattere non vuoto. Un semplice Trim().Length()ti darebbe queste informazioni. Le ricette possono essere raggruppate per lunghezza e memorizzate in un dizionario.

    o

    Una definizione alternativa di "lunghezza" potrebbe essere il numero di caratteri non vuoti. Nient'altro cambia. Puoi anche raggruppare le ricette secondo questi criteri.

  2. Se il punto numero 1 non è sufficiente, le ricette possono anche essere ulteriormente raggruppate in base al tipo del primo ingrediente che appare nella ricetta. Questo sarebbe semplice come fare Trim().CharAt(0)(e proteggersi da Trim risultante in una stringa vuota).

Quindi, ad esempio, dovresti conservare le ricette in un:

Dictionary<int, Dictionary<char, List<string>>> _recipes;

Ed esegui la ricerca come qualcosa di simile:

// A string encode of your current grid configuration 
string grid;

// Get length and first char in our grid
string trim = grid.Trim();
int length = trim.Length();
char firstChar = length==0 ? ' ' : trim[0];

foreach(string recipe in _recipes[length][firstChar])
{
    // Check for a match with the recipe
    if(Regex.Match(grid, recipe))
    {
        // We found a matching recipe, do something with it
    }
}

3
Questo è ... molto, molto intelligente.
Raveline,

18
Hai un problema e vuoi risolverlo usando espressioni regolari? Ora hai 2 problemi :)
Kromster dice di sostenere Monica il

2
@Krom immagino che probabilmente stai solo scherzando, ma qualche ragionamento dietro quel commento? L'uso di un'espressione regolare è solo un modo conciso (abbina la griglia alla ricetta con una singola riga di codice) e flessibile (si adatta a tutti i requisiti di questo problema) per eseguire la corrispondenza su un insieme di dati (il contenuto della griglia). Non vedo svantaggi significativi rispetto al rotolamento del proprio algoritmo di abbinamento e semplifica l'intero processo.
David Gouveia,

2
@DavidGouveia, questa è solo una frase per le persone che non si sentono a proprio agio con Regex. Se decidono di risolvere un problema con regex, ora hanno due problemi: (1) il problema reale che vogliono risolvere (2) scrivere il modello regex per risolvere quel problema
iamserious,

2
Tornando al 'ora che hai due problemi' - Regex avrà problemi non appena avrai più elementi dell'intervallo stampabile ASCII, a meno che il tuo processore regex non supporti Unicode e puoi garantire che certe combinazioni non faranno scattare il compilatore NFA regex su. Potresti mettere insieme il tuo DFA (abbastanza facilmente in realtà) per abbinare i modelli; ma regex sarebbe il modo migliore di procedere durante la prototipazione.
Jonathan Dickinson,

3

Non posso dirti come funziona Minecraft - anche se sono sicuro che se guardassi MCP (se hai una copia legale di Minecraft) potresti scoprirlo.

Lo implementerei come segue:

  1. Avere un dizionario / hashmap basato su disco ( B + Tree / SQL-Light ) - il valore sarebbe l'ID articolo del risultato della ricetta.
  2. Quando un utente crea qualcosa, trova semplicemente la prima riga / colonna con un oggetto INDIPENDENTEMENTE ; combinare quelli in un offset.
  3. Trova l'ultima riga / colonna con un elemento, di nuovo, in modo indipendente.
  4. Calcola la larghezza e l'altezza dagli elementi e aggiungili alla chiave.
  5. Passa sopra l'intervallo del rettangolo di selezione appena calcolato e aggiungi l'ID di ciascun elemento (usando il solito for (y) { for (x) }).
  6. Cerca la chiave nel database.

Ad esempio, supponiamo che abbiamo due ingredienti; X e Y e spazi vuoti sono *. Prendi la seguente ricetta:

**X
**Y
**Y

Per prima cosa elaboriamo il rettangolo di selezione, cedendo (2,0)-(2,2). Pertanto la nostra chiave sarebbe simile a questa [1][3](1 larghezza, 3 altezza). Quindi eseguiamo il ciclo su ciascun elemento all'interno del rettangolo di selezione e aggiungiamo l'ID, quindi la chiave diventa [1][3][X][Y][Y]: quindi lo cerchi nel tuo dizionario / DB e otterrai il risultato di quella ricetta.

Per spiegare più chiaramente l'indipendenza nel passaggio 2, considerare la seguente ricetta:

*XX
XXX
XX*

La parte superiore / sinistra è chiaramente a 0,0 - tuttavia il primo oggetto che incontreresti normalmente sarebbe 0,1 o 1,0 (a seconda del tuo ciclo). Tuttavia, se trova la prima colonna non vuota e la prima riga non vuota e combina le coordinate otterrai 0,0 - lo stesso principio si applica nella parte inferiore / destra del rettangolo di selezione.


Il repository Bukkit non ha alcun codice Minecraft, avresti bisogno di MCP (e una copia di Minecraft)
BlueRaja - Danny Pflughoeft

Non è solo l'inverso dell'altra tua risposta?
David Eyk,

@DavidEyk (Questa è la mia prima risposta) non proprio, dipende più da una struttura hashmap (che sia bigtable-like o in-memory). Il motivo per cui ho risposto di nuovo è perché ero preoccupato per le collisioni di hash che portavano a scarse prestazioni - almeno in una hashmap in memoria. L'altra mia risposta avrebbe funzionato meglio per più elementi e una struttura in memoria - dove questo avrebbe funzionato meglio se fosse in SQL / etc.
Jonathan Dickinson, il

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.