1 /* Asteroids3D - a first person game of blowing up asteroids
2  * Copyright (C) 2000 Stuart Mark Pomerantz <smp [at] psc edu>
3  * Copyright © Jan Engelhardt <jengelh [at] gmx de>, 2003 - 2005
4  *
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  *  Stuart Pomerantz
21  *  3935 Stonecliffe drive
22  *  Monroeville, PA 15146
23  */
24 
25 #include <GL/glut.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <errno.h>
29 #include <math.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34 #include "asteroids3D.h"
35 #include "vecops.h"
36 #ifndef S_IRUGO
37 #	define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH)
38 #endif
39 #ifndef S_IWUGO
40 #	define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH)
41 #endif
42 
43 // This stuff is for the score file
44 #define MAX_FIELD_LEN             64
45 #define MAX_LINE_LEN              256
46 #define SCORE_FILE                "/tmp/asteroids-scores"
47 #define NUM_SCORES                10
48 #define SCORE_FILE_SEPARATOR_CHAR ','
49 #define SCORE_FILE_COMMENT_CHAR   '#'
50 #define SCORE_FILE_ESCAPE_CHAR    '\\'
51 
52 typedef struct score_file_entry {
53 	char name[MAX_FIELD_LEN], date[MAX_FIELD_LEN];
54 	unsigned int score;
55 	int new_score_flag; // this value isn't saved to disk
56 } ScoreFileEntry;
57 
58 static int compare_scores(const void *, const void *);
59 static void read_score_file(void);
60 static void score_display(void);
61 static void score_keyboard_handler(unsigned char, int, int);
62 static void write_score_file(void);
63 
64 // last but not least, what would a game be without a score
65 unsigned int score = 0;
66 double difficulty_multiplier = 1;
67 
68 // Data structure to hold the scores read from the score file
69 static ScoreFileEntry score_entry[NUM_SCORES] = {};
70 
71 //-----------------------------------------------------------------------------
add_rock_to_score(const Asteroid * a)72 void add_rock_to_score(const Asteroid *a)
73 {
74 	/* Scoring works like this:
75 	 *
76 	 * - small rocks are worth more than big ones
77 	 * - fast rocks are worth more than slow ones
78 	 *
79 	 * so I am doing (100/size)*speed <=> (100 * speed / size)
80 	 *
81 	 * Adding one below so I do not inadvertantly
82 	 * score a zero for a stationary or slow asertoid.
83 	 */
84 	score += 100.0 * (vec_length(&a->velocity) + 1) / (a->type + 1.0);
85 	return;
86 }
87 
read_score_file(void)88 static void read_score_file(void) {
89 	char buf[MAX_LINE_LEN];
90 	int i = 0;
91 	FILE *fp;
92 
93 	if ((fp = fopen(SCORE_FILE, "r")) == NULL)
94 		return;
95 
96 	while (i < NUM_SCORES && fgets(buf, sizeof(buf), fp) != NULL) {
97 		char *bp = buf, *res;
98 		ScoreFileEntry *se;
99 
100 		strip_nl(bp);
101 		if (*bp == '#' || *bp == '\0')
102 			continue;
103 
104 		se = &score_entry[i++];
105 
106 		if ((res = strsep(&bp, ",")) != NULL) {
107 			strncpy(se->name, res, sizeof(se->name) - 1);
108 			se->name[sizeof(se->name) - 1] = '\0';
109 		}
110 		if ((res = strsep(&bp, ",")) != NULL)
111 			se->score = strtol(res, NULL, 0);
112 		if (bp != NULL) {
113 			strncpy(se->date, bp, sizeof(se->date) - 1);
114 			se->date[sizeof(se->date) - 1] = '\0';
115 		}
116 
117 		se->new_score_flag = 0;
118 	}
119 
120 	fclose(fp);
121 	return;
122 }
123 
write_score_file(void)124 static void write_score_file(void)
125 {
126 	FILE *fp;
127 	int i;
128 
129 	if ((fp = fopen(SCORE_FILE, "w")) == NULL) {
130 		fprintf(stderr, "Could not open %s for writing: %s\n",
131 		        SCORE_FILE, strerror(errno));
132 		return;
133 	}
134 
135 	fprintf(fp,
136 		"#\n"
137 		"# asteroids score file\n"
138 		"#\n"
139 		"# This file is automatically generated and will be "
140 		"recreated if deleted.\n"
141 		"#\n"
142 		"# name,score,date\n"
143 	);
144 
145 	for (i = 0; i < NUM_SCORES; ++i) {
146 		ScoreFileEntry *se = &score_entry[i];
147 		if (*se->name != '\0' && *se->date != '\0')
148 			fprintf(fp, "%s,%u,%s\n", se->name,
149 			        se->score, se->date);
150 	}
151 
152 	fclose(fp);
153 	chmod(SCORE_FILE, S_IRUGO | S_IWUGO);
154 	return;
155 }
156 
compare_scores(const void * pa,const void * pb)157 static int compare_scores(const void *pa, const void *pb)
158 {
159 	const ScoreFileEntry *a = pa, *b = pb;
160 	return b->score - a->score;
161 }
162 
init_score_display(void)163 void init_score_display(void)
164 {
165 	ScoreFileEntry *se;
166 	time_t now;
167 	char *user;
168 
169 	/* The player's score is scaled to reflect the difficulty of the game
170 	starting with more asteroids gets a higher score than starting with fewer.
171 	NUM_ASTEROIDS asteroids is the "base" score. The difficulty multiplier is
172 	already calculated at game start so that we can display the correct score
173 	already in-game. */
174 	score *= difficulty_multiplier;
175 
176 	read_score_file();
177 	glutDisplayFunc(score_display);
178 	glutKeyboardFunc(score_keyboard_handler);
179 
180 	if (score <= score_entry[NUM_SCORES-1].score)
181 		return;
182 
183 	se = &score_entry[NUM_SCORES-1];
184 
185 	if ((user = getenv("USER")) != NULL)
186 		strncpy(se->name, user, sizeof(se->name) - 1);
187 	else
188 		strncpy(se->name, "(unknown)", sizeof(se->name) - 1);
189 
190 	se->name[sizeof(se->name) - 1] = '\0';
191 	se->score = score;
192 	se->new_score_flag = 1;
193 	now = time(NULL);
194 	strftime(se->date, sizeof(se->date), "%B %d %Y, %H:%M",
195 	         localtime(&now));
196 
197 	qsort(score_entry, NUM_SCORES, sizeof(ScoreFileEntry), compare_scores);
198 	write_score_file();
199 	return;
200 }
201 
score_keyboard_handler(unsigned char key,int x,int y)202 static void score_keyboard_handler(unsigned char key, int x, int y)
203 {
204 	if (key == keymap.end_game)
205 		exit(EXIT_SUCCESS);
206 	return;
207 }
208 
score_display(void)209 static void score_display(void)
210 {
211 	char buf[MAX_LINE_LEN];
212 	double r, g, b, a;
213 	int i;
214 
215 	/* do some error checking for each frame. */
216 	glutReportErrors();
217 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
218 	draw_text_in_color(-0.2, 0.9, "Top Ten Scores", 0, 0, 1, 1);
219 
220 	for (i = 0; i < NUM_SCORES; ++i) {
221 		ScoreFileEntry *se = &score_entry[i];
222 
223 		if (*se->name == '\0' || *se->date == '\0')
224 			continue;
225 
226 		if (!se->new_score_flag) {
227 			r = g = b = a = 1;
228 		} else {
229 			r = a = 1;
230 			g = b = 0;
231 		}
232 
233 		draw_text_in_color(-0.7, 0.7 - static_cast(double, i) /
234 		                   NUM_SCORES, se->name, r, g, b, a);
235 
236 		snprintf(buf, sizeof(buf), "%u", se->score);
237 		draw_text_in_color(-0.2, 0.7 - static_cast(double, i) /
238 		                   NUM_SCORES, buf, r, g, b, a);
239 
240 		draw_text_in_color(0.2, 0.7 - static_cast(double, i) /
241 		                   NUM_SCORES, se->date, r, g, b, a);
242 	}
243 
244 	key_char2str(keymap.end_game, buf, sizeof(buf));
245 	strcat(buf, " key exits");
246 	draw_text_in_color(-0.2, -0.7, buf, 0.5, 0.5, 0.5, 1);
247 	glutSwapBuffers();
248 	return;
249 }
250