我制作了一个原始的地下城生成器(在纯JavaScript上),可以创建附近的房间。链接到一段工作代码位于帖子的底部。
现在我陷入困境:我需要在房间之间建立一个良好的门和通道系统,但我发现很难找到合适的算法。从技术上讲,没有问题:在墙壁上打洞很容易,甚至确保地牢的所有部分都相互连接。
问题是为连接房间找到合适的想法。拧紧所有可能的连接并关闭多余的?选择一个大房间,从中拉出一些走廊,然后将剩余的房间连接到公共系统?从一个角落开始,混乱地移动直到所有房间都连接起来?
我将非常感谢任何想法或建议。
更新即可。问题解决了。在raul.vila的帮助下,可以将算法带到工作状态。现在,发电机完成了到达每个房间所需的最少数量的门,然后创建了几个额外的门来改善地牢的连通性。
旧代码:https://jsfiddle.net/Gerasim/f2a8ujpy/3/
最终代码:https://jsfiddle.net/Gerasim/f2a8ujpy/29/
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
const DWIDTH = 32; // Dungeon width
const DHEIGHT = 32; // Dungeon height
const ROOMINTERVAL = 8; // Room density (3+)
const MINROOM = 3; // Min room size. Must be ROOMINTERVAL - 2 or bigger
const EXTRADOORS = 5; // Additional doors for better connectivity
arrayInstruments();
genDungeon();
showDungeon();
// Displays primitive map of the dungeon
function showDungeon() {
for (var x = 0; x < DWIDTH; x++) {
for (var y = 0; y < DHEIGHT; y++) {
switch (dungeon[x][y]) {
case 0:
ctx.beginPath();
ctx.fillStyle = "lightgrey";
ctx.rect(x * 20, y * 20, 18, 18);
ctx.fill();
break;
case 1:
ctx.beginPath();
ctx.fillStyle = "black";
ctx.rect(x * 20, y * 20, 18, 18);
ctx.fill();
break;
case 2:
ctx.beginPath();
ctx.fillStyle = "black";
ctx.rect(x * 20 + 1, y * 20 + 1, 16, 16);
ctx.rect(x * 20 + 12, y * 20 + 6, 2, 4);
ctx.stroke();
break;
}
}
}
}
// Generates dungeon of rooms and doors
function genDungeon() {
// Fill full dungeon with walls
dungeon = new Array();
dungeon.dup(1, 0, 0, DWIDTH, DHEIGHT);
// Create random seeds of growth in size MINROOM x MINROOM
var seeds = new Array();
while (true) {
var free = dungeon.biggestFree(0, 0, 0, DWIDTH, DHEIGHT);
if (free.biggest < ROOMINTERVAL) {
break;
} else {
roomX = free.x + 1 + rnd(free.biggest - 1 - MINROOM);
roomY = free.y + 1 + rnd(free.biggest - 1 - MINROOM);
dungeon.dup(0, roomX, roomY, MINROOM, MINROOM);
seeds.push({
x: roomX,
y: roomY,
width: MINROOM,
height: MINROOM,
delete: "no"
});
}
}
var rooms = [];
// Now we have seeds of growth in array rooms.
// Lets try to expand seeds by moving their walls
while (seeds.length > 0) {
for (var i = 0; i < seeds.length; i++) {
// Determine the directions in which the room can grow
var dirs = [];
if (seeds[i].x >= 2 && dungeon.freeRect(0, seeds[i].x - 2, seeds[i].y - 1, 1, seeds[i].height + 2)) {
dirs.push('left');
}
if (seeds[i].x + seeds[i].width < DWIDTH - 1 && dungeon.freeRect(0, seeds[i].x + seeds[i].width + 1, seeds[i].y - 1, 1, seeds[i].height + 2)) {
dirs.push('right');
}
if (seeds[i].y >= 2 && dungeon.freeRect(0, seeds[i].x - 1, seeds[i].y - 2, seeds[i].width + 2, 1)) {
dirs.push('up');
}
if (seeds[i].y + seeds[i].height < DHEIGHT - 1 && dungeon.freeRect(0, seeds[i].x - 1, seeds[i].y + seeds[i].height + 1, seeds[i].width + 2, 1)) {
dirs.push('down');
}
// Now expand room in random direction
if (dirs.length > 0) {
dirs.shuffle();
if (dirs[0] == 'left') {
dungeon.dup(0, seeds[i].x - 1, seeds[i].y, 1, seeds[i].height);
seeds[i].x--;
seeds[i].width++;
}
if (dirs[0] == 'right') {
dungeon.dup(0, seeds[i].x + seeds[i].width, seeds[i].y, 1, seeds[i].height);
seeds[i].width++;
}
if (dirs[0] == 'up') {
dungeon.dup(0, seeds[i].x, seeds[i].y - 1, seeds[i].width, 1);
seeds[i].y--;
seeds[i].height++;
}
if (dirs[0] == 'down') {
dungeon.dup(0, seeds[i].x, seeds[i].y + seeds[i].height, seeds[i].width, 1);
seeds[i].height++;
}
} else {
seeds[i].delete = "yes";
rooms.push(seeds[i]);
}
seeds = seeds.filter(o => o.delete == "no");
}
}
// Make required doors
var startRoom = rooms[0];
startRoom.parentRoom = startRoom;
connectToOtherRooms(rooms, startRoom);
// Make extra doors
var i = EXTRADOORS;
var limiter = 1000; // protection from an infinite loop
while (i > 0 && limiter > 0) {
if (connectToRandom(rooms)) {
i--;
}
limiter--;
}
}
// Makes tree of rooms
function connectToOtherRooms(rooms, currentRoom) {
var adyacentRoom = connectToAdyacent(rooms, currentRoom);
if (adyacentRoom) {
connectToOtherRooms(rooms, adyacentRoom);
} else {
if (currentRoom.parentRoom == currentRoom) {
return;
} else {
connectToOtherRooms(rooms, currentRoom.parentRoom);
}
}
}
// Makes door to the adyacent room
function connectToAdyacent(rooms, room) {
var nonVisitedRooms = rooms.filter(o => o != room && !o.parentRoom);
for (var i = 0; i < nonVisitedRooms.length; i++) {
var nonVisitedRoom = nonVisitedRooms[i];
if (makeDoor(room, nonVisitedRoom)) {
nonVisitedRoom.parentRoom = room;
return nonVisitedRoom;
}
}
}
// Makes door to the random room nearby
function connectToRandom(allRooms) {
var room = rnd(allRooms.length);
for (var i = 0; i < allRooms.length; i++) {
if (makeDoor(allRooms[room], allRooms[i])) {
return true;
}
}
return false;
}
// Makes door between two rooms
function makeDoor(room1, room2) {
var walls = commonWalls(room1, room2);
if (walls && !foundDoor(walls)) {
walls.shuffle();
dungeon[walls[0].x][walls[0].y] = 2;
return true;
} else {
return false;
}
}
// Checks if there is a door in walls already to avoid double doors
function foundDoor(walls) {
for (var i = 0; i < walls.length; i++) {
if (dungeon[walls[i].x][walls[i].y] > 1) {
return true;
}
}
return false;
}
// Returns array of cells between two rooms (if any)
function commonWalls(room1, room2) {
var walls = new Array();
var per1 = perimeter(room1);
var per2 = perimeter(room2);
for (var i = 0; i < per1.length; i++) {
var common = per2.filter(o => o.x == per1[i].x && o.y == per1[i].y);
if (common.length > 0) {
walls.push(common[0]);
}
}
if (walls.length > 0) {
return walls;
} else {
return false;
}
}
// Returns array of cells on the external perimeter of room
// Corner cells are excluded, since the door is not made there
function perimeter(room) {
var per = new Array();
for (x = room.x; x < room.x + room.width; x++) {
per.push({
x: x,
y: room.y - 1
});
per.push({
x: x,
y: room.y + room.height
});
}
for (y = room.y; y < room.y + room.height; y++) {
per.push({
x: room.x - 1,
y: y
});
per.push({
x: room.x + room.width,
y: y
});
}
return per;
}
// rnd(4): returns 0, 1, 2 or 3
function rnd(ceil) {
return Math.floor(Math.random() * ceil);
}
// Several instruments that I need to work with arrays
function arrayInstruments() {
// Shuffles array in random order
Array.prototype.shuffle = function() {
var j, temp;
for (var i = 0; i < this.length; i++) {
j = rnd(this.length);
temp = this[i];
this[i] = this[j];
this[j] = temp;
}
return (this);
}
// Fills rectangle in 2D-array with filler
Array.prototype.dup = function(filler, startX, startY, lengthX, lengthY) {
for (var x = startX; x < startX + lengthX; x++) {
if (!Array.isArray(this[x])) {
this[x] = new Array();
}
for (var y = startY; y < startY + lengthY; y++) {
this[x][y] = filler;
}
}
}
// Checks whether the specified area of the two-dimensional array is free of filler
// If it is free, it returns true
Array.prototype.freeRect = function(filler, startX, startY, lengthX, lengthY) {
for (var x = startX; x < startX + lengthX; x++) {
for (var y = startY; y < startY + lengthY; y++) {
if (this[x][y] == filler) {
return false;
}
}
}
return true;
}
// Returns the coordinates of the largest empty square.
// If there are several equally large empty squares, returns the coordinates of the random
Array.prototype.biggestFree = function(filler, startX, startY, lengthX, lengthY) {
var found = new Array();
for (biggest = Math.min(lengthX, lengthY); biggest > 0; biggest--) {
for (var x = startX; x <= startX + lengthX - biggest; x++) {
for (var y = startY; y <= startY + lengthY - biggest; y++) {
if (this.freeRect(filler, x, y, biggest, biggest)) {
found.push({
x: x,
y: y
});
}
}
}
if (found.length > 0) {
found.shuffle();
return {
biggest: biggest,
x: found[0].x,
y: found[0].y
};
}
}
}
}
答案 0 :(得分:0)
我将尝试描述一种算法。我们的想法是从随机房间开始创建一棵树,然后访问从当前房间到未访问过的房间的所有房间。
这应该有效:
currentRoom
变量currentRoom.parentRoom = currentRoom
,识别初始房间的技巧currentRoom
:
adyNonVisRooms
),您可以检查房间是否定义了parentRoom
属性以查看是否已访问过该房间adyNonVisRooms.length > 0
:
nextRoom = adyNonVisRooms[0]
(您可以获得一个非确定性结果的随机空间)currentRoom
与nextRoom
添加门nextRoom.parentRoom = currentRoom
currentRoom = nextRoom
adyNonVisRooms.length == 0
:
currentRoom == currentRoom.parentRoom
:
adyNonVisRooms
)adyNonVisRooms.length > 0
:goto 321 adyNonVisRooms.length == 0
:EXIT currentRoom != currentRoom.parentRoom
:
currentRoom = currentRoom.parentRoom
我有一个工作版本,您需要稍微努力才能获得随机的空间位置(函数connectIfOnTop
和connectIfOnRight
):Jsfiddle
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
const DWIDTH = 32; // Dungeon width
const DHEIGHT = 32; // Dungeon height
const ROOMINTERVAL = 8; // Room density (3+)
const MINROOM = 2; // Min room size. Must be ROOMINTERVAL - 2 or bigger
arrayInstruments();
genDungeon();
showDungeon();
function showDungeon() {
for (var x = 0; x < DWIDTH; x++) {
for (var y = 0; y < DHEIGHT; y++) {
ctx.beginPath();
ctx.rect(x * 20, y * 20, 18, 18);
if (dungeon[x][y] == 1) {
ctx.fillStyle = "black";
} else {
ctx.fillStyle = "lightgrey";
}
ctx.fill();
}
}
}
function genDungeon() {
// Fill dungeon with walls
dungeon = new Array();
dungeon.dup(1, 0, 0, DWIDTH, DHEIGHT);
// Create random seeds of growth in size MINROOM x MINROOM
var rooms = new Array();
while (true) {
var free = dungeon.biggestFree(0, 0, 0, DWIDTH, DHEIGHT);
if (free.biggest < ROOMINTERVAL) {
break;
} else {
roomX = free.x + 1 + rnd(free.biggest - 1 - MINROOM);
roomY = free.y + 1 + rnd(free.biggest - 1 - MINROOM);
dungeon.dup(0, roomX, roomY, MINROOM, MINROOM);
rooms.push({
x: roomX,
y: roomY,
width: MINROOM,
height: MINROOM,
delete: "no"
});
}
}
var finalRooms = [];
// Now we have seeds of growth in array rooms.
// Lets try to expand seeds by moving their walls
while (rooms.length > 0) {
for (i = 0; i < rooms.length; i++) {
// Determine the directions in which the room can grow
var dirs = [];
if (rooms[i].x >= 2 && dungeon.freeRect(0, rooms[i].x - 2, rooms[i].y - 1, 1, rooms[i].height + 2)) {
dirs.push('left');
}
if (rooms[i].x + rooms[i].width < DWIDTH - 1 && dungeon.freeRect(0, rooms[i].x + rooms[i].width + 1, rooms[i].y - 1, 1, rooms[i].height + 2)) {
dirs.push('right');
}
if (rooms[i].y >= 2 && dungeon.freeRect(0, rooms[i].x - 1, rooms[i].y - 2, rooms[i].width + 2, 1)) {
dirs.push('up');
}
if (rooms[i].y + rooms[i].height < DHEIGHT - 1 && dungeon.freeRect(0, rooms[i].x - 1, rooms[i].y + rooms[i].height + 1, rooms[i].width + 2, 1)) {
dirs.push('down');
}
// Now expand room in random direction
if (dirs.length > 0) {
dirs.shuffle();
if (dirs[0] == 'left') {
dungeon.dup(0, rooms[i].x - 1, rooms[i].y, 1, rooms[i].height);
rooms[i].x--;
rooms[i].width++;
}
if (dirs[0] == 'right') {
dungeon.dup(0, rooms[i].x + rooms[i].width, rooms[i].y, 1, rooms[i].height);
rooms[i].width++;
}
if (dirs[0] == 'up') {
dungeon.dup(0, rooms[i].x, rooms[i].y - 1, rooms[i].width, 1);
rooms[i].y--;
rooms[i].height++;
}
if (dirs[0] == 'down') {
dungeon.dup(0, rooms[i].x, rooms[i].y + rooms[i].height, rooms[i].width, 1);
rooms[i].height++;
}
} else {
rooms[i].delete = "yes";
}
finalRooms = finalRooms.concat(rooms.filter(o => o.delete == "yes"));
rooms = rooms.filter(o => o.delete == "no");
}
}
var startRoom = finalRooms[0];
startRoom.parentRoom = startRoom;
connectToOtherRooms(dungeon, finalRooms, startRoom);
}
function connectToOtherRooms(dungeon, allRooms, currentRoom) {
var adyacentRoom = connectToAdyacent(dungeon, allRooms, currentRoom);
if (adyacentRoom) {
connectToOtherRooms(dungeon, allRooms, adyacentRoom);
} else {
if (currentRoom.parentRoom == currentRoom) {
return;
} else {
connectToOtherRooms(dungeon, allRooms, currentRoom.parentRoom);
}
}
}
// {x: 9, y: 12, width: 3, height: 6, delete: "yes"}
function connectToAdyacent(dungeon, allRooms, room) {
var nonVisitedRooms = allRooms.filter(function(r) {
return (r != room) && !r.parentRoom;
});
for (var i = 0; i < nonVisitedRooms.length; i++) {
var nonVisitedRoom = nonVisitedRooms[i];
if (connectIfAdyacent(dungeon, room, nonVisitedRoom)) {
nonVisitedRoom.parentRoom = room;
return nonVisitedRoom;
}
}
}
function connectIfAdyacent(dungeon, room1, room2) {
return connectIfOnTop(dungeon, room1, room2) ||
connectIfOnTop(dungeon, room2, room1) ||
connectIfOnRight(dungeon, room1, room2) ||
connectIfOnRight(dungeon, room2, room1);
}
function connectIfOnTop(dungeon, roomBase, room) {
var roomBase_x1 = roomBase.x - 1;
var roomBase_x2 = roomBase.x + roomBase.width;
var roomBase_y1 = roomBase.y - 1;
//var roomBase_y2 = roomBase.y + roomBase.height;
var room_x1 = room.x - 1;
var room_x2 = room.x + room.width;
var room_y1 = room.y - 1;
var room_y2 = room.y + room.height;
if (
room_y2 == roomBase_y1 &&
(room_x1 < (roomBase_x2 - 1)) &&
(room_x2 > (roomBase_x1 + 1))
) {
dungeon[Math.max(room_x1, roomBase_x1) + 1][room_y2] = 0;
return true;
}
}
function connectIfOnRight(dungeon, roomBase, room) {
var roomBase_x1 = roomBase.x - 1;
//var roomBase_x2 = roomBase.x + roomBase.width;
var roomBase_y1 = roomBase.y - 1;
var roomBase_y2 = roomBase.y + roomBase.height;
var room_x1 = room.x - 1;
var room_x2 = room.x + room.width;
var room_y1 = room.y - 1;
var room_y2 = room.y + room.height;
if (
room_x2 == roomBase_x1 &&
(room_y1 < (roomBase_y2 - 1)) &&
(room_y2 > (roomBase_y1 + 1))
) {
dungeon[room_x2][Math.max(room_y1, roomBase_y1) + 1] = 0;
return true;
}
}
// rnd(4): returns 0, 1, 2 or 3
function rnd(ceil) {
return Math.floor(Math.random() * ceil);
}
// Several instruments that I need to work with arrays
function arrayInstruments() {
// Shuffles array in random order
Array.prototype.shuffle = function() {
var j, temp;
for (var i = 0; i < this.length; i++) {
j = rnd(this.length);
temp = this[i];
this[i] = this[j];
this[j] = temp;
}
return (this);
}
// Fills rectangle in 2D-array with filler
Array.prototype.dup = function(filler, startX, startY, lengthX, lengthY) {
for (var x = startX; x < startX + lengthX; x++) {
if (!Array.isArray(this[x])) {
this[x] = new Array();
}
for (var y = startY; y < startY + lengthY; y++) {
this[x][y] = filler;
}
}
}
// Checks whether the specified area of the two-dimensional array is free of filler
// If it is free, it returns true
Array.prototype.freeRect = function(filler, startX, startY, lengthX, lengthY) {
for (var x = startX; x < startX + lengthX; x++) {
for (var y = startY; y < startY + lengthY; y++) {
if (this[x][y] == filler) {
return false;
}
}
}
return true;
}
// Returns the coordinates of the largest empty square.
// If there are several equally large empty squares, returns the coordinates of the random
Array.prototype.biggestFree = function(filler, startX, startY, lengthX, lengthY) {
var found = new Array();
for (biggest = Math.min(lengthX, lengthY); biggest > 0; biggest--) {
for (var x = startX; x <= startX + lengthX - biggest; x++) {
for (var y = startY; y <= startY + lengthY - biggest; y++) {
if (this.freeRect(filler, x, y, biggest, biggest)) {
found.push({
x: x,
y: y
});
}
}
}
if (found.length > 0) {
found.shuffle();
return {
biggest: biggest,
x: found[0].x,
y: found[0].y
};
}
}
}
}
* {
padding: 0;
margin: 0;
}
canvas {
background: eee;
display: block;
margin: 0 auto;
}
<html>
<head>
<title>Maze Generator</title>
<meta charset="utf-8" />
</head>
<body>
<div style="padding: 15;">
<canvas id="canvas" width="640" height="640" style="background: lightgrey"></canvas>
</div>
</body>
</html>