Existe-t-il un moyen de générer de manière procédurale l'histoire d'un monde?

28

Je suis quelque peu intrigué par le diagramme trouvé ici représentant 1800 ans d'histoire culturelle dans un monde imaginaire créé par un type.

entrez la description de l'image ici

Ce genre de chose semble avoir de fortes applications pour le développement de jeux, dans la mesure où la conception du monde.

On dirait qu'il a fait ce diagramme à la main. Ce qui m'intéresse, c'est de voir s'il existe un moyen de créer ce type de diagramme par programme.

Si vous étiez chargé de générer des diagrammes dans le style de ce qui précède à partir de valeurs aléatoires, comment vous y prendriez-vous? Y a-t-il des structures de données ou des algorithmes particuliers que vous envisageriez?

pdusen
la source
5
Pensez à jeter un œil à la forteresse naine . La source n'est pas disponible et le processus de génération du monde n'est pas documenté (ce qui explique pourquoi je n'en fais pas une réponse) mais vous pouvez examiner l'histoire du monde générée sans avoir à apprendre à jouer au jeu et cela pourrait vous donner une idée du genre des choses que vous pouvez faire.
Josh
Une autre ressource, et pas une réponse, peut être trouvée à: www-cs-students.stanford.edu/~amitp/game-programming/… Ceci est un article pour générer un environnement mais il continue en abordant comment l'environnement peut être utilisé pour définir les limites régionales des royaumes en fonction des ressources (comme l'eau, les terres habitables, etc.) qui pourraient être jetées dans le mélange lorsque les gens entrent en guerre pour quoi et où ou comme .. Encore une fois, juste une ressource, pas une réponse.
James
1
Ce diagramme ressemble beaucoup au graphique de puissance dans Civilization 3. Vous voudrez peut-être consulter cette série pour quelques idées.
WildWeazel

Réponses:

15

À quel point voulez-vous être précis? Un bon choix mais complexe serait de simuler toute cette histoire:

  1. Générez une liste de régions aléatoires et des contiguïtés entre ces régions.
  2. Générez des civilisations aléatoires avec des caractéristiques telles que la population, la belligérance, la technologie ... et peuplez les régions.
  3. Simulez autant d'années d'histoire que vous le souhaitez, en déterminant les résultats en fonction des caractéristiques de la civilisation.

Par exemple: deux civilisations belligérantes adjacentes ont une probabilité plus élevée de déclencher une guerre l'une contre l'autre, ce qui entraîne une diminution de la population au fil du temps. Les civilisations marchandes ont des ressources plus élevées, mais sont une grande cible pour les invasions. Les populations très peuplées croîtront plus rapidement mais auront également plus de risques de faim. Les civs culturellement hétérogènes ont moins de risques de guerres internes (ce qui pourrait conduire à des ruptures.) Et ainsi de suite ... Les résultats modifieraient également les caractéristiques de la civilisation: une technologie plus élevée conduit à un meilleur commerce, à des armes plus fortes, etc.

Cela permet également une narration procédurale: vous pouvez générer non seulement un diagramme de territoire, mais également des descriptions textuelles de l'histoire à travers le temps. Vous pouvez rendre ce système aussi complexe que vous le souhaitez.


EDIT: le défi ici n'est pas d'ordre technique, mais d'ajuster l'heuristique pour une génération d'histoire réaliste et intéressante. Regardez de plus près et pensez aux 3 points susmentionnés ... c'est à peu près votre explication technique! Traduisez-le en boucle (chaque itération peut représenter autant de temps que vous le souhaitez, 1 an, 6 mois, 1 mois ...) et c'est tout. Vous devrez travailler les éléments internes (structures de données, heuristiques) et l'adapter à votre problème et à vos besoins spécifiques. C'est la partie difficile ici et personne ne peut vous aider, car il s'agit d'imagination, d'essais et d'erreurs.

