Maelstrom: Added initial preferences implementation

https://github.com/libsdl-org/Maelstrom/commit/4430b3635bc6a3378aef64db6aae7dce8a790c0f

From 4430b3635bc6a3378aef64db6aae7dce8a790c0f Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 4 Nov 2011 19:32:38 -0400
Subject: [PATCH] Added initial preferences implementation

---
 Maelstrom_Globals.h |   4 +
 Makefile.am         |   2 +
 Makefile.in         |   7 +-
 init.cpp            |  24 ++++--
 prefs.cpp           | 176 ++++++++++++++++++++++++++++++++++++++++++++
 prefs.h             |  50 +++++++++++++
 utils/hashtable.c   |  37 ++++++++++
 utils/hashtable.h   |   1 +
 8 files changed, 292 insertions(+), 9 deletions(-)
 create mode 100644 prefs.cpp
 create mode 100644 prefs.h

diff --git a/Maelstrom_Globals.h b/Maelstrom_Globals.h
index a6ccb3cc..79e7a547 100644
--- a/Maelstrom_Globals.h
+++ b/Maelstrom_Globals.h
@@ -38,6 +38,7 @@
 #include "myerror.h"
 #include "fastrand.h"
 #include "logic.h"
+#include "prefs.h"
 #include "scores.h"
 #include "controls.h"
 
@@ -53,6 +54,9 @@ enum {
 };
 extern MFont *fonts[NUM_FONTS];
 
+// The Preferences
+extern Prefs *prefs;
+
 // The Sound Server *grin*
 extern Sound *sound;
 
diff --git a/Makefile.am b/Makefile.am
index 7eb05b8c..6b4d6853 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,6 +23,8 @@ Maelstrom_SOURCES =		\
 	main.h			\
 	myerror.cpp		\
 	myerror.h		\
+	prefs.cpp		\
+	prefs.h			\
 	rect.cpp		\
 	rect.h			\
 	scores.cpp		\
diff --git a/Makefile.in b/Makefile.in
index 6efbba37..92f3d4ab 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -53,8 +53,8 @@ binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
 PROGRAMS = $(bin_PROGRAMS)
 am_Maelstrom_OBJECTS = MaelstromUI.$(OBJEXT) MacDialog.$(OBJEXT) \
 	controls.$(OBJEXT) fastrand.$(OBJEXT) init.$(OBJEXT) \
-	load.$(OBJEXT) main.$(OBJEXT) myerror.$(OBJEXT) rect.$(OBJEXT) \
-	scores.$(OBJEXT)
+	load.$(OBJEXT) main.$(OBJEXT) myerror.$(OBJEXT) \
+	prefs.$(OBJEXT) rect.$(OBJEXT) scores.$(OBJEXT)
 Maelstrom_OBJECTS = $(am_Maelstrom_OBJECTS)
 Maelstrom_DEPENDENCIES = $(LOGIC)/liblogic.a screenlib/libSDLscreen.a \
 	maclib/libSDLmac.a utils/libutils.a
@@ -220,6 +220,8 @@ Maelstrom_SOURCES = \
 	main.h			\
 	myerror.cpp		\
 	myerror.h		\
+	prefs.cpp		\
+	prefs.h			\
 	rect.cpp		\
 	rect.h			\
 	scores.cpp		\
@@ -334,6 +336,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/load.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/myerror.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/prefs.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rect.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scores.Po@am__quote@
 
diff --git a/init.cpp b/init.cpp
index 030c0586..436b29fc 100644
--- a/init.cpp
+++ b/init.cpp
@@ -35,7 +35,10 @@
 #include "screenlib/UIElement.h"
 
 
+#define GAME_PREFS_FILE	"Maelstrom_Prefs.txt"
+
 // Global variables set in this file...
+Prefs    *prefs = NULL;
 Sound    *sound = NULL;
 FontServ *fontserv = NULL;
 MFont    *fonts[NUM_FONTS];
@@ -658,7 +661,10 @@ void CleanUp(void)
 		delete sound;
 		sound = NULL;
 	}
-	SaveControls();
+	if ( prefs ) {
+		delete prefs;
+		prefs = NULL;
+	}
 	PHYSFS_deinit();
 	SDL_Quit();
 }
@@ -671,6 +677,16 @@ int DoInitializations(Uint32 window_flags, Uint32 render_flags)
 	int i;
 	SDL_Surface *icon;
 
