Created
March 17, 2016 00:13
-
-
Save andwn/b64baecad2ceca8515ff to your computer and use it in GitHub Desktop.
Tried to come up with an algorithm to randomly generate a Sonic level layout and got bored
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdio.h> | |
| #include <math.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #define LAYOUT_WIDTH 40 | |
| #define LAYOUT_HEIGHT 10 | |
| typedef unsigned char byte; | |
| typedef unsigned short word; | |
| // Chunk | |
| // Chunks are the 256x256 areas that make up a level layout in Sonic. | |
| // They are made up of 16x16 blocks (we won't have to worry about that). | |
| // In order to decide which chunks are "compatible" we have 16 | |
| // "connection flags", 3 on each side, and one on each corner. Possible values: | |
| // 0 = Air, 1 = Solid | |
| // So if chunk 1 is left of chunk 2, the cflags on the right side | |
| // of chunk 1 must match the left side of chunk 2. | |
| typedef struct { | |
| // Representing blocks with ascii characters | |
| char* block; // Graphics (5x5) | |
| word cflag; // 3 on each side (3x4) | |
| } chunk; | |
| // Chunk Bank | |
| // A list of chunks available for use in the layout | |
| const byte TOTAL_CHUNKS = 0x1D; | |
| chunk chunk_bank[0x80]; | |
| void PopulateChunkBank() { | |
| // Straightaway, high mid low | |
| chunk_bank[0x00].block = "0........._____##########"; | |
| chunk_bank[0x00].cflag = 03307 | 0x3<<12; | |
| chunk_bank[0x01].block = "1.............._____#####"; | |
| chunk_bank[0x01].cflag = 01107 | 0x3<<12; | |
| chunk_bank[0x02].block = "2...._____###############"; | |
| chunk_bank[0x02].cflag = 07707 | 0x3<<12; | |
| // Change between high mid low straightaway | |
| chunk_bank[0x03].block = "3.........__...##\\__#####"; | |
| chunk_bank[0x03].cflag = 03107 | 0x3<<12; | |
| chunk_bank[0x04].block = "4............____/#######"; | |
| chunk_bank[0x04].cflag = 01307 | 0x3<<12; | |
| chunk_bank[0x05].block = "5.......____/############"; | |
| chunk_bank[0x05].cflag = 03707 | 0x3<<12; | |
| chunk_bank[0x06].block = "6....__...##\\__##########"; | |
| chunk_bank[0x06].cflag = 07307 | 0x3<<12; | |
| // Completely covered or completely vacant | |
| chunk_bank[0x07].block = "7########################"; | |
| chunk_bank[0x07].cflag = 0xFFFF; | |
| chunk_bank[0x08].block = "8........................"; | |
| chunk_bank[0x08].cflag = 0; | |
| // Go up/down to a higher/lower chunk | |
| chunk_bank[0x09].block = "9..............__...##\\.."; | |
| chunk_bank[0x09].cflag = 01006 | 0x2<<12; | |
| chunk_bank[0x0A].block = "A#|..###\\_###############"; | |
| chunk_bank[0x0A].cflag = 07767 | 0xB<<12; | |
| chunk_bank[0x0B].block = "B.................__../##"; | |
| chunk_bank[0x0B].cflag = 00103 | 0x1<<12; | |
| chunk_bank[0x0C].block = "C.|##_/##################"; | |
| chunk_bank[0x0C].cflag = 07737 | 0x7<<12; | |
| // Mid to top curve | |
| chunk_bank[0x0D].block = "D#|..##|..###\\_##########"; | |
| chunk_bank[0x0D].cflag = 07367 | 0xB<<12; | |
| chunk_bank[0x0E].block = "E.|##..|##_/#############"; | |
| chunk_bank[0x0E].cflag = 03737 | 0x7<<12; | |
| // Bottom to top curve | |
| chunk_bank[0x0F].block = "F#|..##|..##|..###\\_#####"; | |
| chunk_bank[0x0F].cflag = 07167 | 0xB<<12; | |
| chunk_bank[0x10].block = "10|##..|##..|##_/########"; | |
| chunk_bank[0x10].cflag = 01737 | 0x7<<12; | |
| // Ceiling | |
| chunk_bank[0x11].block = "11###...................."; | |
| chunk_bank[0x11].cflag = 00070 | 0xC<<12; | |
| chunk_bank[0x12].block = "12/......................"; | |
| chunk_bank[0x12].cflag = 00060 | 0x8<<12; | |
| chunk_bank[0x13].block = "13\\##...................."; | |
| chunk_bank[0x13].cflag = 00030 | 0x4<<12; | |
| // Vertical walls | |
| chunk_bank[0x14].block = "14|..##|..##|..##|..##|.."; | |
| chunk_bank[0x14].cflag = 07066 | 0xA<<12; | |
| chunk_bank[0x15].block = "15|##..|##..|##..|##..|##"; | |
| chunk_bank[0x15].cflag = 00733 | 0x5<<12; | |
| // Wall from ceiling | |
| chunk_bank[0x16].block = "16#####|..##|..##|..##|.."; | |
| chunk_bank[0x16].cflag = 07076 | 0xE<<12; | |
| chunk_bank[0x17].block = "17###..|##..|##..|##..|##"; | |
| chunk_bank[0x17].cflag = 00773 | 0xD<<12; | |
| // Ceiling and floor | |
| chunk_bank[0x18].block = "18###.........._____#####"; | |
| chunk_bank[0x18].cflag = 01177 | 0xF<<12; | |
| chunk_bank[0x19].block = "19###..........__...##\\.."; | |
| chunk_bank[0x19].cflag = 01076 | 0xE<<12; | |
| chunk_bank[0x1A].block = "1A###.............__../##"; | |
| chunk_bank[0x1A].cflag = 00173 | 0xD<<12; | |
| chunk_bank[0x1B].block = "1B/............_____#####"; | |
| chunk_bank[0x1B].cflag = 01167 | 0xB<<12; | |
| chunk_bank[0x1C].block = "1C\\##.........._____#####"; | |
| chunk_bank[0x1C].cflag = 01137 | 0x7<<12; | |
| // Floor to wall corners | |
| // | |
| // Mid and low straightaway with ceiling | |
| //chunk_bank[0x0D].block = "D####....._____##########"; | |
| //chunk_bank[0x0D].cflag = 03377 | 0xF<<12; | |
| //chunk_bank[0x0E].block = "E####.........._____#####"; | |
| //chunk_bank[0x0E].cflag = 01177 | 0xF<<12; | |
| //chunk_bank[0x0F].block = "F####.....__...##\\__#####"; | |
| //chunk_bank[0x0F].cflag = 03177 | 0xF<<12; | |
| //chunk_bank[0x10].block = "10###........____/#######"; | |
| //chunk_bank[0x10].cflag = 01377 | 0xF<<12; | |
| // Mid and high straightaway with no ceiling or filling at the bottom | |
| //chunk_bank[0x11].block = "11........_____#####....."; | |
| //chunk_bank[0x11].cflag = 03300 | 0x0<<12; | |
| //chunk_bank[0x12].block = "12..._____##########....."; | |
| //chunk_bank[0x12].cflag = 07700 | 0x0<<12; | |
| //chunk_bank[0x13].block = "13...__...##\\__#####....."; | |
| //chunk_bank[0x13].cflag = 07300 | 0x0<<12; | |
| //chunk_bank[0x14].block = "14......____/#######....."; | |
| //chunk_bank[0x14].cflag = 03700 | 0x0<<12; | |
| // Transition between having a ceiling | |
| //chunk_bank[0x15].block = "15/......._____##########"; | |
| //chunk_bank[0x15].cflag = 03367 | 0xB<<12; | |
| //chunk_bank[0x16].block = "16/............_____#####"; | |
| //chunk_bank[0x16].cflag = 01167 | 0xB<<12; | |
| //chunk_bank[0x17].block = "17/.......__...##\\__#####"; | |
| //chunk_bank[0x17].cflag = 03167 | 0xB<<12; | |
| //chunk_bank[0x18].block = "18/..........____/#######"; | |
| //chunk_bank[0x18].cflag = 01367 | 0xB<<12; | |
| //chunk_bank[0x19].block = "19\\##....._____##########"; | |
| //chunk_bank[0x19].cflag = 03337 | 0x7<<12; | |
| //chunk_bank[0x1A].block = "1A\\##.........._____#####"; | |
| //chunk_bank[0x1A].cflag = 01137 | 0x7<<12; | |
| //chunk_bank[0x1B].block = "1B\\##.....__...##\\__#####"; | |
| //chunk_bank[0x1B].cflag = 03137 | 0x7<<12; | |
| //chunk_bank[0x1C].block = "1C\\##........____/#######"; | |
| //chunk_bank[0x1C].cflag = 01337 | 0x7<<12; | |
| // Transition between having a filling at the bottom | |
| //chunk_bank[0x1D].block = "1D........_____#######|.."; | |
| //chunk_bank[0x1D].cflag = 03306 | 0x2<<12; | |
| //chunk_bank[0x1E].block = "1E..._____############|.."; | |
| //chunk_bank[0x1E].cflag = 07706 | 0x2<<12; | |
| //chunk_bank[0x1F].block = "1F...__...##\\__#######|.."; | |
| //chunk_bank[0x1F].cflag = 07306 | 0x2<<12; | |
| //chunk_bank[0x20].block = "20......____/#########|.."; | |
| //chunk_bank[0x20].cflag = 03706 | 0x2<<12; | |
| //chunk_bank[0x21].block = "21........_____#####..|##"; | |
| //chunk_bank[0x21].cflag = 03303 | 0x1<<12; | |
| //chunk_bank[0x22].block = "22..._____##########..|##"; | |
| //chunk_bank[0x22].cflag = 07703 | 0x1<<12; | |
| //chunk_bank[0x23].block = "23...__...##\\__#####..|##"; | |
| //chunk_bank[0x23].cflag = 07303 | 0x1<<12; | |
| //chunk_bank[0x24].block = "24......____/#######..|##"; | |
| //chunk_bank[0x24].cflag = 03703 | 0x1<<12; | |
| // Cliff from mod low high | |
| //chunk_bank[0x25].block = "25........__...##|..##|.."; | |
| //chunk_bank[0x25].cflag = 03006 | 0x2<<12; | |
| //chunk_bank[0x26].block = "26.............__...##|.."; | |
| //chunk_bank[0x26].cflag = 01006 | 0x2<<12; | |
| //chunk_bank[0x27].block = "27...__...##|..##|..##|.."; | |
| //chunk_bank[0x27].cflag = 07006 | 0x2<<12; | |
| //chunk_bank[0x28].block = "28...........__..|##..|##"; | |
| //chunk_bank[0x28].cflag = 00303 | 0x1<<12; | |
| //chunk_bank[0x29].block = "29................__..|##"; | |
| //chunk_bank[0x29].cflag = 00103 | 0x1<<12; | |
| //chunk_bank[0x2A].block = "2A......__..|##..|##..|##"; | |
| //chunk_bank[0x2A].cflag = 00703 | 0x1<<12; | |
| // Escape a case where filling lifts on the floor and ceiling comes down | |
| //chunk_bank[0x2B].block = "2B/......._____#######/.."; | |
| //chunk_bank[0x2B].cflag = 03366 | 0xA<<12; | |
| //chunk_bank[0x2C].block = "2C\\##....._____#####..\\##"; | |
| //chunk_bank[0x2C].cflag = 03333 | 0x7<<12; | |
| // Escape a case where only one of the the upper corners is vacant | |
| //chunk_bank[0x2D].block = "2D|##_/##################"; | |
| //chunk_bank[0x2D].cflag = 07767 | 0x7<<12; | |
| //chunk_bank[0x2E].block = "2E|..###\\_###############"; | |
| //chunk_bank[0x2E].cflag = 07737 | 0xB<<12; | |
| // Vertical walls | |
| //chunk_bank[0x2F].block = "2F|..##|..##|..##|..##|.."; | |
| //chunk_bank[0x2F].cflag = 07066 | 0xA<<12; | |
| //chunk_bank[0x30].block = "30|##..|##..|##..|##..|##"; | |
| //chunk_bank[0x30].cflag = 00733 | 0x5<<12; | |
| // Escape a case similar to 2B and 2C but the ceiling is only half above us | |
| //chunk_bank[0x31].block = "31/..........____/#######"; | |
| //chunk_bank[0x31].cflag = 01366 | 0xA<<12; | |
| //chunk_bank[0x32].block = "32\\##.....___..##\\__#####"; | |
| //chunk_bank[0x32].cflag = 03133 | 0x7<<12; | |
| // Escape similar to 2D and 2E but starting from mid instead of high | |
| //chunk_bank[0x34].block = "2D|##..|##_/#############"; | |
| //chunk_bank[0x34].cflag = 03767 | 0x7<<12; | |
| //chunk_bank[0x35].block = "2E|..##|..###\\_##########"; | |
| //chunk_bank[0x35].cflag = 07337 | 0xB<<12; | |
| // Chunk error | |
| chunk_bank[0x7F].block = "7FEEEEEEEEEEEEEEEEEEEEEEE"; | |
| chunk_bank[0x7F].cflag = 0; | |
| return; | |
| } | |
| int CheckCompatibilityH(byte left, byte right) { | |
| // Check sides | |
| byte sleft = chunk_bank[left].cflag>>6 & 07, | |
| sright = chunk_bank[right].cflag>>9 & 07; | |
| //printf("%dh%d ", sleft, sright); | |
| if(sleft == sright) { | |
| // Check corners | |
| byte cleft = chunk_bank[left].cflag>>12 & 0x5; | |
| byte cright = (chunk_bank[right].cflag>>12 & 0xA)>>1; | |
| //printf("%dc%d ", cleft, cright); | |
| if(cleft == cright) | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| int CheckCompatibilityV(byte top, byte bottom) { | |
| byte stop = chunk_bank[top].cflag & 07, | |
| sbottom = chunk_bank[bottom].cflag>>3 & 07; | |
| //printf("%dv%d ", stop, sbottom); | |
| if(stop == sbottom) { | |
| byte ctop = chunk_bank[top].cflag>>12 & 0x3; | |
| byte cbottom = (chunk_bank[bottom].cflag>>12 & 0xC)>>2; | |
| //printf("%dc%d ", ctop, cbottom); | |
| if(ctop == cbottom) | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| // Chunk Layout | |
| // Generated level layout | |
| byte chunk_layout[LAYOUT_WIDTH][LAYOUT_HEIGHT]; | |
| void GenerateLayout(int seed) { | |
| srand(seed); | |
| // Array size is TOTAL_CHUNKS so it can't overflow | |
| byte chunk_list[TOTAL_CHUNKS]; | |
| byte chunk_list_count; | |
| // x and y represent which layout position we are populating | |
| for(int y=0;y<LAYOUT_HEIGHT;y++) { | |
| for(int x=0;x<LAYOUT_WIDTH;x++) { | |
| // Check each of the chunks in chunk_bank if they will fit | |
| chunk_list_count = 0; | |
| for(int i=0;i<TOTAL_CHUNKS;i++) { | |
| // These ifs here make sure we don't underflow on the left/top edges | |
| if(x>0) { // Chunk to the left | |
| if(CheckCompatibilityH(chunk_layout[x-1][y], i)) continue; | |
| } | |
| if(y>0) { // Chunk above | |
| if(CheckCompatibilityV(chunk_layout[x][y-1], i)) continue; | |
| } | |
| // If we got this far, it fits! Add it to the list and increase the count | |
| chunk_list[chunk_list_count] = i; | |
| chunk_list_count++; | |
| } | |
| // Do any of the chunks fit? | |
| if(chunk_list_count > 0) { | |
| // Apply a random chunk from chunk_list | |
| chunk_layout[x][y] = chunk_list[rand() % chunk_list_count]; | |
| } else { | |
| // We can't really do anything about this so apply the "error" chunk | |
| printf("No chunks fit! %d,%d\n", x, y); | |
| chunk_layout[x][y] = 0x7F; | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| // Outputs a graphical representation of the chunk layout to out.txt | |
| void OutputLayout() { | |
| FILE*file = fopen("out.txt", "w"); | |
| for(int cy=0;cy<LAYOUT_HEIGHT;cy++) { // Layout Y | |
| for(int y=0;y<5;y++) { // Y within Chunk | |
| for(int cx=0;cx<LAYOUT_WIDTH;cx++) { // Layout X | |
| for(int x=0;x<5;x++) { // X within chunk | |
| fputc(chunk_bank[chunk_layout[cx][cy]].block[x + y*5], file); | |
| } | |
| } | |
| fputc('\n', file); // Newline at end of row | |
| } | |
| } | |
| fclose(file); | |
| } | |
| int main(int argc, char*argv[]) { | |
| //int chunks_wide = 40; | |
| //int chunks_high = 10; | |
| unsigned int seed=0; | |
| for(int i=1;i<argc;i++) { | |
| if(strcmp(argv[i], "-s") == 0) { | |
| i++; | |
| seed = atoi(argv[i]); | |
| //} else if(strcmp(argv[i], "-w") == 0) { | |
| // i++; | |
| // chunks_wide = atoi(argv[i]); | |
| //} else if(strcmp(argv[i], "-h") == 0) { | |
| // i++; | |
| // chunks_high = atoi(argv[i]); | |
| } else { | |
| printf("Don't know how to '%s' something.\n", argv[i]); | |
| } | |
| } | |
| PopulateChunkBank(); | |
| //GenerateLayout(seed); | |
| for(int i=0;i<1000;i++) GenerateLayout(i); | |
| OutputLayout(); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment