Skip to content

Instantly share code, notes, and snippets.

@gwx
Created September 29, 2013 21:49
Show Gist options
  • Save gwx/6756909 to your computer and use it in GitHub Desktop.
Save gwx/6756909 to your computer and use it in GitHub Desktop.
random rooms
// This is a library for array based integer lists.
// Each list is an array.
// The first element is the length.
// The rest of the elements are the contents.
// Because of this, indices are 1-based.
const int SIZE = 0;
void ArrayList_Clear(int list) {
list[0] = 0;}
void ArrayList_Append(int list, int value) {
list[SIZE]++;
list[list[SIZE]] = value;
//ArrayList_Print(list);
}
int ArrayList_IndexOf(int list, int value) {
int size = list[0];
for (int index = 1; index <= size; index++) {
if (list[index] == value) {
return index;}}
return -1;}
// Returns removed value.
int ArrayList_Remove(int list, int index) {
int size = list[SIZE];
if (index > size) {return -1;}
int value = list[index];
for (; index < size; index++) {
list[index] = list[index + 1];}
list[0]--;
//ArrayList_Print(list);
return value;}
// Removes a random element.
int ArrayList_RemoveRandom(int list) {
return ArrayList_Remove(list, Rand(list[SIZE]) + 1);}
void ArrayList_Print(int list) {
int msg[256];
int format[] = "List (%d):";
sprintf(msg, format, list[SIZE]);
TraceS(msg);
for (int i = 1; i <= list[SIZE]; i++) {
int format[] = " %d";
int msg[32];
sprintf(msg, format, list[i]);
TraceS(msg);}
TraceNL();}
////////////////////////////////////////////////////////////////
//// Constants
const int VARIANT_COUNT = 6;
////////////////
//// Level
// The number of the level.
const int LEVEL_NUMBER = 0;
// The number of rooms in the level.
const int LEVEL_ROOM_COUNT = 1;
// The level's start room.
const int LEVEL_ROOM_START = 2;
// The level's end room.
const int LEVEL_ROOM_END = 3;
////////////////
//// Rooms
// Mask for the direction for the room's source.
const int ROOM_SOURCE_MASK = 0x7;
// Set if the room is currently in use.
const int ROOM_IN_USE = 0x10000;
// Up-going stairs.
const int ROOM_UP = 0x4;
// Down-going stairs.
const int ROOM_DOWN = 0x8;
// Non-terminal room.
const int ROOM_MIDDLE = 0x10;
// The boss key room.
const int ROOM_BKEY = 0x20;
// A normal key room.
const int ROOM_KEY = 0x40;
// Mask for the room containing something already.
const int ROOM_FULL_MASK = 0x6C;
////////////////
//// Doors
const int DOOR_WALL = 1;
const int DOOR_OPEN = 2;
const int DOOR_LOCK = 4;
const int DOOR_BOSS = 5;
const int DOOR_BOMB = 6;
////////////////
//// Flags
// A new room was carved.
const int DGCDF_NEW_ROOM = 1;
////////////////////////////////////////////////////////////////
//// Functions
////////////////
//// Carves a level into an 8x8 array.
////
//// levelInfo - The array of level info. Uses the LEVEL_ constants as
//// indices. Should be at least size 4.
////
//// rooms - The array of rooms. Each level takes 64 rooms, so this
//// array must be at least length <level * 64>.
////
//// doors - The array of doors (4 to a room). Each level has up to
//// 256 doors, so this array must be at least length <level * 256>.
////
//// variants - The array of room variants. Should be the same size as
//// the rooms array.
////
//// start - The index of the start room. From 0 to 63.
void DungeonGen_CarveLevel(
int levelInfo, int rooms, int doors, int variants, int start) {
int level = levelInfo[LEVEL_NUMBER];
// All doors that have been created but not yet assigned.
int list_UnusedDoors[257];
// Offsets based on the current level.
int roomOff = (level - 1) << 6;
int doorOff = (level - 1) << 8;
// Just pick the variants randomly.
for (int i = 0; i < 64; i++) {
variants[roomOff + i] = Rand(VARIANT_COUNT);}
//// Initialize with the start room.
// Mark the starting room with an up staircase.
rooms[roomOff + start] = ROOM_IN_USE + ROOM_UP;
levelInfo[LEVEL_ROOM_COUNT] = 1;
// Add each of the room's doors to the unused list.
for (int dir = 0; dir < 4; dir++) {
ArrayList_Append(list_UnusedDoors, doorOff + (start << 2) + dir);}
//// Loop through every unused door until they are all used. This
//// loop adds doors as they are created, as well.
while (list_UnusedDoors[SIZE] > 0) {
// Grab a random unused door.
int door = ArrayList_RemoveRandom(list_UnusedDoors);
int carve = DungeonGen_CarveDoor(levelInfo, rooms, doors, door);
// If we created a new room.
if (carve & DGCDF_NEW_ROOM) {
int room = DungeonGen_DoorTargetRoom(door);
// Mark the room as in use.
rooms[room] |= ROOM_IN_USE;
// Mark the new room's source direction.
rooms[room] |= (door & 3) ^ 1;
// Count a new created room.
levelInfo[LEVEL_ROOM_COUNT]++;
// Add the unused doors in the new room.
for (int i = 0; i < 4; i++) {
int door = room * 4 + i;
if (doors[door] == 0) {
ArrayList_Append(list_UnusedDoors, door);}}
// Mark the source room as being non-terminal.
room = DungeonGen_DoorSourceRoom(door);
rooms[room] |= ROOM_MIDDLE;}}
// Collect all the terminal rooms.
int list_TerminalRooms[65];
DungeonGen_CollectTerminalRooms(level, rooms, list_TerminalRooms);
// Pick a random terminal room for the exit.
levelInfo[LEVEL_ROOM_END] = ArrayList_RemoveRandom(list_TerminalRooms);
rooms[levelInfo[LEVEL_ROOM_END]] |= ROOM_DOWN;
// Change the exit's source door to the boss key door.
int sourceDoor = DungeonGen_RoomSourceDoor(rooms, levelInfo[LEVEL_ROOM_END]);
doors[sourceDoor] = DOOR_BOSS;
doors[DungeonGen_DoorOpposite(sourceDoor)] = DOOR_BOSS;
// Pick another terminal room for the boss key.
if (list_TerminalRooms[SIZE] > 0) {
int bkeyRoom = ArrayList_RemoveRandom(list_TerminalRooms);
rooms[bkeyRoom] |= ROOM_BKEY;}
// Since there were no more terminal rooms, pick a room completely
// at random to hold the boss key.
else {
// We'll pick the nth empty room we come across. We've used 2
// rooms so far - the start and end.
int count = Rand(levelInfo[LEVEL_ROOM_COUNT] - 2) + 1;
int room = roomOff - 1;
while (count > 0) {
room++;
if ((!(rooms[room] & ROOM_FULL_MASK)) &&
(rooms[room] & ROOM_IN_USE)) {
count--;}}
rooms[room] |= ROOM_BKEY;}
// Lock Doors.
for (int i = 0; i < 64; i++) {
int room = Rand(64) + roomOff;
int sourceDoor = DungeonGen_RoomSourceDoor(rooms, room);
if (sourceDoor != -1) {
if (rooms[room] & ROOM_IN_USE && doors[sourceDoor] == DOOR_OPEN) {
// Pick a random empty source room.
int source = DungeonGen_RandomEmptySource(rooms, room);
if (source != -1) {
// Mark the doors as locked.
doors[sourceDoor] = DOOR_LOCK;
doors[DungeonGen_DoorOpposite(sourceDoor)] = DOOR_LOCK;
// Mark the chosen source room as a key room.
rooms[source] |= ROOM_KEY;}}}}
//
}
////////////////
//// Carves a door in a level. See DungeonGen_CarveLevel for the first
//// 3 arguments, which are passed directly.
////
//// door - The door that is being carved.
////
//// roomCount - The number of rooms already created on this level.
////
//// returns: See the DGCDF_ constants.
int DungeonGen_CarveDoor(int levelInfo, int rooms, int doors, int door) {
int level = levelInfo[LEVEL_NUMBER];
int sourceRoom = DungeonGen_DoorSourceRoom(door);
int targetRoom = DungeonGen_DoorTargetRoom(door);
int opposite = DungeonGen_DoorOpposite(door);
// If the target room doesn't exist, make the door a wall.
if (targetRoom == -1) {
doors[door] = DOOR_WALL;}
// If the opposite door is already defined, make this door match it.
else if (doors[opposite] != 0) {
doors[door] = doors[opposite];}
// If the target room has already been visited, then randomly pick
// the door.
else if (rooms[targetRoom] & ROOM_IN_USE) {
doors[door] = Cond(Rand(2) == 0, DOOR_WALL, DOOR_OPEN);}
// The target room hasn't been visited, so base this on roomCount.
else {
int excessRooms = levelInfo[LEVEL_ROOM_COUNT] - (10 + level * 5);
// If we haven't met the target number of rooms, force an open door.
if (excessRooms <= 0) {
doors[door] = DOOR_OPEN;}
// If we have, then possibly make a bomb wall.
else if (Rand(excessRooms + level) < level) {
doors[door] = DOOR_BOMB;}
// Then possibly make an open door.
else if (Rand(excessRooms + level) < level) {
doors[door] = DOOR_OPEN;}
// Otherwise it's a wall.
else {
doors[door] = DOOR_WALL;}
// Inform that the room is new.
levelInfo[LEVEL_ROOM_COUNT]++;
return DGCDF_NEW_ROOM;}
// No special code.
return 0;}
////////////////
//// Get the room in a given direction. Return -1 if there is no such
//// room (out of bounds).
int DungeonGen_AdjacentRoom(int room, int dir) {
if (dir == DIR_UP) {
if ((room & 0x38) == 0) {return -1;}
else {return room - 8;}}
else if (dir == DIR_DOWN) {
if ((room & 0x38) == 0x38) {return -1;}
else {return room + 8;}}
else if (dir == DIR_LEFT) {
if ((room & 7) == 0) {return -1;}
else {return room - 1;}}
else if (dir == DIR_RIGHT) {
if ((room & 7) == 7) {return -1;}
else {return room + 1;}}
else {return -1;}}
////////////////
//// Get the source room of a door.
int DungeonGen_DoorSourceRoom(int door) {
return door >> 2;}
////////////////
//// Get the target room of a door.
int DungeonGen_DoorTargetRoom(int door) {
return DungeonGen_AdjacentRoom(door >> 2, door & 3);}
////////////////
//// Get the opposite door of a door.
int DungeonGen_DoorOpposite(int door) {
int room = DungeonGen_DoorTargetRoom(door);
if (room == -1) {return -1;}
else {return (room << 2) + ((door & 3) ^ 1);}}
////////////////
//// Collect all the terminal rooms on a level into a list. The list
//// should be size 65.
int DungeonGen_CollectTerminalRooms(int level, int rooms,
int list_TerminalRooms) {
list_TerminalRooms[SIZE] = 0;
for (int i = (level - 1) << 6; i < level << 6; i++) {
if (!(rooms[i] & ROOM_MIDDLE)) {
ArrayList_Append(list_TerminalRooms, i);}}}
////////////////
//// Get the source door of a given room, or -1 if it has none.
int DungeonGen_RoomSourceDoor(int rooms, int room) {
int dir = rooms[room] & ROOM_SOURCE_MASK;
if (dir >= 4) {return -1;}
else {return room << 2 + dir;}}
////////////////
//// Get the source room of a given room, or -1 if it has none.
int DungeonGen_RoomSource(int rooms, int room) {
int info = rooms[room];
int dir = info & ROOM_SOURCE_MASK;
if (dir >= 4) {return -1;}
else {return DungeonGen_AdjacentRoom(room, dir);}}
////////////////
//// Count how many steps away from the level start.
int DungeonGen_SourceDistance(int rooms, int room) {
int count = -1;
while (room != -1) {
count++;
room = DungeonGen_RoomSource(rooms, room);}
return count;}
////////////////
//// Get a random empty source of a room.
int DungeonGen_RandomEmptySource(int rooms, int room) {
// count of empty sources.
int count = 0;
int source = DungeonGen_RoomSource(rooms, room);
while (source != -1) {
if (!(rooms[source] & ROOM_FULL_MASK)) {
count++;}
source = DungeonGen_RoomSource(rooms, source);}
if (count == 0) {return -1;}
count = Rand(count) + 1;
source = room;
while (count > 0) {
source = DungeonGen_RoomSource(rooms, source);
if (!(rooms[source] & ROOM_FULL_MASK)) {
count--;}}
return source;}
import "std.zh"
import "string.zh"
import "random/array-list.zh"
import "random/dungeon-gen.zh"
const int LEVEL_WIDTH = 8;
const int LEVEL_HEIGHT = 8;
const int LEVEL_COUNT = 8;
const int SOURCE_MAP = 9;
// Script 1 flag - used for key / other items.
const int CF_ITEM = 98;
int Rooms[512];
int Doors[2048];
int Variants[512];
int StartRoom = 0;
void GenerateLevels() {
int level1[4];
int msg1[] = "Generating Level 1...";
level1[LEVEL_NUMBER] = 1;
TraceS(msg1);
DungeonGen_CarveLevel(level1, Rooms, Doors, Variants, StartRoom);
int msg2[] = " Done.";
TraceS(msg2); TraceNL();
}
// Load the levels into the game.
void LoadLevels() {
for (int level = 0; level < 8; level++) {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
LoadRoom(level, x, y);}}}}
void CopyLocation(int sourceMap, int sourceScreen,
int destMap, int destScreen, int loc) {
int combo = Game->GetComboData(sourceMap, sourceScreen, loc);
int flag = Game->GetComboFlag(sourceMap, sourceScreen, loc);
int cset = Game->GetComboCSet(sourceMap, sourceScreen, loc);
Game->SetComboData(destMap, destScreen, loc, combo);
Game->SetComboFlag(destMap, destScreen, loc, flag);
Game->SetComboCSet(destMap, destScreen, loc, cset);}
void LoadRoom(int level, int x, int y) {
int map = level + 1;
int screen = y * 16 + x;
int index = level * 64 + y * 8 + x;
// Copy basic room.
for (int loc = 0; loc < 176; loc++) {
CopyLocation(SOURCE_MAP, 16 + Variants[index], map, screen, loc);}
// Copy the up gate (2 rows).
for (int loc = 0; loc < 32; loc++) {
int source = Doors[index * 4 + DIR_UP];
CopyLocation(SOURCE_MAP, source, map, screen, loc);}
// Copy the down gate (2 rows).
for (int loc = 144; loc < 176; loc++) {
int source = Doors[index * 4 + DIR_DOWN];
CopyLocation(SOURCE_MAP, source, map, screen, loc);}
// Copy the left gate (2 columns).
for (int i = 0; i < 22; i++) {
int source = Doors[index * 4 + DIR_LEFT];
int loc = (i >> 1) * 16 + (i % 2);
CopyLocation(SOURCE_MAP, source, map, screen, loc);}
// Copy the right gate (2 columns).
for (int i = 0; i < 22; i++) {
int source = Doors[index * 4 + DIR_RIGHT];
int loc = (i >> 1) * 16 + (i % 2) + 14;
CopyLocation(SOURCE_MAP, source, map, screen, loc);}}
// Find the first combo on the screen with the given flag.
int FindFlag(int flag) {
for (int loc = 0; loc < 176; loc++) {
if (ComboFI(loc, flag)) {
return loc;}}
return -1;}
// Called when you enter a room.
void EnterRoom() {
int map = Game->GetCurMap();
int screen = Game->GetCurScreen();
int level = map - 1;
int y = screen >> 4;
int x = screen & 7;
int index = level * 64 + y * 8 + x;
// Adjust gates.
for (int dir = 0; dir < 4; dir++) {
int type = Doors[index * 4 + dir];
if (type == DOOR_WALL) {Screen->Door[dir] = D_WALL;}
else if (type == DOOR_OPEN) {Screen->Door[dir] = D_OPEN;}
else if (type == DOOR_LOCK) {
if (Screen->State[dir]) {Screen->Door[dir] = D_UNLOCKED;}
else {Screen->Door[dir] = D_LOCKED;}}}
// If this room has a key, add it in.
if ((Rooms[index] & ROOM_KEY) &&
!Screen->State[ST_ITEM]) {
int loc = FindFlag(CF_ITEM);
if (loc != -1) {
item key = Screen->CreateItem(I_LEVELKEY);
key->X = ComboX(loc);
key->Y = ComboY(loc);
key->Pickup |= IP_ST_ITEM;}}}
global script Init {
void run() {
GenerateLevels();
Link->Warp(0, (StartRoom >> 3) * 16 + (StartRoom % 8));}}
global script Active {
void run() {
LoadLevels();
Waitframe();
Link->X = 120;
Link->Y = 80;
//for (int i = 0; i < 20; i++) {
// Trace(Doors[i]);}
while (true) {
Waitdraw();
ScreenChange_Update();
if (ScreenChanged) {
EnterRoom();}
Waitframe();}}}
// Test for changes in screen.
// Last noticed DMap.
int LastDMap = -1;
// Last noticed DScreen.
int LastDScreen = -1;
// Last noticed map.
int LastMap = -1;
// Last noticed screen.
int LastScreen = -1;
// If the screen has changed.
bool ScreenChanged = false;
// If the dmap has changed.
bool DMapChanged = false;
void ScreenChange_Update() {
int dMap = Game->GetCurDMap();
int dScreen = Game->GetCurDMapScreen();
int map = Game->GetCurMap();
int screen = Game->GetCurScreen();
DMapChanged = dMap != LastDMap;
ScreenChanged = DMapChanged || dScreen != LastDScreen;
LastDMap = dMap;
LastDScreen = dScreen;
//if (LastMap != map || LastScreen != screen) {
// Game->SetScreenState(LastMap, LastScreen, ST_VISITED, true);}
LastMap = map;
LastScreen = screen;}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment