1 /*
2  * Copyright (c) 2000 Mark B. Allan. All rights reserved.
3  *
4  * "Chromium B.S.U." is free software; you can redistribute
5  * it and/or use it and/or modify it under the terms of the
6  * "Clarified Artistic License"
7  */
8 
9 #ifdef HAVE_CONFIG_H
10 #include <chromium-bsu-config.h>
11 #endif
12 
13 #include "gettext.h"
14 
15 #include "HiScore.h"
16 #include "extern.h"
17 
18 #include "Config.h"
19 
20 #include "HeroAircraft.h"
21 
22 //====================================================================
23 #include <cstdio>
24 #include <cstdlib>
25 #include <cstring>
26 #include <ctime>
27 
28 #ifdef HAVE_LOCALE_H
29 #include <locale.h>
30 #endif
31 
32 HiScore	*HiScore::instance = 0;
33 
34 static const char* header = "# Chromium B.S.U. high scores file: skill rank score date time(UTC) name (do not remove this comment line)\n";
35 
36 /**
37  * initialize variables, then read high score file
38  */
39 //----------------------------------------------------------
HiScore()40 HiScore::HiScore()
41 {
42 	int i,j;
43 	for(i = 0; i < 10; i++)
44 	{
45 		for(j = 0; j < HI_SCORE_HIST; j++)
46 		{
47 			//-- default high scores
48 			switch(j)
49 			{
50 				case 0: hiScore[i][j] = 250000.0; break;
51 				case 1: hiScore[i][j] = 200000.0; break;
52 				case 2: hiScore[i][j] = 150000.0; break;
53 				case 3: hiScore[i][j] = 100000.0; break;
54 				case 4: hiScore[i][j] =  50000.0; break;
55 				default: hiScore[i][j] = 99.0; break;
56 			}
57 			//-- default player
58 			sprintf(hiScoreName[i][j], _("nobody"));
59 			//-- default date (01/01/2000);
60 			hiScoreDate[i][j] = 946713600;
61 		}
62 	}
63 	readFile();
64 }
65 
~HiScore()66 HiScore::~HiScore()
67 {
68 }
69 
70 /**
71  * create single HiScore object
72  * @returns HiScore::instance
73  */
74 //----------------------------------------------------------
init()75 HiScore *HiScore::init()
76 {
77 	if(!HiScore::instance)
78 	{
79 		HiScore::instance = new HiScore;
80 	}
81 	else
82 	{
83 		fprintf(stderr, _("WARNING: HiScore::init() has already been called.\n"));
84 	}
85 	return HiScore::instance;
86 }
87 
88 /**
89  * @returns HiScore::instance
90  */
91 //----------------------------------------------------------
getInstance()92 HiScore *HiScore::getInstance()
93 {
94 	if(!instance)
95 	{
96 		return HiScore::init();
97 	}
98 	else
99 		return HiScore::instance;
100 }
101 
102 /**
103  * deletes singleton instance and sets HiScore::instance to 0.
104  */
105 //----------------------------------------------------------
destroy()106 void HiScore::destroy()
107 {
108 	delete HiScore::instance;
109 	HiScore::instance = 0;
110 }
111 
112 /**
113  * return score for given level and index.
114  * @returns score, or -1 if skill,index is out of range
115  */
116 //----------------------------------------------------------
getScore(int skill,int index)117 double HiScore::getScore(int skill, int index)
118 {
119 	double retVal = -1.0;
120 	if(skill > 0 && skill < 10)
121 		if(index >= 0 && index < HI_SCORE_HIST)
122 			retVal = hiScore[skill][index];
123 	return retVal;
124 }
125 
126 /**
127  * return high scorer's name for given level and index.
128  * @returns name, or "OUT_OF_RANGE" if skill,index is out of range
129  */
130 //----------------------------------------------------------
getName(int skill,int index)131 const char *HiScore::getName(int skill, int index)
132 {
133 	const char *retVal = "OUT_OF_RANGE";
134 	if(skill > 0 && skill < 10)
135 		if(index >= 0 && index < HI_SCORE_HIST)
136 			retVal = hiScoreName[skill][index];
137 	return retVal;
138 }
139 
140 /**
141  * return date of high score for given level and index.
142  * @returns date (int time_t format), or 0 if skill,index is out of range
143  */
144 //----------------------------------------------------------
getDate(int skill,int index)145 time_t HiScore::getDate(int skill, int index)
146 {
147 	int retVal = 0;
148 	if(skill > 0 && skill < 10)
149 		if(index >= 0 && index < HI_SCORE_HIST)
150 			retVal = hiScoreDate[skill][index];
151 	return retVal;
152 }
153 
154 /**
155  * If CHROMIUM_BSU_SCORE environment variable is set, that
156  * filename will be used. Otherwise, the default score file.
157  * @returns name of score file
158  */
159 //----------------------------------------------------------
getFileName()160 const char *HiScore::getFileName()
161 {
162 	static char	configFilename[256];
163 	const char *envFile = getenv("CHROMIUM_BSU_SCORE");
164 	if(envFile && strlen(envFile) < 256)
165 	{
166 		strcpy(configFilename, envFile);
167 	}
168 	else
169 	{
170 		const char *homeDir = getenv("HOME");
171 		if(!homeDir)
172 			homeDir = "./";
173 		sprintf(configFilename, "%s/%s", homeDir, CONFIG_SCORE_FILE);
174 		alterPathForPlatform(configFilename);
175 	}
176 	return configFilename;
177 }
178 
179 /**
180  * Prints the name of the old score file
181  * @returns name of oldscore file
182  */
183 //----------------------------------------------------------
getOldFileName()184 const char *HiScore::getOldFileName()
185 {
186 	static char	configFilename[256];
187 	const char *homeDir = getenv("HOME");
188 	if(!homeDir)
189 		homeDir = "./";
190 	sprintf(configFilename, "%s/.chromium-score" CONFIG_EXT, homeDir);
191 	alterPathForPlatform(configFilename);
192 	return configFilename;
193 }
194 
195 /**
196  * Save high score file.
197  * @returns success
198  */
199 //----------------------------------------------------------
saveFile()200 bool HiScore::saveFile()
201 {
202 	bool retVal = true;
203 	FILE	*file;
204 
205 	file = fopen(getFileName(), "w");
206 	if(file)
207 	{
208 #ifdef HAVE_LOCALE_H
209 		char* locale = setlocale(LC_NUMERIC,"C");
210 #endif
211 		struct tm* time;
212 		int i,j;
213 		fprintf(file, "%s", header);
214 		for(i = 0; i < 10; i++)
215 		{
216 			for(j = 0; j < HI_SCORE_HIST; j++)
217 			{
218 				time = gmtime(&hiScoreDate[i][j]);
219 				if( time != NULL ){
220 					fprintf(file,
221 					        "%d %d %f %04d-%02d-%02d %02d:%02d:%02d %s\n",
222 					        i, j,
223 					        hiScore[i][j],
224 					        1900+time->tm_year,
225 					        1+time->tm_mon,
226 					        time->tm_mday,
227 					        time->tm_hour,
228 					        time->tm_min,
229 					        time->tm_sec,
230 					        hiScoreName[i][j]);
231 				}
232 			}
233 		}
234 		fclose(file);
235 #ifdef HAVE_LOCALE_H
236 		setlocale(LC_NUMERIC,locale);
237 #endif
238 	}
239 	else
240 	{
241 		fprintf(stderr, _("WARNING: could not write score file (%s)\n"), getFileName());
242 		retVal = false;
243 	}
244 	return retVal;
245 }
246 
247 
248 /**
249  * Read high score file.
250  * @returns success
251  */
252 //----------------------------------------------------------
readFile()253 bool HiScore::readFile()
254 {
255 	bool retVal = true;
256 	FILE	*file;
257 
258 	const char* fileName = getFileName();
259 	file = fopen(fileName, "r");
260 	if(file)
261 	{
262 		int chr = fgetc(file);
263 		if( EOF != chr ){
264 			fseek(file, 0L, SEEK_SET);
265 			if( '#' == chr ){
266 				// Save and reset locale/timezone info
267 #ifdef HAVE_LOCALE_H
268 				char* locale = setlocale(LC_NUMERIC,"C");
269 #endif
270 				char *tz = getenv("TZ");
271 				setenv("TZ", "", 1);
272 				tzset();
273 
274 				// Discard the comment line
275 				if( fscanf(file,"%*[^\n]") == EOF )
276 					fprintf(stderr, _("WARNING: error reading score file (%s)\n"), getFileName());
277 
278 				char name[100];
279 				struct tm time;
280 				int i, j;
281 				double score;
282 				int fields;
283 				do{
284 					i = j = -1;
285 					memset(&time,0,sizeof(time));
286 					fields = fscanf(file,
287 					                "%d %d %lf %d-%d-%d %d:%d:%d %99s%*[^\n]",
288 					                &i, &j,
289 					                &score,
290 					                &time.tm_year,
291 					                &time.tm_mon,
292 					                &time.tm_mday,
293 					                &time.tm_hour,
294 					                &time.tm_min,
295 					                &time.tm_sec,
296 					                name);
297 					if( fields == 10 && i >=0 && i < 10 && j >= 0 && j < HI_SCORE_HIST ){
298 						hiScore[i][j] = score;
299 						time.tm_year -= 1900;
300 						time.tm_mon--;
301 						hiScoreDate[i][j] = mktime(&time);
302 						strncpy(hiScoreName[i][j], name, 99);
303 						hiScoreName[i][j][99] = '\0';
304 					}
305 				} while( fields != EOF );
306 				fclose(file);
307 
308 				// Reset locale/timezone info
309 #ifdef HAVE_LOCALE_H
310 				setlocale(LC_NUMERIC,locale);
311 #endif
312 				if (tz) setenv("TZ", tz, 1);
313 				else unsetenv("TZ");
314 				tzset();
315 
316 			} else {
317 				// Nasty old memory dump format
318 				if( fread(hiScore,        sizeof(double), 10*HI_SCORE_HIST, file) != 10*HI_SCORE_HIST )
319 					fprintf(stderr, _("WARNING: error reading old score file (%s)\n"), getFileName());
320 				if( fread(hiScoreName, 64*sizeof(char),   10*HI_SCORE_HIST, file) != 10*HI_SCORE_HIST )
321 					fprintf(stderr, _("WARNING: error reading old score file (%s)\n"), getFileName());
322 				if( fread(hiScoreDate,    sizeof(time_t), 10*HI_SCORE_HIST, file) != 10*HI_SCORE_HIST )
323 					fprintf(stderr, _("WARNING: error reading old score file (%s)\n"), getFileName());
324 				fclose(file);
325 				// Resave the file in plain text format
326 				saveFile();
327 			}
328 		}
329 		else
330 		{
331 			Config* config = Config::instance();
332 			if( config->debug() ) fprintf(stderr, _("WARNING: empty score file (%s)\n"), getFileName());
333 			retVal = false;
334 		}
335 	}
336 	else
337 	{
338 		fileName = getOldFileName();
339 		file = fopen(fileName, "r");
340 		if(file)
341 		{
342 			if( fread(hiScore,        sizeof(double), 10*HI_SCORE_HIST, file) != 10*HI_SCORE_HIST )
343 				fprintf(stderr, _("WARNING: error reading old score file (%s)\n"), getFileName());
344 			if( fread(hiScoreName, 64*sizeof(char),   10*HI_SCORE_HIST, file) != 10*HI_SCORE_HIST )
345 				fprintf(stderr, _("WARNING: error reading old score file (%s)\n"), getFileName());
346 			if( fread(hiScoreDate,    sizeof(time_t), 10*HI_SCORE_HIST, file) != 10*HI_SCORE_HIST )
347 				fprintf(stderr, _("WARNING: error reading old score file (%s)\n"), getFileName());
348 			fclose(file);
349 
350 			// Try to save the new file and delete the old one if successful
351 			if( saveFile() )
352 				remove(fileName);
353 		}
354 		else
355 		{
356 			Config* config = Config::instance();
357 			if( config->debug() ) fprintf(stderr, _("WARNING: could not read score file (%s)\n"), getFileName());
358 			retVal = false;
359 		}
360 	}
361 
362 	return retVal;
363 }
364 
365 
366 //----------------------------------------------------------
insertScore(int skill,int rank,float score)367 void HiScore::insertScore(int skill, int rank, float score)
368 {
369 	int i;
370 	i = HI_SCORE_HIST-2;
371 	while(i >= rank)
372 	{
373 		hiScore[skill][i+1] = hiScore[skill][i];
374 		strcpy(hiScoreName[skill][i+1], hiScoreName[skill][i]);
375 		memcpy(&(hiScoreDate[skill][i+1]), &(hiScoreDate[skill][i]), sizeof(time_t));
376 		i--;
377 	}
378 	hiScore[skill][rank] = score;
379 	char *name = getenv("USER");
380 	if(name)
381 		strcpy(hiScoreName[skill][rank], name);
382 	else
383 		strcpy(hiScoreName[skill][rank], "player");
384 	time(&hiScoreDate[skill][rank]);
385 }
386 
387 /**
388  * reads high score file, inserts current score (if appropriate), then saves
389  * high score file. If multiple users are sharing a common high score file,
390  * we want to keep it as current as possible.
391  */
392 //----------------------------------------------------------
set(int skill,float score)393 int HiScore::set(int skill, float score)
394 {
395 	int retVal = 0;
396 	if(skill > 0 && skill < 10)
397 	{
398 		readFile();
399 		int i;
400 		int rank = -1;
401 		for(i = HI_SCORE_HIST-1; i >= 0; i--)
402 		{
403 			if(score > hiScore[skill][i])
404 				rank = i;
405 		}
406 		if(rank > -1)
407 		{
408 			insertScore(skill, rank, score);
409 			saveFile();
410 			retVal = rank+1;
411 		}
412 	}
413 
414 	return retVal;
415 }
416 
417 /**
418  * check whether score qualifies as high score
419  * returns rank of player {1..HI_SCORE_HIST}, or 0
420  */
421 //----------------------------------------------------------
check(int skill,float score)422 int HiScore::check(int skill, float score)
423 {
424 	int retVal = 0;
425 	if(skill > 0 && skill < 10)
426 	{
427 		int i;
428 		int rank = -1;
429 		for(i = HI_SCORE_HIST-1; i >= 0; i--)
430 		{
431 			if(score > hiScore[skill][i])
432 				rank = i;
433 		}
434 		if(rank > -1)
435 		{
436 			retVal = rank+1;
437 		}
438 	}
439 	return retVal;
440 }
441 
442 /**
443  * print high scores to stderr
444  */
445 //----------------------------------------------------------
print(int skill)446 void HiScore::print(int skill)
447 {
448 	struct tm *tmptr;
449 	fprintf(stderr, _("high scores:\n"));
450 	for(int j = 0; j < HI_SCORE_HIST; j++)
451 	{
452 		tmptr = localtime(&hiScoreDate[skill][j]);
453 		if (!tmptr)
454 			break;
455 		fprintf(stderr, _("%02d/%02d/%04d %16s %d\n"), 1+tmptr->tm_mon, tmptr->tm_mday, 1900+tmptr->tm_year,
456 				hiScoreName[skill][j], (int)(hiScore[skill][j]));
457 	}
458 }
459