diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/highscore.c	Sat Mar 15 18:41:03 2014 +0100
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2014 Guido Berhoerster <guido+rantaiwarna@berhoerster.name>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#define	_XOPEN_SOURCE	600
+
+#include <stdlib.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <curses.h>	/* for ERR, OK */
+
+#include "highscore.h"
+#include "compat.h"
+#include "util.h"
+
+#define	HIGHSCORE_FILENAME	".rantaiwarna_hiscore"
+#define	HIGHSCORE_TMP_TMPL	".rantaiwarna_hiscoreXXXXXX"
+
+static struct highscore_entry *
+highscore_entry_new(int16_t width, int16_t height, int16_t colors,
+    int32_t score, time_t time)
+{
+	struct highscore_entry *entry;
+
+	entry = rantaiwarna_malloc(sizeof (struct highscore_entry));
+	entry->next = NULL;
+	entry->width = width;
+	entry->height = height;
+	entry->colors = colors;
+	entry->score = score;
+	localtime_r(&time, &entry->time);
+
+	return (entry);
+}
+
+void
+highscore_entry_free(struct highscore_entry *entry)
+{
+	free(entry);
+}
+
+void
+highscore_free(struct highscore_entry *highscore)
+{
+	struct highscore_entry *next_entry = highscore;
+	struct highscore_entry *entry = NULL;
+
+	while (next_entry != NULL) {
+		entry = next_entry;
+		next_entry = entry->next;
+		highscore_entry_free(entry);
+	}
+}
+
+void
+highscore_update(struct highscore_entry **highscorep, int16_t width,
+    int16_t height, int16_t colors, int32_t score, time_t time)
+{
+	struct highscore_entry *new_entry;
+	struct highscore_entry *entry = *highscorep;
+	int	n = 0;
+
+	if ((entry == NULL) || (entry->score < score)) {
+		/*
+		 * if there are no entries or the current score is greater than
+		 * the first entry, prepend a new entry
+		 */
+		new_entry = highscore_entry_new(width, height, colors, score,
+		    time);
+		new_entry->next = entry;
+		*highscorep = new_entry;
+		n++;
+	} else {
+		/*
+		 * if the current score is higher than the score of one of the
+		 * top ten entries, insert a new entry
+		 */
+		for (n = 0; (entry->next != NULL) && (entry->next->score >
+		    score) && (n < 10); entry = entry->next, n++);
+		new_entry = highscore_entry_new(width, height, colors, score,
+		    time);
+		new_entry->next = entry->next;
+		entry->next = new_entry;
+	}
+	if (entry != NULL) {
+		/* trim any entries outside the top ten */
+		while ((entry->next != NULL) && (n < 10)) {
+			entry = entry->next;
+			n++;
+		}
+		highscore_free(entry->next);
+		entry->next = NULL;
+	}
+}
+
+int
+highscore_load(struct highscore_entry **highscorep, int16_t ref_height,
+    int16_t ref_width, int16_t ref_colors)
+{
+	int		error = ERR;
+	struct highscore_entry *highscore = NULL;
+	char		*home_dir;
+	struct passwd	*pw;
+	int		n;
+	char		*highscore_filename = NULL;
+	FILE		*fp = NULL;
+	char		*scan_fmt = NULL;
+	int16_t		width;
+	int16_t		height;
+	int16_t		colors;
+	int32_t		score;
+	char		timestr[4 + 1 + 2 + 1 + 2 + 1];	/* holds %Y-%m-%d */
+	struct tm	tm;
+	time_t		time;
+
+	home_dir = getenv("HOME");
+	if (home_dir == NULL) {
+		pw = getpwuid(getuid());
+		if (pw != NULL) {
+			home_dir = pw->pw_dir;
+		} else {
+			goto out;
+		}
+	}
+
+	n = snprintf(NULL, 0, "%s/%s", home_dir, HIGHSCORE_FILENAME);
+	if (n < 0) {
+		rantaiwarna_err(1, NULL);
+	}
+	highscore_filename = rantaiwarna_malloc((size_t)n + 1);
+	if (snprintf(highscore_filename, (size_t)n + 1,
+	    "%s/%s", home_dir, HIGHSCORE_FILENAME) != n) {
+		rantaiwarna_err(1, NULL);
+	}
+
+	n = snprintf(NULL, 0, "%%" SCNd16 "%%" SCNd16 "%%" SCNd16 "%%" SCNd32
+	    "%%%lus", sizeof (timestr) - 1);
+	if (n < 0) {
+		rantaiwarna_err(1, NULL);
+	}
+	scan_fmt = rantaiwarna_malloc((size_t)n + 1);
+	if (snprintf(scan_fmt, (size_t)n + 1, "%%" SCNd16 "%%" SCNd16 "%%"
+	    SCNd16 "%%" SCNd32 "%%%lus", sizeof (timestr) - 1) != n) {
+		rantaiwarna_err(1, NULL);
+	}
+
+	fp = fopen(highscore_filename, "r");
+	if (fp == NULL) {
+		goto out;
+	}
+	while ((n = fscanf(fp, scan_fmt, &width, &height, &colors, &score,
+	    timestr)) != EOF) {
+		if ((n == 5) && ((width == ref_width) &&
+		    (height == ref_height) && (colors == ref_colors))) {
+			memset(&tm, 0, sizeof (struct tm));
+			time = (time_t)0;
+			if (strptime(timestr, "%Y-%m-%d", &tm) != NULL) {
+				time = mktime(&tm);
+				if (time == (time_t)-1) {
+					time = (time_t)0;
+				}
+			}
+			highscore_update(&highscore, width, height, colors,
+			    score, time);
+		}
+	}
+	if (ferror(fp) != 0) {
+		goto out;
+	}
+
+	error = OK;
+
+out:
+	if (fp != NULL) {
+		fclose(fp);
+	}
+
+	free(scan_fmt);
+	free(highscore_filename);
+
+	if (error == ERR) {
+		highscore_free(highscore);
+	} else {
+		highscore_free(*highscorep);
+		*highscorep = highscore;
+	}
+
+	return (error);
+}
+
+int
+highscore_save(struct highscore_entry *highscore, int16_t ref_height,
+    int16_t ref_width, int16_t ref_colors)
+{
+	int		error = ERR;
+	char		*scan_fmt = NULL;
+	char		*home_dir;
+	struct passwd	*pw;
+	int		n;
+	char		*highscore_filename = NULL;
+	char		*tmp_filename = NULL;
+	mode_t		old_mode;
+	FILE		*fp_in = NULL;
+	int		fd_out = -1;
+	FILE		*fp_out = NULL;
+	int16_t		width;
+	int16_t		height;
+	int16_t		colors;
+	int32_t		score;
+	char		timestr[4 + 1 + 2 + 1 + 2 + 1]; /* holds %Y-%m-%d */
+	struct highscore_entry *entry;
+
+	/*
+	 * file format:
+	 * entries consisting of five fields, entries and fields separated by
+	 * whitespace
+	 *
+	 * fields:
+	 * width (int16_t)
+	 * height (int16_t)
+	 * colors (int16_t)
+	 * score (int32_t)
+	 * time string (YYYY-MM-DD)
+	 */
+	if (highscore == NULL) {
+		goto out;
+	}
+
+	n = snprintf(NULL, 0, "%%" SCNd16 "%%" SCNd16 "%%" SCNd16 "%%" SCNd32
+	    "%%%lus", sizeof (timestr) - 1);
+	if (n < 0) {
+		rantaiwarna_err(1, NULL);
+	}
+	scan_fmt = rantaiwarna_malloc((size_t)n + 1);
+	if (snprintf(scan_fmt, (size_t)n + 1, "%%" SCNd16 "%%" SCNd16 "%%"
+	    SCNd16 "%%" SCNd32 "%%%lus", sizeof (timestr) - 1) != n) {
+		rantaiwarna_err(1, NULL);
+	}
+
+	home_dir = getenv("HOME");
+	if (home_dir == NULL) {
+		pw = getpwuid(getuid());
+		if (pw != NULL) {
+			home_dir = pw->pw_dir;
+		} else {
+			goto out;
+		}
+	}
+
+	n = snprintf(NULL, 0, "%s/%s", home_dir, HIGHSCORE_FILENAME);
+	if (n < 0) {
+		rantaiwarna_err(1, NULL);
+	}
+	highscore_filename = rantaiwarna_malloc((size_t)n + 1);
+	if (snprintf(highscore_filename, (size_t)n + 1, "%s/%s", home_dir,
+	    HIGHSCORE_FILENAME) != n) {
+		rantaiwarna_err(1, NULL);
+	}
+
+	n = snprintf(NULL, 0, "%s/%s", home_dir, HIGHSCORE_TMP_TMPL);
+	if (n < 0) {
+		rantaiwarna_err(1, NULL);
+	}
+	tmp_filename = rantaiwarna_malloc((size_t)n + 1);
+	if (snprintf(tmp_filename, (size_t)n + 1, "%s/%s", home_dir,
+	    HIGHSCORE_TMP_TMPL) != n) {
+		rantaiwarna_err(1, NULL);
+	}
+
+	old_mode = umask(077);
+	fd_out = mkstemp(tmp_filename);
+	umask(old_mode);
+	if (fd_out == -1) {
+		goto out;
+	}
+
+	fp_out = fdopen(fd_out, "w");
+	if (fp_out == NULL) {
+		goto out;
+	}
+	/*
+	 * preserve entries which do not match the current combination of width,
+	 * height, and colors
+	 */
+	fp_in = fopen(highscore_filename, "r");
+	if (fp_in != NULL) {
+		while ((n = fscanf(fp_in, scan_fmt, &width, &height, &colors,
+		    &score, timestr)) != EOF) {
+			if ((n == 5) && ((width != ref_width) ||
+			    (height != ref_height) || (colors != ref_colors))) {
+				if (fprintf(fp_out, "%" PRId16 " %" PRId16 " %"
+				    PRId16 " %" PRId32 " %s\n", width, height,
+				    colors, score, timestr) < 0) {
+					goto out;
+				}
+			}
+		}
+	}
+	/*
+	 * append entries for the current combination of width, height, and
+	 * colors
+	 */
+	for (entry = highscore; entry != NULL; entry = entry->next) {
+		if (strftime(timestr, sizeof (timestr), "%Y-%m-%d",
+		    &entry->time) == 0) {
+			snprintf(timestr, sizeof (timestr) - 1, "1970-01-01");
+		}
+		n = snprintf(NULL, 0, "%" PRId16 " %" PRId16 " %" PRId16 " %"
+		    PRId32 " %s\n", entry->width, entry->height, entry->colors,
+		    entry->score, timestr);
+		if (n < 0) {
+			goto out;
+		}
+		if (fprintf(fp_out, "%" PRId16 " %" PRId16 " %" PRId16 " %"
+		    PRId32 " %s\n", entry->width, entry->height,
+		    entry->colors, entry->score, timestr) != n) {
+			goto out;
+		}
+	}
+	if (fflush(fp_out) == EOF) {
+		goto out;
+	}
+	if (fsync(fd_out) == -1) {
+		goto out;
+	}
+
+	error = OK;
+
+out:
+	if (fp_in != NULL) {
+		fclose(fp_in);
+	}
+
+	if (fp_out != NULL) {
+		fclose(fp_out);
+	}
+
+	if (fd_out != -1) {
+		close(fd_out);
+	}
+
+	if (error == ERR) {
+		if (tmp_filename != NULL) {
+			unlink(tmp_filename);
+		}
+	} else {
+		if (rename(tmp_filename, highscore_filename) == -1) {
+			unlink(tmp_filename);
+		}
+	}
+
+	free(highscore_filename);
+	free(tmp_filename);
+	free(scan_fmt);
+
+	return (error);
+}