+	// -- Initialize some variables
+	gLastHigh = -1;
+
+	// -- Create our scores file
+	LoadScores();
+
+	// -- Load our preferences files
+	prefs = new Prefs(GAME_PREFS_FILE);
+	prefs->Load();
+
 	Uint32 init_flags = (SDL_INIT_VIDEO|SDL_INIT_AUDIO);
 #ifdef SDL_INIT_JOYSTICK
 	init_flags |= SDL_INIT_JOYSTICK;
@@ -683,12 +699,6 @@ int DoInitializations(Uint32 window_flags, Uint32 render_flags)
 		}
 	}
 
-	// -- Initialize some variables
-	gLastHigh = -1;
-
-	// -- Create our scores file
-	LoadScores();
-
 #ifdef SDL_INIT_JOYSTICK
 	/* Initialize the first joystick */
 	if ( SDL_NumJoysticks() > 0 ) {
diff --git a/prefs.cpp b/prefs.cpp
new file mode 100644
index 00000000..aeb32a94
--- /dev/null
+++ b/prefs.cpp
@@ -0,0 +1,176 @@
+/*
+    Maelstrom: Open Source version of the classic game by Ambrosia Software
+    Copyright (C) 1997-2011  Sam Lantinga
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+    Sam Lantinga
+    slouken@libsdl.org
+*/
+
+#include "physfs.h"
+#include "utils/hashtable.h"
+
+#include "prefs.h"
+
+
+static void
+hash_nuke_strings(const void *key, const void *value, void *data)
+{
+	SDL_free((char*)key);
+	SDL_free((char*)value);
+}
+
+Prefs::Prefs(const char *file)
+{
+	m_file = SDL_strdup(file);
+	m_values = hash_create(NULL, hash_hash_string, hash_keymatch_string, hash_nuke_strings);
+}
+
+Prefs::~Prefs()
+{
+	SDL_free(m_file);
+	hash_destroy(m_values);
+}
+
+bool
+Prefs::Load()
+{
+	PHYSFS_File *fp;
+	PHYSFS_sint64 size;
+	char *data;
+	char *key, *value, *next;
+
+	fp = PHYSFS_openRead(m_file);
+	if (!fp) {
+		/* This is fine, we just haven't written them yet */
+		return false;
+	}
+
+	size = PHYSFS_fileLength(fp);
+	data = new char[size+1];
+	if (PHYSFS_readBytes(fp, data, size) != size) {
+		fprintf(stderr, "Warning: Couldn't read from %s: %s\n",
+					m_file, PHYSFS_getLastError());
+		PHYSFS_close(fp);
+		delete[] data;
+		return false;
+	}
+	data[size] = '\0';
+	PHYSFS_close(fp);
+
+	key = data;
+	while (*key) {
+		value = SDL_strchr(key, '=');
+		if (!value) {
+			break;
+		}
+		*value++ = '\0';
+
+		next = value;
+		while (*next && *next != '\r' && *next != '\n') {
+			++next;
+		}
+		if (*next) {
+			*next++ = '\0';
+		}
+
+		SetString(key, value);
+
+		key = next;
+		while (*key && *key == '\r' && *key == '\n') {
+			++key;
+		}
+	}
+	delete[] data;
+
+	return true;
+}
+
+static __inline__ bool
+writeString(PHYSFS_File *fp, const char *string)
+{
+	size_t len = SDL_strlen(string);
+	return ((size_t)PHYSFS_writeBytes(fp, string, len) == len);
+}
+
+bool
+Prefs::Save()
+{
+	PHYSFS_File *fp;
+	const char *key, *value;
+	void *iter;
+
+	fp = PHYSFS_openWrite(m_file);
+	if (!fp) {
+		fprintf(stderr, "Warning: Couldn't open %s: %s\n",
+					m_file, PHYSFS_getLastError());
+		return false;
+	}
+
+	iter = NULL;
+	while (hash_iter(m_values, (const void **)&key, (const void **)&value, &iter)) {
+		if (!writeString(fp, key) ||
+		    !writeString(fp, "=") ||
+		    !writeString(fp, value) ||
+		    !writeString(fp, "\r\n")) {
+			fprintf(stderr, "Warning: Couldn't write to %s: %s\n",
+						m_file, PHYSFS_getLastError());
+			PHYSFS_close(fp);
+			return false;
+		}
+	}
+	PHYSFS_close(fp);
+
+	return true;
+}
+
+void
+Prefs::SetString(const char *key, const char *value)
+{
+	hash_remove(m_values, key);
+	hash_insert(m_values, SDL_strdup(key), SDL_strdup(value));
+}
+
+void
+Prefs::SetNumber(const char *key, Uint32 value)
+{
+	char buf[32];
+
+	SDL_snprintf(buf, sizeof(buf), "%u", value);
+	SetString(key, buf);
+}
+
+const char *
+Prefs::GetString(const char *key, const char *defaultValue)
+{
+	const char *value;
+
+	if (hash_find(m_values, key, (const void **)&value)) {
+		return value;
+	}
+	return defaultValue;
+}
+
+Uint32
+Prefs::GetNumber(const char *key, Uint32 defaultValue)
+{
+	const char *value;
+
+	if (hash_find(m_values, key, (const void **)&value)) {
+		return SDL_atoi(value);
+	}
+	return defaultValue;
+}
diff --git a/prefs.h b/prefs.h
new file mode 100644
index 00000000..0fb7ffb7
--- /dev/null
+++ b/prefs.h
@@ -0,0 +1,50 @@
+/*
+    Maelstrom: Open Source version of the classic game by Ambrosia Software
+    Copyright (C) 1997-2011  Sam Lantinga
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+    Sam Lantinga
+    slouken@libsdl.org
+*/
+
+#ifndef _prefs_h
+#define _prefs_h
+
+#include "SDL.h"
+
+class HashTable;
+
+class Prefs
+{
+public:
+	Prefs(const char *file);
+	virtual ~Prefs();
+
+	bool Load();
+	bool Save();
+
+	void SetString(const char *key, const char *value);
+	void SetNumber(const char *key, Uint32 value);
+
+	const char *GetString(const char *key, const char *defaultValue = NULL);
+	Uint32 GetNumber(const char *key, Uint32 defaultValue = 0);
+
+protected:
+	char *m_file;
+	HashTable *m_values;
+};
+
+#endif /* _prefs_h */
diff --git a/utils/hashtable.c b/utils/hashtable.c
index d991e57d..048505c5 100644
--- a/utils/hashtable.c
+++ b/utils/hashtable.c
@@ -25,12 +25,15 @@ typedef struct HashItem
     const void *key;
     const void *value;
     struct HashItem *next;
+    struct HashItem *global_next;
+    struct HashItem *global_prev;
 } HashItem;
 
 struct HashTable
 {
     HashItem **table;
     unsigned table_len;
+    HashItem *list;
     void *data;
     HashTable_HashFn hash;
     HashTable_KeyMatchFn keymatch;
@@ -72,6 +75,29 @@ int hash_find(const HashTable *table, const void *key, const void **_value)
     return 0;
 } // hash_find
 
