Gibt es eine Möglichkeit, die Geschichte einer Welt prozedural zu generieren?

28

Ich bin ein wenig durch das Diagramm fasziniert Ergebnis hier die 1.800 Jahre Kulturgeschichte in einer imaginären Welt ein Typ erstellt.

Bildbeschreibung hier eingeben

Diese Art von Dingen scheint starke Anwendungen für die Spieleentwicklung zu haben, sofern es sich um Weltdesign handelt.

Es sieht so aus, als hätte er dieses Diagramm von Hand gemacht. Mich interessiert, ob es eine Möglichkeit gibt, ein solches Diagramm programmgesteuert zu erstellen.

Wenn Sie die Aufgabe hätten, Diagramme im oben genannten Stil aus Zufallswerten zu generieren, wie würden Sie vorgehen? Gibt es bestimmte Datenstrukturen oder Algorithmen, die Sie berücksichtigen würden?

pdusen
quelle
5
Betrachten Sie die Zwergenfestung . Die Quelle ist nicht verfügbar und der Prozess der Welterzeugung ist nicht dokumentiert (weshalb ich dies nicht beantworte), aber Sie können die generierte Weltgeschichte untersuchen, ohne tatsächlich lernen zu müssen, das Spiel zu spielen, und es kann Ihnen eine Vorstellung davon geben von Dingen, die Sie tun können.
Josh
Eine weitere Ressource und keine Antwort finden Sie unter: www-cs-students.stanford.edu/~amitp/game-programming/… Dies ist ein Artikel zum Generieren einer Umgebung, der jedoch weiter darauf eingeht , wie die Umgebung aussehen kann wird verwendet, um regionale Grenzen für Königreiche zu definieren, die auf Ressourcen (wie Wasser, bewohnbares Land usw.) basieren, die in die Mischung geworfen werden können, wenn Menschen wegen was und wo oder wie in den Krieg ziehen. Auch hier nur eine Ressource, keine Antwort.
James
1
Dieses Diagramm ähnelt dem Leistungsdiagramm in Civilization 3. Vielleicht möchten Sie diese Reihe auf einige Ideen hin überprüfen.
WildWeazel

Antworten:

15

Wie genau möchten Sie sein? Eine gute, aber komplexe Wahl wäre, all diese Geschichte zu simulieren:

  1. Generieren Sie eine zufällige Regionsliste und Nachbarschaften zwischen diesen Regionen.
  2. Generieren Sie zufällige Zivilisationen mit Merkmalen wie Bevölkerung, Krieg, Technologie ... und bevölkern Sie die Regionen.
  3. Simulieren Sie so viele Jahre der Geschichte, wie Sie möchten, und bestimmen Sie die Ergebnisse anhand der Merkmale der Zivilisation.

ZB: Zwei benachbarte kriegführende Zivilisationen haben eine höhere Wahrscheinlichkeit, einen Krieg gegeneinander zu beginnen, was mit der Zeit zu einer Verringerung der Bevölkerung führt. Händler-Zivilisationen haben höhere Ressourcen, sind aber ein großartiges Ziel für Invasionen. Stark besiedelte werden schneller wachsen, haben aber auch mehr Hunger. Kulturell heterogene Zivilisationen haben eine geringere Wahrscheinlichkeit für interne Kriege (die zu Auseinanderbrechen führen könnten).

Dies ermöglicht auch ein prozedurales Storytelling: Sie können nicht nur ein Gebietsdiagramm, sondern auch Textbeschreibungen der Geschichte über die Zeit ausgeben. Sie können dieses System so komplex gestalten, wie Sie möchten.


BEARBEITEN: Die Herausforderung ist hier keine technische, sondern die Anpassung der Heuristik für eine realistische und interessante Generierung der Geschichte. Schauen Sie sich die 3 oben genannten Punkte genauer an ... das ist so ziemlich Ihre technische Erklärung! Übersetzen Sie es in eine Schleife (jede Iteration kann so viel Zeit darstellen, wie Sie möchten, 1 Jahr, ein halbes Jahr, 1 Monat ...) und fertig. Sie müssen die Inners (Datenstrukturen, Heuristiken) bearbeiten und an Ihr spezifisches Problem und Ihre Bedürfnisse anpassen. Das ist der schwierige Teil hier und niemand kann dir helfen, da es um Vorstellungskraft, Versuch und Irrtum geht.

Es gibt keine gemeinsamen Datenstrukturen für dieses Problem außer denjenigen, die Sie für fast jedes Problem verwenden: Listen, Warteschlangen, Bäume ... und diese werden für Ihre spezifische Implementierung gebunden (benötige ich einen Stammbaum? Eine Liste der Zivilisationen) im Krieg - eine Reihe von Aufgaben für jede Zivilisation ?) Natürlich braucht man auch eine Liste von Zivilisationen. Die Wahlmöglichkeiten sind offensichtlich und ziemlich vernünftig.

