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