import standard; import random; //constants mapped to the given atlas file "tileset.png" var tileset: [string : [int]] const = [ "empty": [-1, -1, 0], "temple-pillar": [0, 0, 0], "temple-floor-0": [0, 1, 1], "temple-floor-1": [1, 1, 1], "temple-floor-2": [2, 1, 1], "temple-floor-3": [3, 1, 1], "temple-wall-t": [0, 2, 0], "temple-wall-b": [1, 2, 0], "temple-wall-l": [2, 2, 0], "temple-wall-r": [3, 2, 0], "temple-corner-tl": [0, 3, 0], "temple-corner-tr": [1, 3, 0], "temple-corner-bl": [2, 3, 0], "temple-corner-br": [3, 3, 0], "temple-edge-tl": [0, 4, 0], "temple-edge-tr": [1, 4, 0], "temple-edge-bl": [3, 4, 0], //would be great if these were the right way around... "temple-edge-br": [2, 4, 0] ]; var themes: [string] const = [ "temple" ]; //constants for generating maps var ROOM_MIN_WIDTH: int const = 4; //minimum safe value 4 var ROOM_MIN_HEIGHT: int const = 4; var ROOM_MAX_WIDTH: int const = 12; //minimum safe value ROOM_MIN_* + 1 var ROOM_MAX_HEIGHT: int const = 12; var CELL_WIDTH: int const = 16; //minimum safe value ROOM_MAX_* + 4 var CELL_HEIGHT: int const = 16; var CELL_COUNT_X: int const = 3; var CELL_COUNT_Y: int const = 3; var MAP_GRID_WIDTH: int const = CELL_WIDTH * CELL_COUNT_X; var MAP_GRID_HEIGHT: int const = CELL_HEIGHT * CELL_COUNT_Y; //raw interleaved tile data var tilemap: [int] = null; //room metadata for placing entities var metadata: [[[string: any]]] = null; //public functions fn generateTilemapData(rng: opaque) { //generate a grid filled with only empty tiles, as a starting point tilemap = []; for (var j: int = 0; j < CELL_COUNT_Y * CELL_HEIGHT; j++) { for (var i: int = 0; i < CELL_COUNT_X * CELL_WIDTH; i++) { tilemap.push(-1); //x tilemap.push(-1); //y tilemap.push(0); //collision } } //generate the metadata of each room var roomData = []; for (var i: int = 0; i < CELL_COUNT_X; i++) { var inner = []; //inner decl for (var j: int = 0; j < CELL_COUNT_Y; j++) { var metadata = generateRoomMetadata(rng, //add a border within the cell that the doors can't touch i * CELL_WIDTH + 1, j * CELL_HEIGHT + 1, CELL_WIDTH - 2, CELL_HEIGHT - 2 ); inner.push(metadata); //BUG: see Toy #70 } roomData.push(inner); } //generate corridor metadata var corridorData = generateCorridorData(rng); //etch each tile string into the tilemap for (var j: int = 0; j < CELL_COUNT_Y; j++) { for (var i: int = 0; i < CELL_COUNT_X; i++) { etchRoom(rng, roomData[i][j]); } } //etch the corridors etchCorridors(roomData, corridorData, rng); //etch the walls with marching squares, based on the room's themes etchWalls(roomData); //save the metadata for later retrieval metadata = roomData; } fn generateRoomMetadata(rng: opaque, left: int, top: int, width: int, height: int) { //I expected this line to break, but I must've fixed calls within indexes at some point var theme: string = themes[rng.generateRandomNumber() % themes.length()]; var x: int = rng.generateRandomNumber() % (ROOM_MAX_WIDTH - width) + left; var y: int = rng.generateRandomNumber() % (ROOM_MAX_HEIGHT - height) + top; var w: int = rng.generateRandomNumber() % (ROOM_MAX_WIDTH - ROOM_MIN_WIDTH) + ROOM_MIN_WIDTH; var h: int = rng.generateRandomNumber() % (ROOM_MAX_HEIGHT - ROOM_MIN_HEIGHT) + ROOM_MIN_HEIGHT; var doorX: int = x + 1 + rng.generateRandomNumber() % (w - 2); var doorY: int = y + 1 + rng.generateRandomNumber() % (h - 2); var metadata: [string: any] = [ "theme" : theme, "x" : x, "y" : y, "w" : w, "h" : h, "doorX": doorX, "doorY": doorY ]; return metadata; } fn etchRoom(rng: opaque, metadata: [string: any]) { //NOTE: using unrolled assignments in this function for speed reasons var theme: string = metadata["theme"]; var x: int = metadata["x"]; var y: int = metadata["y"]; var w: int = metadata["w"]; var h: int = metadata["h"]; //etch the floor-space for (var j: int = y; j < y + h; j++) { for (var i: int = x; i < x + w; i++) { var floorIndex = rng.generateRandomNumber() % 4; //NOTE: there might not always be only 4 floor sprites tilemap[j * CELL_WIDTH * CELL_COUNT_X * 3 + i * 3 + 0] = tileset[theme + "-floor-" + string floorIndex][0]; tilemap[j * CELL_WIDTH * CELL_COUNT_X * 3 + i * 3 + 1] = tileset[theme + "-floor-" + string floorIndex][1]; tilemap[j * CELL_WIDTH * CELL_COUNT_X * 3 + i * 3 + 2] = tileset[theme + "-floor-" + string floorIndex][2]; } } } fn generateCorridorData(rng: opaque) { var result = []; //generate the corridor graph for (var i: int = 0; i < CELL_COUNT_X; i++) { var inner = []; //inner decl for (var j: int = 0; j < CELL_COUNT_Y; j++) { inner.push([:]); } result.push(inner); } //while the corridors are incomplete while (!checkCorridorsValid(result)) { //randomly link two neighbouring rooms that aren't already linked result = randomlyLinkTwoRooms(rng, result); } //finally return result; } fn checkCorridorsValid(corridors) { //mark all rooms connected to [0, 0] fn markRoomAndFlood(x: int, y: int) { //base case if (corridors[x][y]["marked"] == true) { return; } //this room is now marked corridors[x][y]["marked"] = true; //flood to neighbouring rooms if (corridors[x][y]["-1,0"] == true) { markRoomAndFlood(x-1, y); } if (corridors[x][y]["+1,0"] == true) { markRoomAndFlood(x+1, y); } if (corridors[x][y]["0,-1"] == true) { markRoomAndFlood(x, y-1); } if (corridors[x][y]["0,+1"] == true) { markRoomAndFlood(x, y+1); } } //kickoff markRoomAndFlood(0, 0); //look for any unmarked rooms for (var i: int = 0; i < CELL_COUNT_X; i++) { for (var j: int = 0; j < CELL_COUNT_Y; j++) { if (corridors[i][j]["marked"] != true) { return false; } } } //all rooms are connected return true; } fn randomlyLinkTwoRooms(rng: opaque, corridors) { //the number of corridors var count: int = CELL_COUNT_X * (CELL_COUNT_Y - 1) + (CELL_COUNT_X - 1) * CELL_COUNT_Y; //find a random corridor index for (var index: int = rng.generateRandomNumber() % count; /* EMPTY */ ; index = (index + 1) % count) { //determine where the corridor is if (index < floor(count / 2)) { var x: int = floor(index % (CELL_COUNT_X - 1)); var y: int = floor(index / (CELL_COUNT_Y - 1)); //left-right if (corridors[x][y]["+1,0"] == true || corridors[x+1][y]["-1,0"] == true) { continue; } corridors[x][y]["+1,0"] = true; corridors[x+1][y]["-1,0"] = true; break; } else { var idx = index - floor(count / 2); //adjust var x: int = floor(idx / (CELL_COUNT_X - 1)); var y: int = floor(idx % (CELL_COUNT_Y - 1)); //top-bottom if (corridors[x][y]["0,+1"] == true || corridors[x][y+1]["0,-1"] == true) { continue; } corridors[x][y]["0,+1"] = true; corridors[x][y+1]["0,-1"] = true; break; } } // return corridors; } fn etchCorridors(roomData, corridorData, rng) { //for each room that has a corridor, etch that corridor for (var i: int = 0; i < CELL_COUNT_X; i++) { for (var j: int = 0; j < CELL_COUNT_Y; j++) { if (corridorData[i][j]["+1,0"] == true) { //mark me and my linked room as false, then etch our connection corridorData[i][j]["+1,0"] = false; corridorData[i+1][j]["-1,0"] = false; etchOneCorridor( roomData[i][j]["doorX"], roomData[i][j]["doorY"], roomData[i+1][j]["doorX"], roomData[i+1][j]["doorY"], roomData[i][j]["theme"], rng ); } if (corridorData[i][j]["-1,0"] == true) { //mark me and my linked room as false, then etch our connection corridorData[i][j]["-1,0"] = false; corridorData[i-1][j]["+1,0"] = false; etchOneCorridor( roomData[i][j]["doorX"], roomData[i][j]["doorY"], roomData[i-1][j]["doorX"], roomData[i-1][j]["doorY"], roomData[i][j]["theme"], rng ); } if (corridorData[i][j]["0,+1"] == true) { //mark me and my linked room as false, then etch our connection corridorData[i][j]["0,+1"] = false; corridorData[i][j+1]["0,-1"] = false; etchOneCorridor( roomData[i][j]["doorX"], roomData[i][j]["doorY"], roomData[i][j+1]["doorX"], roomData[i][j+1]["doorY"], roomData[i][j]["theme"], rng ); } if (corridorData[i][j]["0,-1"] == true) { //mark me and my linked room as false, then etch our connection corridorData[i][j]["0,-1"] = false; corridorData[i][j-1]["0,+1"] = false; etchOneCorridor( roomData[i][j]["doorX"], roomData[i][j]["doorY"], roomData[i][j-1]["doorX"], roomData[i][j-1]["doorY"], roomData[i][j]["theme"], rng ); } } } } fn etchOneCorridor(x1, y1, x2, y2, theme, rng) { //determine longest path, with a deliberate kink in the middle if (x2 - x1 > y2 - y1) { //determine half-length var xdir = floor((x2 - x1) / 2); //etch etchLine(x1, y1, xdir, 0, theme, rng); etchLine(x1 + xdir, y1, 0, y2-y1, theme, rng); etchLine(x1 + xdir, y2, (x2 - x1) - xdir, 0, theme, rng); } else { //determine half-length var ydir = floor((y2 - y1) / 2); //etch etchLine(x1, y1, 0, ydir, theme, rng); etchLine(x1, y1 + ydir, x2-x1, 0, theme, rng); etchLine(x2, y1 + ydir, 0, (y2 - y1) - ydir, theme, rng); } } fn etchLine(x: int, y: int, xLength: int, yLength: int, theme: string, rng: opaque) { //lengths can be negative, so handle it while (abs(xLength) > 0 || abs(yLength) > 0) { //etch floor at this position var floorIndex = rng.generateRandomNumber() % 4; //NOTE: there might not always be only 4 floor sprites tilemap[y * CELL_WIDTH * CELL_COUNT_X * 3 + x * 3 + 0] = tileset[theme + "-floor-" + string floorIndex][0]; tilemap[y * CELL_WIDTH * CELL_COUNT_X * 3 + x * 3 + 1] = tileset[theme + "-floor-" + string floorIndex][1]; tilemap[y * CELL_WIDTH * CELL_COUNT_X * 3 + x * 3 + 2] = tileset[theme + "-floor-" + string floorIndex][2]; //tick down if (abs(xLength) > 0) { xLength -= sign(xLength); x += sign(xLength); } if (abs(yLength) > 0) { yLength -= sign(yLength); y += sign(yLength); } } } fn etchWalls(roomData) { //zero'd signals var signals: [string] = []; for (var j: int = 0; j < CELL_COUNT_Y * CELL_HEIGHT; j++) { for (var i: int = 0; i < CELL_COUNT_X * CELL_WIDTH; i++) { signals.push(""); //empty } } //determine the walls' layout from the tilemap for (var i: int = 0; i < MAP_GRID_WIDTH; i++) { for (var j: int = 0; j < MAP_GRID_HEIGHT; j++) { signals[j * MAP_GRID_WIDTH + i] = parseTilemapAt(i, j); } } //etch the walls into the tilemap, based on the room theme for (var i: int = 0; i < MAP_GRID_WIDTH; i++) { for (var j: int = 0; j < MAP_GRID_HEIGHT; j++) { if (signals[j * MAP_GRID_WIDTH + i] == "") { continue; } var theme = roomData[floor(i / CELL_WIDTH)][floor(j / CELL_HEIGHT)]["theme"]; tilemap[j * CELL_WIDTH * CELL_COUNT_X * 3 + i * 3 + 0] = tileset[theme + "-" + signals[j * MAP_GRID_WIDTH + i]][0]; tilemap[j * CELL_WIDTH * CELL_COUNT_X * 3 + i * 3 + 1] = tileset[theme + "-" + signals[j * MAP_GRID_WIDTH + i]][1]; tilemap[j * CELL_WIDTH * CELL_COUNT_X * 3 + i * 3 + 2] = tileset[theme + "-" + signals[j * MAP_GRID_WIDTH + i]][2]; } } } /* //lets say snapshot looks like this snapshot: 0, 0, 1 0, 0, 1 0, 0, 1 result should be "wall-l", //lets say filter looks like this filter: 1 1 1 1 1 1 0 0 0 1 1 0 0 0 1 1 0 0 0 1 1 1 1 1 1 find the position within the filter, that gives the result "wall-l" //lets say result looks like this "edge-br" "wall-b" "edge-bl" "wall-r" "pillar" "wall-l" "edge-tr" "wall-t" "edge-tl" let's try this... */ /* 9x9 var marchingFilter: [int] const = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]; var marchingFilterResult: [string] const = [ "", "", "", "", "", "", "", "", "", "", "edge-tr", "wall-b", "wall-b", "wall-b", "wall-b", "wall-b", "edge-tl", "", "", "wall-r", "corner-tl", "wall-t", "wall-t", "wall-t", "corner-tr", "wall-l", "", "", "wall-r", "wall-l", "", "", "", "wall-r", "wall-l", "", "", "wall-r", "wall-l", "", "", "", "wall-r", "wall-l", "", "", "wall-r", "wall-l", "", "", "", "wall-r", "wall-l", "", "", "wall-r", "corner-bl", "wall-b", "wall-b", "wall-b", "corner-br", "wall-l", "", "", "edge-br", "wall-t", "wall-t", "wall-t", "wall-t", "wall-t", "edge-bl", "", "", "", "", "", "", "", "", "", "" ]; */ //this is a nightmare var marchingFilter: [int] const = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ]; var marchingFilterResult: [string] const = [ "" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" , "" ,"" ,"edge-tl" ,"wall-b" ,"wall-b" ,"wall-b" ,"wall-b" ,"wall-b" ,"wall-b" ,"edge-tr" ,"" ,"" , "" ,"edge-tl" ,"corner-br" ,"" ,"corner-tl" ,"wall-t" ,"wall-t" ,"corner-tr" ,"" ,"corner-bl" ,"edge-tr" ,"" , "" ,"wall-r" ,"" ,"" ,"wall-l" ,"" ,"" ,"wall-r" ,"" ,"" ,"wall-l" ,"" , "" ,"wall-r" ,"corner-tl" ,"wall-t" ,"edge-br" ,"" ,"" ,"edge-bl" ,"wall-t" ,"corner-tr" ,"wall-l" ,"" , "" ,"wall-r" ,"wall-l" ,"" ,"" ,"" ,"" ,"" ,"" ,"wall-r" ,"wall-l" ,"" , "" ,"wall-r" ,"wall-l" ,"" ,"" ,"" ,"" ,"" ,"" ,"wall-r" ,"wall-l" ,"" , "" ,"wall-r" ,"corner-bl" ,"wall-b" ,"edge-tr" ,"" ,"" ,"edge-tl" ,"wall-b" ,"corner-br" ,"wall-l" ,"" , "" ,"wall-r" ,"" ,"" ,"wall-l" ,"" ,"" ,"wall-r" ,"" ,"" ,"wall-l" ,"" , "" ,"edge-bl" ,"corner-tr" ,"" ,"corner-bl" ,"wall-b" ,"wall-b" ,"corner-br" ,"" ,"corner-tl" ,"edge-br" ,"" , "" ,"" ,"edge-bl" ,"wall-t" ,"wall-t" ,"wall-t" ,"wall-t" ,"wall-t" ,"wall-t" ,"edge-br" ,"" ,"" , "" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ,"" ]; //if walkable is below, then return top, etc. fn parseTilemapAt(x: int, y: int) { //parse based on walkability, for now if (tilemap[y * CELL_WIDTH * CELL_COUNT_X * 3 + x * 3 + 2] != 0) { return ""; //empty } //generate the snapshot of the current position var snapshot = generateSnapshotAt(x, y); //find the snapshot's position within the filter for (var i: int = 0; i < 12 -2; i++) { for (var j: int = 0; j < 12 -2; j++) { if ( marchingFilter[(j + 0) * 12 + (i + 0)] == snapshot[0 * 3 + 0] && marchingFilter[(j + 0) * 12 + (i + 1)] == snapshot[0 * 3 + 1] && marchingFilter[(j + 0) * 12 + (i + 2)] == snapshot[0 * 3 + 2] && marchingFilter[(j + 1) * 12 + (i + 0)] == snapshot[1 * 3 + 0] && marchingFilter[(j + 1) * 12 + (i + 1)] == snapshot[1 * 3 + 1] && marchingFilter[(j + 1) * 12 + (i + 2)] == snapshot[1 * 3 + 2] && marchingFilter[(j + 2) * 12 + (i + 0)] == snapshot[2 * 3 + 0] && marchingFilter[(j + 2) * 12 + (i + 1)] == snapshot[2 * 3 + 1] && marchingFilter[(j + 2) * 12 + (i + 2)] == snapshot[2 * 3 + 2] ) { return marchingFilterResult[(j+1) * 12 + (i+1)]; } } } //anything else, just plop down a pillar fn nonZero(key: int, value: int) { return value != 0; } if (snapshot.some(nonZero)) { return "pillar"; } return ""; } fn generateSnapshotAt(x: int, y: int) { var result: [int] = []; for (var j: int = -1; j < 2; j++) { for (var i: int = -1; i < 2; i++) { if (x + i < 0 || y + j < 0 || x + i >= MAP_GRID_WIDTH || y + j >= MAP_GRID_HEIGHT) { result.push(0); } else { result.push(tilemap[(y+j) * CELL_WIDTH * CELL_COUNT_X * 3 + (x+i) * 3 + 2] != 0 ? 1 : 0); //walkable } } } return result; } //polyfill fn sign(x) { if (x > 0) return 1; return -1; }