C'è un modo per generare proceduralmente la storia di un mondo?


28

Sono un po 'incuriosito dal diagramma trovato qui che rappresenta 1800 anni di storia culturale in un mondo immaginario creato da un ragazzo.

inserisci qui la descrizione dell'immagine

Questo genere di cose sembrerebbe avere forti applicazioni per lo sviluppo di giochi, nella misura in cui il design mondiale.

Sembra che abbia fatto questo diagramma a mano. Quello che mi interessa è vedere se esiste un modo per creare questo tipo di diagramma programmaticamente.

Se ti venisse assegnato il compito di generare diagrammi nello stile di cui sopra da valori casuali, come faresti? Ci sono particolari strutture di dati o algoritmi che dovresti prendere in considerazione?


5
Considera di dare un'occhiata a Dwarf Fortress . La fonte non è disponibile e il processo di generazione del mondo non è documentato (cosa per cui non lo sto facendo una risposta) ma puoi esaminare la storia del mondo generata senza effettivamente imparare a giocare e potrebbe darti un'idea del tipo di cose che puoi fare.
Josh,

Un'altra risorsa e non una risposta potrebbe essere trovata su: www-cs-students.stanford.edu/~amitp/game-programming/… Questo è un articolo per la generazione di un ambiente, ma continua a toccare come può essere l'ambiente usato per definire i confini regionali per i regni in base a risorse (come acqua, terra vivibile, ecc.) che potrebbero essere gettati nel mix per quando le persone vanno in guerra su cosa e dove o come .. Ancora una volta, solo una risorsa, non una risposta.
James,

1
Questo diagramma è molto simile al grafico del potere in Civilization 3. Potresti voler dare un'occhiata a quella serie per alcune idee.
WildWeazel,

Risposte:


15

Quanto accurato vuoi essere? Una scelta buona ma complessa sarebbe simulare tutta quella storia:

  1. Genera un elenco di regioni casuali e adiacenze tra queste regioni.
  2. Genera civiltà casuali con caratteristiche come popolazione, belligeranza, tecnologia ... e popola le regioni.
  3. Simula quanti anni di storia vuoi, determinando i risultati in base alle caratteristiche della civiltà.

Ad esempio: due civiltà belligeranti adiacenti hanno una maggiore probabilità di iniziare una guerra l'una con l'altra, il che porta ad una riduzione della popolazione nel tempo. Le civiltà mercantili hanno risorse più elevate, ma sono un ottimo obiettivo per le invasioni. Quelli molto popolati cresceranno più velocemente ma avranno anche maggiori possibilità di fame. Le civiltà culturalmente eterogenee hanno una minore possibilità di guerre interne (che potrebbero portare a rotture). E così via ... I risultati modificheranno anche le caratteristiche della civiltà: una tecnologia superiore porta a un commercio migliore, armi più forti, ecc.

Ciò consente anche una certa narrazione procedurale: è possibile produrre non solo un diagramma del territorio, ma anche descrizioni testuali della storia nel tempo. Puoi rendere questo sistema complesso come desideri.


EDIT: la sfida qui non è tecnica, ma regola l'euristica per una generazione di storia realistica e interessante. Dai un'occhiata più da vicino e pensa ai 3 punti di cui sopra ... questa è praticamente la tua spiegazione tecnica! Traducilo in un ciclo (ogni iterazione può rappresentare tutto il tempo che vuoi, 1 anno, mezzo anno, 1 mese ...) e il gioco è fatto. Dovrai lavorare all'interno (strutture dati, euristica) e adattarlo al tuo problema e alle tue esigenze specifiche. Questa è la parte difficile qui e nessuno può aiutarti, dal momento che si tratta di immaginazione, prova ed errore.

Non ci sono strutture di dati comuni per questo problema oltre a quelle che userete per quasi tutti i problemi: elenchi, code, alberi ... e questi saranno legati per la vostra specifica implementazione (ho bisogno di un albero genealogico? Un elenco di civiltà in guerra? una fila di compiti per ogni civiltà?) Naturalmente hai bisogno anche di un elenco di civiltà. Le scelte sono ovvie e praticamente di buon senso.

La simulazione è una questione di probabilità / probabilità e puoi farlo in mille modi diversi con numeri casuali. Pensa a qualsiasi altro gioco in cui è coinvolta la simulazione come manager di calcio, giochi di ruolo (dopo tutto, i punti ferita / le statistiche sono solo simulazione di combattimento ), giochi di strategia ... Sono solo caratteristiche (quindi avrai bisogno di un modo per memorizzare caratteristiche e dati della civiltà) e risultati casuali basati statisticamente su di essi (quindi dovrai cambiare casualmente lo stato di simulazione in base a queste caratteristiche).

Questa è l'essenza del tuo algoritmo: l'euristica difficile da regolare: come distribuire le caratteristiche all'inizio della simulazione per ogni civiltà e come cambiare statisticamente lo stato della simulazione in base a esse.

In breve: il tuo algoritmo è solo un ciclo che varia il tempo simulato con qualsiasi incremento desiderato. Incrementi più brevi portano a una simulazione storica più fine, ma ovviamente richiederà più tempo. All'interno del tuo ciclo ci saranno un sacco di euristiche come (approssimativamente):

for each civilization
  if civ.isAtWar
    civ.population -= civ.population * 0.05;
    civ.wealth -= 1000.0;
    civ.belligerence += 1.0;
  if civ.population < 100
    civ.negotiatePeace()

Dopo tutto questo lavoro (o durante se non si desidera archiviare i dati) è necessario interpretare tutto lo stato della simulazione in un formato leggibile dall'uomo come testo, immagini o qualunque cosa si desideri. Anche questa è una prova ed errore e molto specifica per la tua implementazione.

