Come devo costruire la struttura dei dati per un "labirinto" dinamico e di dimensioni illimitate?


43

In realtà non sono sicuro che "labirinto" sia il termine corretto. Fondamentalmente gli utenti iniziano in un singolo Roomcon 4 porte (N, S, E e W). Possono andare in qualsiasi direzione e ogni stanza successiva contiene un'altra stanza da 1 a 4 porte che vanno in altre stanze.

Il "labirinto" dovrebbe avere dimensioni illimitate e crescere man mano che si spostano le stanze. C'è un numero limitato di Roomsdisponibili, tuttavia il numero disponibile è dinamico e può cambiare.

Il mio problema è che non sono sicuro della migliore struttura di dati per questo tipo di modello

Ho pensato per la prima volta di usare solo una serie di Roomoggetti [X] [X] , ma preferirei davvero evitarlo poiché la cosa dovrebbe crescere in qualsiasi direzione e dovrebbero essere costruite solo stanze "visitate".

L'altro pensiero era che ogni Roomclasse contenesse 4 Roomproprietà collegate per N, S, E e W, e si collegasse solo alla precedente Room, ma il problema con quello non so come identificare se un utente entra in una stanza che ha una stanza adiacente già "costruita"

Per esempio,

--- --- ----------
| | | |
   Inizia 5 4
| | | |
--- --- --- ---
--- --- ---------- --- ---
| | | | | |
| 1 2 3
| | | | | |
--- --- --- --- ----------

Se l'utente si sposta da Start> 1> 2> 3> 4> 5, allora Room# 5 deve sapere che W contiene la stanza iniziale, S è la stanza # 2 e in questo caso non dovrebbe essere disponibile e N può essere una nuovo Roomo un muro (niente).

Forse ho bisogno di un mix di array e stanze collegate, o forse sto solo guardando questo nel modo sbagliato.

Esiste un modo migliore di costruire la struttura dei dati per questo tipo di "labirinto"? O sono sulla strada giusta con il mio attuale processo di pensiero e mi mancano solo alcune informazioni?

(Se sei interessato, il progetto è un gioco molto simile a Munchkin Quest )


Non penso che nessun tipo di array funzionerebbe poiché le stanze crescono in qualsiasi direzione ... Quindi se inizi da [0,0] e ti sposti a sinistra? proverebbe [-1, 0].
Paolo,

@Paul Aggiunge una riga / colonna, sposta tutti i dati dell'array, quindi sposta anche tutte le posizioni del giocatore in modo che corrispondano al nuovo array della mappa. Lento e può essere difficile a seconda di quanto deve essere spostato, ma possibile. Tuttavia, la risposta di Bubblewrap è molto migliore.
Izkata,

Probabilmente sbaglio, ma non sarebbe meglio su GameDev.SE?
Dinamico

1
@Dynamic Questa è una domanda sulla struttura dei dati, quindi si adatta perfettamente qui.
Izkata,

Risposte:


44

Fornisci le coordinate di ogni stanza (inizio sarebbe (0,0)) e memorizza ogni stanza generata in un dizionario / hashmap per coordinate.

È banale determinare le coordinate adiacenti per ogni stanza, che è possibile utilizzare per verificare se esiste già una stanza. È possibile inserire valori null per rappresentare posizioni in cui è già determinato che non esiste alcuna room. (o se ciò non è possibile, non sono sicuro atm, un dizionario / hasmap separato per le coordinate che non contengono una stanza)


2
Rendi ogni oggetto Room contenente informazioni nord-est-sud-ovest ad altri oggetti Room, quindi puoi usare sia il dizionario / hashmap che le indicazioni cardinali Room per navigare nel labirinto (a volte vorrai trovare con coordinate assolute e a volte vorrai sapere cosa c'è a nord dell'oggetto Room X).
Neil,

2
@Paul Penso che l'idea sia quella di creare un dizionario di stanze, e quando si costruisce una nuova Room, cercare stanze adiacenti nel dizionario e aggiungere i loro collegamenti Roomall'oggetto prima di aggiungere nuove stanze ai lati rimanenti. Quindi l'unica volta che si verifica la ricerca del dizionario è quando si crea un nuovo Roomoggetto, non quando si viaggia traRooms
Rachel,

7
Non c'è motivo di archiviare collegamenti ad altre stanze all'interno di un Roomoggetto. Se sei nella stanza (x,y)e vuoi viaggiare verso nord, cerchi la stanza (x,y+1)nel dizionario, creandola se non esiste. Le ricerche nel dizionario sono molto veloci O(1).
Karl Bielefeldt,

3
@KarlBielefeldt: la stanza @ (x,y+1)può esistere, ma non può essere navigata da (x,y)nord. Vale a dire che un vantaggio potrebbe non andare direttamente da (x,y)a (x,y+1).
Steven Evers,

2
Ragazzi, lo state rendendo complicato. Questo è fondamentalmente lo stesso di un array .. tranne che lo stai cercando in un dizionario piuttosto che in un array bidimensionale. Eventuali problemi che incontrerai con un array saranno anche lì ... ma avrebbe comunque usato un array.
user606723,

