@@ 1,4 1,5 @@
#include <errno.h>
+#include <sqlite3.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
@@ 243,11 244,14 @@ struct Piece PIECES[NPIECES];
#define NCLEARS 27
GridField CLEARS[NCLEARS];
+sqlite3 *sqlite;
+
enum GameState {
CHOOSING_PIECE,
CHOOSING_COLUMN,
CHOOSING_ROW,
RESOLUTION,
+ QUIT,
};
struct Game {
@@ 380,20 384,188 @@ void render(struct Game g) {
}
}
+// like strcpy, but doesn't insert null
+void insert(char *dest, char *src, size_t max) {
+ for (; *src && max > 0; --max) {
+ *dest++ = *src++;
+ }
+}
+
+void insert_score(char *dest, int score) {
+ char digits[6] = " ";
+ int rounded = score >= 10000;
+ score = rounded ? 9999 : score;
+ digits[4] = rounded ? '+' : ' ';
+
+ for (int i = 3; score && i >= 0; --i) {
+ digits[i] = score % 10 + '0';
+ score /= 10;
+ }
+
+ insert(dest, digits, 5);
+}
+
+void print_hiscores() {
+ char hiscores[] = " HISCORES \n"
+ "+------------+------------+------------+\n"
+ "| ALL TIME | WEEKLY | TODAY |\n"
+ "+------------+------------+------------+\n"
+ "| | | |\n"
+ "| | | |\n"
+ "| | | |\n"
+ "+------------+------------+------------+\n"
+ "\n\n";
+ const int width = 41;
+ const int r1 = width * 4;
+ const int c1 = 2;
+ const int c2 = 15;
+ const int c3 = 28;
+
+ {
+ sqlite3_stmt *stmt;
+ sqlite3_prepare_v2(
+ sqlite, "SELECT name, score FROM scores ORDER BY score DESC LIMIT 3;",
+ -1, &stmt, NULL);
+
+ int offset = r1 + c1;
+ while (sqlite3_step(stmt) == SQLITE_ROW) {
+ const unsigned char *name = sqlite3_column_text(stmt, 0);
+ const int score = sqlite3_column_int(stmt, 1);
+
+ insert(hiscores + offset, (char *)name, 5);
+ insert_score(hiscores + offset + 6, score);
+
+ offset += width;
+ }
+
+ sqlite3_finalize(stmt);
+ }
+
+ {
+ sqlite3_stmt *stmt;
+ sqlite3_prepare_v2(
+ sqlite,
+ "SELECT name, score FROM scores "
+ "WHERE julianday(CURRENT_TIMESTAMP) - julianday(timestamp) < 7 "
+ "ORDER BY score DESC "
+ "LIMIT 3;",
+ -1, &stmt, NULL);
+
+ int offset = r1 + c2;
+ while (sqlite3_step(stmt) == SQLITE_ROW) {
+ const unsigned char *name = sqlite3_column_text(stmt, 0);
+ const int score = sqlite3_column_int(stmt, 1);
+
+ insert(hiscores + offset, (char *)name, 5);
+ insert_score(hiscores + offset + 6, score);
+
+ offset += width;
+ }
+
+ sqlite3_finalize(stmt);
+ }
+
+ {
+ sqlite3_stmt *stmt;
+ sqlite3_prepare_v2(
+ sqlite,
+ "SELECT name, score FROM scores "
+ "WHERE julianday(CURRENT_TIMESTAMP) - julianday(timestamp) < 1 "
+ "ORDER BY score DESC "
+ "LIMIT 3;",
+ -1, &stmt, NULL);
+
+ int offset = r1 + c3;
+ while (sqlite3_step(stmt) == SQLITE_ROW) {
+ const unsigned char *name = sqlite3_column_text(stmt, 0);
+ const int score = sqlite3_column_int(stmt, 1);
+
+ insert(hiscores + offset, (char *)name, 5);
+ insert_score(hiscores + offset + 6, score);
+
+ offset += width;
+ }
+
+ sqlite3_finalize(stmt);
+ }
+ fputs(hiscores, stdout);
+}
+
+void handle_quit(int score) {
+ char name[6] = {0};
+ int i = 0;
+
+#define splash() \
+ clear_screen(); \
+ printf("Thanks for playing! Your score is %d\n", score); \
+ puts("Enter your name for the hiscores: "); \
+ fputs(" ", stdout); \
+ puts(name)
+
+ while (1) {
+ splash();
+
+ char c = getc(stdin);
+
+ if (c == '\n') {
+ break;
+ }
+
+ if (c == 127) {
+ if (i > 0) {
+ --i;
+ }
+
+ name[i] = 0;
+
+ continue;
+ }
+
+ if (c >= 'a' && c <= 'z') {
+ c += ('A' - 'a');
+ }
+
+ if (c >= 'A' && c <= 'Z') {
+ if (i < 5) {
+ name[i++] = c;
+ }
+
+ continue;
+ }
+ }
+
+ splash();
+
+#undef splash
+
+ sqlite3_stmt *stmt;
+ sqlite3_prepare_v2(sqlite, "INSERT INTO scores(name, score) VALUES (?, ?);",
+ -1, &stmt, NULL);
+
+ sqlite3_bind_text(stmt, 1, name, 6, SQLITE_STATIC);
+ sqlite3_bind_int(stmt, 2, score);
+ sqlite3_step(stmt);
+
+ sqlite3_finalize(stmt);
+
+ clear_screen();
+ print_hiscores();
+}
+
void game_main() {
long seed;
time(&seed);
srand(seed);
- char splash[] =
- " _ _ \n"
- " ___ __ _ _ _ ___| | |\n"
- "/ __|/ _` | | | |/ _ \\ | |\n"
- "\\__ \\ (_| | |_| | __/ | |\n"
- "|___/\\__, |\\__,_|\\___|_|_|\n"
- " |_| \n"
- "\n\n"
+ char header[] = " _ _ \n"
+ " ___ __ _ _ _ ___| | |\n"
+ " / __|/ _` | | | |/ _ \\ | |\n"
+ " \\__ \\ (_| | |_| | __/ | |\n"
+ " |___/\\__, |\\__,_|\\___|_|_|\n"
+ " |_| \n"
+ " \n\n";
+ char intro[] =
"squell is a game that combines elements of tetris and sudoku.\n"
"Place blocks to fill and clear rows, columns, or squares.\n"
"\n"
@@ 406,7 578,9 @@ void game_main() {
"Press any key to begin.";
clear_screen();
- fputs(splash, stdout);
+ fputs(header, stdout);
+ print_hiscores();
+ fputs(intro, stdout);
getc(stdin);
struct Game game = {0};
@@ 444,7 618,8 @@ void game_main() {
}
case 'Q':
- return;
+ game.state = QUIT;
+ continue;
}
if (c < 'a' || c > 'c')
@@ 469,7 644,8 @@ void game_main() {
continue;
case 'Q':
- return;
+ game.state = QUIT;
+ continue;
}
if (c < 'a' || c > 'i') {
@@ 491,7 667,8 @@ void game_main() {
continue;
case 'Q':
- return;
+ game.state = QUIT;
+ continue;
}
if (c < '1' || c > '9') {
@@ 538,6 715,10 @@ void game_main() {
break;
}
+
+ case QUIT:
+ handle_quit(game.score);
+ return;
}
printf("\n%d\n", game.active_piece);
@@ 698,6 879,15 @@ fatal:
return -1;
}
+int init_db() {
+ static const char dbname[] = ".local/share/squell.db";
+ if (sqlite3_open(dbname, &sqlite) != SQLITE_OK) {
+ fputs(sqlite3_errmsg(sqlite), stderr);
+ }
+
+ return 0;
+}
+
int main(int argc, char *argv[]) {
if (init_screen() != 0) {
return 1;
@@ 705,6 895,11 @@ int main(int argc, char *argv[]) {
init_pieces();
init_clears();
+
+ if (init_db() != 0) {
+ return 1;
+ }
+
game_main();
return 0;