Introduced playername option, fancied up the replayview menu, etc

This commit is contained in:
Andrew "Akari" Alexeyew 2012-07-29 23:39:52 +03:00
parent d1f4aa03e3
commit a322371b5f
17 changed files with 311 additions and 67 deletions

View file

@ -12,7 +12,8 @@
#include "parser.h"
typedef struct Config {
int intval[64];
int intval[64];
char* strval[64];
} Config;
extern Config tconfig;
@ -41,6 +42,8 @@ enum {
VID_WIDTH,
VID_HEIGHT,
PLAYERNAME
};
void parse_config(char *filename);

View file

@ -44,6 +44,8 @@
"vid_width" { yylval = VID_WIDTH; return tVID_WIDTH; }
"vid_height" { yylval = VID_HEIGHT; return tVID_HEIGHT; }
"playername" { yylval = PLAYERNAME; return tPLAYERNAME; }
"shift" { yylval = SDLK_LSHIFT; return SKEY; }
"ctrl" { yylval = SDLK_LCTRL; return SKEY; }
"return" { yylval = SDLK_RETURN; return SKEY; }
@ -54,7 +56,6 @@
"right" { yylval = SDLK_RIGHT; return SKEY; }
"left" { yylval = SDLK_LEFT; return SKEY; }
[0-9]+ { yylval = atoi(yytext); return NUMBER; }
[a-zA-Z] { yylval = yytext[0]; return tCHAR; }
@ -65,6 +66,16 @@
K[0-9]+ { yylval = atoi(yytext + 1); return tCHAR; }
\"[^\"\n\r]+\" {
int l = strlen(yytext) - 1;
char *s = malloc(l);
strcpy(s, yytext + 1);
s[l-1] = 0;
yylval = (int)s;
return tSTRING;
}
\n return LB;
[ \t] ;

View file

