xref: /original-bsd/games/xroach/xroach.c (revision c3e32dec)
1 /*
2     Xroach - A game of skill.  Try to find the roaches under your windows.
3 
4     Copyright 1991 by J.T. Anderson
5 
6     jta@locus.com
7 
8     This program may be freely distributed provided that all
9     copyright notices are retained.
10 
11     To build:
12       cc -o xroach roach.c -lX11 [-lsocketorwhatever] [-lm] [-l...]
13 
14     Dedicated to Greg McFarlane.   (gregm@otc.otca.oz.au)
15 */
16 #include <X11/Xlib.h>
17 #include <X11/Xutil.h>
18 #include <X11/Xos.h>
19 
20 #include <stdio.h>
21 #include <math.h>
22 #include <signal.h>
23 #include <stdlib.h>
24 
25 char Copyright[] = "Xroach\nCopyright 1991 J.T. Anderson";
26 
27 #include "roachmap.h"
28 
29 typedef unsigned long Pixel;
30 typedef int ErrorHandler();
31 
32 #define SCAMPER_EVENT	(LASTEvent + 1)
33 
34 #if !defined(GRAB_SERVER)
35 #define GRAB_SERVER	0
36 #endif
37 
38 Display *display;
39 int screen;
40 Window rootWin;
41 unsigned int display_width, display_height;
42 int center_x, center_y;
43 GC gc;
44 char *display_name = NULL;
45 Pixel black, white;
46 
47 int done = 0;
48 int eventBlock = 0;
49 int errorVal = 0;
50 
51 typedef struct Roach {
52     RoachMap *rp;
53     int index;
54     float x;
55     float y;
56     int intX;
57     int intY;
58     int hidden;
59     int turnLeft;
60     int steps;
61 } Roach;
62 
63 Roach *roaches;
64 int maxRoaches = 10;
65 int curRoaches = 0;
66 float roachSpeed = 20.0;
67 
68 Region rootVisible = NULL;
69 
70 void Usage();
71 void SigHandler();
72 void AddRoach();
73 void MoveRoach();
74 void DrawRoaches();
75 void CoverRoot();
76 int CalcRootVisible();
77 int MarkHiddenRoaches();
78 Pixel AllocNamedColor();
79 
80 void
81 main(ac, av)
82 int ac;
83 char *av[];
84 {
85     XGCValues xgcv;
86     int ax;
87     char *arg;
88     RoachMap *rp;
89     int rx;
90     float angle;
91     XEvent ev;
92     char *roachColor = "black";
93     int nVis;
94     int needCalc;
95 
96     /*
97        Process command line options.
98     */
99     for (ax=1; ax<ac; ax++) {
100 	arg = av[ax];
101 	if (strcmp(arg, "-display") == 0) {
102 	    display_name = av[++ax];
103 	}
104 	else if (strcmp(arg, "-rc") == 0) {
105 	    roachColor = av[++ax];
106 	}
107 	else if (strcmp(arg, "-speed") == 0) {
108 	    roachSpeed = atof(av[++ax]);
109 	}
110 	else if (strcmp(arg, "-roaches") == 0) {
111 	    maxRoaches = strtol(av[++ax], (char **)NULL, 0);
112 	}
113 	else {
114 	    Usage();
115 	}
116     }
117 
118     srand((int)time((long *)NULL));
119 
120     /*
121        Catch some signals so we can erase any visible roaches.
122     */
123     signal(SIGKILL, SigHandler);
124     signal(SIGINT, SigHandler);
125     signal(SIGTERM, SigHandler);
126     signal(SIGHUP, SigHandler);
127 
128     display = XOpenDisplay(display_name);
129     if (display == NULL) {
130 	if (display_name == NULL) display_name = getenv("DISPLAY");
131 	(void) fprintf(stderr, "%s: cannot connect to X server %s\n", av[0],
132 	    display_name ? display_name : "(default)");
133 	exit(1);
134     }
135 
136     screen = DefaultScreen(display);
137     rootWin = RootWindow(display, screen);
138     black = BlackPixel(display, screen);
139     white = WhitePixel(display, screen);
140 
141     display_width = DisplayWidth(display, screen);
142     display_height = DisplayHeight(display, screen);
143     center_x = display_width / 2;
144     center_y = display_height / 2;
145 
146     /*
147        Create roach pixmaps at several orientations.
148     */
149     for (ax=0; ax<360; ax+=ROACH_ANGLE) {
150 	rx = ax / ROACH_ANGLE;
151 	angle = rx * 0.261799387799;
152 	rp = &roachPix[rx];
153 	rp->pixmap = XCreateBitmapFromData(display, rootWin,
154 	    rp->roachBits, rp->width, rp->height);
155 	rp->sine = sin(angle);
156 	rp->cosine = cos(angle);
157     }
158 
159     roaches = (Roach *)malloc(sizeof(Roach) * maxRoaches);
160 
161     gc = XCreateGC(display, rootWin, 0L, &xgcv);
162     XSetForeground(display, gc, AllocNamedColor(roachColor, black));
163     XSetFillStyle(display, gc, FillStippled);
164 
165     while (curRoaches < maxRoaches)
166 	AddRoach();
167 
168     XSelectInput(display, rootWin, ExposureMask | SubstructureNotifyMask);
169 
170     needCalc = 1;
171     while (!done) {
172 	if (XPending(display)) {
173 	    XNextEvent(display, &ev);
174 	}
175 	else {
176 	    if (needCalc) {
177 		needCalc = CalcRootVisible();
178 	    }
179 	    nVis = MarkHiddenRoaches();
180 	    if (nVis) {
181 		ev.type = SCAMPER_EVENT;
182 	    }
183 	    else {
184 		DrawRoaches();
185 		eventBlock = 1;
186 		XNextEvent(display, &ev);
187 		eventBlock = 0;
188 	    }
189 	}
190 
191 	switch (ev.type) {
192 
193 	    case SCAMPER_EVENT:
194 		for (rx=0; rx<curRoaches; rx++) {
195 		    if (!roaches[rx].hidden)
196 			MoveRoach(rx);
197 		}
198 		DrawRoaches();
199 		XSync(display, False);
200 		break;
201 
202 	    case Expose:
203 	    case MapNotify:
204 	    case UnmapNotify:
205 	    case ConfigureNotify:
206 		needCalc = 1;
207 		break;
208 
209 	}
210     }
211 
212     CoverRoot();
213 
214     XCloseDisplay(display);
215 }
216 
217 #define USEPRT(msg) fprintf(stderr, msg)
218 
219 void
220 Usage()
221 {
222     USEPRT("Usage: xroach [options]\n\n");
223     USEPRT("Options:\n");
224     USEPRT("       -display displayname\n");
225     USEPRT("       -rc      roachcolor\n");
226     USEPRT("       -roaches numroaches\n");
227     USEPRT("       -speed   roachspeed\n");
228 
229     exit(1);
230 }
231 
232 void
233 SigHandler()
234 {
235 
236     /*
237        If we are blocked, no roaches are visible and we can just bail
238        out.  If we are not blocked, then let the main procedure clean
239        up the root window.
240     */
241     if (eventBlock) {
242 	XCloseDisplay(display);
243 	exit(0);
244     }
245     else {
246 	done = 1;
247     }
248 }
249 
250 /*
251    Generate random integer between 0 and maxVal-1.
252 */
253 int
254 RandInt(maxVal)
255 int maxVal;
256 {
257 	return rand() % maxVal;
258 }
259 
260 /*
261    Check for roach completely in specified rectangle.
262 */
263 int
264 RoachInRect(roach, rx, ry, x, y, width, height)
265 Roach *roach;
266 int rx;
267 int ry;
268 int x;
269 int y;
270 unsigned int width;
271 unsigned int height;
272 {
273     if (rx < x) return 0;
274     if ((rx + roach->rp->width) > (x + width)) return 0;
275     if (ry < y) return 0;
276     if ((ry + roach->rp->height) > (y + height)) return 0;
277 
278     return 1;
279 }
280 
281 /*
282    Check for roach overlapping specified rectangle.
283 */
284 int
285 RoachOverRect(roach, rx, ry, x, y, width, height)
286 Roach *roach;
287 int rx;
288 int ry;
289 int x;
290 int y;
291 unsigned int width;
292 unsigned int height;
293 {
294     if (rx >= (x + width)) return 0;
295     if ((rx + roach->rp->width) <= x) return 0;
296     if (ry >= (y + height)) return 0;
297     if ((ry + roach->rp->height) <= y) return 0;
298 
299     return 1;
300 }
301 
302 /*
303    Give birth to a roach.
304 */
305 void
306 AddRoach()
307 {
308     Roach *r;
309 
310     if (curRoaches < maxRoaches) {
311 	r = &roaches[curRoaches++];
312 	r->index = RandInt(ROACH_HEADINGS);
313 	r->rp = &roachPix[r->index];
314 	r->x = RandInt(display_width - r->rp->width);
315 	r->y = RandInt(display_height - r->rp->height);
316 	r->intX = -1;
317 	r->intY = -1;
318 	r->hidden = 0;
319 	r->steps = RandInt(200);
320 	r->turnLeft = RandInt(100) >= 50;
321     }
322 }
323 
324 /*
325    Turn a roach.
326 */
327 void
328 TurnRoach(roach)
329 Roach *roach;
330 {
331     if (roach->index != (roach->rp - roachPix)) return;
332 
333     if (roach->turnLeft) {
334 	roach->index += (RandInt(30) / 10) + 1;
335 	if (roach->index >= ROACH_HEADINGS)
336 	    roach->index -= ROACH_HEADINGS;
337     }
338     else {
339 	roach->index -= (RandInt(30) / 10) + 1;
340 	if (roach->index < 0)
341 	    roach->index += ROACH_HEADINGS;
342     }
343 }
344 
345 /*
346    Move a roach.
347 */
348 void
349 MoveRoach(rx)
350 int rx;
351 {
352     Roach *roach;
353     Roach *r2;
354     float newX;
355     float newY;
356     int ii;
357 
358     roach = &roaches[rx];
359     newX = roach->x + (roachSpeed * roach->rp->cosine);
360     newY = roach->y - (roachSpeed * roach->rp->sine);
361 
362     if (RoachInRect(roach, (int)newX, (int)newY,
363 			    0, 0, display_width, display_height)) {
364 
365 	roach->x = newX;
366 	roach->y = newY;
367 
368 	if (roach->steps-- <= 0) {
369 	    TurnRoach(roach);
370 	    roach->steps = RandInt(200);
371 	}
372 
373 	for (ii=rx+1; ii<curRoaches; ii++) {
374 	    r2 = &roaches[ii];
375 	    if (RoachOverRect(roach, (int)newX, (int)newY,
376 		r2->intX, r2->intY, r2->rp->width, r2->rp->height)) {
377 
378 		TurnRoach(roach);
379 	    }
380 	}
381     }
382     else {
383 	TurnRoach(roach);
384     }
385 }
386 
387 /*
388    Draw all roaches.
389 */
390 void
391 DrawRoaches()
392 {
393     Roach *roach;
394     int rx;
395 
396     for (rx=0; rx<curRoaches; rx++) {
397 	roach = &roaches[rx];
398 
399 	if (roach->intX >= 0) {
400 	    XClearArea(display, rootWin, roach->intX, roach->intY,
401 		roach->rp->width, roach->rp->height, False);
402 	}
403     }
404 
405     for (rx=0; rx<curRoaches; rx++) {
406 	roach = &roaches[rx];
407 
408 	if (!roach->hidden) {
409 	    roach->intX = roach->x;
410 	    roach->intY = roach->y;
411 	    roach->rp = &roachPix[roach->index];
412 
413 	    XSetStipple(display, gc, roach->rp->pixmap);
414 	    XSetTSOrigin(display, gc, roach->intX, roach->intY);
415 	    XFillRectangle(display, rootWin, gc,
416 		roach->intX, roach->intY, roach->rp->width, roach->rp->height);
417 	}
418 	else {
419 	    roach->intX = -1;
420 	}
421     }
422 }
423 
424 /*
425    Cover root window to erase roaches.
426 */
427 void
428 CoverRoot()
429 {
430     XSetWindowAttributes xswa;
431     long wamask;
432     Window roachWin;
433 
434     xswa.background_pixmap = ParentRelative;
435     xswa.override_redirect = True;
436     wamask = CWBackPixmap | CWOverrideRedirect;
437     roachWin = XCreateWindow(display, rootWin, 0, 0,
438 		    display_width, display_height, 0, CopyFromParent,
439 		    InputOutput, CopyFromParent, wamask, &xswa);
440     XLowerWindow(display, roachWin);
441     XMapWindow(display, roachWin);
442     XFlush(display);
443 }
444 
445 #if !GRAB_SERVER
446 
447 int
448 RoachErrors(dpy, err)
449 Display *dpy;
450 XErrorEvent *err;
451 {
452     errorVal = err->error_code;
453 
454     return 0;
455 }
456 
457 #endif /* GRAB_SERVER */
458 
459 /*
460    Calculate Visible region of root window.
461 */
462 int
463 CalcRootVisible()
464 {
465     Region covered;
466     Region visible;
467     Window *children;
468     int nChildren;
469     Window dummy;
470     XWindowAttributes wa;
471     int wx;
472     XRectangle rect;
473     int winX, winY;
474     unsigned int winHeight, winWidth;
475     unsigned int borderWidth;
476     unsigned int depth;
477 
478     /*
479        If we don't grab the server, the XGetWindowAttribute or XGetGeometry
480        calls can abort us.  On the other hand, the server grabs can make for
481        some annoying delays.
482     */
483 #if GRAB_SERVER
484     XGrabServer(display);
485 #else
486     XSetErrorHandler(RoachErrors);
487 #endif
488 
489     /*
490        Get children of root.
491     */
492     XQueryTree(display, rootWin, &dummy, &dummy, &children, &nChildren);
493 
494     /*
495        For each mapped child, add the window rectangle to the covered
496        region.
497     */
498     covered = XCreateRegion();
499     for (wx=0; wx<nChildren; wx++) {
500 	if (XEventsQueued(display, QueuedAlready)) return 1;
501 	errorVal = 0;
502 	XGetWindowAttributes(display, children[wx], &wa);
503 	if (errorVal) continue;
504 	if (wa.map_state == IsViewable) {
505 	    XGetGeometry(display, children[wx], &dummy, &winX, &winY,
506 		&winWidth, &winHeight, &borderWidth, &depth);
507 	    if (errorVal) continue;
508 	    rect.x = winX;
509 	    rect.y = winY;
510 	    rect.width = winWidth + (borderWidth * 2);
511 	    rect.height = winHeight + (borderWidth * 2);
512 	    XUnionRectWithRegion(&rect, covered, covered);
513 	}
514     }
515     XFree((char *)children);
516 
517 #if GRAB_SERVER
518     XUngrabServer(display);
519 #else
520     XSetErrorHandler((ErrorHandler *)NULL);
521 #endif
522 
523     /*
524        Subtract the covered region from the root window region.
525     */
526     visible = XCreateRegion();
527     rect.x = 0;
528     rect.y = 0;
529     rect.width = display_width;
530     rect.height = display_height;
531     XUnionRectWithRegion(&rect, visible, visible);
532     XSubtractRegion(visible, covered, visible);
533     XDestroyRegion(covered);
534 
535     /*
536        Save visible region globally.
537     */
538     if (rootVisible)
539 	XDestroyRegion(rootVisible);
540     rootVisible = visible;
541 
542 
543     /*
544        Mark all roaches visible.
545     */
546     for (wx=0; wx<curRoaches; wx++)
547 	roaches[wx].hidden = 0;
548 
549     return 0;
550 }
551 
552 /*
553    Mark hidden roaches.
554 */
555 int
556 MarkHiddenRoaches()
557 {
558     int rx;
559     Roach *r;
560     int nVisible;
561 
562     nVisible = 0;
563     for (rx=0; rx<curRoaches; rx++) {
564 	r = &roaches[rx];
565 
566 	if (!r->hidden) {
567 	    if (r->intX > 0 && XRectInRegion(rootVisible, r->intX, r->intY,
568 			    r->rp->width, r->rp->height) == RectangleOut) {
569 		r->hidden = 1;
570 	    }
571 	    else {
572 		nVisible++;
573 	    }
574 	}
575     }
576 
577     return nVisible;
578 }
579 
580 /*
581    Allocate a color by name.
582 */
583 Pixel
584 AllocNamedColor(colorName, dfltPix)
585 char *colorName;
586 Pixel dfltPix;
587 {
588 	Pixel pix;
589 	XColor scrncolor;
590 	XColor exactcolor;
591 
592 	if (XAllocNamedColor(display, DefaultColormap(display, screen),
593 		colorName, &scrncolor, &exactcolor)) {
594 		pix = scrncolor.pixel;
595 	}
596 	else {
597 		pix = dfltPix;
598 	}
599 
600 	return pix;
601 }
602 
603