diff --git a/README.md b/README.md index 1a176df..d060754 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,5 @@ if (ret != ESP_OK) { * `IDF_TOOLS_PATH` is the path to the toolchain * `source export.sh` +![Current Status](https://austinmorlan.com/posts/embedded_game_programming_8/media/demo.gif) + diff --git a/assets/export/tiles/black.bmp b/assets/export/tiles/black.bmp new file mode 100644 index 0000000..e6a3b69 Binary files /dev/null and b/assets/export/tiles/black.bmp differ diff --git a/assets/export/tiles/dark_grey.bmp b/assets/export/tiles/dark_grey.bmp new file mode 100644 index 0000000..a9ea37e Binary files /dev/null and b/assets/export/tiles/dark_grey.bmp differ diff --git a/assets/export/tiles/light_grey.bmp b/assets/export/tiles/light_grey.bmp new file mode 100644 index 0000000..0741f13 Binary files /dev/null and b/assets/export/tiles/light_grey.bmp differ diff --git a/assets/export/tiles/white.bmp b/assets/export/tiles/white.bmp new file mode 100644 index 0000000..a8b9952 Binary files /dev/null and b/assets/export/tiles/white.bmp differ diff --git a/assets/source/palettes/grey.pal b/assets/source/palettes/grey.pal new file mode 100644 index 0000000..173741f --- /dev/null +++ b/assets/source/palettes/grey.pal @@ -0,0 +1,7 @@ +JASC-PAL +0100 +4 +255 255 255 +171 171 171 +84 84 84 +0 0 0 diff --git a/assets/source/tiles/black.aseprite b/assets/source/tiles/black.aseprite new file mode 100644 index 0000000..4cde534 Binary files /dev/null and b/assets/source/tiles/black.aseprite differ diff --git a/assets/source/tiles/dark_grey.aseprite b/assets/source/tiles/dark_grey.aseprite new file mode 100644 index 0000000..f26daff Binary files /dev/null and b/assets/source/tiles/dark_grey.aseprite differ diff --git a/assets/source/tiles/light_grey.aseprite b/assets/source/tiles/light_grey.aseprite new file mode 100644 index 0000000..6b8624d Binary files /dev/null and b/assets/source/tiles/light_grey.aseprite differ diff --git a/assets/source/tiles/white.aseprite b/assets/source/tiles/white.aseprite new file mode 100644 index 0000000..f9c5cf9 Binary files /dev/null and b/assets/source/tiles/white.aseprite differ diff --git a/game/src/main.c b/game/src/main.c index 4180719..eab0959 100644 --- a/game/src/main.c +++ b/game/src/main.c @@ -15,6 +15,139 @@ static const char* LOG_TAG = "Main"; static uint16_t gFramebuffer[LCD_WIDTH * LCD_HEIGHT]; +static const uint16_t palette[4] = +{ + 0xFFFF, + 0x55AD, + 0xAA52, + 0x0000, +}; + +static const uint8_t tiles[][16*16] = +{ + // White + { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + }, + + // Light Grey + { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + }, + + // Dark Grey + { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0, + 0,0,2,2,2,2,2,2,2,2,2,2,2,2,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + }, + + // Black + { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,3,3,3,3,3,3,3,3,3,3,3,3,0,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0, + 0,0,3,3,3,3,3,3,3,3,3,3,3,3,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + }, +}; + +static int tileBuffer[15][40] = +{ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0}, + {0, 0, 3, 0, 0, 0, 3, 3, 3, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0}, + {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, +}; + + +void DrawTile(int index, int x, int y) +{ + int startX = x * 16; + int startY = y * 16; + + for (int row = 0; row < 16; ++row) + { + for (int col = 0; col < 16; ++col) + { + uint8_t paletteIndex = tiles[index][row * 16 + col]; + + int screenY = startY + row; + int screenX = startX + col; + + uint16_t color = palette[paletteIndex]; + + gFramebuffer[screenY * LCD_WIDTH + screenX] = color; + } + } +} + + void app_main(void) { Odroid_InitializeInput(); @@ -23,113 +156,63 @@ void app_main(void) Odroid_InitializeBatteryReader(); Odroid_InitializeAudio(); - // Load sprite - uint16_t* sprite = (uint16_t*)malloc(64 * 64 * sizeof(uint16_t)); - { - FILE* spriteFile = fopen("/sdcard/key", "r"); - assert(spriteFile); - - for (int i = 0; i < 64; ++i) - { - for (int j = 0; j < 64; ++j) - { - fread(sprite, sizeof(uint16_t), 64 * 64, spriteFile); - } - } - - fclose(spriteFile); - } - - // Load sound effect - uint16_t* soundBuffer; - int soundEffectLength = 1441; - { - FILE* soundFile = fopen("/sdcard/jump", "r"); - assert(soundFile); - - uint8_t* soundEffect = malloc(soundEffectLength); - assert(soundEffect); - - soundBuffer = malloc(soundEffectLength*2); - assert(soundBuffer); - - fread(soundEffect, soundEffectLength, 1, soundFile); - - for (int i = 0; i < soundEffectLength; ++i) - { - // 16 bits required but only MSB is actually sent to the DAC - soundBuffer[i] = (soundEffect[i] << 8u); - } - } - - ESP_LOGI(LOG_TAG, "Odroid initialization complete - entering main loop"); - int x = 0; - int y = 0; - uint16_t color = 0xffff; - - int lastState = 0; + uint8_t frameIndex = 0; + char snapFilename[20]; + int xLeft = 0; for (;;) { - memset(gFramebuffer, 0, 320 * 240 * 2); + memset(gFramebuffer, 0xff, 320*240*2); Odroid_Input input = Odroid_PollInput(); - if (input.left) { x -= 20; } - else if (input.right) { x += 20; } - - if (input.up) { y -= 20; } - else if (input.down) { y += 20; } - - if (input.a) { color = SWAP_ENDIAN_16(RGB565(0xff, 0, 0)); } - else if (input.b) { color = SWAP_ENDIAN_16(RGB565(0, 0xff, 0)); } - else if (input.start) { color = SWAP_ENDIAN_16(RGB565(0, 0, 0xff)); } - else if (input.select) { color = SWAP_ENDIAN_16(RGB565(0xff, 0xff, 0xff)); } - - - int thisState = input.volume; - - if ((thisState == 1) && (thisState != lastState)) + if (input.left) { - Odroid_PlayAudio(soundBuffer, soundEffectLength*2); - } + xLeft -= 1; - lastState = thisState; - - - DrawText(gFramebuffer, "The Quick Brown Fox", 19, 0, 5, SWAP_ENDIAN_16(RGB565(0xFF, 0, 0))); - DrawText(gFramebuffer, "Jumped Over The:", 16, 0, 6, SWAP_ENDIAN_16(RGB565(0, 0xFF, 0))); - DrawText(gFramebuffer, "Lazy Dog?!", 10, 0, 7, SWAP_ENDIAN_16(RGB565(0, 0, 0xFF))); - - - int spriteRow = 0; - int spriteCol = 0; - - for (int row = y; row < y + 64; ++row) - { - spriteCol = 0; - - for (int col = x; col < x + 64; ++col) + if (xLeft < 0) { - uint16_t pixelColor = sprite[64 * spriteRow + spriteCol]; - - if (pixelColor != 0) - { - gFramebuffer[row * LCD_WIDTH + col] = color; - } - - ++spriteCol; + xLeft = 39; } - - ++spriteRow; } + else if (input.right) + { + xLeft += 1; + + if (xLeft > 39) + { + xLeft = 0; + } + } + + for (int tileY = 0; tileY < 15; ++tileY) + { + for (int tileX = xLeft; tileX < xLeft + 20; ++tileX) + { + int tile = tileX % 40; + + int tileIndex = tileBuffer[tileY][tile]; + + DrawTile(tileIndex, tileX - xLeft, tileY); + } + } + + + char string[5]; + snprintf(string, 5, "%02d", xLeft); + DrawText(gFramebuffer, string, 2, 0, 0, palette[3]); + + int tileRight = (xLeft + 20) % 40; + snprintf(string, 5, "%02d", tileRight); + DrawText(gFramebuffer, string, 2, 18, 0, palette[3]); + if (input.menu) { - const char* snapFilename = "/sdcard/framebuf"; + snprintf(snapFilename, 20, "/sdcard/frame%02d", frameIndex); ESP_LOGI(LOG_TAG, "Writing snapshot to %s", snapFilename); @@ -139,19 +222,8 @@ void app_main(void) fwrite(gFramebuffer, 1, LCD_WIDTH * LCD_HEIGHT * sizeof(gFramebuffer[0]), snapFile); fclose(snapFile); - } - - uint32_t batteryLevel = Odroid_ReadBatteryLevel(); - ESP_LOGI(LOG_TAG, "Battery level: %u\n", batteryLevel); - - if (batteryLevel < 3600) - { - Odroid_EnableBatteryLight(); - } - else - { - Odroid_DisableBatteryLight(); + ++frameIndex; } diff --git a/game/src/text.c b/game/src/text.c index b719c04..25073f6 100644 --- a/game/src/text.c +++ b/game/src/text.c @@ -8,8 +8,8 @@ static const int MAX_GLYPHS_PER_COL = LCD_HEIGHT / GLYPH_HEIGHT; void DrawText(uint16_t* framebuffer, char* string, int length, int x, int y, uint16_t color) { - assert(x + length < MAX_GLYPHS_PER_ROW); - assert(y < MAX_GLYPHS_PER_COL); + assert(x + length <= MAX_GLYPHS_PER_ROW); + assert(y <= MAX_GLYPHS_PER_COL); for (int charIndex = 0; charIndex < length; ++charIndex) { diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 0a32c12..ac11799 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,5 +4,7 @@ project(tools) set(CMAKE_C_FLAGS "-Wall -Werror") -add_executable(font_generator src/font_generator.c) +add_executable(font_processor src/font_processor.c) +add_executable(palette_processor src/palette_processor.c) +add_executable(tile_processor src/tile_processor.c) diff --git a/tools/src/font_generator.c b/tools/src/font_processor.c similarity index 100% rename from tools/src/font_generator.c rename to tools/src/font_processor.c diff --git a/tools/src/palette_processor.c b/tools/src/palette_processor.c new file mode 100644 index 0000000..0b714f6 --- /dev/null +++ b/tools/src/palette_processor.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (argc != 3) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + + return 1; + } + + const char* inFilename = argv[1]; + const char* outFilename = argv[2]; + + // Read the file + uint16_t* palette; + int paletteSize; + { + FILE* inFile = fopen(inFilename, "r"); + assert(inFile); + + char* line; + size_t len = 0; + + getline(&line, &len, inFile); + assert(strncmp(line, "JASC-PAL", 8) == 0); + + getline(&line, &len, inFile); + assert(strncmp(line, "0100", 4) == 0); + + getline(&line, &len, inFile); + paletteSize = atoi(line); + + palette = malloc(sizeof(*palette) * paletteSize); + assert(palette); + + // Each line is of form R G B + for (int i = 0; i < paletteSize; ++i) + { + getline(&line, &len, inFile); + + char* tok = strtok(line, " "); + int red = atoi(tok); + + tok = strtok(NULL, " "); + int green = atoi(tok); + + tok = strtok(NULL, " "); + int blue = atoi(tok); + + uint16_t rgb565 = + ((red >> 3u) << 11u) + | ((green >> 2u) << 5u) + | (blue >> 3u); + + uint16_t endianSwap = ((rgb565 & 0xFFu) << 8u) | (rgb565 >> 8u); + + palette[i] = endianSwap; + } + + fclose(inFile); + } + + + printf("Input: %s\n", inFilename); + printf("Output: %s\n", outFilename); + printf("Palette Size: %d\n", paletteSize); + + + // Output to the file + { + FILE* outFile = fopen(outFilename, "w"); + assert(outFile); + + fprintf(outFile, "// AUTOMATICALLY GENERATED. DO NOT EDIT.\n"); + fprintf(outFile, "\n"); + fprintf(outFile, "#pragma once\n"); + fprintf(outFile, "\n"); + fprintf(outFile, "\n"); + + fprintf(outFile, "uint16_t palette[%d] =\n", paletteSize); + fprintf(outFile, "{\n"); + + for (int i = 0; i < paletteSize; ++i) + { + fprintf(outFile, " 0x%04X,\n", palette[i]); + } + + fprintf(outFile, "};\n"); + fprintf(outFile, "\n"); + + fclose(outFile); + } + + printf("\n"); + printf("DONE\n"); + + + return 0; +} + diff --git a/tools/src/tile_processor.c b/tools/src/tile_processor.c new file mode 100644 index 0000000..01cc046 --- /dev/null +++ b/tools/src/tile_processor.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + + +int main(int argc, char** argv) +{ + if (argc != 3) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + + return 1; + } + + const char* inFilename = argv[1]; + const char* outFilename = argv[2]; + + + int tileWidth; + int tileHeight; + uint8_t* tileBuffer; + { + FILE* inFile = fopen(inFilename, "rb"); + assert(inFile); + + #pragma pack(push,1) + struct BmpHeader + { + char magic[2]; + uint32_t totalSize; + uint32_t reserved; + uint32_t offset; + uint32_t headerSize; + int32_t width; + int32_t height; + uint16_t planes; + uint16_t depth; + uint32_t compression; + uint32_t imageSize; + int32_t horizontalResolution; + int32_t verticalResolution; + uint32_t paletteColorCount; + uint32_t importantColorcount; + } bmpHeader; + #pragma pack(pop) + + // Read the BMP header so we know where the image data is located + fread(&bmpHeader, 1, sizeof(bmpHeader), inFile); + assert(bmpHeader.magic[0] == 'B' && bmpHeader.magic[1] == 'M'); + assert(bmpHeader.depth == 8); + assert(bmpHeader.headerSize == 40); + + // Go to location in file of image data + fseek(inFile, bmpHeader.offset, SEEK_SET); + + // Read in the image data + tileBuffer = malloc(bmpHeader.imageSize); + assert(tileBuffer); + fread(tileBuffer, 1, bmpHeader.imageSize, inFile); + + tileWidth = bmpHeader.width; + tileHeight = bmpHeader.height; + + fclose(inFile); + } + + + printf("Input: %s\n", inFilename); + printf("Output: %s\n", outFilename); + printf("Width: %d\n", tileWidth); + printf("Height: %d\n", tileHeight); + + FILE* outFile = fopen(outFilename, "w"); + assert(outFile); + + + // Generate the preamble + { + fprintf(outFile, "// AUTOMATICALLY GENERATED. DO NOT EDIT.\n"); + fprintf(outFile, "\n"); + fprintf(outFile, "#pragma once\n"); + fprintf(outFile, "\n"); + fprintf(outFile, "#include \n"); + fprintf(outFile, "\n"); + fprintf(outFile, "\n"); + } + + + // Generate the font map with the calculated glyph bytes + { + fprintf(outFile, "static const uint8_t tile[%d][%d] =\n", tileHeight, tileWidth); + fprintf(outFile, "{\n"); + fprintf(outFile, " "); + + int count = 0; + + for (int row = 0; row < tileHeight; ++row) + { + for (int col = 0; col < tileWidth; ++col) + { + // BMP is laid out bottom-to-top, but we want top-to-bottom (0-indexed) + int y = tileHeight - row - 1; + + uint8_t paletteIndex = tileBuffer[y * tileWidth + col]; + + fprintf(outFile, "%d,", paletteIndex); + ++count; + + // Put a newline after sixteen values to keep it orderly + if ((count % 16) == 0) + { + fprintf(outFile, "\n"); + fprintf(outFile, " "); + + count = 0; + } + } + } + + fprintf(outFile, "};\n"); + } + + fclose(outFile); + + printf("\n"); + printf("DONE\n"); + + return 0; +}