From 254417271c2869175c8be57d804bd4c30d554db0 Mon Sep 17 00:00:00 2001 From: Linus Probert Date: Sun, 12 Aug 2018 16:14:07 +0200 Subject: [PATCH] Began #33 Create score screen Adds the backend (db) part of hiscores, next step is to create the screen. --- CMakeLists.txt | 1 + src/db.c | 48 ++++++++++++- src/db.h | 33 +++++++++ src/hiscore.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ src/hiscore.h | 45 +++++++++++++ src/main.c | 5 ++ src/settings.c | 67 +++++++------------ src/settings.h | 2 +- 8 files changed, 333 insertions(+), 46 deletions(-) create mode 100644 src/hiscore.c create mode 100644 src/hiscore.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4db994b..6b931fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,7 @@ add_executable(breakhack src/trap src/artifact src/screen + src/hiscore ) # Sqlite has some warnings that I we don't need to see diff --git a/src/db.c b/src/db.c index 43ab8a2..725bf71 100644 --- a/src/db.c +++ b/src/db.c @@ -1,3 +1,22 @@ +/* + * BreakHack - A dungeone crawler RPG + * Copyright (C) 2018 Linus Probert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include #include "db.h" #include "sqlite3.h" #include "util.h" @@ -14,6 +33,33 @@ db_open(const char *file, sqlite3 **db) return result == 0; } +sqlite3_stmt * +db_prepare(sqlite3 *db, const char *query) +{ + const char *pzTest; + sqlite3_stmt *stmt; + int result; + + result = sqlite3_prepare_v2(db, + query, + (int) strlen(query), + &stmt, + &pzTest); + if (result != SQLITE_OK) + fatal("Failed to prepare statement: %s", query); + + return stmt; +} + +void +db_execute(sqlite3 *db, DbQuery *query) +{ + if (!db_execute_stmnt(query->stmt, db, query->cb, query->cb_arg)) { + db_close(&db); + fatal("Exiting"); + } +} + bool db_execute_stmnt(const char *stmnt, sqlite3 *db, @@ -24,7 +70,7 @@ db_execute_stmnt(const char *stmnt, char *errorMsg = NULL; if (sqlite3_exec(db, stmnt, cb, cb_arg, &errorMsg)) { - error("Faled to execute statement: %s", stmnt); + error("Failed to execute statement: %s", stmnt); error("Sqlite3: %s", errorMsg); sqlite3_free(errorMsg); return false; diff --git a/src/db.h b/src/db.h index 2fe22a5..ca5b189 100644 --- a/src/db.h +++ b/src/db.h @@ -1,12 +1,45 @@ +/* + * BreakHack - A dungeone crawler RPG + * Copyright (C) 2018 Linus Probert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef DB_H_ #define DB_H_ #include #include "sqlite3.h" +#define DB_FILE ".data.db" + +typedef struct DbQuery { + char *stmt; + int (*cb)(void*, int, char**, char**); + void *cb_arg; +} DbQuery; + + bool db_open(const char *file, sqlite3 **db); +sqlite3_stmt* +db_prepare(sqlite3*, const char *query); + +void +db_execute(sqlite3*, DbQuery*); + bool db_execute_stmnt(const char *stmnt, sqlite3 *db, diff --git a/src/hiscore.c b/src/hiscore.c new file mode 100644 index 0000000..c9169d9 --- /dev/null +++ b/src/hiscore.c @@ -0,0 +1,178 @@ +/* + * BreakHack - A dungeone crawler RPG + * Copyright (C) 2018 Linus Probert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "hiscore.h" +#include "sqlite3.h" +#include "db.h" +#include "util.h" + +static +DbQuery MIGRATE_COMMANDS[] = { + { + "CREATE TABLE IF NOT EXISTS hiscore(" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "time DATETIME DEFAULT CURRENT_TIMESTAMP, " + "gold FLOAT, " + "playerLevel INTEGER, " + "dungeonLevel INTEGER)", + NULL, NULL + }, + { + "CREATE TABLE IF NOT EXISTS hiscore_artifacts(" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "hiscoreId INTEGER, " + "artifactId INTEGER, " + "FOREIGN KEY(hiscoreId) REFERENCES hiscore(id))", + NULL, NULL + }, + { NULL } // Sentinel +}; + +static int +load_hiscore_cb(void *unused, int count, char **values, char **colNames); + +static +DbQuery GET_COMMANDS[] = { + { + "SELECT datetime(time, 'localtime') as time, gold, playerLevel, dungeonLevel " + "FROM hiscore " + "ORDER BY gold DESC " + "LIMIT 10", + load_hiscore_cb, NULL + }, + { NULL } +}; + +static sqlite3 *db = NULL; + +static void +create_tables(void) +{ + for (unsigned int i = 0;; ++i) { + DbQuery *query = &MIGRATE_COMMANDS[i]; + if (query->stmt == NULL) + break; + + db_execute(db, query); + } +} + +void +hiscore_init(void) +{ + if (!db_open(DB_FILE, &db)) { + db_close(&db); + fatal("Exiting"); + } + create_tables(); +} + +static void +save_hiscore(double gold, int lvl, int dlvl) +{ + const char *query = "INSERT INTO hiscore(gold, playerLevel, dungeonLevel) values (?, ?, ?)"; + sqlite3_stmt *stmt = db_prepare(db, query); + + debug("Saving high score: %dg %dpl %dl", + gold, lvl, dlvl); + sqlite3_bind_double(stmt, 1, gold); + sqlite3_bind_int(stmt, 2, lvl); + sqlite3_bind_int(stmt, 3, dlvl); + sqlite3_step(stmt); + sqlite3_finalize(stmt); +} + +static void +save_hiscore_artifact(int hid, int aid) +{ + if (aid <= 0) + return; + + const char *query = "INSERT INTO hiscore_artifacts(hiscoreId, artifactId) values(?, ?)"; + sqlite3_stmt *stmt = db_prepare(db, query); + sqlite3_bind_int(stmt, 1, hid); + sqlite3_bind_int(stmt, 2, aid); + sqlite3_step(stmt); + sqlite3_finalize(stmt); +} + +void +hiscore_register(Player *p, unsigned int dungeonLevel) +{ + save_hiscore(p->gold, p->stats.lvl, dungeonLevel); + int hiscoreId = (int) sqlite3_last_insert_rowid(db); + if (!hiscoreId) { + error("Failed to retrieve last inserted hiscore id"); + return; + } + + for (int i = 0; i < LAST_ARTIFACT_EFFECT; ++i) { + save_hiscore_artifact(hiscoreId, player_has_artifact(p, i) > 0 ? i : 0); + } +} + +static int +load_hiscore_cb(void *result, int count, char **values, char **colNames) +{ + HiScore *score = ec_malloc(sizeof(HiScore)); + for (int i = 0; i < count; ++i) { + if (strcmp(colNames[i], "time") == 0) { + long unsigned int len = strlen(values[i]) + 1; + score->timestamp = ec_malloc(sizeof(char) * len); + m_strcpy(score->timestamp, len, values[i]); + } + else if (strcmp(colNames[i], "gold") == 0) + score->gold = atof(values[i]); + else if (strcmp(colNames[i], "playerLevel") == 0) + score->playerLevel = atoi(values[i]); + else if (strcmp(colNames[i], "dungeonLevel") == 0) + score->dungeonLevel = atoi(values[i]); + } + LinkedList **scores = result; + linkedlist_append(scores, score); + return 0; +} + +LinkedList * +hiscore_get_top10(void) +{ + LinkedList *scores = linkedlist_create(); + for (unsigned int i = 0;; ++i) { + DbQuery *query = &GET_COMMANDS[i]; + query->cb_arg = &scores; + if (query->stmt == NULL) + break; + + db_execute(db, query); + } + + return scores; +} + +void +hiscore_close(void) +{ + db_close(&db); +} + +void +hiscore_destroy(HiScore *score) +{ + free(score->timestamp); + free(score); +} diff --git a/src/hiscore.h b/src/hiscore.h new file mode 100644 index 0000000..a70f97c --- /dev/null +++ b/src/hiscore.h @@ -0,0 +1,45 @@ +/* + * BreakHack - A dungeone crawler RPG + * Copyright (C) 2018 Linus Probert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include "linkedlist.h" +#include "player.h" + +typedef struct HiScore { + char *timestamp; + double gold; + unsigned int playerLevel; + unsigned int dungeonLevel; + LinkedList *artifacts; +} HiScore; + +void +hiscore_init(void); + +void +hiscore_register(Player *p, unsigned int dungeonLevel); + +LinkedList * +hiscore_get_top10(void); + +void +hiscore_close(void); + +void +hiscore_destroy(HiScore *); diff --git a/src/main.c b/src/main.c index 3b6d90b..192a9f8 100644 --- a/src/main.c +++ b/src/main.c @@ -50,6 +50,7 @@ #include "actiontextbuilder.h" #include "input.h" #include "screen.h" +#include "hiscore.h" typedef enum Turn_t { PLAYER, @@ -390,6 +391,7 @@ init(void) } settings_init(); + hiscore_init(); initMainMenu(); gCamera->pos = (Position) { 0, 0 }; @@ -605,6 +607,7 @@ run_game(void) mixer_play_effect(SPLAT); gGameState = GAME_OVER; createInGameGameOverMenu(); + hiscore_register(gPlayer, cLevel); } else { check_next_level(); } @@ -702,6 +705,7 @@ void run(void) static void close(void) { + if (gPlayer) player_destroy(gPlayer); if (gMap) @@ -725,6 +729,7 @@ void close(void) mixer_close(); texturecache_close(); settings_close(); + hiscore_close(); SDL_DestroyRenderer(gRenderer); SDL_DestroyWindow(gWindow); diff --git a/src/settings.c b/src/settings.c index 53468bd..fdce103 100644 --- a/src/settings.c +++ b/src/settings.c @@ -24,27 +24,25 @@ #include "defines.h" #include "db.h" -#define SETTINGS_DB_FILE ".data.db" - static sqlite3 *db = NULL; static Settings settings; static const char *KEY_MUSIC_ENABLED = "music_enabled"; static const char *KEY_SOUND_ENABLED = "sound_enabled"; -typedef struct db_query { - char *stmt; - int (*cb)(void*, int, char**, char**); - void *cb_arg; -} db_query; - -db_query MIGRATE_COMMANDS[] = { - { "CREATE TABLE IF NOT EXISTS settings_int(key TEXT PRIMARY KEY, value INTEGER)", NULL, NULL }, - { NULL, NULL, NULL } // Sentinel +static +DbQuery MIGRATE_COMMANDS[] = { + { + "CREATE TABLE IF NOT EXISTS settings_int(" + "key TEXT PRIMARY KEY, " + "value INTEGER)", + NULL, NULL + }, + { NULL } // Sentinel }; static int load_settings_cb(void *unused, int count, char **values, char **colNames); -db_query LOAD_SETTINGS = { "SELECT * FROM settings_int", load_settings_cb, NULL }; +DbQuery LOAD_SETTINGS = { "SELECT * FROM settings_int", load_settings_cb, NULL }; static void set_default_settings(void) @@ -53,24 +51,15 @@ set_default_settings(void) settings.sound_enabled = true; } -static void -execute_statement(db_query *query) -{ - if (!db_execute_stmnt(query->stmt, db, query->cb, query->cb_arg)) { - db_close(&db); - fatal("Exiting"); - } -} - static void create_tables(void) { for (unsigned int i = 0;; ++i) { - db_query *query = &MIGRATE_COMMANDS[i]; + DbQuery *query = &MIGRATE_COMMANDS[i]; if (query->stmt == NULL) break; - execute_statement(query); + db_execute(db, query); } } @@ -100,13 +89,13 @@ load_settings_cb(void *unused, int count, char **values, char **colNames) static void load_settings(void) { - execute_statement(&LOAD_SETTINGS); + db_execute(db, &LOAD_SETTINGS); } void settings_init(void) { - if (!db_open(SETTINGS_DB_FILE, &db)) { + if (!db_open(DB_FILE, &db)) { db_close(&db); fatal("Exiting"); } @@ -118,29 +107,19 @@ settings_init(void) static void save_setting_int(const char *key, int value) { - // TODO(Linus): Move this into db.c, probably using varargs - const char *stmtStr = "INSERT OR REPLACE INTO settings_int(key, value) values (?, ?)"; - const char *pzTest; - sqlite3_stmt *stmt; - int result; - - result = sqlite3_prepare_v2(db, stmtStr, (int) strlen(stmtStr), &stmt, &pzTest); + sqlite3_stmt *stmt = db_prepare(db, stmtStr); debug("Saving setting: %s = %d", key, value); - if (result == SQLITE_OK) { - sqlite3_bind_text(stmt, - 1, - key, - (int) strlen(key), - NULL); + sqlite3_bind_text(stmt, + 1, + key, + (int) strlen(key), + NULL); - sqlite3_bind_int(stmt, 2, value); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - } else { - error("Failed to prepare storage statement for: %s", key); - } + sqlite3_bind_int(stmt, 2, value); + sqlite3_step(stmt); + sqlite3_finalize(stmt); } static void diff --git a/src/settings.h b/src/settings.h index bb1b387..99a7857 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,7 +21,7 @@ #include -typedef struct Settings_t { +typedef struct Settings { bool music_enabled; bool sound_enabled; } Settings;