Il n'y a pas de structures de données communes pour ce problème en dehors de celles que vous utiliserez pour presque tous les problèmes: listes, files d'attente, arbres ... et ceux-ci seront liés pour votre implémentation spécifique (ai-je besoin d'un arbre généalogique? Une liste des civilisations à la guerre? une file de tâches pour chaque civ??) Bien sûr, vous avez besoin d'une liste de civilisations aussi. Les choix sont évidents et relèvent à peu près du bon sens.

La simulation est une question de hasard / probabilité et vous pouvez la réaliser de mille manières différentes avec des nombres aléatoires. Pensez à tout autre jeu où la simulation est impliquée, comme les managers de football, les RPG (après tout, les points de vie / statistiques ne sont que de la simulation de combat ), des jeux de stratégie ... Ce ne sont que des caractéristiques (vous aurez donc besoin d'un moyen de stocker les caractéristiques et les données de la civilisation) et des résultats aléatoires statistiquement basés sur eux (vous devrez donc changer aléatoirement l'état de simulation en fonction de ces caractéristiques.)

C'est l'essence de votre algorithme: l'heuristique difficile à ajuster: comment répartir les caractéristiques au début de la simulation pour chaque civilisation et comment changer statistiquement l'état de simulation en fonction de celles-ci.

En bref: votre algorithme est juste une boucle allant du temps simulé avec tout incrément souhaité. Des incréments plus courts conduisent à une simulation historique plus fine, mais prendront évidemment plus de temps. Dans votre boucle, il y aura un tas d'heuristiques comme (à peu près):

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

Après tout ce travail (ou pendant si vous ne voulez pas stocker les données), vous devez interpréter tout l'état de simulation dans un format lisible par l'homme comme du texte, des images ou tout ce que vous désirez. Il s'agit également d'essais et d'erreurs et très spécifique pour votre implémentation.

Spécifique à votre question: pour générer un diagramme comme celui de votre question, vous devrez suivre les régions du monde (haut du diagramme, axe x, c'est le point 1: générer la liste des régions dans ma réponse) et leurs civilisations (couleurs dans le diagramme, point 2 ) à travers le temps (axe y, la boucle de simulation au point 3. )

Machines d'étatsont assez bons pour simuler des sujets généraux (l'exemple de code ci-dessus est une approximation d'une machine d'état codée en dur) - vous pouvez donc commencer par implémenter un cadre de machine d'état simple qui est globalement facile à modifier. Chaque civilisation commencerait avec une de ces machines d'état et la simulation exécuterait chaque machine d'état pour chaque tour. Chaque machine d'État devrait pouvoir interagir avec une autre machine d'État: par exemple, le déclenchement d'une guerre affecterait la machine d'État d'une autre civilisation, avec éventuellement des résultats différents en fonction de leur état interne - par exemple, s'ils sont dans un état de «famine», ils seraient probablement veulent négocier la paix, mais une civilisation «à la recherche de problèmes» riposterait probablement. Chaque état de la machine aurait des effets significatifs sur la civilisation » s les paramètres décrits ci-dessus au cours de chaque «cadre» (richesse, belligérance, population, etc.). Plus important encore, vous n'avez pas besoin de transitionner les états sur chaque image - juste au moment où l'opportunité et / ou le hasard se présentent: cela permet à des événements prolongés (comme la guerre) de se produire.

kaoD
la source
Merci pour une très belle réponse, même si elle ne touche pas aux aspects techniques qui me préoccupent
pdusen
@pdusen le commentaire est devenu assez long, j'ai donc mis à jour ma réponse avec sous la marque "EDIT".
kaoD
2
Je vais ajouter à cette réponse, si cela ne vous dérange pas?
Jonathan Dickinson
@JonathanDickinson bien sûr, allez-y :)
kaoD
@pdusen J'ai ajouté des détails plus spécifiques à l'implémentation.
Jonathan Dickinson
8

Oui il y a. Voici un générateur d'histoire très simple:

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

Ce qui produit une sortie comme celle-ci:

entrez la description de l'image ici

Ajustez l'heuristique pour créer différents graphiques.

La manière la plus simple de le faire serait de changer la func = random.choice([merge, split, revolt, conquest])ligne pour avoir plus d'une fonction du même nom. Par exemple func = random.choice([merge, split, revolt, conquest, merge, merge]), les pays fusionneront plus souvent.

brice
la source