dungeon-generator/src/main.c

314 lines
7.0 KiB
C

#include "raylib.h"
#include "stdlib.h"
#include "stdio.h"
#include "math.h"
#include "time.h"
#include "mst.h"
#include "const.h"
const int screenWidth = 800;
const int screenHeight = 800;
const int radius = 20;
typedef struct R {
int x;
int y;
int w;
int h;
} Room;
// In this example, the idea is that rooms will be made up of tiles in a game
// 1 pixel/unit in this algorithm is intended to
Room rooms[ROOM_COUNT];
// define each corridor as a branch between a room and its "parent"
int *parentRooms;
// then afterwards we can define corridors as individual lines
typedef struct {
int ax, ay, bx, by;
} Corridor;
// Since one connecting branch is at most two perpendicular corridors
// we can
Corridor corridors[ROOM_COUNT*2];
// extend build in C random function to return a value from 0.0d to 1.0d
double randomDouble() {
return rand() / (double)(RAND_MAX);
}
// Randomly position rooms around the map, spanning around a circular space
void positionRooms() {
for (int i = 0; i < ROOM_COUNT; i++) {
double t = 2 * PI * randomDouble();
double u = 2 * randomDouble();
double r;
if (u > 1) {
r = 2-u;
} else {
r = u;
}
int x = (screenWidth/2) + radius*r*cos(t);
int y = (screenHeight/2) + radius*r*sin(t);
int w = MIN_SIZE + randomDouble() * (MAX_SIZE-MIN_SIZE);
int h = MIN_SIZE + randomDouble() * (MAX_SIZE-MIN_SIZE);
rooms[i] = (Room){x, y, w, h};
}
}
// Determine if two rooms are colliding with each other or not.
// true: colliding
// false: not colliding
bool areColliding(Room a, Room b) {
bool h1 = (b.x < a.x+a.w);
bool h2 = (b.x+b.w > a.x);
bool v1 = (b.y < a.y+a.h);
bool v2 = (b.y+b.h > a.y);
return h1 && h2 && v1 && v2;
}
void separateRooms() {
// Move the rooms away from each other until there are no collisions left
bool d = true;
while (d) {
// set d to false. If no rooms are moved, then we are done with this
d = false;
// iterate through all rooms and check if all other rooms are colliding with each room
for (int i = 0; i < ROOM_COUNT; i++) {
for (int j = 0; j < ROOM_COUNT; j++) {
// only check if they are different rooms
if (j != i) {
// only move it if they are colliding
if (areColliding(rooms[i], rooms[j])) {
d = true;
Room *room1 = &rooms[i];
Room *room2 = &rooms[j];
// calculate the difference in centers of the two rooms
int c1x = room1->x + (room1->w/2);
int c1y = room1->y + (room1->h/2);
int c2x = room2->x + (room2->w/2);
int c2y = room2->y + (room2->h/2);
int dx = c1x - c2x;
int dy = c1y - c2y;
// move the current room away from the other room by the difference of centers + half the width of the room
room1->x += dx;
room1->y += dy;
room1->x += (abs(dx)/dx) * (room1->w/2);
room1->y += (abs(dy)/dy) * (room1->h/2);
}
}
}
}
}
}
// function to get the center of each room as a Vector2 point.
Vector2 getCenter(Room *room) {
return (Vector2){room->x+(room->w/2), room->y+(room->h/2)};
}
void mapCorridors() {
// Make things easier for us by working with only the center points of each room
Vector2 nodes[ROOM_COUNT];
for (int i = 0; i < ROOM_COUNT; i++) {
nodes[i] = getCenter(&rooms[i]);
}
// Map the rooms points into an adjacency matrix
int graph[ROOM_COUNT][ROOM_COUNT];
for (int i = 0; i < ROOM_COUNT; i++) {
for (int j = 0; j < ROOM_COUNT; j++) {
// we only really need to compare distances between nodes, so to save time, we will leave distances squared
int dx = (nodes[i].x - nodes[j].x);
int dy = (nodes[i].y - nodes[j].y);
int distanceSquared = dx*dx + dy*dy;
graph[i][j] = distanceSquared;
}
}
parentRooms = primMST(graph);
}
void createCorridors() {
// for each branch between parent and child, calculate the best corridor arrangement
for (int i = 0; i < ROOM_COUNT; i++) {
if (parentRooms[i] > -1) {
Room child = rooms[i];
Room parent = rooms[parentRooms[i]];
if ((child.x > parent.x && child.x < parent.x+parent.w)
|| (parent.x > child.x && parent.x < child.x+child.w)) {
// case 1: the two can have a single vertical corridor
int cx, ay, by;
if (child.x > parent.x) cx = (child.x + parent.x+parent.w) / 2;
else cx = (parent.x + child.x+child.w) / 2;
if (child.y > parent.y+parent.h) {
ay = child.y;
by = parent.y+parent.h;
} else if (parent.y > child.y+child.h) {
ay = parent.y;
by = child.y+child.h;
}
corridors[i*2] = (Corridor){cx, ay, cx, by};
} else if ((child.y > parent.y && child.y < parent.y+parent.h)
|| (parent.y > child.y && parent.y < child.y+child.h)) {
// case 2: the two can have a single horizontal corridor
int cy, ax, bx;
if (child.y > parent.y) cy = (child.y + parent.y+parent.h) / 2;
else cy = (parent.y + child.y+child.h) / 2;
if (child.x > parent.x+parent.w) {
ax = child.x;
bx = parent.x+parent.w;
} else if (parent.x > child.x+child.w) {
ax = parent.x;
bx = child.x+child.w;
}
corridors[i*2] = (Corridor){ax, cy, bx, cy};
} else {
// case 3: the two need to have an L or Г shaped double corridor
Vector2 a = getCenter(&child);
Vector2 b = getCenter(&parent);
// point on child
int ax, ay;
// point on parent
int bx, by;
// common point
int cx, cy;
if (randomDouble() > 0.5) {
cx = a.x;
cy = b.y;
} else {
cx = b.x;
cy = a.y;
}
if (cx == a.x) {
ax = cx;
if (abs(child.y - cy) < abs(child.y+child.h - cy)) {
ay = child.y;
} else {
ay = child.y+child.h;
}
}
if (cx == b.x) {
bx = cx;
if (abs(parent.y - cy) < abs(parent.y+parent.h - cy)) {
by = parent.y;
} else {
by = parent.y+parent.h;
}
}
if (cy == a.y) {
ay = cy;
if (abs(child.x - cx) < abs(child.x+child.w - cx)) {
ax = child.x;
} else {
ax = child.x+child.w;
}
}
if (cy == b.y) {
by = cy;
if (abs(parent.x - cx) < abs(parent.x+parent.w - cx)) {
bx = parent.x;
} else {
bx = parent.x+parent.w;
}
}
corridors[i*2] = (Corridor){ax, ay, cx, cy};
corridors[i*2+1] = (Corridor){bx, by, cx, cy};
}
}
}
}
void generateRooms() {
positionRooms();
separateRooms();
mapCorridors();
createCorridors();
}
int main(void) {
if (SEED > 0) srand(SEED);
else srand(time(NULL));
generateRooms();
InitWindow(screenWidth, screenHeight, "dungeon generation");
SetTargetFPS(20);
while (!WindowShouldClose()) {
BeginDrawing();
ClearBackground(GRAY);
for (int i = 0; i < ROOM_COUNT; i++) {
Color c;
if (i == 0) c = BLACK;
else c = LIGHTGRAY;
DrawRectangleLines(rooms[i].x, rooms[i].y, rooms[i].w, rooms[i].h, c);
int p = parentRooms[i];
if (p > -1 && p < ROOM_COUNT) {
Vector2 a = getCenter(&rooms[i]);
Vector2 b = getCenter(&rooms[p]);
}
}
for (int i = 0; i < ROOM_COUNT*2; i++) {
Corridor c = corridors[i];
DrawLine(c.ax, c.ay, c.bx, c.by, LIGHTGRAY);
}
EndDrawing();
}
CloseWindow();
return 0;
}