15

Nella maggior parte dei casi, ciò che stai descrivendo suona come un grafico. Dati alcuni dei tuoi requisiti (l'aspetto crescente) probabilmente sceglierei un elenco di adiacenza come implementazione (l'altra opzione comune sarebbe una matrice di adiacenza ).

Non sono sicuro di quale lingua si sta utilizzando, ma un buon tutorial / spiegazione con dettagli di implementazione per un grafico implementato con una lista di adiacenza in .NET è qui .


1
Non sono sicuro che un grafico sia sufficiente per descriverlo poiché N / E / S / W non fanno realmente parte del concetto di grafico; c'è solo connesso e possibilmente in / out.
Edward Strange,

3
@CrazyEddie: tieni presente che la struttura di dati / a non è destinata a mappare direttamente su un dominio particolare (in effetti, questo è il loro vantaggio). In questo esempio particolare, il grafico sarebbe direzionale e i bordi possono essere banalmente annotati con un enum.
Steven Evers,

L'Annotazione potrebbe addirittura far rispettare banalmente il rapporto est-ovest, nord-sud. Ciò eliminerebbe i problemi di andare ad ovest dalla stanza A alla stanza B e quindi di andare ad est dalla stanza B alla stanza C (invece della stanza A) a causa di un cattivo collegamento.
Peter Smith,

3
Diciamo che volevamo implementare una stanza popolata da un mago che si protegge teletrasportando il giocatore dieci quadrati in una direzione casuale. Trovare quel punto in una struttura di dati basata su grafici sarebbe molto costoso, specialmente se quella stanza non esistesse e tu dovessi generare tutte le stanze che collegano quella stanza a un'altra stanza nel grafico (poiché la struttura non consentirebbe più, sezioni reciprocamente disconnesse del sotterraneo).
Mark Booth,

2
@MarkBooth ma poi abbiamo cambiato i requisiti, no?
Joshua Drake,

9

Un paio di pensieri sull'implementazione (ho pensato a Carcassonne che ha una serie di altri aspetti stimolanti per la costruzione della matrice - è stata persino una domanda di intervista che mi è stata posta una volta).

Ci sono alcune domande poste a questa struttura:

  1. c'è una stanza in X, Y?
  2. c'è una stanza [N / S / E / W] della posizione vuota X, Y?

Il problema con semplici elenchi e grafici

La difficoltà con elenchi semplici è che si deve percorrere la struttura per verificare se qualcosa può essere posizionato in una determinata posizione. Il sistema ha bisogno di un modo per accedere a una posizione arbitraria O (1).

Ritenere:

1 2 3 ? 4
5 . . * 6
7 8 9 A B

Per trovare le informazioni sulla possibile posizione '?', Quando al punto 3, si deve camminare tutto intorno per arrivare a 4. La risposta del collegamento con informazioni extra su quanti nodi salta (in modo che 3 sia collegato a 4 con informazioni extra 'salto 1') richiede ancora delle passeggiate quando si trova l'adiacenza di '*' da 6 o A.

Matrici semplici, grandi

C'è un numero limitato di camere disponibili

Se questo non è un numero grande, basta creare un array 2N x 2N e lasciarlo lì. Un array 100 x 100 è "solo" 10.000 puntatori e 50 oggetti. Indice direttamente nell'array.

Questo potrebbe essere ridotto a NxN solo se le attività fuori limite spostassero l'array in modo da essere sempre entro i limiti. Ad esempio, se si dovesse aggiungere una stanza che avrebbe underflow l'array, avere la griglia per spostare ogni elemento di una posizione in modo che non ci fosse più underflow. A questo punto, le dimensioni del mondo per 50 stanze sarebbero 2500 puntatori e 50 oggetti.

Matrici e matrici sparse

Per creare questo in modo più ottimale, guarda in una matrice sparsa in cui la maggior parte degli elementi sono 0 o null. Per due dimensioni, questa è nota come matrice sparsa . Molti linguaggi di programmazione hanno varie librerie per lavorare con questa struttura. Se la libreria si limita a numeri interi, è possibile utilizzare questo numero intero come indice in una matrice fissa di stanze.

Il mio approccio preferito sarebbe quello di avere le stanze come una struttura (puntatori alle stanze adiacenti e coordinate) e avere anche la stanza un puntatore da una tabella di hash che è incisa sulle coordinate. In questa situazione per chiedere quale stanza è [N / S / E / W] da una stanza nulla, si dovrebbe interrogare la tabella hash. Per inciso, questo è il formato "DOK" per la memorizzazione di una matrice sparsa.


1
Bella risposta, come suggerisce Bubblewrap , tuttavia, un dizionario con una tupla di posizione come chiave è una struttura ragionevole da usare per implementare una matrice sparsa.
Mark Booth,

2

Cosa ne pensi di questo..

