Creating generic term renderer.

This commit is contained in:
2025-10-28 16:48:35 -05:00
parent 358fa9a493
commit b6aab03370
27 changed files with 770 additions and 155 deletions

View File

@@ -11,9 +11,16 @@ project(microrpg
) )
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
set(ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets" CACHE PATH "Path to the assets directory")
set(TOOLS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tools" CACHE PATH "Path to the tools directory")
# Executable # Executable
add_executable(microrpg) add_executable(microrpg)
# Tools
add_subdirectory(tools)
# Add sources # Add sources
add_subdirectory(src) add_subdirectory(src)

View File

@@ -0,0 +1,10 @@
This is a simple 32x32 bitmap font I made as a byproduct of messing about with Xlib. Because of its a power of 2 sizing, it should be able to be resized easily, even if you're writting your own render.
font.png is a screenshot of the output of my rendering text using font.c.
font.zip contains font.c, which is just a C array of 64bit unsigned integers, these are the original bitmaps for the 8x8 font which I resized 4x to get font.png.
Note the font.c is licensed GPLv3 or later only, the font.png is also licensed GPLv2 and CC-BY-SA.
Copyright/Attribution Notice:
To satisfy CC-BY-SA's attribution clause, just give a link back to OGA.
# Original URL: https://opengameart.org/content/8x8-ascii-bitmap-font-with-c-source

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

61
cmake/Findraylib.cmake Normal file
View File

@@ -0,0 +1,61 @@
# Findraylib.cmake — minimal: system paths → FetchContent
# Exposes: raylib::raylib, raylib_FOUND
include_guard(GLOBAL)
set(_RAYLIB_TAG "5.5")
set(_RAYLIB_URL "https://github.com/raysan5/raylib/archive/refs/tags/${_RAYLIB_TAG}.tar.gz")
set(_RAYLIB_ROOT "")
# Try system install first
find_path(_RAYLIB_INCLUDE_DIR raylib.h
HINTS "${_RAYLIB_ROOT}/include"
PATHS /usr/include /usr/local/include
PATH_SUFFIXES raylib
)
find_library(_RAYLIB_LIBRARY raylib
HINTS "${_RAYLIB_ROOT}/lib" "${_RAYLIB_ROOT}/lib64"
PATHS /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64
)
if (_RAYLIB_INCLUDE_DIR AND _RAYLIB_LIBRARY)
add_library(raylib UNKNOWN IMPORTED)
set_target_properties(raylib PROPERTIES
IMPORTED_LOCATION "${_RAYLIB_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${_RAYLIB_INCLUDE_DIR}"
)
else()
include(FetchContent)
# Fix CMP0135 warning
if(POLICY CMP0135)
cmake_policy(SET CMP0135 NEW)
endif()
if (DEFINED FETCHCONTENT_SOURCE_DIR_RAYLIB AND
NOT EXISTS "${FETCHCONTENT_SOURCE_DIR_RAYLIB}")
unset(FETCHCONTENT_SOURCE_DIR_RAYLIB CACHE)
endif()
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(BUILD_GAMES OFF CACHE BOOL "" FORCE)
set(RAYLIB_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(RAYLIB_BUILD_GAMES OFF CACHE BOOL "" FORCE)
FetchContent_Declare(raylib
URL "${_RAYLIB_URL}"
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(raylib)
if (NOT TARGET raylib)
message(FATAL_ERROR "raylib target not defined after fetch.")
endif()
endif()
# Normalize exported target
if (NOT TARGET raylib::raylib)
add_library(raylib::raylib ALIAS raylib)
endif()
set(raylib_FOUND TRUE)

View File

@@ -8,6 +8,10 @@
#pragma once #pragma once
#include "cutscene.h" #include "cutscene.h"
static void testCutsceneTest() {
abort();
}
cutsceneitem_t TEST_CUTSCENE_ITEMS[] = { cutsceneitem_t TEST_CUTSCENE_ITEMS[] = {
{ .type = CUTSCENE_ITEM_TEXTBOX, .textbox = "Hello World" } { .type = CUTSCENE_ITEM_CALLBACK, .callback = testCutsceneTest }
}; };

View File

@@ -10,10 +10,10 @@
int main(int argc, char** argv) { int main(int argc, char** argv) {
gameInit(); gameInit();
platformInit(); if(platformInit() != PLATFORM_OK) return 1;
while(1) { while(1) {
platformUpdate(); if(platformUpdate() != PLATFORM_OK) break;
gameTick(); gameTick();

View File

@@ -13,3 +13,5 @@
#include <stdbool.h> #include <stdbool.h>
typedef bool bool_t; typedef bool bool_t;
typedef char char_t;
typedef float float_t;

View File

@@ -3,5 +3,6 @@
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
# Change inclusion to platform-specific add_subdirectory(raylib)
add_subdirectory(term) add_subdirectory(term)
#add_subdirectory(term)

View File

@@ -8,15 +8,23 @@
#pragma once #pragma once
#include "microrpg.h" #include "microrpg.h"
#define PLATFORM_OK 0
#define PLATFORM_EXIT 1
#define PLATFORM_ERROR 2
/** /**
* Initialize the platform-specific subsystem. * Initialize the platform-specific subsystem.
*
* @return 0 on success, non-zero on failure.
*/ */
void platformInit(void); uint8_t platformInit(void);
/** /**
* Update the platform-specific subsystem. * Update the platform-specific subsystem.
*
* @return 0 to continue, 1 to exit, anything else for error.
*/ */
void platformUpdate(void); uint8_t platformUpdate(void);
/** /**
* Render the platform-specific subsystem. * Render the platform-specific subsystem.

View File

@@ -0,0 +1,27 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Libraries
find_package(raylib REQUIRED)
target_link_libraries(microrpg PRIVATE
raylib::raylib
)
# Sources
target_sources(microrpg PRIVATE
inputraylib.c
termraylib.c
platform.c
font.c
)
# Embed resources
embed_file(microrpg "${ASSETS_DIR}/8x8ascii-darkrose/font.png" "assets/font.h" FONT)
# Compiler flags
target_compile_definitions(microrpg PRIVATE
RPG_RAYLIB=1
)

View File

@@ -0,0 +1,89 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "font.h"
#include "assets/font.h"
font_t FONT;
int32_t fontInit() {
memset(&FONT, 0, sizeof(FONT));
FONT.image = LoadImageFromMemory(".png", FONT_DATA, FONT_SIZE);
if(FONT.image.data == NULL) {
printf("Failed to load font image from memory.\n");
return -1; // Failed to load image
}
FONT.texture = LoadTextureFromImage(FONT.image);
if(FONT.texture.id == 0) {
printf("Failed to load font texture from image.\n");
UnloadImage(FONT.image);
return -1; // Failed to load texture
}
FONT.charWidthRaw = FONT.texture.width / FONT_COLUMN_COUNT;
FONT.charHeightRaw = FONT.texture.height / FONT_ROW_COUNT;
FONT.charWidth = FONT.charWidthRaw * FONT_SCALE;
FONT.charHeight = FONT.charHeightRaw * FONT_SCALE;
return 0;
}
void fontDraw(
const char_t c, const float_t x, const float_t y, const Color color
) {
int32_t charAdjusted = (int32_t)c - 1;
if(c >= '`') charAdjusted += 1;// For some reason the font has empty slot?
int32_t col = (charAdjusted % FONT_COLUMN_COUNT);
int32_t row = (charAdjusted / FONT_COLUMN_COUNT);
Rectangle sourceRec = {
.x = (float_t)(col * FONT.charWidthRaw),
.y = (float_t)(row * FONT.charHeightRaw),
.width = (float_t)FONT.charWidthRaw,
.height = (float_t)FONT.charHeightRaw
};
Rectangle destRec = {
.x = x,
.y = y,
.width = (float_t)FONT.charWidth,
.height = (float_t)FONT.charHeight
};
DrawTexturePro(
FONT.texture, sourceRec, destRec, (Vector2){0, 0}, 0.0f, color
);
}
void fontDrawString(
const char_t *str, const float_t x, const float_t y, const Color color
) {
float_t cx, cy;
cx = x;
cy = y;
const char_t *c = (char_t *)str;
while(*c) {
if(*c == '\n') {
cx = x;
cy += (float_t)(FONT.charHeight + FONT_CHAR_SPACING);
c++;
continue;
}
fontDraw(*c, cx, cy, color);
cx += (float_t)(FONT.charWidth + FONT_CHAR_SPACING);
c++;
}
}
void fontDispose() {
UnloadTexture(FONT.texture);
UnloadImage(FONT.image);
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "microrpg.h"
#include <raylib.h>
#define FONT_COLUMN_COUNT 16
#define FONT_ROW_COUNT 8
#define FONT_CHAR_SPACING 0// Font has built in spacing
#define FONT_SCALE 3.0f
typedef struct font_s {
Texture2D texture;
Image image;
int32_t charWidthRaw;
int32_t charHeightRaw;
int32_t charWidth;
int32_t charHeight;
} font_t;
extern font_t FONT;
/**
* Initializes the font.
*/
int32_t fontInit();
/**
* Draws a character at the specified position.
*
* @param c The character to draw.
* @param x The x position.
* @param y The y position.
* @param color The color to draw the character with.
*/
void fontDraw(
const char_t c, const float_t x, const float_t y, const Color color
);
/**
* Draws a string at the specified position. No wrapping is performed.
*
* @param str The string to draw.
* @param x The x position.
* @param y The y position.
* @param color The color to draw the string with.
*/
void fontDrawString(
const char_t *str, const float_t x, const float_t y, const Color color
);
/**
* Disposes of the font.
*/
void fontDispose();

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "input.h"
bool_t inputDown(const uint8_t action) {
return false;
}
bool_t inputUp(const uint8_t action) {
return true;
}
bool_t inputWasDown(const uint8_t action) {
return false;
}
bool_t inputWasUp(const uint8_t action) {
return true;
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "platform/platform.h"
#include <raylib.h>
#include "font.h"
#include "platform/term/term.h"
uint8_t platformInit(void) {
InitWindow(800, 600, "Micro JRPG");
SetTargetFPS(60);
if(fontInit() != 0) return PLATFORM_ERROR;
SetWindowSize(40 * FONT.charWidth, 30 * FONT.charHeight);
return PLATFORM_OK;
}
uint8_t platformUpdate() {
if(WindowShouldClose()) {
return PLATFORM_EXIT;
}
return PLATFORM_OK;
}
void platformDraw() {
BeginDrawing();
ClearBackground(BLACK);
termDraw();
EndDrawing();
}
void platformDispose() {
fontDispose();
CloseWindow();
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "font.h"
#include "platform/term/term.h"
typedef struct termraylibcolormap_t {
termcolor_t termColor;
Color raylibColor;
} termraylibcolormap_t;
typedef struct termraylib_s {
Color currentColor;
float_t x, y;
} termraylib_t;
termraylibcolormap_t TERM_RAYLIB_COLORMAP[] = {
{ TERM_COLOR_BLACK, BLACK },
{ TERM_COLOR_RED, RED },
{ TERM_COLOR_GREEN, GREEN },
{ TERM_COLOR_YELLOW, YELLOW },
{ TERM_COLOR_BLUE, BLUE },
{ TERM_COLOR_MAGENTA, MAGENTA },
{ TERM_COLOR_CYAN, SKYBLUE },
{ TERM_COLOR_WHITE, WHITE }
};
#define TERM_RAYLIB_INITIAL_COLOR WHITE
termraylib_t TERM_RAYLIB;
void termClear() {
TERM_RAYLIB.currentColor = WHITE;
TERM_RAYLIB.x = 0.0f;
TERM_RAYLIB.y = 0.0f;
}
void termFlush() {
// Nothing to do
}
uint8_t termGetColumnCount() {
// Get Window width
int32_t windowWidth = GetScreenWidth();
return (uint8_t)(windowWidth / FONT.charWidth);
}
uint8_t termGetRowCount() {
// Get Window height
int32_t windowHeight = GetScreenHeight();
return (uint8_t)(windowHeight / FONT.charHeight);
}
void termPushColor(termcolor_t color) {
// Find the corresponding raylib color
size_t colors = sizeof(TERM_RAYLIB_COLORMAP) / sizeof(termraylibcolormap_t);
for(size_t i = 0; i < colors; i++) {
if(TERM_RAYLIB_COLORMAP[i].termColor == color) {
TERM_RAYLIB.currentColor = TERM_RAYLIB_COLORMAP[i].raylibColor;
return;
}
}
}
void termPushChar(char_t c) {
if(c == '\n') {
TERM_RAYLIB.x = 0.0f;
TERM_RAYLIB.y += (float_t)(FONT.charHeight + FONT_CHAR_SPACING);
return;
}
fontDraw(c, TERM_RAYLIB.x, TERM_RAYLIB.y, TERM_RAYLIB.currentColor);
TERM_RAYLIB.x += (float_t)(FONT.charWidth + FONT_CHAR_SPACING);
// Wrapping
if(TERM_RAYLIB.x + FONT.charWidth > (float_t)GetScreenWidth()) {
TERM_RAYLIB.x = 0.0f;
TERM_RAYLIB.y += (float_t)(FONT.charHeight + FONT_CHAR_SPACING);
}
}

15
src/platform/term/CMakeLists.txt Executable file → Normal file
View File

@@ -3,24 +3,9 @@
# This software is released under the MIT License. # This software is released under the MIT License.
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
# Libraries
find_package(Curses REQUIRED)
target_link_libraries(microrpg PRIVATE
${CURSES_LIBRARIES}
)
target_include_directories(microrpg PRIVATE
${CURSES_INCLUDE_DIR}
)
target_link_libraries(microrpg PRIVATE
${CURSES_LIBRARIES}
)
# Sources # Sources
target_sources(microrpg PRIVATE target_sources(microrpg PRIVATE
term.c term.c
inputterm.c
platform.c
) )
# Compiler flags # Compiler flags

108
src/platform/term/term.c Executable file → Normal file
View File

@@ -6,107 +6,11 @@
*/ */
#include "term.h" #include "term.h"
#include "input.h" #include "game.h"
#include <time.h>
typedef struct terminputmap_s {
int key;
uint8_t action;
} terminputmap_t;
static const terminputmap_t TERM_INPUT_MAP[] = {
{ KEY_UP, INPUT_ACTION_UP },
{ 'w', INPUT_ACTION_UP },
{ KEY_DOWN, INPUT_ACTION_DOWN },
{ 's', INPUT_ACTION_DOWN },
{ KEY_LEFT, INPUT_ACTION_LEFT },
{ 'a', INPUT_ACTION_LEFT },
{ KEY_RIGHT, INPUT_ACTION_RIGHT },
{ 'd', INPUT_ACTION_RIGHT },
{ 'j', INPUT_ACTION_A },
{ 'e', INPUT_ACTION_A },
{ 'k', INPUT_ACTION_B },
{ 'q', INPUT_ACTION_B },
{ KEY_ENTER, INPUT_ACTION_START },
{ ' ', INPUT_ACTION_SELECT },
{ -1, 0 }
};
term_t TERM;
void termInit() {
memset(&TERM, 0, sizeof(TERM));
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
nodelay(stdscr, TRUE);
curs_set(0);
start_color();
use_default_colors();
init_pair(1, COLOR_GREEN, -1);
// Set in-game time to real world time.
time_t now = time(NULL);
struct tm *t = localtime(&now);
GAME.time.days = t->tm_wday;
GAME.time.hours = t->tm_hour;
GAME.time.minutes = t->tm_min;
GAME.time.seconds = t->tm_sec;
}
void termUpdate() {
TERM.inputPrevious = TERM.inputCurrent;
TERM.inputCurrent = 0;
int ch = getch();
if(ch == ERR) {
TERM.lastch = ERR;
return;
}
if(ch == TERM.lastch) {
return;
}
TERM.lastch = ch;
const terminputmap_t *map = TERM_INPUT_MAP;
while(map->key != -1) {
if(map->key == ch) {
TERM.inputCurrent |= map->action;
break;
}
map++;
}
}
void termDraw() {
clear();
switch(GAME.scene) {
case SCENE_OVERWORLD:
termDrawOverworld();
break;
default:
break;
}
attroff(COLOR_PAIR(1));
refresh();
// 16ms delay (60FPS)
napms(16);
}
void termDrawOverworld() { void termDrawOverworld() {
// Draw map. // Draw map.
// Draw entities.
attron(COLOR_PAIR(1));
termDrawEntity(&GAME.player); termDrawEntity(&GAME.player);
entity_t *start = GAME.overworld.map.entities; entity_t *start = GAME.overworld.map.entities;
@@ -138,9 +42,13 @@ void termDrawEntity(const entity_t *ent) {
break; break;
} }
mvaddch(ent->position.y, ent->position.x, c); termPushChar
} }
void termDispose() { void termDraw() {
endwin(); termClear();
termFlush();
} }

62
src/platform/term/term.h Executable file → Normal file
View File

@@ -7,45 +7,51 @@
#pragma once #pragma once
#include "microrpg.h" #include "microrpg.h"
#include "game.h" #include "termcolor.h"
#include <ncurses.h>
typedef struct term_s {
uint8_t inputCurrent;
uint8_t inputPrevious;
int lastch;
} term_t;
extern term_t TERM;
/** /**
* Initialize the terminal subsystem. * Common implementation to draw the game state to the terminal (whatever the
*/ * terminal implementation by the platform is).
void termInit();
/**
* Update the terminal prior to game update.
*/
void termUpdate();
/**
* Draw the terminal game.
*/ */
void termDraw(); void termDraw();
/** /**
* Draw the overworld scene. * Clears the terminal screen and resets the cursor to the top left. Called at
* the start of each "frame". Should also reset any terminal state (e.g. color).
*/ */
void termDrawOverworld(); void termClear();
/** /**
* Draw an entity to the terminal. * Flushes any pending terminal operations. Called at the end of each "frame".
*/
void termFlush();
/**
* Get the number of columns in the terminal.
* *
* @param ent The entity to draw. * @return The number of columns.
*/ */
void termDrawEntity(const entity_t *ent); uint8_t termGetColumnCount();
/** /**
* Dispose of the terminal subsystem. * Get the number of rows in the terminal.
*
* @return The number of rows.
*/ */
void termDispose(); uint8_t termGetRowCount();
/**
* Push a color onto the terminal color stack.
*
* @param color The color to push.
*/
void termPushColor(termcolor_t color);
/**
* Push a character onto the terminal at the current cursor position. This will
* also advance the cursor position by 1 character. Wrapping will be handled by
* the terminal implementation.
*
* @param c The character to push.
*/
void termPushChar(char_t c);

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "microrpg.h"
#define TERM_COLOR_WHITE 0
#define TERM_COLOR_RED 1
#define TERM_COLOR_GREEN 2
#define TERM_COLOR_YELLOW 3
#define TERM_COLOR_BLUE 4
#define TERM_COLOR_MAGENTA 5
#define TERM_COLOR_CYAN 6
#define TERM_COLOR_BLACK 7
typedef uint8_t termcolor_t;

29
src/platform/tty/CMakeLists.txt Executable file
View File

@@ -0,0 +1,29 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Libraries
find_package(Curses REQUIRED)
target_link_libraries(microrpg PRIVATE
${CURSES_LIBRARIES}
)
target_include_directories(microrpg PRIVATE
${CURSES_INCLUDE_DIR}
)
target_link_libraries(microrpg PRIVATE
${CURSES_LIBRARIES}
)
# Sources
target_sources(microrpg PRIVATE
term.c
inputterm.c
platform.c
)
# Compiler flags
target_compile_definitions(microrpg PRIVATE
RPG_TERM=1
)

146
src/platform/tty/term.c Executable file
View File

@@ -0,0 +1,146 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "term.h"
#include "input.h"
#include <time.h>
typedef struct terminputmap_s {
int key;
uint8_t action;
} terminputmap_t;
static const terminputmap_t TERM_INPUT_MAP[] = {
{ KEY_UP, INPUT_ACTION_UP },
{ 'w', INPUT_ACTION_UP },
{ KEY_DOWN, INPUT_ACTION_DOWN },
{ 's', INPUT_ACTION_DOWN },
{ KEY_LEFT, INPUT_ACTION_LEFT },
{ 'a', INPUT_ACTION_LEFT },
{ KEY_RIGHT, INPUT_ACTION_RIGHT },
{ 'd', INPUT_ACTION_RIGHT },
{ 'j', INPUT_ACTION_A },
{ 'e', INPUT_ACTION_A },
{ 'k', INPUT_ACTION_B },
{ 'q', INPUT_ACTION_B },
{ KEY_ENTER, INPUT_ACTION_START },
{ ' ', INPUT_ACTION_SELECT },
{ -1, 0 }
};
term_t TERM;
void termInit() {
memset(&TERM, 0, sizeof(TERM));
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
nodelay(stdscr, TRUE);
curs_set(0);
start_color();
use_default_colors();
init_pair(1, COLOR_GREEN, -1);
// Set in-game time to real world time.
time_t now = time(NULL);
struct tm *t = localtime(&now);
GAME.time.days = t->tm_wday;
GAME.time.hours = t->tm_hour;
GAME.time.minutes = t->tm_min;
GAME.time.seconds = t->tm_sec;
}
void termUpdate() {
TERM.inputPrevious = TERM.inputCurrent;
TERM.inputCurrent = 0;
int ch = getch();
if(ch == ERR) {
TERM.lastch = ERR;
return;
}
if(ch == TERM.lastch) {
return;
}
TERM.lastch = ch;
const terminputmap_t *map = TERM_INPUT_MAP;
while(map->key != -1) {
if(map->key == ch) {
TERM.inputCurrent |= map->action;
break;
}
map++;
}
}
void termDraw() {
clear();
switch(GAME.scene) {
case SCENE_OVERWORLD:
termDrawOverworld();
break;
default:
break;
}
attroff(COLOR_PAIR(1));
refresh();
// 16ms delay (60FPS)
napms(16);
}
void termDrawOverworld() {
// Draw map.
// Draw entities.
attron(COLOR_PAIR(1));
termDrawEntity(&GAME.player);
entity_t *start = GAME.overworld.map.entities;
entity_t *end = start + MAP_ENTITY_COUNT;
while(start < end) {
if(start->type != ENTITY_TYPE_NULL) termDrawEntity(start);
start++;
}
}
void termDrawEntity(const entity_t *ent) {
// Placeholder: Draw entity at its position
char c;
switch(ent->direction) {
case DIRECTION_NORTH:
c = '^';
break;
case DIRECTION_EAST:
c = '>';
break;
case DIRECTION_SOUTH:
c = 'v';
break;
case DIRECTION_WEST:
c = '<';
break;
default:
c = '@';
break;
}
mvaddch(ent->position.y, ent->position.x, c);
}
void termDispose() {
endwin();
}

51
src/platform/tty/term.h Executable file
View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "microrpg.h"
#include "game.h"
#include <ncurses.h>
typedef struct term_s {
uint8_t inputCurrent;
uint8_t inputPrevious;
int lastch;
} term_t;
extern term_t TERM;
/**
* Initialize the terminal subsystem.
*/
void termInit();
/**
* Update the terminal prior to game update.
*/
void termUpdate();
/**
* Draw the terminal game.
*/
void termDraw();
/**
* Draw the overworld scene.
*/
void termDrawOverworld();
/**
* Draw an entity to the terminal.
*
* @param ent The entity to draw.
*/
void termDrawEntity(const entity_t *ent);
/**
* Dispose of the terminal subsystem.
*/
void termDispose();

6
tools/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_subdirectory(embed)

View File

@@ -0,0 +1,22 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
function(embed_file TARGET_NAME INPUT_FILE_ABS OUTPUT_FILE_HEADER C_VAR_NAME)
set(OUTPUT_FILE_ABS "${CMAKE_BINARY_DIR}/generated_headers/${OUTPUT_FILE_HEADER}")
# Create a custom target
add_custom_target(
"${INPUT_FILE}_embed"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/generated_headers"
COMMAND ${CMAKE_COMMAND} -DINPUT_FILE="${INPUT_FILE_ABS}" -DOUTPUT_FILE="${OUTPUT_FILE_ABS}" -DC_VAR_NAME="${C_VAR_NAME}" -P "${TOOLS_DIR}/embed/embed.cmake"
DEPENDS "${INPUT_FILE_ABS}"
)
# Add the generated header to the target's include directories
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/generated_headers")
# Make the target depend on the custom target
add_dependencies(${TARGET_NAME} "${INPUT_FILE}_embed")
endfunction()

21
tools/embed/embed.cmake Normal file
View File

@@ -0,0 +1,21 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# We are passed -DINPUT_FILE (absolute) and -DOUTPUT_FILE (absolute) and -DC_VAR_NAME (name of the C variable to hold the data)
file(READ "${INPUT_FILE}" RAW_DATA HEX)
string(LENGTH "${RAW_DATA}" DATA_LENGTH)
set(OUTPUT_CONTENT "/* This file is auto-generated. Do not edit directly. */\n")
set(OUTPUT_CONTENT "${OUTPUT_CONTENT}#pragma once\n#include \"microrpg.h\"\n\n")
set(OUTPUT_CONTENT "${OUTPUT_CONTENT}static const uint8_t ${C_VAR_NAME}_DATA[] = {\n ")
set(INDEX 0)
while(INDEX LESS DATA_LENGTH)
string(SUBSTRING "${RAW_DATA}" ${INDEX} 2 BYTE)
set(OUTPUT_CONTENT "${OUTPUT_CONTENT}0x${BYTE}, ")
math(EXPR INDEX "${INDEX} + 2")
endwhile()
set(OUTPUT_CONTENT "${OUTPUT_CONTENT}\n};\n\n")
set(OUTPUT_CONTENT "${OUTPUT_CONTENT}#define ${C_VAR_NAME}_SIZE ${DATA_LENGTH}u\n")
file(WRITE "${OUTPUT_FILE}" "${OUTPUT_CONTENT}")