Skip to content

Instantly share code, notes, and snippets.

@andwn
Created March 17, 2016 00:13
Show Gist options
  • Save andwn/b64baecad2ceca8515ff to your computer and use it in GitHub Desktop.
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
#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