グラフを段階的に生成するにはどうすればよいですか?


8

テレポーターで接続された手続き的に生成された場所でゲームの世界を構成したいという新しいプロジェクトを始めたところです。少し調べてみたところ、誰が議論しているかによって、「グラフ理論」または「流血複雑」と呼ばれることがわかりました。残念ながら、グラフの生成に関する情報はほとんど見つかりませんでした。私が見たほとんどのツールは、既存のグラフを調べることに向けられています。

用語が正しく整理されていると仮定すると、私の要件はグラフが次のとおりであることです。

  • シンプル—位置(頂点)にはそれ自体に接続するテレポーター(エッジ)がなく、2つの頂点にそれらを接続する複数のエッジがあるべきではありません。
  • connected —グラフ内の任意の2つの頂点の間を移動できるはずです(ただし、パスを見つける必要があるとは思いもしません。プレイヤーがパスを選択すれば、パスを見つけることができると知っていれば十分です)。
  • 循環—任意の2つの頂点間に複数のパスが必要です
  • 無向—すべてのエッジはどちらの方向にも移動できます
  • 無限—プレイヤーが望めば、無限に移動できるはずです。探索されていない最も外側の頂点に近づくにつれて、グラフは徐々に生成され続けます。
  • ローカルで有限—頂点の次数は、プレーヤーが訪問した後は決して変更されません。
  • 安定したラベル—各頂点は、それ自体がシードから手続き的に生成される場所を表します。プレイヤーがそこを移動するために使用したパスや、グラフがどれだけ大きいかには関係なく、同じシードを頂点に割り当てる必要があります。

頂点として2Dパーリンノイズの極大値を使用する(入力xyをラベルとして使用できるようにする)ことについていくつかのアイデア(まだ実装しようとしていません)がありましたが、それは不格好で複雑すぎます。

このようなグラフを生成するより良い方法はありますか?私はPanda3Dとnumpyを使用してPython 2.6で開発しています。もちろん、この問題を解決するために他のライブラリを含めてもかまいません。

編集する

私は自分の要件のいくつかを説明するのが苦手だったと思うので、それはイラストの時間です!うまくいけば、これは物事を片付けるでしょう。

ラベルが安定しているということは、たとえば、プレーヤーAがたくさんの探索を行って、特に開始位置に戻る循環パスや猫のような山を見つけられるようにしたいということです。彼のゲームは次のようになります(頂点にはシードとエッジがあり、プレイヤーがトラバースした順序で番号が付けられています)。彼は頂点8329(緑)から始め、ハッピーキャットマウンテンは頂点6745(青)にあります。

プレーヤーAの世界グラフ

プレイヤーAの仲良しプレイヤーBは猫のファンなので、彼女に見せたいと思っています。彼は彼女に彼の世界のルートシードと興味のある山へのより短いルートに沿った方向を与えます。彼女のゲームは次のようになります。

プレーヤーBの世界グラフ

私が現在最も苦労している問題は、「彼女の探索が同じ経路をたどっていないときに、プレーヤーBに同じシードを生成するにはどうすればよいですか?」です。 これが、Perlinノイズを使用するアイデアに私を導いたものです。同じルートシードが使用されている限り、最大値は移動しないため、それらの座標は安定した頂点シードとして使用できます。


接続グラフがこれに適さないのはなぜですか?mathworld.wolfram.com/ConnectedGraph.html要点が足りないかもしれません。ユーザーが1つの場所から別の場所に移動する必要があり、すべてが接続されている場合は、場所のリストを提供し、世界地図上のユーザーの位置を新しい場所に移動します。
ブランドン、2012

@brandon —「接続済み」は、私がリストする2番目のプロパティです。:-)
ベンブランク

私のポイントは、あるノードから他のノードに移動できるかどうかです。彼らがテレポーターを訪問するとき、これを除いて訪問したすべてのノードのリストを彼らに与えてください。グラフを作成する必要はありません。訪問したすべてのノードとその場所のリストを保持し、ノードが選択した場所にテレポートするだけです。私は誤解していますか?
ブランドン、2012

「周期的」および「局所的に有限」を除いて、それらの用語のほとんどすべての説明は正しいです。1つ目は、グラフを円のように制限します。ある頂点から別の頂点へのパスが複数あるという要件に使用できる用語は、「2接続」です。「局所的に有限」とは、各頂点に有限数のエッジがあることを意味します。
Harry Stern

@Harry Stern —私の理解では、循環グラフは少なくとも1つのグラフサイクルを含むグラフであるということです。単一のグラフサイクルのみで構成されるグラフであるサイクルグラフについて話しているようです。私は特に、「2接続」(「単純」を参照)のグラフを探していません。そして、そう、それが私が「局所的に有限」という意味でした。
ベンブランク

