forked from Mirrors/openclonk
262 lines
7.0 KiB
C
262 lines
7.0 KiB
C
/*--
|
|
Dynamic maze
|
|
|
|
Author: Sven2
|
|
--*/
|
|
|
|
#include Library_Map
|
|
|
|
static g_caves, g_end_cave_x, g_end_cave_y;
|
|
|
|
local caves, n_caves, start_cave, end_cave;
|
|
|
|
func FindCaves(int n)
|
|
{ //n=6;
|
|
var mask = CreateLayer();
|
|
mask->Draw("Rock");
|
|
caves = [];
|
|
var min_cave_dist = 12, border = 5;
|
|
while (n--)
|
|
{
|
|
var cave = {};
|
|
if (!mask->FindPosition(cave, "Rock", [border,border,this.Wdt-border*2,this.Hgt-border*2])) continue;
|
|
mask->Draw("Tunnel", {Algo=MAPALGO_Ellipsis, X=cave.X, Y=cave.Y, Wdt=min_cave_dist, Hgt=min_cave_dist});
|
|
cave.links = [];
|
|
cave.dirs = 0;
|
|
cave.rand = Random(65536);
|
|
cave.depth = -1;
|
|
caves[n_caves++] = cave;
|
|
}
|
|
/* caves[0].X = 10; caves[0].Y = 10;
|
|
caves[1].X = 30; caves[1].Y = 10;
|
|
caves[2].X = 10; caves[2].Y = 30;
|
|
caves[3].X = 30; caves[3].Y = 70;
|
|
caves[4].X = 50; caves[4].Y = 10;
|
|
caves[5].X = 50; caves[5].Y = 30;*/
|
|
return n_caves;
|
|
}
|
|
|
|
func GetCaveLinkDir(c1, c2)
|
|
{
|
|
// Returns if c2 is left (1), right (2), atop (4) or below (8) c1. Returns only one direction.
|
|
var dx=c2.X-c1.X, dy=c2.Y-c1.Y, adx=Abs(dx), ady=Abs(dy);
|
|
//Log("%d,%d to %d,%d dx=%d dy=%d", c1.X, c1.Y, c2.X, c2.Y, dx, dy);
|
|
return (dx<-ady) | (dx>ady)<<1 | (dy<=-adx)<<2 | (dy>=adx)<<3;
|
|
}
|
|
|
|
func FindCaveConnections()
|
|
{
|
|
var i, j, cave, cave2, caves2, dir, dir2, n, check_link, all_links = [];
|
|
// Connect all caves to neighbours
|
|
for (i=0; i<n_caves; ++i)
|
|
{
|
|
cave = caves[i];
|
|
caves2 = caves[i+1:n_caves];
|
|
for (cave2 in caves2) cave2.d = Distance(cave.X, cave.Y, cave2.X, cave2.Y);
|
|
SortArrayByProperty(caves2, "d");
|
|
for (cave2 in caves2)
|
|
{
|
|
// Make sure there's a max of one connection per direction (up/left/right/down)
|
|
dir = GetCaveLinkDir(cave, cave2);
|
|
//Log("Cave %d to %d direction %d.", GetIndexOf(caves, cave), GetIndexOf(caves, cave2), dir);
|
|
if (cave.dirs & dir) continue;
|
|
dir2 = dir >> 1& 5|10 &dir << 1;
|
|
//Log("dir %d opposite to %d.", dir2, dir);
|
|
if (cave2.dirs & dir2) continue;
|
|
// Make sure the connectors don't cross each other
|
|
// Note that connectors may still overlap the edges of caves, effectively connecting them
|
|
// But since nothing really "breaks" on this occasion, just stick with the simple check for now.
|
|
var has_overlap = false;
|
|
for (check_link in all_links)
|
|
if (check_link[0] != cave && check_link[1] != cave)
|
|
if (check_link[0] != cave2 && check_link[1] != cave2)
|
|
if (IsLineOverlap(cave.X, cave.Y, cave2.X, cave2.Y, check_link[0].X, check_link[0].Y, check_link[1].X, check_link[1].Y))
|
|
{ has_overlap=true; break; }
|
|
if (has_overlap) continue;
|
|
// Connect these caves
|
|
cave.links[GetLength(cave.links)] = cave2;
|
|
cave2.links[GetLength(cave2.links)] = cave;
|
|
cave.dirs |= dir;
|
|
cave2.dirs |= dir2;
|
|
// Register and count connections
|
|
all_links[n++] = [cave, cave2];
|
|
// All directions connected?
|
|
if (cave.dirs == 0xf) break;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
func FindStart()
|
|
{
|
|
SortArrayByProperty(caves, "X");
|
|
start_cave = caves[0];
|
|
return start_cave;
|
|
}
|
|
|
|
func MakeMaze()
|
|
{
|
|
// Make maze by removing unnecesseriy links
|
|
start_cave.path = [];
|
|
var open = [start_cave], n_open = 1;
|
|
while (n_open)
|
|
{
|
|
var i_cave = n_open-1-Random(1+Random(n_open)); // Prefer depth-first generation so stray paths are deeper
|
|
var cave = open[i_cave];
|
|
open[i_cave] = open[--n_open]; // SetLength(open, n_open) not nessessery because length is stored in n_open
|
|
var path_length = GetLength(cave.path);
|
|
var path_to_cave = cave.path[:];
|
|
path_to_cave[path_length] = cave;
|
|
cave.depth = path_length;
|
|
for (var cave2 in cave.links[:]) // force a copy because cave.links is modified in the loop
|
|
{
|
|
if (path_length && cave2 == path_to_cave[path_length-1]) continue;
|
|
// Only first path survives
|
|
if (cave2.path)
|
|
{
|
|
// Remove circular path
|
|
RemoveCaveLinks(cave, cave2);
|
|
}
|
|
else
|
|
{
|
|
// Remember path to this cave
|
|
cave2.path = path_to_cave;
|
|
open[n_open++] = cave2;
|
|
}
|
|
}
|
|
}
|
|
// Sort from start to goal
|
|
SortArrayByProperty(caves, "depth");
|
|
// Close one connection 2/3rds of the way to the goal
|
|
var main_path = caves[n_caves-1].path;
|
|
var main_path_length = GetLength(main_path);
|
|
if (main_path_length > 5) RemoveCaveLinks(main_path[main_path_length*2/3], main_path[main_path_length*2/3+1]);
|
|
// Kill unreachable caves
|
|
var i;
|
|
for (i=0; i<n_caves; ++i) if (caves[i].depth>=0) break;
|
|
caves = caves[i:n_caves];
|
|
n_caves -= i;
|
|
end_cave = caves[n_caves-1];
|
|
return true;
|
|
}
|
|
|
|
func RemoveCaveLinks(c1, c2)
|
|
{
|
|
var i = GetIndexOf(c1.links, c2), n = GetLength(c1.links) - 1;
|
|
if (i>=0)
|
|
{
|
|
c1.links[i] = c1.links[n];
|
|
SetLength(c1.links, n);
|
|
c1.dirs &= ~GetCaveLinkDir(c1,c2);
|
|
}
|
|
i = GetIndexOf(c2.links, c1); n = GetLength(c2.links) - 1;
|
|
if (i>=0)
|
|
{
|
|
c2.links[i] = c2.links[n];
|
|
SetLength(c2.links, n);
|
|
c2.dirs &= ~GetCaveLinkDir(c2,c1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
func DrawVariations(string mat, int ratio, int sx, int sy)
|
|
{
|
|
var rand_algo = {Algo=MAPALGO_RndChecker, Ratio=ratio, Wdt=sx, Hgt=sy};
|
|
var turb_algo = {Algo=MAPALGO_Turbulence, Amplitude=12, Scale=8, Op=rand_algo};
|
|
return Draw(mat, turb_algo);
|
|
}
|
|
|
|
func DrawBackground()
|
|
{
|
|
Draw("Rock");
|
|
DrawVariations("Rock", 50, 5,15);
|
|
DrawVariations("Ore", 10, 8,8);
|
|
DrawVariations("Firestone", 8, 12,3);
|
|
DrawVariations("Coal", 8, 8,3);
|
|
DrawVariations("Gold", 5, 4,4);
|
|
DrawVariations("Granite", 14, 15,5);
|
|
DrawVariations("Granite", 14, 5,15);
|
|
return true;
|
|
}
|
|
|
|
func DrawCaves()
|
|
{
|
|
for (var cave in caves)
|
|
{
|
|
var cave_algo = {Algo=MAPALGO_Ellipsis, X=cave.X, Y=cave.Y, Wdt=4, Hgt=4};
|
|
var turb_algo = {Algo=MAPALGO_Turbulence, Amplitude=8, Scale=15, Op=cave_algo};
|
|
Draw("Tunnel", turb_algo);
|
|
//var src = cave.path;
|
|
//if (src && GetLength(src)) src = Format("%d,%d", src[GetLength(src)-1].X, src[GetLength(src)-1].Y);
|
|
//Log("Cave at %d,%d src %v", cave.X, cave.Y, src);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
func DrawTunnels()
|
|
{
|
|
for (var cave in caves)
|
|
{
|
|
for (var cave2 in cave.links)
|
|
{
|
|
if (cave2.done) continue;
|
|
var tunnel_algo = {Algo=MAPALGO_Polygon, X=[cave.X, cave2.X], Y=[cave.Y, cave2.Y], Wdt=2, Open=1, Empty=1 };
|
|
var turb_algo = {Algo=MAPALGO_Turbulence, Amplitude=9, Scale=10, Op=tunnel_algo};
|
|
Draw("Tunnel", turb_algo);
|
|
}
|
|
cave.done = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
func DrawStart()
|
|
{
|
|
Draw("Tunnel", nil, [0, start_cave.Y - 8, start_cave.X + 4, 8]);
|
|
Draw("Brick", nil, [0, start_cave.Y, start_cave.X-2, 2]);
|
|
return true;
|
|
}
|
|
|
|
func DrawEnd()
|
|
{
|
|
Draw("Ruby", {Algo=MAPALGO_Ellipsis, X=end_cave.X, Y=end_cave.Y, Wdt=4, Hgt=4});
|
|
g_end_cave_x = end_cave.X;
|
|
g_end_cave_y = end_cave.Y;
|
|
return true;
|
|
}
|
|
|
|
protected func InitializeMap(map)
|
|
{
|
|
var s = SCENPAR_MapSize-1;
|
|
//var d = SCENPAR_Difficulty-1;
|
|
var map_size = [[120,120], [200,200], [330,330]][s];
|
|
var find_cave_iterations = [100,150,300][s];
|
|
|
|
Resize(map_size[0],map_size[1]);
|
|
|
|
FindCaves(find_cave_iterations);
|
|
FindCaveConnections();
|
|
FindStart();
|
|
MakeMaze();
|
|
|
|
DrawBackground();
|
|
DrawTunnels();
|
|
DrawCaves();
|
|
DrawStart();
|
|
DrawEnd();
|
|
|
|
// Caves to global var: Need to clean up circular prop list references because they cause crashes
|
|
var cave;
|
|
for (cave in end_cave.path)
|
|
{
|
|
cave.is_main_path = true;
|
|
}
|
|
for (cave in caves)
|
|
{
|
|
cave.n_links = GetLength(cave.links);
|
|
cave.links = cave.path = nil;
|
|
}
|
|
g_caves = caves;
|
|
return true;
|
|
}
|
|
|