このようなレベルを作成する最も簡単な方法は、それを解決する方法を見つけることです。このようにして、基本的にランダムな開始構成を生成し、それを解決しようとすることで有効なレベルであるかどうかを判断できます。これにより、最も多様なレベルが生成されます。
また、別の方法でレベルを生成する方法を見つけたとしても、この解決アルゴリズムを適用して、生成されたレベルが適切であることを証明する必要があります ;)
ブルートフォース列挙
ボードのサイズが NxN セルで、N 色も利用できる場合、考えられるすべての構成を (開始ノードと終了ノード間の実際のパスを形成するかどうかに関係なく) 力ずくで列挙すると、次のようになります。
- 合計 N^2 セル
- 開始ノードと終了ノードですでに占有されている 2N セル
- 色がまだ決定されていない N^2 - 2N 個のセル
- N色をご用意。
- N^(N^2 - 2N) 通りの組み合わせ。
そう、
- N=5 の場合、これは 5^15 = 30517578125 の組み合わせを意味します。
- N=6 の場合、これは 6^24 = 4738381338321616896 の組み合わせを意味します。
つまり、可能な組み合わせの数は最初はかなり多いのですが、ボードを大きくし始めると途方もなく速く増えます。
色ごとのセル数の制限
明らかに、構成の数をできるだけ減らすようにする必要があります。これを行う 1 つの方法は、各色の開始セルと終了セルの間の最小距離 ("dMin") を考慮することです。少なくとも、その色のセルがこれだけ多く存在する必要があることがわかっています。最小距離の計算は、単純なフラッド フィルまたはダイクストラのアルゴリズムで行うことができます。(注意: この次のセクション全体では、セルの数についてのみ説明していますが、それらの場所については何も述べていません)
あなたの例では、これは(開始セルと終了セルを数えない)ことを意味します
dMin(orange) = 1
dMin(red) = 1
dMin(green) = 5
dMin(yellow) = 3
dMin(blue) = 5
これは、まだ色が決定されていない 15 個のセルのうち、少なくとも 1 個のオレンジ、1 個の赤、5 個の緑、3 個の黄、および 5 個の青のセルが必要であり、合計 15 個のセルになることを意味します。この特定の例では、各色の開始セルと終了セルを最短パス (の 1 つ) で接続すると、ボード全体が塗りつぶされます。つまり、ボードを最短パスで塗りつぶした後、色のないセルは残りません。(これは「運」と考えるべきです。ボードのすべての開始構成がこれを引き起こすわけではありません)。
通常、このステップの後、自由に色付けできる多数のセルができます。この数を U と呼びましょう。N=5 の場合、
U = 15 - (dMin(orange) + dMin(red) + dMin(green) + dMin(yellow) + dMin(blue))
これらのセルは任意の色を取ることができるため、特定の色を持つことができるセルの最大数を決定することもできます。
dMax(orange) = dMin(orange) + U
dMax(red) = dMin(red) + U
dMax(green) = dMin(green) + U
dMax(yellow) = dMin(yellow) + U
dMax(blue) = dMin(blue) + U
(この特定の例では、U=0 であるため、色ごとのセルの最小数は最大数でもあります)。
距離制約を使用した経路探索
これらの色の制約を使用して可能なすべての組み合わせを力ずくで列挙すると、心配する組み合わせがはるかに少なくなります。より具体的には、この特定の例では次のようになります。
15! / (1! * 1! * 5! * 3! * 5!)
= 1307674368000 / 86400
= 15135120 combinations left, about a factor 2000 less.
ただし、これでも実際のパスはわかりません。したがって、より良いアイデアは、バックトラッキング検索を使用することです。この場合、各色を順番に処理し、次のすべてのパスを見つけようとします。
- 既に色付けされたセルを横切らない
- dMin(色) より短くなく、dMax(色) より長くない。
2 番目の基準は、色ごとに報告されるパスの数を減らします。これにより、試行されるパスの総数が大幅に削減されます (組み合わせ効果による)。
擬似コード:
function SolveLevel(initialBoard of size NxN)
{
foreach(colour on initialBoard)
{
Find startCell(colour) and endCell(colour)
minDistance(colour) = Length(ShortestPath(initialBoard, startCell(colour), endCell(colour)))
}
//Determine the number of uncoloured cells remaining after all shortest paths have been applied.
U = N^(N^2 - 2N) - (Sum of all minDistances)
firstColour = GetFirstColour(initialBoard)
ExplorePathsForColour(
initialBoard,
firstColour,
startCell(firstColour),
endCell(firstColour),
minDistance(firstColour),
U)
}
}
function ExplorePathsForColour(board, colour, startCell, endCell, minDistance, nrOfUncolouredCells)
{
maxDistance = minDistance + nrOfUncolouredCells
paths = FindAllPaths(board, colour, startCell, endCell, minDistance, maxDistance)
foreach(path in paths)
{
//Render all cells in 'path' on a copy of the board
boardCopy = Copy(board)
boardCopy = ApplyPath(boardCopy, path)
uRemaining = nrOfUncolouredCells - (Length(path) - minDistance)
//Recursively explore all paths for the next colour.
nextColour = NextColour(board, colour)
if(nextColour exists)
{
ExplorePathsForColour(
boardCopy,
nextColour,
startCell(nextColour),
endCell(nextColour),
minDistance(nextColour),
uRemaining)
}
else
{
//No more colours remaining to draw
if(uRemaining == 0)
{
//No more uncoloured cells remaining
Report boardCopy as a result
}
}
}
}
すべてのパスを検索
これにより、FindAllPaths(board, color, startCell, endCell, minDistance, maxDistance) のみが実装されます。ここで注意が必要なのは、最短パスを検索するのではなく、minDistance と maxDistance によって決定される範囲内にあるすべてのパスを検索することです。したがって、ダイクストラや A* だけを使用することはできません。それらは各セルへの最短経路のみを記録し、可能な迂回経路は記録しないからです。
これらのパスを見つける 1 つの方法は、ボードに多次元配列を使用することです。この場合、各セルは複数のウェイポイントを格納でき、ウェイポイントはペア (前のウェイポイント、原点までの距離) として定義されます。目的地に到達した後にパス全体を再構築できるようにするには、前のウェイポイントが必要であり、原点までの距離により、maxDistance を超えることはできません。
次に、startCell から外側に向かってフラッド フィルのような探索を使用して、すべてのパスを見つけることができます。この場合、特定のセルについて、色付けされていない、または現在の色と同じ色の隣接する各セルが再帰的に探索されます (形成されるものを除く)。 endCell に到達するか maxDistance を超えるまで、原点への現在のパス)。
この戦略の改善点は、startCell から外側の endCell まで探索するのではなく、Floor(maxDistance / 2) と Ceil(maxDistance / 2) をそれぞれの最大距離。maxDistance の値が大きい場合、探索されるセルの数が 2 * maxDistance^2 から maxDistance^2 に減少します。