+int hash_iter(const HashTable *table, const void **key,
+              const void **value, void **iter)
+{
+    HashItem *i = *iter;
+    if (i == NULL)
+        i = table->list;
+    else
+        i = i->global_next;
+
+    if (i != NULL)
+    {
+        *key = i->key;
+        *value = i->value;
+        *iter = i;
+        return 1;
+    }
+
+    *key = NULL;
+    *value = NULL;
+    *iter = NULL;
+    return 0;
+} // hash_iter
+
 int hash_insert(HashTable *table, const void *key, const void *value)
 {
     HashItem *item = NULL;
@@ -87,6 +113,12 @@ int hash_insert(HashTable *table, const void *key, const void *value)
     item->key = key;
     item->value = value;
     item->next = table->table[hash];
+    item->global_next = table->list;
+    if (table->list) {
+        table->list->global_prev = item;
+    }
+    item->global_prev = NULL;
+    table->list = item;
     table->table[hash] = item;
 
     return 1;
@@ -107,6 +139,11 @@ int hash_remove(HashTable *table, const void *key)
             else
                 table->table[hash] = item->next;
 
+            if (item->global_prev)
+                item->global_prev->global_next = item->global_next;
+            if (item->global_next)
+                item->global_next->global_prev = item->global_prev;
+
             table->nuke(item->key, item->value, data);
             free(item);
             return 1;
diff --git a/utils/hashtable.h b/utils/hashtable.h
index 6acae01f..dad098c1 100644
--- a/utils/hashtable.h
+++ b/utils/hashtable.h
@@ -37,6 +37,7 @@ void hash_destroy(HashTable *table);
 int hash_insert(HashTable *table, const void *key, const void *value);
 int hash_remove(HashTable *table, const void *key);
 int hash_find(const HashTable *table, const void *key, const void **_value);
+int hash_iter(const HashTable *table, const void **key, const void **value, void **iter);
 
 unsigned hash_hash_string(const void *sym, void *unused);
 int hash_keymatch_string(const void *a, const void *b, void *unused);