/* Copyright (C) 2018 C. McEnroe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include typedef unsigned uint; uint play2048(void); enum { ScoresLen = 1000 }; static struct Score { time_t date; uint score; char name[32]; } scores[ScoresLen]; static FILE *scoresOpen(const char *path) { int fd = open(path, O_RDWR | O_CREAT, 0644); if (fd < 0) err(EX_CANTCREAT, "%s", path); FILE *file = fdopen(fd, "r+"); if (!file) err(EX_CANTCREAT, "%s", path); return file; } static void scoresLock(FILE *file) { int error = flock(fileno(file), LOCK_EX); if (error) err(EX_IOERR, "flock"); } static void scoresRead(FILE *file) { memset(scores, 0, sizeof(scores)); rewind(file); fread(scores, sizeof(struct Score), ScoresLen, file); if (ferror(file)) err(EX_IOERR, "fread"); } static void scoresWrite(FILE *file) { rewind(file); fwrite(scores, sizeof(struct Score), ScoresLen, file); if (ferror(file)) err(EX_IOERR, "fwrite"); } static size_t scoresInsert(struct Score new) { if (!new.score) return ScoresLen; for (size_t i = 0; i < ScoresLen; ++i) { if (scores[i].score > new.score) continue; memmove( &scores[i + 1], &scores[i], sizeof(struct Score) * (ScoresLen - i - 1) ); scores[i] = new; return i; } return ScoresLen; } static void curse(void) { initscr(); cbreak(); echo(); curs_set(1); keypad(stdscr, true); leaveok(stdscr, false); start_color(); use_default_colors(); attr_set(A_NORMAL, 0, NULL); erase(); } enum { ScoresTop = 15, ScoresY = 0, ScoresX = 2, ScoreX = ScoresX + 5, NameX = ScoreX + 12, DateX = NameX + 33, ScoresWidth = DateX + 11 - ScoresX, }; static const char ScoresTitle[] = "TOP SCORES"; static void drawScore(int y, size_t i) { char buf[11]; snprintf(buf, sizeof(buf), "%3zu.", 1 + i); mvaddstr(y, ScoresX, buf); snprintf(buf, sizeof(buf), "%10d", scores[i].score); mvaddstr(y, ScoreX, buf); mvaddstr(y, NameX, scores[i].name); struct tm *time = localtime(&scores[i].date); if (!time) err(EX_SOFTWARE, "localtime"); char date[sizeof("YYYY-MM-DD")]; strftime(date, sizeof(date), "%F", time); mvaddstr(y, DateX, date); } static void draw(size_t new) { mvaddstr( ScoresY, ScoresX + (ScoresWidth - sizeof(ScoresTitle) + 2) / 2, ScoresTitle ); mvhline(ScoresY + 1, ScoresX, '=', ScoresWidth); int newY; for (size_t i = 0; i < ScoresTop; ++i) { if (!scores[i].score) break; if (i == new) newY = ScoresY + 2 + i; attr_set(i == new ? A_BOLD : A_NORMAL, 0, NULL); drawScore(ScoresY + 2 + i, i); } if (new == ScoresLen) return; if (new >= ScoresTop) { newY = ScoresY + ScoresTop + 5; mvhline(newY - 3, ScoresX, '=', ScoresWidth); drawScore(newY - 2, new - 2); drawScore(newY - 1, new - 1); attr_set(A_BOLD, 0, NULL); drawScore(newY, new); attr_set(A_NORMAL, 0, NULL); if (new + 1 < ScoresLen && scores[new + 1].score) { drawScore(newY + 1, new + 1); } if (new + 2 < ScoresLen && scores[new + 2].score) { drawScore(newY + 2, new + 2); } } move(newY, NameX); } int main(void) { struct Score new = { .date = time(NULL) }; new.score = play2048(); FILE *file = scoresOpen("2048.scores"); scoresRead(file); size_t index = scoresInsert(new); curse(); draw(index); if (index < ScoresLen) { attr_set(A_BOLD, 0, NULL); getnstr(new.name, sizeof(new.name) - 1); scoresLock(file); scoresRead(file); scoresInsert(new); scoresWrite(file); fclose(file); } curs_set(0); getch(); endwin(); printf( "This program is AGPLv3 Free Software!\n" "Code is available from .\n" ); }