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
main(ac,av)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
Usage()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
SigHandler()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
RandInt(maxVal)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
RoachInRect(roach,rx,ry,x,y,width,height)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
RoachOverRect(roach,rx,ry,x,y,width,height)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
AddRoach()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
TurnRoach(roach)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
MoveRoach(rx)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
DrawRoaches()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
CoverRoot()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
RoachErrors(dpy,err)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
CalcRootVisible()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
MarkHiddenRoaches()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
AllocNamedColor(colorName,dfltPix)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