#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SDL_Window *window = NULL; SDL_Renderer *renderer = NULL; akgl_Frame ball; akgl_Frame paddle1; akgl_Frame paddle2; akgl_Frame table; akgl_Tilemap gamemap; MIX_Audio *bgm = NULL; MIX_Mixer *akgl_mixer = NULL; MIX_Track *akgl_tracks[AKGL_GAME_AUDIO_MAX_TRACKS]; SDL_FRect camera; akgl_Game game; akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init() { int i = 0; PREPARE_ERROR(errctx); ATTEMPT { strncpy((char *)&game.libversion, AKGL_VERSION, 32); game.gameStartTime = SDL_GetTicksNS(); game.lastIterTime = game.gameStartTime; game.lastFPSTime = game.gameStartTime; FAIL_ZERO_BREAK(errctx, strlen((char *)&game.name), AKERR_NULLPOINTER, "Must provide game name"); FAIL_ZERO_BREAK(errctx, strlen((char *)&game.version), AKERR_NULLPOINTER, "Must provide game version"); FAIL_ZERO_BREAK(errctx, strlen((char *)&game.uri), AKERR_NULLPOINTER, "Must provide game uri"); CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_registry_init_actor()); CATCH(errctx, akgl_registry_init_sprite()); CATCH(errctx, akgl_registry_init_spritesheet()); CATCH(errctx, akgl_registry_init_character()); CATCH(errctx, akgl_registry_init_font()); CATCH(errctx, akgl_registry_init_music()); CATCH(errctx, akgl_registry_init_properties()); CATCH(errctx, akgl_registry_init_actor_state_strings()); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true) SDL_SetAppMetadata(game.name, game.version, game.uri); for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) { memset(&GAME_ControlMaps[i], 0x00, sizeof(akgl_ControlMap)); } FAIL_ZERO_RETURN( errctx, SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD | SDL_INIT_AUDIO), AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError()); // Load the Game Controller DB for ( i = 0; i < AKGL_SDL_GAMECONTROLLER_DB_LEN ; i++ ) { if ( SDL_AddGamepadMapping(SDL_GAMECONTROLLER_DB[i]) == -1 ) { FAIL_ZERO_RETURN(errctx, 0, AKGL_ERR_SDL, "%s", SDL_GetError()); } } FAIL_ZERO_RETURN( errctx, SDL_CreateWindowAndRenderer(game.uri, game.screenwidth, game.screenheight, 0, &window, &renderer), AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError()); FAIL_ZERO_RETURN( errctx, MIX_Init(), AKGL_ERR_SDL, "Couldn't initialize audio: %s", SDL_GetError()); akgl_mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, 0); FAIL_ZERO_RETURN( errctx, akgl_mixer, AKGL_ERR_SDL, "Unable to create mixer device: %s", SDL_GetError()); FAIL_ZERO_RETURN( errctx, TTF_Init(), AKGL_ERR_SDL, "Couldn't initialize front engine: %s", SDL_GetError()); camera.x = 0; camera.y = 0; camera.w = game.screenwidth; camera.h = game.screenheight; SUCCEED(errctx); } void akgl_game_updateFPS() { SDL_Time curTime; curTime = SDL_GetTicksNS(); if ( (curTime - game.lastFPSTime) > AKGL_TIME_ONESEC_NS ) { game.fps = game.framesSinceUpdate; game.framesSinceUpdate = 0; game.lastFPSTime = curTime; } game.framesSinceUpdate += 1; game.lastIterTime = curTime; } /* * entity name -> pointer map tables */ void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, const char *name) { FILE *fp = (FILE *)userdata; akgl_Actor *actor = NULL; PREPARE_ERROR(errctx); ATTEMPT { FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); fwrite((char *)name, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp); actor = SDL_GetPointerProperty(props, name, NULL); fwrite(&actor, 1, sizeof(akgl_Actor *), fp); } CLEANUP { } PROCESS(errctx) { } FINISH_NORETURN(errctx); } void akgl_game_save_spritename_iterator(void *userdata, SDL_PropertiesID props, const char *name) { FILE *fp = (FILE *)userdata; akgl_Sprite *sprite = NULL; PREPARE_ERROR(errctx); ATTEMPT { FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); sprite = SDL_GetPointerProperty(props, name, NULL); fwrite((char *)name, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp); fwrite(&sprite, 1, sizeof(akgl_Sprite *), fp); } CLEANUP { } PROCESS(errctx) { } FINISH_NORETURN(errctx); } void akgl_game_save_spritesheetname_iterator(void *userdata, SDL_PropertiesID props, const char *name) { FILE *fp = (FILE *)userdata; akgl_SpriteSheet *spritesheet = NULL; PREPARE_ERROR(errctx); ATTEMPT { FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); spritesheet = SDL_GetPointerProperty(props, name, NULL); fwrite((char *)name, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp); fwrite(&spritesheet, 1, sizeof(akgl_SpriteSheet *), fp); } CLEANUP { } PROCESS(errctx) { } FINISH_NORETURN(errctx); } void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID props, const char *name) { FILE *fp = (FILE *)userdata; PREPARE_ERROR(errctx); akgl_Character *character = NULL; ATTEMPT { FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); character = SDL_GetPointerProperty(props, name, NULL); fwrite((char *)name, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp); fwrite(&character, 1, sizeof(akgl_Character *), fp); } CLEANUP { } PROCESS(errctx) { } FINISH_NORETURN(errctx); } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp) { PREPARE_ERROR(errctx); char nullval = 0x00; ATTEMPT { FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); // write the actor name pointer table SDL_EnumerateProperties( AKGL_REGISTRY_ACTOR, &akgl_game_save_actorname_iterator, (void *)fp); fwrite((void *)&nullval, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp); fwrite((void *)&nullval, 1, sizeof(akgl_Actor *), fp); // write the sprite name pointer table SDL_EnumerateProperties( AKGL_REGISTRY_SPRITE, &akgl_game_save_spritename_iterator, (void *)fp); fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp); fwrite((void *)&nullval, 1, sizeof(akgl_Sprite *), fp); // write the spritesheet name pointer table SDL_EnumerateProperties( AKGL_REGISTRY_SPRITESHEET, &akgl_game_save_spritesheetname_iterator, (void *)fp); fwrite((void *)&nullval, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp); fwrite((void *)&nullval, 1, sizeof(akgl_SpriteSheet *), fp); // write the character name pointer table SDL_EnumerateProperties( AKGL_REGISTRY_CHARACTER, &akgl_game_save_charactername_iterator, (void *)fp); fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp); fwrite((void *)&nullval, 1, sizeof(akgl_Character *), fp); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath) { FILE *fp = NULL; PREPARE_ERROR(errctx); ATTEMPT { FAIL_ZERO_BREAK(errctx, fpath, AKERR_NULLPOINTER, "NULL file path"); fp = fopen(fpath, "wb"); FAIL_ZERO_BREAK(errctx, fp, errno, "%s", fpath); fwrite(&game, 1, sizeof(akgl_Game), fp); CATCH(errctx, akgl_game_save_actors(fp)); } PROCESS(errctx) { } CLEANUP { if ( fp != NULL ) fclose(fp); } FINISH(errctx, true); SUCCEED_RETURN(errctx); // SUCCEED_NORETURN if in main(). } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_PropertiesID map, int namelength, int ptrlength, SDL_PropertiesID registry) { void *ptr = NULL; char ptrstring[32]; char objname[namelength]; int retval = 0; PREPARE_ERROR(errctx); while ( 1 ) { retval = fread((void *)&objname, 1, namelength, fp); FAIL_NONZERO_RETURN( errctx, (retval != namelength), AKERR_IO, "Corrupt save file"); retval = fread((void *)&ptr, 1, ptrlength, fp); FAIL_NONZERO_RETURN( errctx, (retval != ptrlength), AKERR_IO, "Corrupt save file"); // End of the map if ( ptr == 0x00 && objname[0] == 0x00 ) { break; } // The map allows us to say "Object X has a reference to object Y at // address Z. The object they had at address Z was named A. Our current // instance of object named A is at address B. So we map address Z to // address B, so that we can reconnect function pointers on objects loaded // from the save game state." // SDL_Properties objects can only use string keys, so we can't use the // old pointer as a key without first converting it to a string. memset((void *)&ptrstring, 0x00, 32); snprintf((char *)&ptrstring, 32, "%p", ptr); SDL_SetPointerProperty( map, ptrstring, SDL_GetPointerProperty(registry, objname, NULL)); }; SUCCEED_RETURN(errctx); } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion) { semver_t current_version = {}; semver_t compare_version = {}; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, versiontype, AKERR_NULLPOINTER, "NULL argument"); FAIL_ZERO_RETURN(errctx, curversion, AKERR_NULLPOINTER, "NULL argument"); FAIL_ZERO_RETURN(errctx, newversion, AKERR_NULLPOINTER, "NULL argument"); ATTEMPT { // Check save game library version FAIL_NONZERO_BREAK( errctx, semver_parse((const char *)curversion, ¤t_version), AKERR_VALUE, "Invalid semantic %s version in current game: %s", versiontype, (char *)curversion); FAIL_NONZERO_BREAK( errctx, semver_parse((const char *)newversion, &compare_version), AKERR_VALUE, "Invalid semantic %s version in save game: %s", versiontype, (char *)&newversion); FAIL_ZERO_BREAK( errctx, semver_satisfies(compare_version, current_version, "="), AKERR_API, "Incompatible save game %s version (%s != %s)", versiontype, curversion, (char *)&newversion); } CLEANUP { semver_free(¤t_version); semver_free(&compare_version); } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) { akgl_Game savegame; SDL_PropertiesID actormap; SDL_PropertiesID spritemap; SDL_PropertiesID spritesheetmap; SDL_PropertiesID charactermap; FILE *fp = NULL; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, fpath, AKERR_NULLPOINTER, "NULL file path"); ATTEMPT { fp = fopen(fpath, "rb"); FAIL_ZERO_BREAK(errctx, fp, errno, "%s", fpath); FAIL_NONZERO_BREAK( errctx, (fread((void *)&savegame, 1, sizeof(akgl_Game), fp) < sizeof(akgl_Game)), AKERR_IO, "Corrupt save file"); CATCH(errctx, akgl_game_load_versioncmp("library", (char *)&savegame.libversion, (char *)AKGL_VERSION)); CATCH(errctx, akgl_game_load_versioncmp("game", (char *)&savegame.version, (char *)&game.version)); FAIL_NONZERO_RETURN( errctx, strncmp((char *)&savegame.name, (char *)&game.name, 256), AKERR_API, "Savegame is not compatible with this game"); FAIL_NONZERO_RETURN( errctx, strncmp((char *)&savegame.uri, (char *)&game.uri, 256), AKERR_API, "Savegame is not compatible with this game"); memcpy((void *)&game, (void *)&savegame, sizeof(akgl_Game)); // Load actor name map actormap = SDL_CreateProperties(); CATCH(errctx, akgl_game_load_objectnamemap(fp, actormap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Actor *), AKGL_REGISTRY_ACTOR)); // Load sprite name map spritemap = SDL_CreateProperties(); CATCH(errctx, akgl_game_load_objectnamemap(fp, spritemap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Sprite *), AKGL_REGISTRY_SPRITE)); // Load spritesheet name map spritesheetmap = SDL_CreateProperties(); CATCH(errctx, akgl_game_load_objectnamemap(fp, spritesheetmap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_SpriteSheet *), AKGL_REGISTRY_SPRITESHEET)); // Load character name map charactermap = SDL_CreateProperties(); CATCH(errctx, akgl_game_load_objectnamemap(fp, charactermap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Character *), AKGL_REGISTRY_CHARACTER)); // Now that we have all of our pointer maps built, we can load the actual binary objects and reset their pointers } CLEANUP { if ( fp != NULL ) { fclose(fp); } } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); }