1 /* Copyright (C) 1998 Jim Hall <jhall1@isd.net>
2  * Copyright (C) 2008 Bradley Smith <brad@brad-smith.co.uk>
3  *
4  * GNU Robots game engine. This is the main() program, using GNU
5  * Guile as the backend to handle the language.
6  *
7  * GNU Robots is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * GNU Robots is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with GNU Robots.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <stdio.h>
22 #include <unistd.h>			/* for getopt */
23 #include <string.h>			/* for strdup */
24 
25 #include <glib.h>
26 #include <glib/gprintf.h>
27 
28 #include <libguile.h>
29 
30 #include <getopt.h>			/* for GNU getopt_long */
31 
32 #include "grobot.h"			/* the robot structure, and robot manipulation
33 								   routines */
34 
35 #include "api.h"			/* robot API, as Scheme functions */
36 #include "ui-cmdwin.h"
37 #include "ui-arena.h"
38 #include "ui-window.h"
39 #include "ui-window-private.h"
40 #include "config.h"
41 #include "configs.h"
42 #include "map.h"			/* Game Map */
43 #include "main.h"			/* for this source file */
44 
45 #define BUFF_LEN 1024
46 
47 /* Globals (share with api.c) */
48 GList *robots = NULL;
49 GRobot *robot = NULL;		/* The current robot */
50 UIWindow *ui;
51 UIArena *arena;
52 Map *map;
53 
54 gpointer callback(gpointer data);
55 SCM catch_handler(void *data, SCM tag, SCM throw_args);
56 gint is_file_readable(const gchar * filename);
57 
58 /************************************************************************
59  * gint main(gint argc, gchar *argv[])                                  *
60  *                                                                      *
61  * Entry point                                                          *
62  ************************************************************************/
main(gint argc,gchar * argv[])63 gint main(gint argc, gchar *argv[])
64 {
65 	gint opt;				/* the option read from getopt */
66 	gint flag;				/* flag passed back from getopt - NOT USED */
67 
68 	gchar maps_path[MAX_PATH], scripts_path[MAX_PATH];
69 
70 	gchar *main_argv[5] = { "GNU Robots",
71 		NULL,
72 		NULL,
73 		NULL,
74 		NULL
75 	};
76 
77 	struct option long_opts[] = {
78 		{"version", 0, NULL, 'V'},
79 		{"help", 0, NULL, 'h'},
80 		{"map-file", 1, NULL, 'f'},
81 		{"shields", 1, NULL, 's'},
82 		{"energy", 1, NULL, 'e'},
83 		{NULL, 0, NULL, 0}
84 	};
85 
86 	/* Initialize the GType system first */
87 	g_type_init();
88 
89 	/* Check command line */
90 
91 	/* Create a robot Object */
92 	robot = g_robot_new(1, 1, 1, 0, DEFAULT_ENERGY, DEFAULT_SHIELDS, 0, 0,
93 		NULL, NULL);
94 
95 	g_assert(robot != NULL);
96 
97 	/* And add to to the list of robots */
98 	robots = g_list_append(robots, robot);
99 
100 	while ((opt = getopt_long(argc, argv, "Vhf:s:e:p:", long_opts,
101 				&flag)) != EOF)
102 	{
103 		switch (opt)
104 		{
105 		case 'V':
106 			/* Display version, then quit */
107 			g_printf("%s\n\n", PKGINFO);
108 			g_printf("%s\n\n", COPYRIGHT);
109 			g_printf
110 ("GNU Robots is free software: you can redistribute it and/or modify\n"
111  "it under the terms of the GNU General Public License as published by\n"
112  "the Free Software Foundation, either version 3 of the License, or\n"
113  "(at your option) any later version.\n");
114 			g_printf
115 ("\nGNU Robots is distributed in the hope that it will be useful,\n"
116  "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
117  "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
118  "GNU General Public License for more details.\n");
119 			g_printf
120 ("\nYou should have received a copy of the GNU General Public License\n"
121  "along with GNU Robots.  If not, see <http://www.gnu.org/licenses/>.\n");
122 
123 			exit(0);
124 			break;
125 
126 		case 'h':
127 			/* Display help, then quit. */
128 			usage(argv[0]);
129 			exit(0);
130 			break;
131 
132 		case 'f':
133 			/* Set map file */
134 			main_argv[1] = optarg;	/* pointer assignment */
135 			break;
136 
137 		case 's':
138 			/* Set shields */
139 			robot->shields = (glong) atol(optarg);
140 			break;
141 
142 		case 'e':
143 			/* Set energy */
144 			robot->energy = (glong) atol(optarg);
145 			break;
146 
147 		default:
148 			/* invalid option */
149 			usage(argv[0]);
150 			exit(1);
151 			break;
152 		}						/* switch */
153 	}							/* while */
154 
155 	/* Extra arg is the Scheme file */
156 
157 	if (optind < argc)
158 	{
159 		/* Set Scheme file */
160 		main_argv[2] = argv[optind];	/* pointer assignment */
161 	}
162 
163 	/* Check that files have been given */
164 	if (main_argv[1] == NULL)
165 	{
166 		main_argv[1] = g_malloc(MAX_PATH);
167 		g_strlcpy(main_argv[1], DEFAULT_MAP, MAX_PATH);
168 		g_fprintf(stderr, "map file not specified, trying default: %s\n",
169 			main_argv[1]);
170 	}
171 
172 	/* Check that files exist */
173 	g_strlcpy(maps_path, main_argv[1], MAX_PATH);
174 
175 	if (!is_file_readable(maps_path))
176 	{
177 		g_strlcpy(maps_path, MAPS_PATH, MAX_PATH);
178 		g_strlcat(maps_path, "/", MAX_PATH);
179 		g_strlcat(maps_path, main_argv[1], MAX_PATH);
180 
181 		if (!is_file_readable(maps_path))
182 		{
183 			gchar *env = getenv(MAPS_PATH_ENV);
184 
185 			if (env != NULL)
186 			{
187 				g_strlcpy(maps_path, env, MAX_PATH);
188 				g_strlcat(maps_path, "/", MAX_PATH);
189 				g_strlcat(maps_path, main_argv[1], MAX_PATH);
190 
191 				if (!is_file_readable(maps_path))
192 				{
193 					g_fprintf(stderr,
194 				"%s: %s: game map file does not exist or is not readable\n",
195 						argv[0], main_argv[1]);
196 					exit(1);
197 				}
198 			}
199 			else
200 			{
201 				g_fprintf(stderr,
202 				"%s: %s: game map file does not exist or is not readable\n",
203 					argv[0], main_argv[1]);
204 				exit(1);
205 			}
206 		}
207 	}
208 
209 	main_argv[1] = maps_path;
210 
211 	/* Now the Scheme file */
212 	if (main_argv[2] != NULL)
213 	{
214 		g_strlcpy(scripts_path, main_argv[2], MAX_PATH);
215 
216 		if (!is_file_readable(scripts_path))
217 		{
218 			g_strlcpy(scripts_path, SCRIPTS_PATH, MAX_PATH);
219 			g_strlcat(scripts_path, "/", MAX_PATH);
220 			g_strlcat(scripts_path, main_argv[2], MAX_PATH);
221 
222 			if (!is_file_readable(scripts_path))
223 			{
224 				gchar *env = getenv(SCRIPTS_PATH_ENV);
225 
226 				if (env != NULL)
227 				{
228 					g_strlcpy(scripts_path, env, MAX_PATH);
229 					g_strlcat(scripts_path, "/", MAX_PATH);
230 					g_strlcat(scripts_path, main_argv[2], MAX_PATH);
231 
232 					if (!is_file_readable(scripts_path))
233 					{
234 						g_fprintf(stderr,
235 				"%s: %s: Scheme file does not exist or is not readable\n",
236 							argv[0], main_argv[2]);
237 						exit(1);
238 					}
239 				}
240 				else
241 				{
242 					g_fprintf(stderr,
243 				"%s: %s: Scheme file does not exist or is not readable\n",
244 						argv[0], main_argv[2]);
245 					exit(1);
246 				}
247 			}
248 		}
249 
250 		main_argv[2] = scripts_path;
251 	}
252 	else
253 	{
254 		/* argv[2] can't be NULL as argv[3] may also be NULL */
255 		main_argv[2] = "";
256 	}
257 
258 	/* Start Guile environment.  Does not exit */
259 	g_printf("%s\n", PKGINFO);
260 	g_printf("%s\n", COPYRIGHT);
261 	g_printf("GNU Robots comes with ABSOLUTELY NO WARRANTY\n");
262 	g_printf
263 		("This is free software, and you are welcome to redistribute it\n");
264 	g_printf
265 		("under certain conditions; see the file `COPYING' for details.\n");
266 	g_printf("Loading Guile ... Please wait\n\n");
267 
268 	scm_boot_guile(3, main_argv, main_prog, NULL);
269 
270 	return 0;				/* never gets here, but keeps compiler happy */
271 }
272 
273 /************************************************************************
274  * void main_prog(void *closure, gint argc, gchar *argv[])              *
275  *                                                                      *
276  * The main program code that is executed after Guile starts up. Pass   *
277  * the Scheme program as argv[1] and the map file as argv[2]. The       *
278  * program name is still argv[0].                                       *
279  ************************************************************************/
main_prog(void * closure,gint argc,gchar * argv[])280 void main_prog(void *closure, gint argc, gchar *argv[])
281 {
282 	gchar *map_file = argv[1];
283 	gchar *robot_program = argv[2];
284 	gboolean loading = TRUE;
285 
286 	api_init();
287 
288 	g_printf("Map file: %s\n", map_file);
289 
290 	map = map_new_from_file(map_file, DEFAULT_MAP_ROWS,
291 		DEFAULT_MAP_COLUMNS);
292 
293 	if (map == NULL)
294 	{
295 		exit_nicely();
296 	}
297 
298 	g_thread_init(NULL);
299 	gdk_threads_init();
300 	g_thread_create(callback, &loading, FALSE, NULL);
301 
302 	/* ensure the robot is placed properly */
303 	MAP_SET_OBJECT(map, G_ROBOT_POSITION_Y(robot),
304 		G_ROBOT_POSITION_X(robot), ROBOT);
305 
306 	g_printf("Loading GTK Interface ... Please wait\n\n");
307 	while(loading);
308 
309 	/* Now initialize the rest of the Robot properties */
310 	g_object_set(G_OBJECT(robot),
311 		"user-interface", G_OBJECT(arena), "map", G_OBJECT(map), NULL);
312 
313 	g_signal_connect(G_OBJECT(robot), "death", G_CALLBACK(death), NULL);
314 
315 	if (strlen(robot_program) != 0)
316 	{
317 		/* execute a Scheme file */
318 		g_printf("Robot program: %s\n", robot_program);
319 		scm_c_primitive_load(robot_program);
320 	}
321 	else
322 	{
323 		gchar buf[BUFF_LEN];
324 
325 		g_printf
326 			("Robot program not specified. Entering interactive mode..\n");
327 
328 		while (1)
329 		{
330 			memset(&buf, 0, BUFF_LEN);
331 
332 			ui_cmdwin_get_string(UI_CMDWIN(ui->priv->cmdwin), "guile> ",
333 				buf, BUFF_LEN);
334 
335 			scm_internal_catch(SCM_BOOL_T,
336 				(scm_t_catch_body) scm_c_eval_string, buf,
337 				catch_handler, NULL);
338 		}
339 	}
340 
341 	/* done */
342 	exit_nicely();
343 }
344 
callback(gpointer data)345 gpointer callback(gpointer data)
346 {
347 	gtk_init(0, NULL);
348 
349 	ui = UI_WINDOW(ui_window_new());
350 	ui_window_postinit(ui, map);
351 
352 	arena = UI_ARENA(ui->priv->arena);
353 
354 	/* draw the map */
355 	ui_arena_draw(arena);
356 	ui_arena_update_status(arena, "Welcome to GNU Robots", -1, -1, -1);
357 
358 	*(gboolean*)data = FALSE;
359 
360 	gdk_threads_enter();
361 	gtk_main();
362 	gdk_threads_leave();
363 
364 	return NULL;
365 }
366 
367 /************************************************************************
368  * SCM catch_handler (void *data, SCM tag, SCM throw_args);             *
369  *                                                                      *
370  * Responsible for handling errors                                      *
371  ************************************************************************/
catch_handler(void * data,SCM tag,SCM throw_args)372 SCM catch_handler(void *data, SCM tag, SCM throw_args)
373 {
374 
375 /*
376   gchar *message = "Couldn't get error message\n";
377 
378   if (scm_ilength (throw_args) > 1
379       && SCM_NFALSEP (scm_string_p (SCM_CADR (throw_args))))
380   {
381     message = SCM_STRING_CHARS (SCM_CADR (throw_args));
382   }
383 
384   else if (SCM_NFALSEP (scm_symbol_p (tag)))
385   {
386     message = SCM_SYMBOL_CHARS (tag);
387   }
388 
389   user_interface_update_status (ui, message, -1, -1, -1);
390 */
391 
392 	g_printf("Invalid Instruction\n");
393 
394 	return SCM_BOOL_F;
395 }
396 
death(GRobot * robot)397 void death(GRobot *robot)
398 {
399 	/* We get a ref increment on a signal */
400 	g_object_unref(G_OBJECT(robot));
401 
402 	exit_nicely();
403 }
404 
405 /************************************************************************
406  * void exit_nicely()                                                   *
407  *                                                                      *
408  * A function that allows the program to exit nicely, after freeing all *
409  * memory pointers, etc.                                                *
410  ************************************************************************/
exit_nicely()411 void exit_nicely()
412 {
413 	glong score, energy, shields, shots, units;
414 
415 	/* Stop the UI */
416 	if (ui != NULL)
417 	{
418 		g_object_unref(G_OBJECT(ui));
419 	}
420 
421 	/* Get rid of the map object */
422 	if (map != NULL)
423 	{
424 		g_object_unref(G_OBJECT(map));
425 	}
426 
427 	/* Show statistics */
428 	g_object_get(G_OBJECT(robot),
429 		"shields", &shields,
430 		"energy", &energy,
431 		"units", &units, "shots", &shots, "score", &score, NULL);
432 
433 	g_list_foreach(robots, (GFunc) g_object_unref, NULL);
434 	g_list_free(robots);
435 
436 	g_printf(
437 		"\n-----------------------STATISTICS-----------------------\n");
438 	g_printf("Shields: %ld\n", (shields < 0 ? 0 : shields));
439 	g_printf("Energy: %ld\n", (energy < 0 ? 0 : energy));
440 	g_printf("Units walked: %ld\n", (units < 0 ? 0 : units));
441 	g_printf("Shots: %ld\n", (shots < 0 ? 0 : shots));
442 	g_printf("Score: %ld\n", score);
443 
444 	/* Show results, if any */
445 	if (shields < 1)
446 	{
447 		g_printf("** Robot took too much damage, and died.\n");
448 	}
449 
450 	else if (energy < 1)
451 	{
452 		g_printf("** Robot ran out of energy.\n");
453 	}
454 
455 	/* Quit program */
456 	exit(0);
457 }
458 
459 /************************************************************************
460  * void usage(const gchar *argv0)                                       *
461  *                                                                      *
462  * A function that prints the usage of GNU Robots to the user. Assume   *
463  * text mode for this function. We have not initialized X Windows yet.  *
464  ************************************************************************/
usage(const gchar * argv0)465 void usage(const gchar *argv0)
466 {
467 	g_printf("%s\n", PKGINFO);
468 	g_printf("%s\n", COPYRIGHT);
469 	g_printf
470 	("Game/diversion where you construct a program for a little robot\n");
471 	g_printf
472 	("then set him loose and watch him explore a world on his own.\n\n");
473 
474 	g_printf("Usage: %s [OPTION]... [FILE]\n\n", argv0);
475 	g_printf("  -f, --map-file=FILE    Load map file\n");
476 	g_printf("  -s, --shields=N        Set initial shields to N\n");
477 	g_printf("  -e, --energy=N         Set initial energy to N\n");
478 	g_printf
479 		("  -V, --version          Output version information and exit\n");
480 	g_printf("  -h, --help             Display this help and exit\n");
481 	g_printf("\nNote: FILE refers to a scheme file and %s enters into \n",
482 		argv0);
483 	g_printf("      interactive mode if it is not specified.\n");
484 
485 	g_printf("\nReport bugs to <%s>.\n", PACKAGE_BUGREPORT);
486 }
487 
488 /************************************************************************
489  * gint is_file_readable (const gchar *filename)                        *
490  *                                                                      *
491  * Checks if a file is a readable file. We will use this function as    *
492  * part of a sanity check, before we get anywhere near having to open   *
493  * files. This will save on error checking later on, when we may have   *
494  * already initialized another environment (Curses, X Windows, ...)     *
495  ************************************************************************/
is_file_readable(const gchar * filename)496 gint is_file_readable(const gchar *filename)
497 {
498 	FILE *stream;
499 
500 	stream = fopen(filename, "r");
501 	if (stream == NULL)
502 	{
503 		/* Failed */
504 		return (0);
505 	}
506 
507 	/* Success */
508 	fclose(stream);
509 	return (1);
510 }
511