1 #include <stdio.h>
2 #include <pwd.h>
3 #include <X11/Xlib.h>
4 #include <X11/Xresource.h>
5 #include <stdlib.h>
6 #include <unistd.h>
7 #include <string.h>
8 #include <ctype.h>
9
10 #include "externs.h"
11 #include "globals.h"
12 #include "options.h"
13 #include "errors.h"
14 #include "display.h"
15 #include "score.h"
16 #include "www.h"
17
18 /* locally-defined functions */
19 static short VerifyScore(short optlevel);
20 static short GameLoop(void);
21 static short GetGamePassword(void);
22 static void Error(short);
23 static void Usage(void);
24 static short CheckCommandLine(int *, char **);
25 static char *FixUsername(char *name);
26
27 /* exported globals */
28 Boolean scoring = _true_;
29 short level, packets, savepack, moves, pushes, rows, cols;
30 unsigned short scorelevel, scoremoves, scorepushes;
31 POS ppos;
32 char map[MAXROW + 1][MAXCOL + 1];
33 char *username = 0, *progname = 0, *bitpath = 0;
34 char *optfile = 0;
35 XrmDatabase rdb;
36 Boolean ownColormap = _false_, datemode = _false_, headermode = _false_;
37
38 static short optlevel = 0, userlevel = 0;
39 static short line1 = 0, line2 = 0;
40 static Boolean opt_show_score = _false_, opt_make_score = _false_,
41 optrestore = _false_, owner = _false_,
42 opt_verify = _false_, opt_partial_score = _false_,
43 opt_user_level = _false_;
44 static struct passwd *pwd;
45
46
47 static int movelen;
48 /* Length of the verified move sequence waiting on stdin if -v is used */
49
main(int argc,char ** argv)50 void main(int argc, char **argv)
51 {
52 short ret = 0;
53
54 DEBUG_SERVER("starting");
55
56 scorelevel = 0;
57 moves = pushes = packets = savepack = 0;
58
59 /* make the program name be what it is invoked with */
60 progname = strrchr(argv[0], '/');
61 if (progname == NULL)
62 progname = argv[0];
63 else
64 progname++;
65
66 /* Parse the command line */
67 ret = CheckCommandLine(&argc, argv);
68
69 /* find out who is playing us. (pwd will be kept around in case we need to
70 * build the Xresources stuff later.
71 */
72 pwd = getpwuid(getuid());
73 if (pwd == NULL)
74 /* we MUST be being played by somebody, sorry */
75 ret = E_NOUSER;
76 else {
77 /* find out who we are. */
78 #if !WWW
79 if (!username) username = FixUsername(pwd->pw_name);
80 /* else we already got a fixed username from the -v option */
81 #else
82 /* If running in Web mode, append HERE to the username. */
83 if (!username) {
84 /* username might have already been set by the X resource mechanism */
85 char *here = HERE;
86 char *namebuf = (char *)malloc(strlen(pwd->pw_name) + strlen(here) + 1);
87 strcpy(namebuf, pwd->pw_name);
88 strcat(namebuf, here);
89 username = FixUsername(namebuf);
90 free(namebuf);
91 } else {
92 username = FixUsername(username);
93 }
94 #endif
95 /* see if we are the owner */
96 owner = (strcmp(username, OWNER) == 0) ? _true_ : _false_;
97 if (ret == 0) {
98 if (opt_show_score) {
99 DEBUG_SERVER("sending score file");
100 ret = OutputScore(optlevel);
101 } else if (opt_verify) {
102 DEBUG_SERVER("verifying score");
103 ret = VerifyScore(optlevel);
104 } else if (opt_partial_score) {
105 ret = OutputScoreLines(line1, line2);
106 } else if (opt_make_score) {
107 if (owner) {
108 /* make sure of that, shall we? */
109 ret = GetGamePassword();
110 if (ret == 0)
111 ret = MakeNewScore(optfile);
112 } else
113 /* sorry, BAD owner */
114 ret = E_NOSUPER;
115 } else if (optrestore) {
116 ret = RestoreGame();
117 } else if (opt_user_level) {
118 ret = GetUserLevel(&userlevel);
119 if (ret == 0) {
120 printf("Level: %d\n", userlevel);
121 ret = E_ENDGAME;
122 }
123 } else {
124 ret = GetUserLevel(&userlevel);
125 if (ret == 0) {
126 if (optlevel > 0) {
127 #if !ANYLEVEL
128 if (userlevel < optlevel) {
129 if (owner) {
130 /* owners can play any level (but not score),
131 * which is useful for testing out new boards.
132 */
133 level = optlevel;
134 scoring = _false_;
135 } else {
136 ret = E_LEVELTOOHIGH;
137 }
138 } else
139 #endif
140 level = optlevel;
141 } else
142 level = userlevel;
143 }
144 }
145 }
146 }
147 if (ret == 0) {
148 /* play till we drop, then nuke the good stuff */
149 ret = GameLoop();
150 DestroyDisplay();
151 XCloseDisplay(dpy); /* factored this out to allow re-init */
152 display_alloc = _false_;
153 }
154 /* always report here since the game returns E_ENDGAME when the user quits.
155 * Sigh.. it would be so much easier to just do it right.
156 */
157 Error(ret);
158
159 /* exit with whatever status we ended with */
160 switch(ret)
161 {
162 case E_ENDGAME:
163 case E_SAVED:
164 ret = 0; /* normal exits */
165 break;
166 }
167 DEBUG_SERVER("ending");
168 exit(ret);
169 }
170
171 /* FixUsername makes sure that the username contains no spaces or
172 unprintable characters, and is less than MAXUSERNAME characters
173 long. */
FixUsername(char * name)174 static char *FixUsername(char *name)
175 {
176 char namebuf[MAXUSERNAME];
177 char *c = namebuf;
178 strncpy(namebuf, name, MAXUSERNAME);
179 namebuf[MAXUSERNAME-1] = 0;
180 while (*c) {
181 if (!isprint(*c) || *c == ' ' || *c == ',') *c = '_';
182 c++;
183 }
184 return strdup(namebuf);
185 }
186
mode_selected()187 static Boolean mode_selected()
188 {
189 return (opt_show_score || opt_make_score || optrestore || (optlevel > 0) ||
190 opt_verify || opt_partial_score || opt_user_level)
191 ? _true_ : _false_;
192 }
193
194 /* Oh boy, the fun stuff.. Follow along boys and girls as we parse the command
195 * line up into little bitty pieces and merge in all the xdefaults that we
196 * need.
197 *
198 * May set "username" to some value.
199 */
CheckCommandLine(int * argcP,char ** argv)200 short CheckCommandLine(int *argcP, char **argv)
201 {
202 XrmDatabase command = NULL, temp = NULL;
203 char *res;
204 char buf[1024];
205 int option;
206
207 /* let's do this the sensible way, Command line first! */
208 /* we will also OPEN the display here, though we won't do anything with it */
209 XrmInitialize();
210
211 /* build an XrmDB from the command line based on the options (options.h) */
212 XrmParseCommand(&command, options, sizeof(options)/sizeof(*options),
213 progname, argcP, argv);
214
215 /* okay, we now have the X command line options parsed, we might as well
216 * make sure we need to go further before we do. These command line options
217 * are NOT caught by XrmParseCommand(), so we need to do them ourselves.
218 * Remember, they are all exclusive of one another.
219 */
220 for(option = 1; option < *argcP; option++) {
221 if (argv[option][0] == '-') {
222 char *optarg;
223 switch(argv[option][1]) {
224 case 's':
225 if (mode_selected()) return E_USAGE;
226 opt_show_score = _true_;
227 optarg = &argv[option][2];
228 if (!isdigit(*optarg) && argv[option+1] && isdigit(argv[option+1][0]))
229 {
230 optarg = &argv[option+1][0];
231 option++;
232 }
233 optlevel = atoi(optarg);
234 break;
235 case 'c':
236 if (mode_selected()) return E_USAGE;
237 optfile = 0;
238 if (argv[option][2] != 0)
239 optfile = &argv[option][2];
240 else if (argv[option+1] && argv[option + 1][0] != '-') {
241 optfile = &argv[option + 1][0];
242 option++;
243 }
244 opt_make_score = _true_;
245 break;
246 case 'C':
247 ownColormap = _true_;
248 break;
249 case 'r':
250 if (mode_selected()) return E_USAGE;
251 optrestore = _true_;
252 break;
253 case 'v':
254 if (mode_selected()) return E_USAGE;
255 option++;
256 optlevel = atoi(argv[option++]);
257 if (!optlevel || !argv[option]) return E_USAGE;
258 username = FixUsername(argv[option++]);
259 if (!argv[option]) return E_USAGE;
260 movelen = atoi(argv[option++]);
261 if (!movelen) return E_USAGE;
262 opt_verify = _true_;
263 break;
264 case 'L':
265 if (mode_selected()) return E_USAGE;
266 option++;
267 if (!argv[option]) return E_USAGE;
268 line1 = atoi(argv[option++]);
269 if (!argv[option]) return E_USAGE;
270 line2 = atoi(argv[option]);
271 if (line1 > line2) return E_USAGE;
272 opt_partial_score = _true_;
273 break;
274 case 'u':
275 option++;
276 if (!argv[option]) return E_USAGE;
277 username = FixUsername(argv[option]);
278 break;
279 case 'U':
280 if (mode_selected()) return E_USAGE;
281 opt_user_level = _true_;
282 break;
283 case 'D':
284 datemode = _true_;
285 break;
286 case 'H':
287 headermode = _true_;
288 break;
289 default:
290 if (mode_selected()) return E_USAGE;
291 optlevel = atoi(argv[option]+1);
292 if (optlevel == 0) return E_USAGE;
293 break;
294 }
295 } else
296 /* found an option that didn't begin with a - (oops) */
297 return E_USAGE;
298 }
299
300 if (opt_partial_score || opt_show_score || opt_make_score || opt_verify ||
301 opt_user_level)
302 return 0; /* Don't mess with X any more */
303 /* okay.. NOW, find out what display we are currently attached to. This
304 * allows us to put the display on another machine
305 */
306 res = GetDatabaseResource(command, "display");
307
308 /* open up the display */
309 dpy = XOpenDisplay(res);
310 if (dpy == (Display *)NULL)
311 return E_NODISPLAY;
312 display_alloc = _true_;
313
314 /* okay, we have a display, now we can get the std xdefaults and stuff */
315 res = XResourceManagerString(dpy);
316 if (res != NULL)
317 /* try to get it off the server first (ya gotta love R4) */
318 rdb = XrmGetStringDatabase(res);
319 else {
320 /* can't get it from the server, let's do it the slow way */
321 /* try HOME first in case you have people sharing accounts :) */
322 res = getenv("HOME");
323 if (res != NULL)
324 strcpy(buf, res);
325 else
326 /* no HOME, let's try and make one from the pwd (whee) */
327 strcpy(buf, pwd->pw_dir);
328 strcat(buf, "/.Xdefaults");
329 rdb = XrmGetFileDatabase(buf);
330 }
331
332 /* let's merge in the X environment */
333 res = getenv("XENVIRONMENT");
334 if (res != NULL) {
335 temp = XrmGetFileDatabase(res);
336 XrmMergeDatabases(temp, &rdb);
337 }
338
339 /* now merge in the rest of the X command line options! */
340 XrmMergeDatabases(command, &rdb);
341
342 if (!username) username = GetResource("username");
343 return 0;
344 }
345
346 /* Read a move sequence from stdin in a newly-allocated string. */
ReadMoveSeq()347 static char *ReadMoveSeq()
348 {
349 char *moveseq = (char *)malloc(movelen);
350 int ch = 0;
351 while (ch < movelen) {
352 int n = read(0, moveseq + ch, movelen - ch); /* read from stdin */
353 if (n <= 0) { perror("Move sequence"); return 0; }
354 ch += n;
355 }
356 return moveseq;
357 }
358
VerifyScore(short optlevel)359 short VerifyScore(short optlevel)
360 {
361 short ret;
362 char *moveseq = ReadMoveSeq();
363 if (!moveseq) { return E_WRITESCORE; }
364 level = optlevel;
365 ret = ReadScreen();
366 if (ret) return ret;
367 if (Verify(movelen, moveseq)) {
368 ret = Score(_false_);
369 scorelevel = 0; /* don't score again */
370 if (ret == 0) ret = E_ENDGAME;
371 } else {
372 ret = E_WRITESCORE;
373 }
374 free(moveseq);
375 return ret;
376 }
377
378 /* we just sit here and keep playing level after level after level after .. */
GameLoop(void)379 static short GameLoop(void)
380 {
381 short ret = 0;
382
383 /* make sure X is all set up and ready for us */
384 ret = InitX();
385 if (ret == E_NOCOLOR && !ownColormap) {
386 DestroyDisplay();
387 ownColormap = _true_;
388 fprintf(stderr,
389 "xsokoban: Couldn't allocate enough colors, trying own colormap\n");
390 ret = InitX();
391 }
392
393 if (ret != 0) return ret;
394
395 /* get where we are starting from */
396 if (!optrestore) ret = ReadScreen();
397
398 /* until we quit or get an error, just keep on going. */
399 while(ret == 0) {
400 ret = Play();
401 if ((scorelevel > 0) && scoring) {
402 int ret2;
403 ret2 = Score(_false_);
404 Error(ret2);
405 scorelevel = 0;
406 }
407 if (ret == 0 || ret == E_ABORTLEVEL) {
408 short newlev = 0;
409 short ret2;
410 ret2 = DisplayScores(&newlev);
411 if (ret2 == 0) {
412 if (newlev > 0 &&
413 #if !ANYLEVEL
414 newlev <= userlevel &&
415 #endif
416 1) {
417 level = newlev;
418 } else {
419 if (ret == 0) level++;
420 }
421 ret = 0;
422 } else {
423 ret = ret2;
424 }
425
426 }
427 if (ret == 0) {
428 moves = pushes = packets = savepack = 0;
429 ret = ReadScreen();
430 }
431 }
432 return ret;
433 }
434
435 /* Does this really need a comment :) */
GetGamePassword(void)436 static short GetGamePassword(void)
437 {
438 return ((strcmp(getpass("Password: "), PASSWORD) == 0) ? 0 : E_ILLPASSWORD);
439 }
440
441 /* display the correct error message based on the error number given us.
442 * There are 2 special cases, E_ENDGAME (in which case we don't WANT a
443 * silly error message cause it's not really an error, and E_USAGE, in which
444 * case we want to give a really nice list of all the legal options.
445 */
Error(short err)446 static void Error(short err)
447 {
448 switch(err) {
449 case E_FOPENSCREEN:
450 case E_PLAYPOS1:
451 case E_ILLCHAR:
452 case E_PLAYPOS2:
453 case E_TOMUCHROWS:
454 case E_TOMUCHCOLS:
455 case E_NOUSER:
456 case E_FOPENSAVE:
457 case E_WRITESAVE:
458 case E_STATSAVE:
459 case E_READSAVE:
460 case E_ALTERSAVE:
461 case E_SAVED:
462 case E_TOMUCHSE:
463 case E_FOPENSCORE:
464 case E_READSCORE:
465 case E_WRITESCORE:
466 case E_USAGE:
467 case E_ILLPASSWORD:
468 case E_LEVELTOOHIGH:
469 case E_NOSUPER:
470 case E_NOSAVEFILE:
471 case E_NOBITMAP:
472 case E_NODISPLAY:
473 case E_NOFONT:
474 case E_NOMEM:
475 case E_NOCOLOR:
476 fprintf(stderr, "%s: %s\n", progname, errmess[err]);
477 if (err == E_USAGE)
478 Usage();
479 break;
480 default:
481 if (err != E_ENDGAME && err != E_ABORTLEVEL)
482 fprintf(stderr, "%s: %s\n", progname, errmess[0]);
483 break;
484 }
485 }
486
487 /* this simply prints out the usage string nicely. */
Usage(void)488 static void Usage(void)
489 {
490 short i;
491
492 fprintf(stderr, USAGESTR, progname);
493 for (i = 0; usages[i] != NULL; i++)
494 fprintf(stderr, "%s", usages[i]);
495 }
496