回答:


6

あなたは無限のグラフを作ることはできません。あなたの記憶は有限なので、頂点と辺の数も有限です。あなたができることは、有限グラフを作成し、それにさらにグラフを追加することです。あなたはこれに気づいたようですが、行き止まりの道を下らないように明示的に述べることが重要だと思います。

「最も外側の頂点」について話すときは、十分に注意する必要があります。グラフは、頂点のセット、エッジのセット、およびこの2つを関連付ける関数です。適用しない限り、幾何学的解釈はありません。たとえば、これらの画像はどちらもまったく同じグラフを示しています。最初の画像では、頂点2は「最も外側の」頂点と見なされますが、2番目の画像では、頂点2は「最も外側の」頂点と見なされません。3次元を考えた場合、すべての頂点が「最外部」であると言えます。

グラフ1 グラフ2

つまり、「最も外側の」頂点が何であるかを知るには、他の情報が必要です。(x、y)のペアを使用すると、幾何学的表現を視覚化しやすくなりますが、それほど遠くに移動する必要はないと思います。あなたが言うことから、あなたが知る必要があるのは、どの頂点がすでにグラフにあるかだけです。

頂点にアクセスするたびにこれを実行した場合:

if(this.needsNeighbours)
{
    List<int> connections = genRandomNumbers(n);
    foreach (int connection in connections)
    {
        //Simple graph
        if(connection == this.seed || this.hasNeighbour(connection))
        {
            continue;
        }
        //Connections to already existing, unvisited vertices
        else if(nodeMap.containsKey(connection) && 
                nodeMap.getByKey(connection).needsNeighbours)
        {
            nodeMap.getByKey(connection).addNeighbour(this.seed);
            this.addNeighbour(connection);
        }
        //Grow graph with new vertices
        else
        {
            nodeMap.add(connection, new Node(connection));
            nodeMap.getByKey(connection).addNeighbour(this.seed);
            this.addNeighbour(connection);
        }
    }
    this.needsNeighbours = false;
}

グラフは、循環的であることを除いて、すべての要件を満たします。実際に保証が必要かどうかはわかりません。その場合、未訪問のノードを具体的に選択して接続することができます。これにより、現在のノードと既に訪問済みのノード間のパスが保証されます。現在の場所を取得するためにノードにアクセスしました。少なくとも2つのパスがあります。

すべての新しいノードが少なくとも1つの接続を取得するために接続され、明示的にチェックされるため、接続は単純です。エッジは、訪問前または最初の訪問時にのみ追加され、訪問されていないノードにのみ追加されるため、ローカルで有限です。技術的には無向ではありませんが、機能的には、両方向に方向エッジを作成するのと同じです。ノードには任意のラベルを付けることができます。生成された乱数を使用しますが、1つはシードである他のパラメーターをコンストラクターに追加できます。


あなたは私が「無限」によって意味したことを正確に認識し、「最も外側の」についてのあなたの主張はよく理解されています—私は私の質問で言葉使いを微調整しました。ただし、私はあなたのジェネレーターを正しく理解していないか、これが私のニーズを十分に説明していません。これは、これが同じ頂点への異なるパスに対して同じシードを生成しないためです。質問にいくつかのイラストを追加しました。これにより、達成しようとしていることをより明確に理解できます。:-)
ベンブランク

私はあなたが今何を意味するかを理解しています。それは少し難しいです。必要なことは、genRandomNumbersの呼び出しを、入力に対して常に同じ数のセットを返す関数に変更し、ノードのシードを引数として使用することです。これにより、どのパスを使用する場合でも、どのノードで開始する場合でも、同じ接続とシードが確実に得られます。Aが接続するノードBの数のセットにもAが含まれていることに注意して、無向グラフプロパティを取得する必要があります。これを行わないと、有向グラフが表示されます。
チューイーガムボール

1

1つの方法:

init:
  root = generateLocation using random seed
  store root's seed
  place player in root location

when the player enters a new location:
  release the memory used by locations that are further than 1 edge away, but keep their seeds
  generate some neighbors for the new location. for every neighbor n:
    gen.seed(getSeed(n))
    n = generateLocation using n's random seed
    numCyclicEdges = gen.randint(0, 1)
    create numCycleEdges edges to existing locations

getSeed(n):
  if(n already has a seed) return n's seed
  else return randomSeed()

省略した詳細はたくさんありますが、これは一般的なアイデアを捉えたものです。ポータル間に存在する世界距離の大きさ、利用可能なメモリの量などに応じて、現在の場所からより離れたエッジのメモリ内にネイバーを保持したい場合があります。

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