世界の歴史を手続き的に生成する方法はありますか?


28

ここにある図は、ある人が作成した想像上の世界の1800年の文化史を表しています。

ここに画像の説明を入力してください

この種のものは、世界のデザインに関する限り、ゲーム開発に強力な用途があるように思われます。

彼はこの図を手でやったようです。私が興味を持っているのは、この種のダイアグラムをプログラムで作成する方法があるかどうかを見ることです。

ランダムな値から上記のスタイルのダイアグラムを生成するタスクを課された場合、どのように対処しますか?検討する特定のデータ構造またはアルゴリズムはありますか?


5
ドワーフ要塞を見てみましょう。ソースは入手できず、世界生成プロセスは文書化されていません(これを答えにしない理由です)が、実際にゲームをプレイすることを学ぶ必要なく、生成された世界の歴史を調べることができます。あなたができることの。
ジョシュ

答えではなく、別のリソースがwww-cs-students.stanford.edu/~amitp/game-programming / ...にあります。これは環境を生成するための記事ですが、環境がどのようになりうるかについて触れます。人々が何に、どこで、または何を好むかを争うときのためにミックスに投入される可能性のあるリソース(水、居住可能な土地など)に基づいて王国の地域境界を定義するために使用されます。
ジェームズ

1
この図は、Civilization 3のパワーグラフに非常によく似ています。いくつかのアイデアについては、そのシリーズを確認してください。
-WildWeazel

回答:


15

どのくらい正確になりたいですか?良いが複雑な選択は、そのすべての履歴をシミュレートすることです

  1. ランダムなリージョンリストとこれらのリージョン間の隣接関係を生成します。
  2. 人口、好戦性、技術などの特性を持つランダムな文明を生成し、地域に居住します。
  3. あなたが望む限り何年もの歴史をシミュレートし、文明の特性に基づいて結果を決定します。

たとえば、2つの隣接する好戦的な文明は、互いに戦争を開始する可能性が高く、時間の経過とともに人口が減少します。商人文明はより高い資源を持っていますが、侵略の大きな標的です。人口の多い人はより速く成長しますが、空腹の可能性も高くなります。文化的に異質な都市は、内部戦争の可能性が低くなります(解散につながる可能性があります)など...結果は文明の特性も変更します。技術の向上は、より良い取引、より強力な武器などにつながります。

これにより、いくつかの手続き的なストーリーテリングも可能になります。テリトリーダイアグラムだけでなく、時間全体の履歴のテキスト記述も出力できます。このシステムは、必要に応じて複雑にすることができます。


編集:ここでの課題は技術的なものではなく、現実的で興味深い履歴生成のためにヒューリスティックを調整します。よく見て、前述の3つのポイントについて考えてみてください...それはほとんど技術的な説明です!それをループに変換します(各反復は、1年、半年、1か月など、必要なだけの時間を表すことができます)。内部(データ構造、ヒューリスティック)を処理し、特定の問題やニーズに合わせて調整する必要があります。それはここでは難しい部分であり、想像力、試行錯誤に関するものなので、誰もあなたを助けることができません。

ほとんどすべての問題に使用するもの以外に、この問題に共通のデータ構造はありません:リスト、キュー、ツリー...これらは特定の実装に結び付けられます(系図ツリーが必要ですか?文明のリスト戦争中ですか?各civのタスクのキュー?)もちろん、文明のリストも必要です。選択は明白であり、ほとんど常識です。

シミュレーションは偶然/確率の問題であり、乱数を使って何千もの異なる方法を作ることができます。フットボールマネージャー、RPG(結局、ヒットポイント/統計は単なる戦闘シミュレーションです)、戦略ゲームなど、シミュレーションが関係する他のゲームを考えてください...それは単なる特徴です(したがって、文明の特徴とデータを保存する方法が必要です)そして、それらに統計的に基づくランダムな結果(したがって、これらの特性に基づいてシミュレーション状態をランダムに変更する必要があります)

これがアルゴリズムの本質です。ヒューリスティックを調整するのが難しいことです。各文明のシミュレーションの開始時に特性を配布する方法、およびそれらに基づいてシミュレーション状態を統計的に変更する方法。

要するに、あなたのアルゴリズムは、シミュレートされた時間を任意の増分で調整する単なるループです。インクリメントを短くすると、履歴シミュレーションが細かくなりますが、明らかに時間がかかります。ループ内には、(大まかに)次のようなヒューリスティックの束があります。

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

このすべての作業の後(またはデータを保存したくない場合)、すべてのシミュレーション状態を、テキスト、画像、または必要なものなど、人間が読み取れる形式に変換する必要があります。これも試行錯誤であり、実装に非常に固有のものです。

あなたの質問に固有:あなたの質問のような図を生成するには、世界の地域(図の上部、x軸、それがポイント1:私の回答で地域リスト生成)とその文明(ダイアグラム、ポイント2)から時間(y軸、ポイント3のシミュレーションループ)

ステートマシン幅広いトピックのシミュレーションが得意です(上記のコードサンプルはハードコードされた状態マシンの近似です)。したがって、全体的に微調整しやすい単純な状態マシンフレームワークを実装することから始めることができます。各文明はこれらのステートマシンの1つから始まり、各ターンでシミュレーションが各ステートマシンを実行します。各ステートマシンは他のステートマシンと対話できる必要があります。たとえば、戦争の開始は別の文明のステートマシンに影響を与え、内部状態に基づいて結果が異なる場合があります。たとえば、「飢f」状態の場合平和を交渉したいが、「トラブルを探している」文明は報復するでしょう。機械の各状態は、文明に意味のある影響を与えます。s各「フレーム」中の上記の指標(富、好戦性、人口など)。最も重要なことは、すべてのフレームで状態を移行する必要がないことです-機会および/またはランダムな機会が発生した場合のみ:これにより、長期にわたるイベント(戦争など)が発生します。


私が心配している技術的な側面には触れていませんが、非常に良い答えをありがとう
-pdusen

@pdusenのコメントはかなり長くなったので、「EDIT」マークの下にある回答を更新しました。
kaoD

2
気にしない場合は、この回答に追加しますか?
ジョナサンディキンソン

@JonathanDickinson確かに、どうぞ:)
kaoD

@pdusenさらに実装固有の詳細を追加しました。
ジョナサンディキンソン

8

はいあります。これは簡単な履歴ジェネレーターです。

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

次のような出力が生成されます。

ここに画像の説明を入力してください

ヒューリスティックを調整して、さまざまなグラフを作成します。

これを行う最も簡単な方法func = random.choice([merge, split, revolt, conquest])は、同じ名前の複数の関数を持つように行を変更することです。たとえばfunc = random.choice([merge, split, revolt, conquest, merge, merge])、国がより頻繁に合併することになります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.