comparison highscore.c @ 0:a9a7ad180c3b version-1

Initial revision
author Guido Berhoerster <guido+rantaiwarna@berhoerster.name>
date Sat, 15 Mar 2014 18:41:03 +0100
parents
children 4f6bf50dbc4a
comparison
equal deleted inserted replaced
-1:000000000000 0:a9a7ad180c3b
1 /*
2 * Copyright (C) 2014 Guido Berhoerster <guido+rantaiwarna@berhoerster.name>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24 #define _XOPEN_SOURCE 600
25
26 #include <stdlib.h>
27 #include <pwd.h>
28 #include <unistd.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <inttypes.h>
32 #include <sys/stat.h>
33 #include <curses.h> /* for ERR, OK */
34
35 #include "highscore.h"
36 #include "compat.h"
37 #include "util.h"
38
39 #define HIGHSCORE_FILENAME ".rantaiwarna_hiscore"
40 #define HIGHSCORE_TMP_TMPL ".rantaiwarna_hiscoreXXXXXX"
41
42 static struct highscore_entry *
43 highscore_entry_new(int16_t width, int16_t height, int16_t colors,
44 int32_t score, time_t time)
45 {
46 struct highscore_entry *entry;
47
48 entry = rantaiwarna_malloc(sizeof (struct highscore_entry));
49 entry->next = NULL;
50 entry->width = width;
51 entry->height = height;
52 entry->colors = colors;
53 entry->score = score;
54 localtime_r(&time, &entry->time);
55
56 return (entry);
57 }
58
59 void
60 highscore_entry_free(struct highscore_entry *entry)
61 {
62 free(entry);
63 }
64
65 void
66 highscore_free(struct highscore_entry *highscore)
67 {
68 struct highscore_entry *next_entry = highscore;
69 struct highscore_entry *entry = NULL;
70
71 while (next_entry != NULL) {
72 entry = next_entry;
73 next_entry = entry->next;
74 highscore_entry_free(entry);
75 }
76 }
77
78 void
79 highscore_update(struct highscore_entry **highscorep, int16_t width,
80 int16_t height, int16_t colors, int32_t score, time_t time)
81 {
82 struct highscore_entry *new_entry;
83 struct highscore_entry *entry = *highscorep;
84 int n = 0;
85
86 if ((entry == NULL) || (entry->score < score)) {
87 /*
88 * if there are no entries or the current score is greater than
89 * the first entry, prepend a new entry
90 */
91 new_entry = highscore_entry_new(width, height, colors, score,
92 time);
93 new_entry->next = entry;
94 *highscorep = new_entry;
95 n++;
96 } else {
97 /*
98 * if the current score is higher than the score of one of the
99 * top ten entries, insert a new entry
100 */
101 for (n = 0; (entry->next != NULL) && (entry->next->score >
102 score) && (n < 10); entry = entry->next, n++);
103 new_entry = highscore_entry_new(width, height, colors, score,
104 time);
105 new_entry->next = entry->next;
106 entry->next = new_entry;
107 }
108 if (entry != NULL) {
109 /* trim any entries outside the top ten */
110 while ((entry->next != NULL) && (n < 10)) {
111 entry = entry->next;
112 n++;
113 }
114 highscore_free(entry->next);
115 entry->next = NULL;
116 }
117 }
118
119 int
120 highscore_load(struct highscore_entry **highscorep, int16_t ref_height,
121 int16_t ref_width, int16_t ref_colors)
122 {
123 int error = ERR;
124 struct highscore_entry *highscore = NULL;
125 char *home_dir;
126 struct passwd *pw;
127 int n;
128 char *highscore_filename = NULL;
129 FILE *fp = NULL;
130 char *scan_fmt = NULL;
131 int16_t width;
132 int16_t height;
133 int16_t colors;
134 int32_t score;
135 char timestr[4 + 1 + 2 + 1 + 2 + 1]; /* holds %Y-%m-%d */
136 struct tm tm;
137 time_t time;
138
139 home_dir = getenv("HOME");
140 if (home_dir == NULL) {
141 pw = getpwuid(getuid());
142 if (pw != NULL) {
143 home_dir = pw->pw_dir;
144 } else {
145 goto out;
146 }
147 }
148
149 n = snprintf(NULL, 0, "%s/%s", home_dir, HIGHSCORE_FILENAME);
150 if (n < 0) {
151 rantaiwarna_err(1, NULL);
152 }
153 highscore_filename = rantaiwarna_malloc((size_t)n + 1);
154 if (snprintf(highscore_filename, (size_t)n + 1,
155 "%s/%s", home_dir, HIGHSCORE_FILENAME) != n) {
156 rantaiwarna_err(1, NULL);
157 }
158
159 n = snprintf(NULL, 0, "%%" SCNd16 "%%" SCNd16 "%%" SCNd16 "%%" SCNd32
160 "%%%lus", sizeof (timestr) - 1);
161 if (n < 0) {
162 rantaiwarna_err(1, NULL);
163 }
164 scan_fmt = rantaiwarna_malloc((size_t)n + 1);
165 if (snprintf(scan_fmt, (size_t)n + 1, "%%" SCNd16 "%%" SCNd16 "%%"
166 SCNd16 "%%" SCNd32 "%%%lus", sizeof (timestr) - 1) != n) {
167 rantaiwarna_err(1, NULL);
168 }
169
170 fp = fopen(highscore_filename, "r");
171 if (fp == NULL) {
172 goto out;
173 }
174 while ((n = fscanf(fp, scan_fmt, &width, &height, &colors, &score,
175 timestr)) != EOF) {
176 if ((n == 5) && ((width == ref_width) &&
177 (height == ref_height) && (colors == ref_colors))) {
178 memset(&tm, 0, sizeof (struct tm));
179 time = (time_t)0;
180 if (strptime(timestr, "%Y-%m-%d", &tm) != NULL) {
181 time = mktime(&tm);
182 if (time == (time_t)-1) {
183 time = (time_t)0;
184 }
185 }
186 highscore_update(&highscore, width, height, colors,
187 score, time);
188 }
189 }
190 if (ferror(fp) != 0) {
191 goto out;
192 }
193
194 error = OK;
195
196 out:
197 if (fp != NULL) {
198 fclose(fp);
199 }
200
201 free(scan_fmt);
202 free(highscore_filename);
203
204 if (error == ERR) {
205 highscore_free(highscore);
206 } else {
207 highscore_free(*highscorep);
208 *highscorep = highscore;
209 }
210
211 return (error);
212 }
213
214 int
215 highscore_save(struct highscore_entry *highscore, int16_t ref_height,
216 int16_t ref_width, int16_t ref_colors)
217 {
218 int error = ERR;
219 char *scan_fmt = NULL;
220 char *home_dir;
221 struct passwd *pw;
222 int n;
223 char *highscore_filename = NULL;
224 char *tmp_filename = NULL;
225 mode_t old_mode;
226 FILE *fp_in = NULL;
227 int fd_out = -1;
228 FILE *fp_out = NULL;
229 int16_t width;
230 int16_t height;
231 int16_t colors;
232 int32_t score;
233 char timestr[4 + 1 + 2 + 1 + 2 + 1]; /* holds %Y-%m-%d */
234 struct highscore_entry *entry;
235
236 /*
237 * file format:
238 * entries consisting of five fields, entries and fields separated by
239 * whitespace
240 *
241 * fields:
242 * width (int16_t)
243 * height (int16_t)
244 * colors (int16_t)
245 * score (int32_t)
246 * time string (YYYY-MM-DD)
247 */
248 if (highscore == NULL) {
249 goto out;
250 }
251
252 n = snprintf(NULL, 0, "%%" SCNd16 "%%" SCNd16 "%%" SCNd16 "%%" SCNd32
253 "%%%lus", sizeof (timestr) - 1);
254 if (n < 0) {
255 rantaiwarna_err(1, NULL);
256 }
257 scan_fmt = rantaiwarna_malloc((size_t)n + 1);
258 if (snprintf(scan_fmt, (size_t)n + 1, "%%" SCNd16 "%%" SCNd16 "%%"
259 SCNd16 "%%" SCNd32 "%%%lus", sizeof (timestr) - 1) != n) {
260 rantaiwarna_err(1, NULL);
261 }
262
263 home_dir = getenv("HOME");
264 if (home_dir == NULL) {
265 pw = getpwuid(getuid());
266 if (pw != NULL) {
267 home_dir = pw->pw_dir;
268 } else {
269 goto out;
270 }
271 }
272
273 n = snprintf(NULL, 0, "%s/%s", home_dir, HIGHSCORE_FILENAME);
274 if (n < 0) {
275 rantaiwarna_err(1, NULL);
276 }
277 highscore_filename = rantaiwarna_malloc((size_t)n + 1);
278 if (snprintf(highscore_filename, (size_t)n + 1, "%s/%s", home_dir,
279 HIGHSCORE_FILENAME) != n) {
280 rantaiwarna_err(1, NULL);
281 }
282
283 n = snprintf(NULL, 0, "%s/%s", home_dir, HIGHSCORE_TMP_TMPL);
284 if (n < 0) {
285 rantaiwarna_err(1, NULL);
286 }
287 tmp_filename = rantaiwarna_malloc((size_t)n + 1);
288 if (snprintf(tmp_filename, (size_t)n + 1, "%s/%s", home_dir,
289 HIGHSCORE_TMP_TMPL) != n) {
290 rantaiwarna_err(1, NULL);
291 }
292
293 old_mode = umask(077);
294 fd_out = mkstemp(tmp_filename);
295 umask(old_mode);
296 if (fd_out == -1) {
297 goto out;
298 }
299
300 fp_out = fdopen(fd_out, "w");
301 if (fp_out == NULL) {
302 goto out;
303 }
304 /*
305 * preserve entries which do not match the current combination of width,
306 * height, and colors
307 */
308 fp_in = fopen(highscore_filename, "r");
309 if (fp_in != NULL) {
310 while ((n = fscanf(fp_in, scan_fmt, &width, &height, &colors,
311 &score, timestr)) != EOF) {
312 if ((n == 5) && ((width != ref_width) ||
313 (height != ref_height) || (colors != ref_colors))) {
314 if (fprintf(fp_out, "%" PRId16 " %" PRId16 " %"
315 PRId16 " %" PRId32 " %s\n", width, height,
316 colors, score, timestr) < 0) {
317 goto out;
318 }
319 }
320 }
321 }
322 /*
323 * append entries for the current combination of width, height, and
324 * colors
325 */
326 for (entry = highscore; entry != NULL; entry = entry->next) {
327 if (strftime(timestr, sizeof (timestr), "%Y-%m-%d",
328 &entry->time) == 0) {
329 snprintf(timestr, sizeof (timestr) - 1, "1970-01-01");
330 }
331 n = snprintf(NULL, 0, "%" PRId16 " %" PRId16 " %" PRId16 " %"
332 PRId32 " %s\n", entry->width, entry->height, entry->colors,
333 entry->score, timestr);
334 if (n < 0) {
335 goto out;
336 }
337 if (fprintf(fp_out, "%" PRId16 " %" PRId16 " %" PRId16 " %"
338 PRId32 " %s\n", entry->width, entry->height,
339 entry->colors, entry->score, timestr) != n) {
340 goto out;
341 }
342 }
343 if (fflush(fp_out) == EOF) {
344 goto out;
345 }
346 if (fsync(fd_out) == -1) {
347 goto out;
348 }
349
350 error = OK;
351
352 out:
353 if (fp_in != NULL) {
354 fclose(fp_in);
355 }
356
357 if (fp_out != NULL) {
358 fclose(fp_out);
359 }
360
361 if (fd_out != -1) {
362 close(fd_out);
363 }
364
365 if (error == ERR) {
366 if (tmp_filename != NULL) {
367 unlink(tmp_filename);
368 }
369 } else {
370 if (rename(tmp_filename, highscore_filename) == -1) {
371 unlink(tmp_filename);
372 }
373 }
374
375 free(highscore_filename);
376 free(tmp_filename);
377 free(scan_fmt);
378
379 return (error);
380 }