2048 over SSH https://ascii.town
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

play.c 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. /* Copyright (C) 2018 C. McEnroe <june@causal.agency>
  2. *
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU Affero General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. #include <curses.h>
  17. #include <err.h>
  18. #include <fcntl.h>
  19. #include <stdbool.h>
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include <sys/file.h>
  24. #include <sysexits.h>
  25. #include <time.h>
  26. #ifdef __FreeBSD__
  27. #include <sys/capsicum.h>
  28. #endif
  29. typedef unsigned uint;
  30. uint play2048(void);
  31. enum { ScoresLen = 1000 };
  32. static struct Score {
  33. time_t date;
  34. uint score;
  35. char name[32];
  36. } scores[ScoresLen];
  37. static FILE *scoresOpen(const char *path) {
  38. int fd = open(path, O_RDWR | O_CREAT, 0644);
  39. if (fd < 0) err(EX_CANTCREAT, "%s", path);
  40. FILE *file = fdopen(fd, "r+");
  41. if (!file) err(EX_CANTCREAT, "%s", path);
  42. return file;
  43. }
  44. static void scoresLock(FILE *file) {
  45. int error = flock(fileno(file), LOCK_EX);
  46. if (error) err(EX_IOERR, "flock");
  47. }
  48. static void scoresRead(FILE *file) {
  49. memset(scores, 0, sizeof(scores));
  50. rewind(file);
  51. fread(scores, sizeof(struct Score), ScoresLen, file);
  52. if (ferror(file)) err(EX_IOERR, "fread");
  53. }
  54. static void scoresWrite(FILE *file) {
  55. rewind(file);
  56. fwrite(scores, sizeof(struct Score), ScoresLen, file);
  57. if (ferror(file)) err(EX_IOERR, "fwrite");
  58. }
  59. static size_t scoresInsert(struct Score new) {
  60. if (!new.score) return ScoresLen;
  61. for (size_t i = 0; i < ScoresLen; ++i) {
  62. if (scores[i].score > new.score) continue;
  63. memmove(
  64. &scores[i + 1], &scores[i],
  65. sizeof(struct Score) * (ScoresLen - i - 1)
  66. );
  67. scores[i] = new;
  68. return i;
  69. }
  70. return ScoresLen;
  71. }
  72. static void curse(void) {
  73. initscr();
  74. cbreak();
  75. echo();
  76. curs_set(1);
  77. keypad(stdscr, true);
  78. leaveok(stdscr, false);
  79. start_color();
  80. use_default_colors();
  81. attr_set(A_NORMAL, 0, NULL);
  82. erase();
  83. }
  84. enum {
  85. ScoresTop = 15,
  86. ScoresY = 0,
  87. ScoresX = 2,
  88. ScoreX = ScoresX + 5,
  89. NameX = ScoreX + 12,
  90. DateX = NameX + 33,
  91. ScoresWidth = DateX + 11 - ScoresX,
  92. };
  93. static const char ScoresTitle[] = "TOP SCORES";
  94. static void drawScore(int y, size_t i) {
  95. char buf[11];
  96. snprintf(buf, sizeof(buf), "%3zu.", 1 + i);
  97. mvaddstr(y, ScoresX, buf);
  98. snprintf(buf, sizeof(buf), "%10d", scores[i].score);
  99. mvaddstr(y, ScoreX, buf);
  100. mvaddstr(y, NameX, scores[i].name);
  101. struct tm *time = localtime(&scores[i].date);
  102. if (!time) err(EX_SOFTWARE, "localtime");
  103. char date[sizeof("YYYY-MM-DD")];
  104. strftime(date, sizeof(date), "%F", time);
  105. mvaddstr(y, DateX, date);
  106. }
  107. static void draw(size_t new) {
  108. mvaddstr(
  109. ScoresY, ScoresX + (ScoresWidth - sizeof(ScoresTitle) + 2) / 2,
  110. ScoresTitle
  111. );
  112. mvhline(ScoresY + 1, ScoresX, '=', ScoresWidth);
  113. int newY = -1;
  114. for (size_t i = 0; i < ScoresTop; ++i) {
  115. if (!scores[i].score) break;
  116. if (i == new) newY = ScoresY + 2 + i;
  117. attr_set(i == new ? A_BOLD : A_NORMAL, 0, NULL);
  118. drawScore(ScoresY + 2 + i, i);
  119. }
  120. if (new == ScoresLen) return;
  121. if (new >= ScoresTop) {
  122. newY = ScoresY + ScoresTop + 5;
  123. mvhline(newY - 3, ScoresX, '=', ScoresWidth);
  124. drawScore(newY - 2, new - 2);
  125. drawScore(newY - 1, new - 1);
  126. attr_set(A_BOLD, 0, NULL);
  127. drawScore(newY, new);
  128. attr_set(A_NORMAL, 0, NULL);
  129. if (new + 1 < ScoresLen && scores[new + 1].score) {
  130. drawScore(newY + 1, new + 1);
  131. }
  132. if (new + 2 < ScoresLen && scores[new + 2].score) {
  133. drawScore(newY + 2, new + 2);
  134. }
  135. }
  136. move(newY, NameX);
  137. }
  138. int main(void) {
  139. curse();
  140. FILE *file = scoresOpen("2048.scores");
  141. #ifdef __FreeBSD__
  142. int error = cap_enter();
  143. if (error) err(EX_OSERR, "cap_enter");
  144. cap_rights_t rights;
  145. cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_FLOCK);
  146. error = cap_rights_limit(fileno(file), &rights);
  147. if (error) err(EX_OSERR, "cap_rights_limit");
  148. #endif
  149. struct Score new = {
  150. .date = time(NULL),
  151. .score = play2048(),
  152. };
  153. scoresRead(file);
  154. size_t index = scoresInsert(new);
  155. curse();
  156. draw(index);
  157. if (index < ScoresLen) {
  158. attr_set(A_BOLD, 0, NULL);
  159. getnstr(new.name, sizeof(new.name) - 1);
  160. for (char *ch = new.name; *ch; ++ch) {
  161. if (*ch < ' ') *ch = ' ';
  162. }
  163. scoresLock(file);
  164. scoresRead(file);
  165. scoresInsert(new);
  166. scoresWrite(file);
  167. fclose(file);
  168. }
  169. noecho();
  170. curs_set(0);
  171. getch();
  172. endwin();
  173. printf(
  174. "This program is AGPLv3 Free Software!\n"
  175. "Code is available from <https://code.causal.agency/june/play>.\n"
  176. );
  177. }