@ -14,7 +14,7 @@
#include "paths/native.h"
#include "taisei_err.h"
Config tconfig;
int lineno;
@ -55,10 +55,13 @@
%token tVID_WIDTH
%token tVID_HEIGHT
%token tPLAYERNAME
%token SKEY
%token NUMBER
%token tCHAR
%token tSTRING
%token SEMI
%token EQ
@ -74,6 +77,14 @@ line : key_key EQ key_val {
if($1 > sizeof(tconfig.intval)/sizeof(int))
errx(-1, "config index out of range"); // should not happen
tconfig.intval[$1] = $3;
}
| key_strkey EQ tSTRING {
if($1 > sizeof(tconfig.strval)/sizeof(char*))
errx(-1, "config index out of range"); // should not happen
if(tconfig.strval[$1])
free(tconfig.strval[$1]);
tconfig.strval[$1] = $3;
};
key_val : SKEY
@ -98,6 +109,9 @@ key_key : tKEY_UP
| tVID_HEIGHT
| tSAVE_RPY;
key_strkey : tPLAYERNAME;
nl : LB { lineno++; };
%%
@ -113,7 +127,6 @@ void parse_config(char *filename) {
strcat(buf, "/");
strcat(buf, filename);
yyin = fopen(buf, "r");
printf("parse_config():\n");
@ -129,6 +142,8 @@ void parse_config(char *filename) {
}
void config_preset() {
memset(tconfig.strval, 0, sizeof(tconfig.strval));
tconfig.intval[KEY_UP] = SDLK_UP;
tconfig.intval[KEY_DOWN] = SDLK_DOWN;
tconfig.intval[KEY_LEFT] = SDLK_LEFT;
@ -153,6 +168,10 @@ void config_preset() {
tconfig.intval[VID_WIDTH] = RESX;
tconfig.intval[VID_HEIGHT] = RESY;
char *name = "Player";
tconfig.strval[PLAYERNAME] = malloc(strlen(name)+1);
strcpy(tconfig.strval[PLAYERNAME], name);
}
int config_sym2key(int sym) {

View file

@ -176,3 +176,34 @@ void global_processevent(SDL_Event *event)
video_toggle_fullscreen();
}
}
int strendswith(char *s, char *e) {
int ls = strlen(s);
int le = strlen(e);
if(le > ls)
return False;
int i; for(i = ls - 1; i < le; ++i)
if(s[i] != e[i])
return False;
return True;
}
char* difficulty_name(Difficulty diff) {
switch(diff) {
case D_Easy: return "Easy"; break;
case D_Normal: return "Nromal"; break;
case D_Hard: return "Hard"; break;
case D_Lunatic: return "Lunatic"; break;
default: return "Unknown"; break;
}
}
void stralloc(char **dest, char *src) {
if(*dest)
free(*dest);
*dest = malloc(strlen(src)+1);
strcpy(*dest, src);
}

View file

@ -139,6 +139,9 @@ void global_processevent(SDL_Event*);
void take_screenshot();
double approach(double v, double t, double d);
int strendswith(char *s, char *e);
char* difficulty_name(Difficulty diff);
void stralloc(char **dest, char *src);
// this is used by both player and replay code
enum {

View file

@ -49,7 +49,7 @@ void taisei_shutdown() {
int main(int argc, char** argv) {
if(tsrand_test())
return 0;
MKDIR(get_config_path());
MKDIR(get_screenshots_path());
MKDIR(get_replays_path());
@ -106,10 +106,12 @@ int main(int argc, char** argv) {
}
#endif
printf("playename = %s\n", tconfig.strval[PLAYERNAME]);
MenuData menu;
create_main_menu(&menu);
printf("-- menu\n");
main_menu_loop(&menu);
return 1;
}

View file

@ -10,12 +10,13 @@
void add_menu_entry(MenuData *menu, char *name, void (*action)(void *), void *arg) {
menu->entries = realloc(menu->entries, (++menu->ecount)*sizeof(MenuEntry));
menu->entries[menu->ecount-1].name = malloc(strlen(name)+1);
strcpy(menu->entries[menu->ecount-1].name, name);
menu->entries[menu->ecount-1].action = action;
menu->entries[menu->ecount-1].freearg = False;
menu->entries[menu->ecount-1].arg = arg;
menu->entries[menu->ecount-1].drawdata = 0;
MenuEntry *e = &(menu->entries[menu->ecount-1]);
memset(e, 0, sizeof(MenuEntry));
e->name = malloc(strlen(name)+1);
strcpy(e->name, name);
e->action = action;
e->arg = arg;
}
void add_menu_separator(MenuData *menu) {
@ -34,7 +35,7 @@ void destroy_menu(MenuData *menu) {
free(e->name);
if(e->freearg)
free(e->arg);
e->freearg(e->arg);
}
free(menu->entries);

View file

@ -14,10 +14,11 @@
typedef struct {
char *name;
void (*action)(void* arg);
void (*action)(void *arg);
void *arg;
float drawdata;
int freearg;
void (*freearg)(void *arg);
void (*draw)(void *e, int i, int cnt);
} MenuEntry;
typedef enum MenuType { // whether to close on selection or not.

View file

@ -60,8 +60,7 @@ void free_bindings(MenuData *m)
// Binds the last entry to an integer config option having limited values (BT_IntValue type binding).
// Values are defined with bind_addvalue.
OptionBinding* bind_option(MenuData *m, char *optname, int cfgentry, BindingGetter getter, BindingSetter setter)
{
OptionBinding* bind_option(MenuData *m, char *optname, int cfgentry, BindingGetter getter, BindingSetter setter) {
OptionBinding *bind;
bind = allocate_binding(m);
@ -77,8 +76,7 @@ OptionBinding* bind_option(MenuData *m, char *optname, int cfgentry, BindingGett
}
// Binds the last entry to a keybinding config option (BT_KeyBinding type binding).
OptionBinding* bind_keybinding(MenuData *m, char *optname, int cfgentry)
{
OptionBinding* bind_keybinding(MenuData *m, char *optname, int cfgentry) {
OptionBinding *bind;
bind = allocate_binding(m);
@ -91,6 +89,25 @@ OptionBinding* bind_keybinding(MenuData *m, char *optname, int cfgentry)
return bind;
}
// For string values, with a "textbox" editor
OptionBinding* bind_stroption(MenuData *m, char *optname, int cfgentry) {
OptionBinding *bind;
bind = allocate_binding(m);
bind->configentry = cfgentry;
bind->optname = malloc(strlen(optname) + 1);
strcpy(bind->optname, optname);
bind->enabled = True;
bind->type = BT_StrValue;
bind->valcount = 1;
bind->values = malloc(sizeof(char*));
*bind->values = malloc(128);
strncpy(*bind->values, tconfig.strval[cfgentry], 128);
return bind;
}
// Super-special binding type for the resolution setting
OptionBinding* bind_resolution(MenuData *m) {
OptionBinding *bind;
@ -340,6 +357,10 @@ void menu_save_config(MenuData *m, char *filename)
fprintf(out, "%s = K%i\n", bind->optname, tconfig.intval[bind->configentry]);
break;
case BT_StrValue:
fprintf(out, "%s = \"%s\"\n", bind->optname, tconfig.strval[bind->configentry]);
break;
case BT_Resolution:
// at this point, the intended resolution is what the user has picked
fprintf(out, "vid_width = %i\nvid_height = %i\n", video.intended.width, video.intended.height);
@ -393,6 +414,12 @@ void create_options_menu(MenuData *m) {
#define bind_onoff(b) bind_addvalue(b, "on"); bind_addvalue(b, "off")
add_menu_entry(m, "Player Name", do_nothing, NULL);
b = bind_stroption(m, "playername", PLAYERNAME);
add_menu_separator(m);
allocate_binding(m);
add_menu_entry(m, "Video Mode", do_nothing, NULL);
b = bind_resolution(m);
@ -430,7 +457,7 @@ void create_options_menu(MenuData *m) {
bind_addvalue(b, "off");
bind_addvalue(b, "ask");
add_menu_entry(m, " ", NULL, NULL);
add_menu_separator(m);
allocate_binding(m);
add_menu_entry(m, "Move up", do_nothing, NULL);
@ -445,7 +472,7 @@ void create_options_menu(MenuData *m) {
add_menu_entry(m, "Move right", do_nothing, NULL);
bind_keybinding(m, "key_right", KEY_RIGHT);
add_menu_entry(m, " ", NULL, NULL);
add_menu_separator(m);
allocate_binding(m);
add_menu_entry(m, "Fire", do_nothing, NULL);
@ -457,7 +484,7 @@ void create_options_menu(MenuData *m) {
add_menu_entry(m, "Bomb", do_nothing, NULL);
bind_keybinding(m, "key_bomb", KEY_BOMB);
add_menu_entry(m, " ", NULL, NULL);
add_menu_separator(m);
allocate_binding(m);
add_menu_entry(m, "Toggle fullscreen", do_nothing, NULL);
@ -466,7 +493,7 @@ void create_options_menu(MenuData *m) {
add_menu_entry(m, "Take a screenshot", do_nothing, NULL);
bind_keybinding(m, "key_screenshot", KEY_SCREENSHOT);
add_menu_entry(m, " ", NULL, NULL);
add_menu_separator(m);
allocate_binding(m);
add_menu_entry(m, "Return to the main menu", backtomain, m);
@ -508,6 +535,9 @@ void draw_options_menu(MenuData *menu) {
int i, caption_drawn = 0;
for(i = 0; i < menu->ecount; i++) {
if(!menu->entries[i].name)
continue;
menu->entries[i].drawdata += 0.2 * (10*(i == menu->cursor) - menu->entries[i].drawdata);
bind = &(binds[i]);
@ -559,15 +589,22 @@ void draw_options_menu(MenuData *menu) {
caption_drawn = 1;
}
if(bind->blockinput)
{
if(bind->blockinput) {
glColor4f(0,1,0,0.7);
draw_text(AL_Right, origin, 20*i, "Press a key to assign, ESC to cancel", _fonts.standard);
}
else
} else
draw_text(AL_Right, origin, 20*i, SDL_GetKeyName(tconfig.intval[bind->configentry]), _fonts.standard);
break;
case BT_StrValue:
if(bind->blockinput) {
glColor4f(0,1,0,0.7);
if(strlen(*bind->values))
draw_text(AL_Right, origin, 20*i, *bind->values, _fonts.standard);
} else
draw_text(AL_Right, origin, 20*i, tconfig.strval[bind->configentry], _fonts.standard);
break;
case BT_Resolution: {
char tmp[16];
int w, h;
@ -600,27 +637,58 @@ void draw_options_menu(MenuData *menu) {
void bind_input(MenuData *menu, OptionBinding *b)
{
// yes, no global_input() here.
SDL_Event event;
if(b->type != BT_KeyBinding) // shouldn't happen, but just in case.
{
b->blockinput = False;
return;
}
SDL_EnableUNICODE(True);
while(SDL_PollEvent(&event)) {
int sym = event.key.keysym.sym;
int uni = event.key.keysym.unicode;
if(event.type == SDL_KEYDOWN) {
if(sym != SDLK_ESCAPE) // escape means cancel
tconfig.intval[b->configentry] = sym;
b->blockinput = False;
if(sym != SDLK_ESCAPE) {
switch(b->type) {
case BT_KeyBinding:
tconfig.intval[b->configentry] = sym;
b->blockinput = False;
break;
case BT_StrValue: {
// Very basic editor for string config values
// b->values is a pointer to the editor buffer string here (char**)
// Normally it's used to store the value names for BT_IntValue binds, though.
// TODO: implement a cursor here (so we can use arrow keys for editing)
char c = (char)(uni & 0x7F);
char *dest = *b->values;
if(sym == SDLK_RETURN) {
if(strlen(dest))
stralloc(&(tconfig.strval[b->configentry]), dest);
else
strncpy(dest, tconfig.strval[b->configentry], 128);
b->blockinput = False;
} else if(sym == SDLK_BACKSPACE) {
if(strlen(dest))
dest[strlen(dest)-1] = 0;
} else if(uni && sym != SDLK_TAB && c != ':') { // we use ':' as a separator for replays (and might use it as such somewhere else), and I don't feel like escaping it.
strncat(dest, &c, 128);
dest[strlen(dest)-1] = 0;
}
break;
}
default: // should never get there anyway
b->blockinput = False;
break;
}
} else
b->blockinput = False;
} else if(event.type == SDL_QUIT) {
exit(1);
}
}
SDL_EnableUNICODE(False);
}
static void options_key_action(MenuData *menu, int sym) {
@ -649,7 +717,11 @@ static void options_key_action(MenuData *menu, int sym) {
if(bind->enabled) switch(bind->type)
{
case BT_IntValue: case BT_Resolution: bind_setnext(bind); break;
case BT_KeyBinding: bind->blockinput = True; break;
case BT_KeyBinding: bind->blockinput = True; break;
case BT_StrValue:
bind->selected = strlen(tconfig.strval[bind->configentry]);
bind->blockinput = True;
break;
} else menu->quit = 1;
} else if(sym == tconfig.intval[KEY_LEFT] || sym == SDLK_LEFT) {
menu->selected = menu->cursor;

View file

@ -23,6 +23,7 @@ typedef int (*BindingDependence)();
typedef enum BindingType {
BT_IntValue,
BT_KeyBinding,
BT_StrValue,
BT_Resolution
} BindingType;

View file

@ -7,17 +7,19 @@
*/
#include <dirent.h>
#include <time.h>
#include "global.h"
#include "menu.h"
#include "options.h"
#include "mainmenu.h"
#include "replayview.h"
#include "paths/native.h"
#include "plrmodes.h"
void backtomain(void*);
void start_replay(void *arg) {
replay_load(&global.replay, (char*)arg);
replay_copy(&global.replay, (Replay*)arg);
StageInfo *s = stage_get(global.replay.stage);
if(!s) {
@ -33,18 +35,73 @@ void start_replay(void *arg) {
global.game_over = 0;
}
int strendswith(char *s, char *e) {
int ls = strlen(s);
int le = strlen(e);
static void replayview_freearg(void *a) {
Replay *r = (Replay*)a;
replay_destroy(r);
free(r);
}
static void replayview_drawitem(void *n, int item, int cnt) {
MenuEntry *e = (MenuEntry*)n;
Replay *rpy = (Replay*)e->arg;
float sizes[] = {0.7, 1.5, 0.8, 0.9, 1.0};
if(le > ls)
return False;
//draw_text(AL_Left, 20 - e->drawdata, 20*i, "lol replay omg", _fonts.standard);
int i; for(i = ls - 1; i < le; ++i)
if(s[i] != e[i])
return False;
return True;
int columns = 5, i, j;
for(i = 0; i < columns; ++i) {
char tmp[128];
int csize = sizes[i] * (SCREEN_W - 210)/columns;
int o = 0;
for(j = 0; j < i; ++j)
o += sizes[j] * (SCREEN_W - 210)/columns;
Alignment a = AL_Center;
// hell yeah, another loop-switch sequence
switch(i) {
case 0:
a = AL_Left;
snprintf(tmp, 128, "Stage %i", rpy->stage);
break;
case 1:
a = AL_Center;
strncpy(tmp, rpy->playername, 128);
break;
case 2:
plrmode_repr(tmp, 128, rpy->plr_char, rpy->plr_shot);
tmp[0] = tmp[0] - 'a' + 'A';
break;
case 3:
snprintf(tmp, 128, difficulty_name(rpy->diff));
break;
case 4:
a = AL_Left;
time_t t = rpy->seed;
struct tm* timeinfo = localtime(&t);
strftime(tmp, 128, "%H:%M %m/%d/%y", timeinfo);
break;
}
switch(a) {
case AL_Center: o += csize * 0.5 - stringwidth(tmp, _fonts.standard) * 0.5; break;
case AL_Right: o += csize - stringwidth(tmp, _fonts.standard); break;
default: break;
}
draw_text(AL_Left, o + 10, 20*item, tmp, _fonts.standard);
}
}
int replayview_cmp(const void *a, const void *b) {
return ((Replay*)(((MenuEntry*)b)->arg))->seed - ((Replay*)(((MenuEntry*)a)->arg))->seed;
}
int fill_replayview_menu(MenuData *m) {
@ -64,17 +121,25 @@ int fill_replayview_menu(MenuData *m) {
if(!strendswith(e->d_name, ext))
continue;
Replay *rpy = malloc(sizeof(Replay));
replay_load(rpy, e->d_name);
/*
int size = strlen(e->d_name) - strlen(ext) + 1;
char *s = (char*)malloc(size);
strncpy(s, e->d_name, size);
s[size-1] = 0;
*/
add_menu_entry(m, s, start_replay, s);
m->entries[m->ecount-1].freearg = True;
add_menu_entry(m, " ", start_replay, rpy);
m->entries[m->ecount-1].freearg = replayview_freearg;
m->entries[m->ecount-1].draw = replayview_drawitem;
++rpys;
}
closedir(dir);
qsort(m->entries, m->ecount, sizeof(MenuEntry), replayview_cmp);
return rpys;
}
@ -93,6 +158,8 @@ void create_replayview_menu(MenuData *m) {
add_menu_separator(m);
add_menu_entry(m, "Back", backtomain, m);
}
printf("create_replayview_menu()\n");
}
void draw_stage_menu(MenuData *m);

View file

@ -18,7 +18,8 @@ void save_rpy(void *a) {
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
// time when the game was *initiated*
rawtime = (time_t)rpy->seed;
timeinfo = localtime(&rawtime);
strftime(strtime, 128, "%Y%m%d_%H-%M-%S_%Z", timeinfo);

View file

@ -35,10 +35,7 @@ void create_stage_menu(MenuData *m) {
void draw_stage_menu(MenuData *m) {
draw_options_menu_bg(m);
int w, h;
TTF_SizeText(_fonts.mainmenu, m->title, &w, &h);
draw_text(AL_Right, (w + 10) * (1-m->fade), 30, m->title, _fonts.mainmenu);
draw_text(AL_Right, (stringwidth(m->title, _fonts.mainmenu) + 10) * (1-m->fade), 30, m->title, _fonts.mainmenu);
glPushMatrix();
glTranslatef(100, 100 + min(0, SCREEN_H * 0.7 - 100 - m->drawdata[2]), 0);
@ -64,7 +61,9 @@ void draw_stage_menu(MenuData *m) {
else
glColor4f(1, 1, 1, 0.7);
if(e->name)
if(e->draw)
e->draw(e, i, m->ecount);
else if(e->name)
draw_text(AL_Left, 20 - e->drawdata, 20*i, e->name, _fonts.standard);
}

View file

@ -41,6 +41,8 @@ void replay_init(Replay *rpy, StageInfo *stage, int seed, Player *plr) {
void replay_destroy(Replay *rpy) {
if(rpy->events)
free(rpy->events);
if(rpy->playername)
free(rpy->playername);
memset(rpy, 0, sizeof(Replay));
printf("Replay destroyed.\n");
}
@ -95,7 +97,7 @@ int replay_write(Replay *rpy, FILE *file) {
// header
replay_write_int(file, REPLAY_MAGICNUMBER);
replay_write_string(file, "Taisei replay file");
replay_write_string(file, tconfig.strval[PLAYERNAME]);
replay_write_string(file, "This file is not for your eyes, move on");
replay_write_int(file, 1);
@ -163,6 +165,7 @@ int replay_read(Replay *rpy, FILE *file) {
int readstate = RPY_H_MAGIC;
int bufidx = 0, eidx = 0;
char buf[REPLAY_READ_MAXSTRLEN], c;
memset(rpy, 0, sizeof(Replay));
while((c = fgetc(file)) != EOF) {
if(c == ':') {
@ -181,7 +184,8 @@ int replay_read(Replay *rpy, FILE *file) {
}
case RPY_H_META1:
printf("replay_read(): %s\n", buf);
stralloc(&rpy->playername, buf);
printf("replay_read(): replay META1 is: %s\n", buf);
break;
case RPY_H_META2: case RPY_H_REPLAYCOUNT: // skip
@ -231,14 +235,17 @@ int replay_read(Replay *rpy, FILE *file) {
#undef FLOATOF
#undef INTOF
char* replay_getpath(char *name) {
char* replay_getpath(char *name, int ext) {
char *p = (char*)malloc(strlen(get_replays_path()) + strlen(name) + strlen(REPLAY_EXTENSION) + 3);
sprintf(p, "%s/%s.%s", get_replays_path(), name, REPLAY_EXTENSION);
if(ext)
sprintf(p, "%s/%s.%s", get_replays_path(), name, REPLAY_EXTENSION);
else
sprintf(p, "%s/%s", get_replays_path(), name);
return p;
}
int replay_save(Replay *rpy, char *name) {
char *p = replay_getpath(name);
char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
printf("replay_save(): saving %s\n", p);
FILE *fp = fopen(p, "w");
@ -256,7 +263,7 @@ int replay_save(Replay *rpy, char *name) {
}
int replay_load(Replay *rpy, char *name) {
char *p = replay_getpath(name);
char *p = replay_getpath(name, !strendswith(name, REPLAY_EXTENSION));
printf("replay_load(): loading %s\n", p);
FILE *fp = fopen(p, "r");
@ -271,3 +278,10 @@ int replay_load(Replay *rpy, char *name) {
fclose(fp);
return result;
}
void replay_copy(Replay *dst, Replay *src) {
memcpy(dst, src, sizeof(Replay));
dst->capacity = dst->ecount;
dst->events = (ReplayEvent*)malloc(sizeof(ReplayEvent) * dst->capacity);
memcpy(dst->events, src->events, dst->capacity * sizeof(ReplayEvent));
}

View file

@ -19,9 +19,12 @@ typedef struct ReplayEvent {
} ReplayEvent;
typedef struct Replay {
// metadata
char *playername;
// initial game settings
int stage;
int seed;
int seed; // this also happens to be the game initiation time - and we use this property, don't break it please
int diff;
int points;
@ -33,6 +36,7 @@ typedef struct Replay {
int plr_lifes;
int plr_bombs;
// events
ReplayEvent *events;
int ecount;
@ -54,9 +58,10 @@ void replay_event(Replay *rpy, int type, int key);
int replay_write(Replay *rpy, FILE *file);
int replay_read(Replay *rpy, FILE *file);
char* replay_getpath(char *name); // must be freed
char* replay_getpath(char *name, int ext); // must be freed
int replay_save(Replay *rpy, char *name);
int replay_load(Replay *rpy, char *name);
void replay_copy(Replay *dst, Replay *src);
#define REPLAY_ALLOC_INITIAL 100
#define REPLAY_ALLOC_ADDITIONAL 100

View file

@ -76,3 +76,15 @@ void draw_text(Alignment align, float x, float y, const char *text, TTF_Font *fo
free(buf);
}
int stringwidth(char *s, TTF_Font *font) {
int w;
TTF_SizeText(font, s, &w, NULL);
return w;
}
int charwidth(char c, TTF_Font *font) {
char s[2];
s[0] = c;
s[1] = 0;
return stringwidth(s, font);
}

View file

@ -21,6 +21,8 @@ typedef enum {
Texture *load_text(const char *text, TTF_Font *font);
void draw_text(Alignment align, float x, float y, const char *text, TTF_Font *font);
void init_fonts();
int stringwidth(char *s, TTF_Font *font);
int charwidth(char c, TTF_Font *font);
struct Fonts {
TTF_Font *standard;
@ -29,4 +31,4 @@ struct Fonts {
extern struct Fonts _fonts;
#endif
#endif