Dai a ogni cella un collegamento a ciascuno dei suoi vicini. Assegna a ogni cella una sorta di nome (ad esempio "0/0", "-10/0" (Supponi di iniziare da 0,0)). Aggiungi un HashSet con tutti i nomi al suo interno. Mentre provi a spostarti in un'altra cella, controlla che il vicino non sia già presente nell'HashSet.

Inoltre, se si crea un'apertura in un'altra stanza, ciò implica che le stanze esistono? Quindi creeresti un collegamento a una stanza vuota senza collegamenti a nessuna stanza. Probabilmente vorresti anche controllare i vicini delle nuove stanze. Se ne esiste uno e si apre alla tua nuova stanza, aggiungi una porta a quella stanza.

   Empty   
   (0,1)        

---    ---            ----------
|        |            |        |
    0,0       1,0        2,0       Empty
|        |            |        |   (3,0)
---    --- ---------- ---    ---
|        | |        | |        |
|   0,-1      1,-1       2,-1      Empty
|        | |        | |        |   (3,-1)
---    --- ---    --- ----------

   Empty     Empty   
  (0,-2)    (1,-2)

HashSet = {0 | 0, 1 | 0, 2 | 0, 3 | 0, 0 | -1, 1 | -1 ....} 1,0: W = 0,0 / Porta; 1,0: N = 1,1 / Vuoto; E = 2,0 / Porta; S = 1, -1 / Muro

Dovresti anche assicurarti di dare ad ogni nuova stanza almeno una porta non adiacente (ad un'altra stanza) in modo che il labirinto possa crescere in quella direzione.


1

Quello che stai progettando assomiglia molto a un grafico.

Un grafico del labirinto

Questi sono spesso rappresentati come un insieme di stati Q , uno stato iniziale q 0Q e alcune transizioni Δ . Quindi, suggerirei di conservarlo in questo modo.

  • Un set Q : {s, 1, 2, 3, 5, 6}
  • Uno stato iniziale q 0 : s
  • Una mappa delle relazioni di transizione Δ : {s → 1, s → 5, 1 → 2, 2 → 3, 3 → 4, 4 → 5}

Le lingue più ragionevoli hanno sia mappe che set.


0

Potresti prendere in considerazione un elenco di link a 4 vie ...

Per la prima volta ho pensato di usare solo una matrice [X] [X] di oggetti Room, ma preferirei davvero evitarlo poiché la cosa dovrebbe crescere in qualsiasi direzione e dovrebbero essere costruite solo le stanze "visitate".

Puoi ancora usare un [] [] per quello. La crescita dinamica potrebbe essere lenta, specialmente quando si aggiunge all'inizio, ma forse non è poi così male. Dovresti profilare per verificare. Cercare di manipolare un elenco o una mappa collegati potrebbe effettivamente essere peggio, e su un livello costante piuttosto che occasionale.

Puoi costruire solo stanze visitate implementando una valutazione pigra. Un oggetto può essere in posizione che contiene una stanza e non crea quella stanza fino a quando non room()viene richiamato su di essa. Quindi restituisce lo stesso ogni volta dopo. Non è difficile da fare.


1
Potresti espandere ciò che intendi con un "elenco collegato a 4 vie"? L'unica cosa che ho trovato è stato un articolo di CodeProject, che si riduce a un elenco di adiacenza.
Steven Evers,

0

Ho imparato a farlo tramite il libro "Creazione di giochi di avventura sul tuo computer". Se sei disposto a scavare attraverso il codice BASIC (il libro è così vecchio) vai a leggere qui:

http://www.atariarchives.org/adventure/chapter1.php

Per la scorciatoia, ciò che fanno è avere una matrice di stanze, con un elemento in ogni matrice che è un puntatore a un'altra stanza in cui puoi andare, con 0 (vorrei usare -1 in questi giorni) per indicare che non puoi andare in quel modo. Ad esempio, creeresti una struttura di una stanza (o classe o che cosa hai)

room {
 int north_dir;
 int south_dir;
 int west_dir;
 int east_dir;
 // All other variables and code you care about being attached to the rooms here
}

Quindi potresti avere una matrice o una struttura di elenchi collegati o comunque vuoi gestire le tue stanze. Per lo stile di array (o vettori C ++) userei quel codice e le direzioni avrebbero l'indice della stanza a cui si collegano; per un elenco collegato, è possibile utilizzare i puntatori alla stanza successiva.

Come altri hanno già detto, se devi generare dinamicamente nuove stanze quando le persone vi accedono, puoi anche farlo.


0

Non cercare di risolvere tutti i problemi con una struttura.

Definisci l'oggetto della tua stanza per conservare le cose sulla stanza, non le sue relazioni con le altre stanze. Quindi un array 1D, un elenco, ecc. Possono crescere a piacere.

Una struttura separata può contenere "raggiungibilità" - quali stanze sono accessibili dalla stanza in cui mi trovo. Quindi decidere se la raggiungibilità transitiva deve essere un calcolo rapido o meno. Potresti volere un calcolo della forza bruta e una cache.

Evita l'ottimizzazione precoce. Usa strutture davvero semplici e stupidi algoritmi facilmente comprensibili, quindi ottimizza quelli che vengono utilizzati.

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.