有没有办法以程序方式产生一个世界的历史?


28

我发现这里的图表代表了一个人在一个虚构的世界中1800年的文化历史,对此我颇有兴趣。

在此处输入图片说明

就世界设计而言,这种东西似乎在游戏开发中具有强大的应用。

看起来他是手工绘制的。我感兴趣的是查看是否有一种以编程方式创建这种图表的方法。

如果您负责从随机值生成上述样式的图表,您将如何处理?您是否会考虑任何特定的数据结构或算法?


5
考虑看看矮人要塞。来源不可用,世界生成过程也没有记录(这就是我为什么不回答这个问题),但是您可以查看生成的世界历史,而无需实际学习玩游戏,它可能会带给您类似的想法您可以做的事情。
乔什2012年

可能在以下位置找到另一种资源,但未找到答案:www-cs-students.stanford.edu/~amitp/game-programming / ...这是一篇有关生成环境的文章,但继续探讨如何实现环境用于根据资源(如水,宜居土地等)为王国定义区域边界,这些资源可以在人们就什么地点,地点或地点进行战争时投入使用。再一次,这只是一种资源,而不是答案。
詹姆斯

1
该图看起来与《文明3》中的功效图非常相似。您可能需要查看该系列的一些想法。
WildWeazel

Answers:


15

您想要多精确?一个好的但复杂的选择将是模拟所有历史:

  1. 生成随机区域列表以及这些区域之间的邻接关系。
  2. 产生具有人口,好战,技术……等特征的随机文明,并在各地区进行填充。
  3. 根据需要模拟多年的历史,根据文明特征确定结果。

例如:两个相邻的交战文明相互之间发动战争的可能性更高,随着时间的推移,导致人口减少。商人文明拥有更高的资源,但是入侵的主要目标。人口稠密的国家将增长更快,但也有更多的饥饿机会。具有不同文化背景的公民发生内战的可能性较低(可能导致分裂)。依此类推……结果也将改变文明的特征:更高的技术会导致更好的交易,更强大的武器等。

这也允许进行一些过程性的故事讲述:您不仅可以输出区域图,还可以输出历史上的文字描述。您可以根据需要使此系统复杂。


编辑:这里的挑战不是技术性的挑战,而是调整启发法以实现现实和有趣的历史。仔细研究一下上述3点...这几乎是您的技术解释!将其转换为循环(每个迭代可以代表您想要的时间,一年,半年,1个月……),就是这样。您必须处理内部结构(数据结构,试探法),并使之适应您的特定问题和需求。这是最困难的部分,没有人可以帮助您,因为这与想象力,试验和错误有关。

除了几乎可以用于任何问题的数据结构外,没有通用的数据结构:列表,队列,树...这些将与您的特定实现相关联(我需要家谱树吗?文明列表)在战争中吗?每个文明的任务要排队吗?)当然,您还需要一份文明清单。选择是显而易见的,几乎是常识。

模拟是一个偶然性/概率问题,您可以使用随机数以千种不同的方式进行模拟。考虑一下涉及模拟的任何其他游戏,例如足球经理,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的仿真循环)。

状态机非常擅长模拟广泛的主题(上面的代码示例是硬编码状态机的近似)-因此,您可能首先要实现一个总体上易于调整的简单状态机框架。每个文明都将从这些状态机之一开始,而仿真将在每个回合中运行每个状态机。每个状态机都需要能够与其他状态机进行交互:例如,发动战争将影响另一个文明的状态机,并可能基于其内部状态而产生不同的结果-例如,如果它们处于“饥荒”状态,则可能希望谈判和平,但“寻找麻烦”的文明可能会进行报复。机器中的每个状态都会对文明产生重大影响。在每个“框架”(财富,好战,平民等)中概述的指标。最重要的是,您不需要在每个帧上都转移状态-只是在机会和/或随机机会出现时就可以了:这允许长时间的事件(例如战争)发生。


感谢您提供非常好的回答,尽管它并未涉及我所关注的技术问题
pdusen 2012年

@pdusen评论已经很长了,所以我用“ EDIT”标记更新了答案。
kaoD 2012年

2
如果您不介意,我将添加此答案。
乔纳森·迪金森'02

@JonathanDickinson当然,继续:)
kaoD 2012年

@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])将导致国家更频繁地合并。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.