Specifico per la tua domanda: per generare un diagramma come quello nella tua domanda dovrai tracciare le regioni del mondo (parte superiore del diagramma, asse x, questo è il punto 1: genera un elenco di regioni nella mia risposta) e le loro civiltà (colori nella diagramma, punto 2 ) nel tempo (asse y, il ciclo di simulazione al punto 3 ).

Macchine statalisono abbastanza bravi a simulare argomenti di ampia portata (l'esempio di codice sopra riportato è un'approssimazione di una macchina a stati hardcoded) - quindi potresti iniziare implementando un semplice framework di macchine a stati che è nel complesso facile da modificare. Ogni civiltà inizierà con una di queste macchine statali e la simulazione eseguirà ciascuna macchina statale per ogni turno. Ogni macchina statale dovrebbe essere in grado di interagire con altre macchine statali: per esempio, l'avvio di una guerra influenzerebbe la macchina statale di un'altra civiltà, possibilmente con esiti diversi basati sul loro stato interno - ad esempio se si trovano nello stato di "carestia" probabilmente vuole negoziare la pace, ma una civiltà "alla ricerca di guai" probabilmente reagirebbe. Ogni stato nella macchina avrebbe effetti significativi sulla civiltà " s metriche descritte sopra durante ogni "frame" (ricchezza, belligeranza, popolazione, ecc.). Ancora più importante, non è necessario che gli stati di transizione su ogni fotogramma - proprio quando si presentano opportunità e / o possibilità casuali: ciò consente che si verifichino eventi prolungati (come la guerra).


Grazie per la bella risposta, anche se non tocca gli aspetti tecnici di cui mi preoccupo
pdusen,

@pdusen il commento è diventato piuttosto lungo, quindi ho aggiornato la mia risposta con il segno "EDIT".
Kaao

2
Aggiungerò a questa risposta, se non ti dispiace?
Jonathan Dickinson,

@JonathanDickinson certo, vai avanti :)
kaoD

@pdusen Ho aggiunto alcuni dettagli più specifici per l'implementazione.
Jonathan Dickinson,

8

Si C'è. Ecco un generatore di storia semplice e sporco:

#!/usr/bin/env python
# to create a visualisation, run like this:
#    ./timeline.py --dot | dot -Tpng > filename.png
import sys
import random
from pprint import pprint
# Names is a newline separated list of nation names.
file = "names.txt"
names = open(file, "r").read().split("\n") 
history = []
dot = False
if len(sys.argv) > 1 and sys.argv[1] == "--dot":
  dot = True

def wrap(str, wrap='"'):
  return wrap+str+wrap

def merge(states, names):
  number = random.randint(2,3)
  mergers = [] 
  if number < len(states):
    mergers = random.sample(states, number)
    new_name = random.choice(names)
    states = list(set(states).difference(set(mergers)))
    states.append(new_name)
    names.remove(new_name)
    if dot:
      for state in mergers:
        print '"%s" -> "%s"'%(state, new_name)
      print '{rank=same; %s }'%wrap(new_name)
    else:
      print "MERGE %s ==> '%s'"%( ", ".join(map(wrap,mergers)), new_name)
  return states, names 


def split(states, names):
  number = random.randint(2,3)
  if number < len(names):
    splitter = random.choice(states)
    states.remove(splitter)
    new_states = random.sample(names, number)
    names = list(set(names).difference(set(new_states)))
    states = list(set(states).union(set(new_states)))
    if dot:
      for state in new_states:
        print '"%s" -> "%s"'%(splitter, state)
      print '{rank=same; %s }'%("; ".join(map(wrap, new_states)))
    else:
      print "SPLIT '%s' ==> %s"%(splitter, ", ".join(map(wrap,new_states)))
  return states, names

def revolt(states, names):
  old = random.choice(states)
  new = random.choice(names)
  names.remove(new)
  states.remove(old)
  states.append(new)
  if dot:
    print '"%s" -> "%s"'%(old, new)
    print '{rank=same; "%s"}'%new
  else:
    print "REVOLT '%s' ==> '%s'"%(old, new)
  return states, names

def conquest(states, names):
  if len(states) > 1:
    loser = random.choice(states)
    states.remove(loser)
    winner = random.choice(states)
    if dot:
      print '"%s" -> "%s" [label="conquered by"]'%(loser, winner)
    else:
      print "CONQUEST '%s' conquered '%s'"%(winner, loser)
  return states, names


#ignore empty names
names = [name for name in names if name] #yes, really.

origin = random.sample(names, random.randint(1,3))
names = list(set(names).difference(set(origin)))
history.append(origin) #random starting states

if dot:
  print "digraph g {"
  print "{rank=same; %s}"%("; ".join(map(wrap,origin)))
else:
  print("BEGIN %s"%(", ".join(map(wrap,history[0]))))

while names:
  func = random.choice([merge, split, revolt, conquest])
  states, names = func(history[-1], names)
  history.append(states)

if dot:
  print '{rank=same; %s}'%("; ".join(map(wrap,history[-1])))
  print "}"
else:
  print "END %s"%(", ".join(map(wrap,history[-1])))

Che produce output in questo modo:

inserisci qui la descrizione dell'immagine

Regola l'euristica per creare diversi grafici.

Il modo più semplice per farlo sarebbe cambiare la func = random.choice([merge, split, revolt, conquest])linea per avere più di una funzione con lo stesso nome. Ad esempio func = random.choice([merge, split, revolt, conquest, merge, merge]), le nazioni si fonderanno più spesso.

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.