1 /* unslist.c: Functions to manage the list of unsolvable levels.
2 *
3 * Copyright (C) 2001-2006 by Brian Raiter, under the GNU General Public
4 * License. No warranty. See COPYING for details.
5 */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include "defs.h"
12 #include "err.h"
13 #include "fileio.h"
14 #include "res.h"
15 #include "solution.h"
16 #include "unslist.h"
17
18 /* The information comprising one entry in the list of unsolvable
19 * levels.
20 */
21 typedef struct unslistentry {
22 int setid; /* the ID of the level set's name */
23 int levelnum; /* the level's number */
24 int size; /* the levels data's compressed size */
25 unsigned long hashval; /* the levels data's hash value */
26 int note; /* the entry's annotation ID, if any */
27 } unslistentry;
28
29 /* The pool of strings. In here are stored the level set names and the
30 * annotations. The string IDs are simple offsets from the strings
31 * pointer.
32 */
33 static int stringsused = 0;
34 static int stringsallocated = 0;
35 static char *strings = NULL;
36
37 /* The list of level set names for which unsolvable levels appear on
38 * the list. This list allows the program to quickly find the level
39 * set name's string ID.
40 */
41 static int namescount = 0;
42 static int namesallocated = 0;
43 static int *names = NULL;
44
45 /* The list of unsolvable levels proper.
46 */
47 static int listcount = 0;
48 static int listallocated = 0;
49 static unslistentry *unslist = NULL;
50
51 /*
52 * Managing the pool of strings.
53 */
54
55 /* Turn a string ID back into a string pointer. (Note that a string ID
56 * of zero will always return a null string, assuming that
57 * storestring() has been called at least once.)
58 */
59 #define getstring(id) (strings + (id))
60
61 /* Make a copy of a string and add it to the string pool. The new
62 * string's ID is returned.
63 */
storestring(char const * str)64 static int storestring(char const *str)
65 {
66 int len;
67
68 len = strlen(str) + 1;
69 if (stringsused + len > stringsallocated) {
70 stringsallocated = stringsallocated ? 2 * stringsallocated : 256;
71 xalloc(strings, stringsallocated);
72 if (!stringsused) {
73 *strings = '\0';
74 ++stringsused;
75 }
76 }
77 memcpy(strings + stringsused, str, len);
78 stringsused += len;
79 return stringsused - len;
80 }
81
82 /*
83 * Managing the list of set names.
84 */
85
86 /* Return the string ID of the given set name. If the set name is not
87 * already in the list, then if add is TRUE the set name is added to
88 * the list; otherwise zero is returned.
89 */
lookupsetname(char const * name,int add)90 static int lookupsetname(char const *name, int add)
91 {
92 int i;
93
94 for (i = 0 ; i < namescount ; ++i)
95 if (!strcmp(getstring(names[i]), name))
96 return names[i];
97 if (!add)
98 return 0;
99
100 if (namescount >= namesallocated) {
101 namesallocated = namesallocated ? 2 * namesallocated : 8;
102 xalloc(names, namesallocated * sizeof *names);
103 }
104 names[namescount] = storestring(name);
105 return names[namescount++];
106 }
107
108 /*
109 * Managing the list of unsolvable levels.
110 */
111
112 /* Add a new entry with the given data to the list.
113 */
addtounslist(int setid,int levelnum,int size,unsigned long hashval,int note)114 static int addtounslist(int setid, int levelnum,
115 int size, unsigned long hashval, int note)
116 {
117 if (listcount == listallocated) {
118 listallocated = listallocated ? listallocated * 2 : 16;
119 xalloc(unslist, listallocated * sizeof *unslist);
120 }
121 unslist[listcount].setid = setid;
122 unslist[listcount].levelnum = levelnum;
123 unslist[listcount].size = size;
124 unslist[listcount].hashval = hashval;
125 unslist[listcount].note = note;
126 ++listcount;
127 return TRUE;
128 }
129
130 /* Remove all entries for the given level from the list. FALSE is
131 * returned if the level was not on the list to begin with.
132 */
removefromunslist(int setid,int levelnum)133 static int removefromunslist(int setid, int levelnum)
134 {
135 int i, f = FALSE;
136
137 for (i = 0 ; i < listcount ; ++i) {
138 if (unslist[i].setid == setid && unslist[i].levelnum == levelnum) {
139 --listcount;
140 unslist[i] = unslist[listcount];
141 f = TRUE;
142 }
143 }
144 return f;
145 }
146
147 /* Add the information in the given file to the list of unsolvable
148 * levels. Errors in the file are flagged but do not prevent the
149 * function from reading the rest of the file.
150 */
readunslist(fileinfo * file)151 static int readunslist(fileinfo *file)
152 {
153 char buf[256], token[256];
154 char const *p;
155 unsigned long hashval;
156 int setid, size;
157 int lineno, levelnum, n;
158
159 setid = 0;
160 for (lineno = 1 ; ; ++lineno) {
161 n = sizeof buf - 1;
162 if (!filegetline(file, buf, &n, NULL))
163 break;
164 for (p = buf ; isspace(*p) ; ++p) ;
165 if (!*p || *p == '#')
166 continue;
167 if (sscanf(p, "[%[^]]]", token) == 1) {
168 setid = lookupsetname(token, TRUE);
169 continue;
170 }
171 n = sscanf(p, "%d: %04X%08lX: %[^\n\r]",
172 &levelnum, &size, &hashval, token);
173 if (n > 0 && levelnum > 0 && levelnum < 65536 && setid) {
174 if (n == 1) {
175 n = sscanf(p, "%*d: %s", token);
176 if (n > 0 && !strcmp(token, "ok")) {
177 removefromunslist(setid, levelnum);
178 continue;
179 }
180 } else if (n >= 3) {
181 addtounslist(setid, levelnum, size, hashval,
182 n == 4 ? storestring(token) : 0);
183 continue;
184 }
185 }
186 warn("%s:%d: syntax error", file->name, lineno);
187 }
188 return TRUE;
189 }
190
191 /*
192 * Exported functions.
193 */
194
195 /* Return TRUE if the given level in the list of unsolvable levels. No
196 * set name is supplied, so this function relies on the other three
197 * data. A copy of the level's annotation is made if note is not NULL.
198 */
islevelunsolvable(gamesetup const * game,char * note)199 int islevelunsolvable(gamesetup const *game, char *note)
200 {
201 int i;
202
203 for (i = 0 ; i < listcount ; ++i) {
204 if (unslist[i].levelnum == game->number
205 && unslist[i].size == game->levelsize
206 && unslist[i].hashval == game->levelhash) {
207 if (note)
208 strcpy(note, getstring(unslist[i].note));
209 return TRUE;
210 }
211 }
212 return FALSE;
213 }
214
215 /* Look up the levels that constitute the given series and find which
216 * levels appear in the list. Those that do will have the unsolvable
217 * field in the gamesetup structure initialized.
218 */
markunsolvablelevels(gameseries * series)219 int markunsolvablelevels(gameseries *series)
220 {
221 int count = 0;
222 int setid, i, j;
223
224 for (j = 0 ; j < series->count ; ++j)
225 series->games[j].unsolvable = NULL;
226
227 setid = lookupsetname(series->name, FALSE);
228 if (!setid)
229 return 0;
230
231 for (i = 0 ; i < listcount ; ++i) {
232 if (unslist[i].setid != setid)
233 continue;
234 for (j = 0 ; j < series->count ; ++j) {
235 if (series->games[j].number == unslist[i].levelnum
236 && series->games[j].levelsize == unslist[i].size
237 && series->games[j].levelhash == unslist[i].hashval) {
238 series->games[j].unsolvable = getstring(unslist[i].note);
239 ++count;
240 break;
241 }
242 }
243 }
244 return count;
245 }
246
247 /* Read the list of unsolvable levels from the given filename. If the
248 * filename does not contain a path, then the function looks for the
249 * file in the resource directory and the user's save directory.
250 */
loadunslistfromfile(char const * filename)251 int loadunslistfromfile(char const *filename)
252 {
253 fileinfo file;
254
255 memset(&file, 0, sizeof file);
256 if (openfileindir(&file, resdir, filename, "r", NULL)) {
257 readunslist(&file);
258 fileclose(&file, NULL);
259 }
260 if (!haspathname(filename)) {
261 memset(&file, 0, sizeof file);
262 if (openfileindir(&file, savedir, filename, "r", NULL)) {
263 readunslist(&file);
264 fileclose(&file, NULL);
265 }
266 }
267 return TRUE;
268 }
269
270 /* Free all memory associated with the list of unsolvable levels.
271 */
clearunslist(void)272 void clearunslist(void)
273 {
274 free(unslist);
275 listcount = 0;
276 listallocated = 0;
277 unslist = NULL;
278
279 free(names);
280 namescount = 0;
281 namesallocated = 0;
282 names = NULL;
283
284 free(strings);
285 stringsused = 0;
286 stringsallocated = 0;
287 strings = NULL;
288 }
289