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