Simulation ist eine Frage des Zufalls / der Wahrscheinlichkeit und Sie können sie auf tausend verschiedene Arten mit Zufallszahlen erstellen. Denken Sie an jedes andere Spiel, in dem Simulationen eine Rolle spielen, wie Fußballmanager, RPGs (schließlich sind Trefferpunkte / Statistiken nur Kampfsimulationen ), Strategiespiele ... Es sind nur Merkmale (Sie müssen also eine Möglichkeit zum Speichern von Zivilisationsmerkmalen und -daten finden). und statistisch auf diesen basierende zufällige Ergebnisse (daher müssen Sie den Simulationsstatus basierend auf diesen Merkmalen zufällig ändern.)

Das ist die Essenz Ihres Algorithmus: die schwer einstellbaren Heuristiken: wie Sie die Charakteristiken zu Beginn der Simulation für jede Zivilisation verteilen und wie Sie den Simulationsstatus basierend darauf statistisch ändern.

Kurz gesagt: Ihr Algorithmus ist nur eine Schleife, die die simulierte Zeit mit einem beliebigen Inkrement abdeckt. Kürzere Inkremente führen zu einer feineren historischen Simulation, werden aber offensichtlich länger dauern. In Ihrer Schleife gibt es eine Reihe von Heuristiken wie (ungefähr):

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

Nach all dieser Arbeit (oder wenn Sie die Daten nicht speichern möchten) müssen Sie den gesamten Simulationsstatus in ein für Menschen lesbares Format wie Text, Bilder oder was auch immer Sie wünschen interpretieren. Dies ist ebenfalls ein Versuch und Irrtum und sehr spezifisch für Ihre Implementierung.

Speziell für Ihre Frage: Um ein Diagramm wie das in Ihrer Frage zu erstellen, müssen Sie die Weltregionen (oben im Diagramm, x-Achse, Punkt 1: Regionsliste in meiner Antwort erstellen) und deren Zivilisationen (Farben in der Grafik) verfolgen Diagramm, Punkt 2 ) durch die Zeit (y-Achse, die Simulationsschleife in Punkt 3. )

Zustandsautomatensind ziemlich gut darin, allgemeine Themen zu simulieren (das obige Codebeispiel ist eine Annäherung an eine hartcodierte Zustandsmaschine). Sie können also mit der Implementierung eines einfachen Zustandsmaschinen-Frameworks beginnen, das insgesamt leicht zu optimieren ist. Jede Zivilisation würde mit einem dieser Zustandsautomaten beginnen und die Simulation würde jeden Zustandsautomaten für jede Runde ausführen. Jede Staatsmaschine müsste in der Lage sein, mit einer anderen Staatsmaschine zu interagieren. Beispielsweise würde die Einleitung eines Krieges die Staatsmaschine einer anderen Zivilisation beeinflussen, möglicherweise mit unterschiedlichen Ergebnissen, die von ihrem internen Zustand abhängen - z Ich möchte über Frieden verhandeln, aber eine Zivilisation, die nach Ärger sucht, würde sich wahrscheinlich revanchieren. Jeder Zustand in der Maschine hätte bedeutende Auswirkungen auf die Zivilisation. “ s Metriken, die oben für jeden "Rahmen" angegeben wurden (Wohlstand, Krieg, Bevölkerung usw.). Am wichtigsten ist, dass Sie nicht bei jedem Frame einen Statuswechsel durchführen müssen - gerade dann, wenn sich eine Gelegenheit und / oder eine zufällige Chance ergibt: Dies ermöglicht das Eintreten von längeren Ereignissen (wie z. B. Krieg).

kaoD
quelle
Vielen Dank für eine sehr nette Antwort, auch wenn es nicht die technischen Aspekte betrifft, um die ich mir Sorgen
mache
@pdusen der Kommentar wurde ziemlich lang und ich habe meine Antwort mit dem "EDIT" -Zeichen aktualisiert.
KaoD
2
Ich werde zu dieser Antwort hinzufügen, wenn es Ihnen nichts ausmacht?
Jonathan Dickinson
@ Jonathan Dickinson sicher, mach weiter :)
KaoD
@pdusen Ich habe einige weitere implementierungsspezifische Details hinzugefügt.
Jonathan Dickinson
8

Ja da ist. Hier ist ein unkomplizierter Verlaufsgenerator:

#!/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])))

Welche Ausgabe erzeugt wie folgt:

Bildbeschreibung hier eingeben

Passen Sie die Heuristik an, um verschiedene Diagramme zu erstellen.

Am einfachsten ist es, wenn Sie die func = random.choice([merge, split, revolt, conquest])Zeile so ändern , dass sie mehr als eine Funktion mit demselben Namen enthält. Zum Beispiel func = random.choice([merge, split, revolt, conquest, merge, merge])führt dies dazu, dass Nationen häufiger fusionieren.

brice
quelle