xref: /csrg-svn/games/xroach/xroach.c (revision 61144)
161126Selan /*
261126Selan     Xroach - A game of skill.  Try to find the roaches under your windows.
361126Selan 
461126Selan     Copyright 1991 by J.T. Anderson
561126Selan 
661126Selan     jta@locus.com
761126Selan 
861126Selan     This program may be freely distributed provided that all
961126Selan     copyright notices are retained.
1061126Selan 
1161126Selan     To build:
1261126Selan       cc -o xroach roach.c -lX11 [-lsocketorwhatever] [-lm] [-l...]
1361126Selan 
1461126Selan     Dedicated to Greg McFarlane.   (gregm@otc.otca.oz.au)
1561126Selan */
1661126Selan #include <X11/Xlib.h>
1761126Selan #include <X11/Xutil.h>
1861126Selan #include <X11/Xos.h>
1961126Selan 
2061126Selan #include <stdio.h>
2161126Selan #include <math.h>
2261126Selan #include <signal.h>
2361126Selan #include <stdlib.h>
2461126Selan 
2561126Selan char Copyright[] = "Xroach\nCopyright 1991 J.T. Anderson";
2661126Selan 
2761126Selan #include "roachmap.h"
2861126Selan 
2961126Selan typedef unsigned long Pixel;
3061126Selan typedef int ErrorHandler();
3161126Selan 
3261126Selan #define SCAMPER_EVENT	(LASTEvent + 1)
3361126Selan 
3461126Selan #if !defined(GRAB_SERVER)
3561126Selan #define GRAB_SERVER	0
3661126Selan #endif
3761126Selan 
3861126Selan Display *display;
3961126Selan int screen;
4061126Selan Window rootWin;
4161126Selan unsigned int display_width, display_height;
4261126Selan int center_x, center_y;
4361126Selan GC gc;
4461126Selan char *display_name = NULL;
4561126Selan Pixel black, white;
4661126Selan 
4761126Selan int done = 0;
4861126Selan int eventBlock = 0;
4961126Selan int errorVal = 0;
5061126Selan 
5161126Selan typedef struct Roach {
5261126Selan     RoachMap *rp;
5361126Selan     int index;
5461126Selan     float x;
5561126Selan     float y;
5661126Selan     int intX;
5761126Selan     int intY;
5861126Selan     int hidden;
5961126Selan     int turnLeft;
6061126Selan     int steps;
6161126Selan } Roach;
6261126Selan 
6361126Selan Roach *roaches;
6461126Selan int maxRoaches = 10;
6561126Selan int curRoaches = 0;
6661126Selan float roachSpeed = 20.0;
6761126Selan 
6861126Selan Region rootVisible = NULL;
6961126Selan 
7061126Selan void Usage();
7161126Selan void SigHandler();
7261126Selan void AddRoach();
7361126Selan void MoveRoach();
7461126Selan void DrawRoaches();
7561126Selan void CoverRoot();
7661126Selan int CalcRootVisible();
7761126Selan int MarkHiddenRoaches();
7861126Selan Pixel AllocNamedColor();
7961126Selan 
8061126Selan void
main(ac,av)8161126Selan main(ac, av)
8261126Selan int ac;
8361126Selan char *av[];
8461126Selan {
8561126Selan     XGCValues xgcv;
8661126Selan     int ax;
8761126Selan     char *arg;
8861126Selan     RoachMap *rp;
8961126Selan     int rx;
9061126Selan     float angle;
9161126Selan     XEvent ev;
9261126Selan     char *roachColor = "black";
9361126Selan     int nVis;
9461126Selan     int needCalc;
9561126Selan 
9661126Selan     /*
9761126Selan        Process command line options.
9861126Selan     */
9961126Selan     for (ax=1; ax<ac; ax++) {
10061126Selan 	arg = av[ax];
10161126Selan 	if (strcmp(arg, "-display") == 0) {
10261126Selan 	    display_name = av[++ax];
10361126Selan 	}
10461126Selan 	else if (strcmp(arg, "-rc") == 0) {
10561126Selan 	    roachColor = av[++ax];
10661126Selan 	}
10761126Selan 	else if (strcmp(arg, "-speed") == 0) {
10861126Selan 	    roachSpeed = atof(av[++ax]);
10961126Selan 	}
11061126Selan 	else if (strcmp(arg, "-roaches") == 0) {
11161126Selan 	    maxRoaches = strtol(av[++ax], (char **)NULL, 0);
11261126Selan 	}
11361126Selan 	else {
11461126Selan 	    Usage();
11561126Selan 	}
11661126Selan     }
11761126Selan 
11861126Selan     srand((int)time((long *)NULL));
11961126Selan 
12061126Selan     /*
12161126Selan        Catch some signals so we can erase any visible roaches.
12261126Selan     */
12361126Selan     signal(SIGKILL, SigHandler);
12461126Selan     signal(SIGINT, SigHandler);
12561126Selan     signal(SIGTERM, SigHandler);
12661126Selan     signal(SIGHUP, SigHandler);
12761126Selan 
12861126Selan     display = XOpenDisplay(display_name);
12961126Selan     if (display == NULL) {
13061126Selan 	if (display_name == NULL) display_name = getenv("DISPLAY");
13161126Selan 	(void) fprintf(stderr, "%s: cannot connect to X server %s\n", av[0],
13261126Selan 	    display_name ? display_name : "(default)");
13361126Selan 	exit(1);
13461126Selan     }
13561126Selan 
13661126Selan     screen = DefaultScreen(display);
13761126Selan     rootWin = RootWindow(display, screen);
13861126Selan     black = BlackPixel(display, screen);
13961126Selan     white = WhitePixel(display, screen);
14061126Selan 
14161126Selan     display_width = DisplayWidth(display, screen);
14261126Selan     display_height = DisplayHeight(display, screen);
14361126Selan     center_x = display_width / 2;
14461126Selan     center_y = display_height / 2;
14561126Selan 
14661126Selan     /*
14761126Selan        Create roach pixmaps at several orientations.
14861126Selan     */
14961126Selan     for (ax=0; ax<360; ax+=ROACH_ANGLE) {
15061126Selan 	rx = ax / ROACH_ANGLE;
15161126Selan 	angle = rx * 0.261799387799;
15261126Selan 	rp = &roachPix[rx];
15361126Selan 	rp->pixmap = XCreateBitmapFromData(display, rootWin,
15461126Selan 	    rp->roachBits, rp->width, rp->height);
15561126Selan 	rp->sine = sin(angle);
15661126Selan 	rp->cosine = cos(angle);
15761126Selan     }
15861126Selan 
15961126Selan     roaches = (Roach *)malloc(sizeof(Roach) * maxRoaches);
16061126Selan 
16161126Selan     gc = XCreateGC(display, rootWin, 0L, &xgcv);
16261126Selan     XSetForeground(display, gc, AllocNamedColor(roachColor, black));
16361126Selan     XSetFillStyle(display, gc, FillStippled);
16461126Selan 
16561126Selan     while (curRoaches < maxRoaches)
16661126Selan 	AddRoach();
16761126Selan 
16861126Selan     XSelectInput(display, rootWin, ExposureMask | SubstructureNotifyMask);
16961126Selan 
17061126Selan     needCalc = 1;
17161126Selan     while (!done) {
17261126Selan 	if (XPending(display)) {
17361126Selan 	    XNextEvent(display, &ev);
17461126Selan 	}
17561126Selan 	else {
17661126Selan 	    if (needCalc) {
17761126Selan 		needCalc = CalcRootVisible();
17861126Selan 	    }
17961126Selan 	    nVis = MarkHiddenRoaches();
18061126Selan 	    if (nVis) {
18161126Selan 		ev.type = SCAMPER_EVENT;
18261126Selan 	    }
18361126Selan 	    else {
18461126Selan 		DrawRoaches();
18561126Selan 		eventBlock = 1;
18661126Selan 		XNextEvent(display, &ev);
18761126Selan 		eventBlock = 0;
18861126Selan 	    }
18961126Selan 	}
19061126Selan 
19161126Selan 	switch (ev.type) {
19261126Selan 
19361126Selan 	    case SCAMPER_EVENT:
19461126Selan 		for (rx=0; rx<curRoaches; rx++) {
19561126Selan 		    if (!roaches[rx].hidden)
19661126Selan 			MoveRoach(rx);
19761126Selan 		}
19861126Selan 		DrawRoaches();
19961126Selan 		XSync(display, False);
20061126Selan 		break;
20161126Selan 
20261126Selan 	    case Expose:
20361126Selan 	    case MapNotify:
20461126Selan 	    case UnmapNotify:
20561126Selan 	    case ConfigureNotify:
20661126Selan 		needCalc = 1;
20761126Selan 		break;
20861126Selan 
20961126Selan 	}
21061126Selan     }
21161126Selan 
21261126Selan     CoverRoot();
21361126Selan 
21461126Selan     XCloseDisplay(display);
21561126Selan }
21661126Selan 
21761126Selan #define USEPRT(msg) fprintf(stderr, msg)
21861126Selan 
21961126Selan void
Usage()22061126Selan Usage()
22161126Selan {
22261126Selan     USEPRT("Usage: xroach [options]\n\n");
22361126Selan     USEPRT("Options:\n");
22461126Selan     USEPRT("       -display displayname\n");
22561126Selan     USEPRT("       -rc      roachcolor\n");
22661126Selan     USEPRT("       -roaches numroaches\n");
22761126Selan     USEPRT("       -speed   roachspeed\n");
22861126Selan 
22961126Selan     exit(1);
23061126Selan }
23161126Selan 
23261126Selan void
SigHandler()23361126Selan SigHandler()
23461126Selan {
23561126Selan 
23661126Selan     /*
23761126Selan        If we are blocked, no roaches are visible and we can just bail
23861126Selan        out.  If we are not blocked, then let the main procedure clean
23961126Selan        up the root window.
24061126Selan     */
24161126Selan     if (eventBlock) {
24261126Selan 	XCloseDisplay(display);
24361126Selan 	exit(0);
24461126Selan     }
24561126Selan     else {
24661126Selan 	done = 1;
24761126Selan     }
24861126Selan }
24961126Selan 
25061126Selan /*
25161126Selan    Generate random integer between 0 and maxVal-1.
25261126Selan */
25361126Selan int
RandInt(maxVal)25461126Selan RandInt(maxVal)
25561126Selan int maxVal;
25661126Selan {
25761126Selan 	return rand() % maxVal;
25861126Selan }
25961126Selan 
26061126Selan /*
26161126Selan    Check for roach completely in specified rectangle.
26261126Selan */
26361126Selan int
RoachInRect(roach,rx,ry,x,y,width,height)26461126Selan RoachInRect(roach, rx, ry, x, y, width, height)
26561126Selan Roach *roach;
26661126Selan int rx;
26761126Selan int ry;
26861126Selan int x;
26961126Selan int y;
27061126Selan unsigned int width;
27161126Selan unsigned int height;
27261126Selan {
27361126Selan     if (rx < x) return 0;
27461126Selan     if ((rx + roach->rp->width) > (x + width)) return 0;
27561126Selan     if (ry < y) return 0;
27661126Selan     if ((ry + roach->rp->height) > (y + height)) return 0;
27761126Selan 
27861126Selan     return 1;
27961126Selan }
28061126Selan 
28161126Selan /*
28261126Selan    Check for roach overlapping specified rectangle.
28361126Selan */
28461126Selan int
RoachOverRect(roach,rx,ry,x,y,width,height)28561126Selan RoachOverRect(roach, rx, ry, x, y, width, height)
28661126Selan Roach *roach;
28761126Selan int rx;
28861126Selan int ry;
28961126Selan int x;
29061126Selan int y;
29161126Selan unsigned int width;
29261126Selan unsigned int height;
29361126Selan {
29461126Selan     if (rx >= (x + width)) return 0;
29561126Selan     if ((rx + roach->rp->width) <= x) return 0;
29661126Selan     if (ry >= (y + height)) return 0;
29761126Selan     if ((ry + roach->rp->height) <= y) return 0;
29861126Selan 
29961126Selan     return 1;
30061126Selan }
30161126Selan 
30261126Selan /*
30361126Selan    Give birth to a roach.
30461126Selan */
30561126Selan void
AddRoach()30661126Selan AddRoach()
30761126Selan {
30861126Selan     Roach *r;
30961126Selan 
31061126Selan     if (curRoaches < maxRoaches) {
31161126Selan 	r = &roaches[curRoaches++];
31261126Selan 	r->index = RandInt(ROACH_HEADINGS);
31361126Selan 	r->rp = &roachPix[r->index];
31461126Selan 	r->x = RandInt(display_width - r->rp->width);
31561126Selan 	r->y = RandInt(display_height - r->rp->height);
31661126Selan 	r->intX = -1;
31761126Selan 	r->intY = -1;
31861126Selan 	r->hidden = 0;
31961126Selan 	r->steps = RandInt(200);
32061126Selan 	r->turnLeft = RandInt(100) >= 50;
32161126Selan     }
32261126Selan }
32361126Selan 
32461126Selan /*
32561126Selan    Turn a roach.
32661126Selan */
32761126Selan void
TurnRoach(roach)32861126Selan TurnRoach(roach)
32961126Selan Roach *roach;
33061126Selan {
33161126Selan     if (roach->index != (roach->rp - roachPix)) return;
33261126Selan 
33361126Selan     if (roach->turnLeft) {
33461126Selan 	roach->index += (RandInt(30) / 10) + 1;
33561126Selan 	if (roach->index >= ROACH_HEADINGS)
33661126Selan 	    roach->index -= ROACH_HEADINGS;
33761126Selan     }
33861126Selan     else {
33961126Selan 	roach->index -= (RandInt(30) / 10) + 1;
34061126Selan 	if (roach->index < 0)
34161126Selan 	    roach->index += ROACH_HEADINGS;
34261126Selan     }
34361126Selan }
34461126Selan 
34561126Selan /*
34661126Selan    Move a roach.
34761126Selan */
34861126Selan void
MoveRoach(rx)34961126Selan MoveRoach(rx)
35061126Selan int rx;
35161126Selan {
35261126Selan     Roach *roach;
35361126Selan     Roach *r2;
35461126Selan     float newX;
35561126Selan     float newY;
35661126Selan     int ii;
35761126Selan 
35861126Selan     roach = &roaches[rx];
35961126Selan     newX = roach->x + (roachSpeed * roach->rp->cosine);
36061126Selan     newY = roach->y - (roachSpeed * roach->rp->sine);
36161126Selan 
36261126Selan     if (RoachInRect(roach, (int)newX, (int)newY,
36361126Selan 			    0, 0, display_width, display_height)) {
36461126Selan 
36561126Selan 	roach->x = newX;
36661126Selan 	roach->y = newY;
36761126Selan 
36861126Selan 	if (roach->steps-- <= 0) {
36961126Selan 	    TurnRoach(roach);
37061126Selan 	    roach->steps = RandInt(200);
37161126Selan 	}
37261126Selan 
37361126Selan 	for (ii=rx+1; ii<curRoaches; ii++) {
37461126Selan 	    r2 = &roaches[ii];
37561126Selan 	    if (RoachOverRect(roach, (int)newX, (int)newY,
37661126Selan 		r2->intX, r2->intY, r2->rp->width, r2->rp->height)) {
37761126Selan 
37861126Selan 		TurnRoach(roach);
37961126Selan 	    }
38061126Selan 	}
38161126Selan     }
38261126Selan     else {
38361126Selan 	TurnRoach(roach);
38461126Selan     }
38561126Selan }
38661126Selan 
38761126Selan /*
38861126Selan    Draw all roaches.
38961126Selan */
39061126Selan void
DrawRoaches()39161126Selan DrawRoaches()
39261126Selan {
39361126Selan     Roach *roach;
39461126Selan     int rx;
39561126Selan 
39661126Selan     for (rx=0; rx<curRoaches; rx++) {
39761126Selan 	roach = &roaches[rx];
39861126Selan 
39961126Selan 	if (roach->intX >= 0) {
40061126Selan 	    XClearArea(display, rootWin, roach->intX, roach->intY,
40161126Selan 		roach->rp->width, roach->rp->height, False);
40261126Selan 	}
40361126Selan     }
40461126Selan 
40561126Selan     for (rx=0; rx<curRoaches; rx++) {
40661126Selan 	roach = &roaches[rx];
40761126Selan 
40861126Selan 	if (!roach->hidden) {
40961126Selan 	    roach->intX = roach->x;
41061126Selan 	    roach->intY = roach->y;
41161126Selan 	    roach->rp = &roachPix[roach->index];
41261126Selan 
41361126Selan 	    XSetStipple(display, gc, roach->rp->pixmap);
41461126Selan 	    XSetTSOrigin(display, gc, roach->intX, roach->intY);
41561126Selan 	    XFillRectangle(display, rootWin, gc,
41661126Selan 		roach->intX, roach->intY, roach->rp->width, roach->rp->height);
41761126Selan 	}
41861126Selan 	else {
41961126Selan 	    roach->intX = -1;
42061126Selan 	}
42161126Selan     }
42261126Selan }
42361126Selan 
42461126Selan /*
42561126Selan    Cover root window to erase roaches.
42661126Selan */
42761126Selan void
CoverRoot()42861126Selan CoverRoot()
42961126Selan {
43061126Selan     XSetWindowAttributes xswa;
43161126Selan     long wamask;
43261126Selan     Window roachWin;
43361126Selan 
43461126Selan     xswa.background_pixmap = ParentRelative;
43561126Selan     xswa.override_redirect = True;
43661126Selan     wamask = CWBackPixmap | CWOverrideRedirect;
43761126Selan     roachWin = XCreateWindow(display, rootWin, 0, 0,
43861126Selan 		    display_width, display_height, 0, CopyFromParent,
43961126Selan 		    InputOutput, CopyFromParent, wamask, &xswa);
44061126Selan     XLowerWindow(display, roachWin);
44161126Selan     XMapWindow(display, roachWin);
44261126Selan     XFlush(display);
44361126Selan }
44461126Selan 
44561126Selan #if !GRAB_SERVER
44661126Selan 
44761126Selan int
RoachErrors(dpy,err)44861126Selan RoachErrors(dpy, err)
44961126Selan Display *dpy;
45061126Selan XErrorEvent *err;
45161126Selan {
45261126Selan     errorVal = err->error_code;
45361126Selan 
45461126Selan     return 0;
45561126Selan }
45661126Selan 
45761126Selan #endif /* GRAB_SERVER */
45861126Selan 
45961126Selan /*
46061126Selan    Calculate Visible region of root window.
46161126Selan */
46261126Selan int
CalcRootVisible()46361126Selan CalcRootVisible()
46461126Selan {
46561126Selan     Region covered;
46661126Selan     Region visible;
46761126Selan     Window *children;
46861126Selan     int nChildren;
46961126Selan     Window dummy;
47061126Selan     XWindowAttributes wa;
47161126Selan     int wx;
47261126Selan     XRectangle rect;
47361126Selan     int winX, winY;
47461126Selan     unsigned int winHeight, winWidth;
47561126Selan     unsigned int borderWidth;
47661126Selan     unsigned int depth;
47761126Selan 
47861126Selan     /*
47961126Selan        If we don't grab the server, the XGetWindowAttribute or XGetGeometry
48061126Selan        calls can abort us.  On the other hand, the server grabs can make for
48161126Selan        some annoying delays.
48261126Selan     */
48361126Selan #if GRAB_SERVER
48461126Selan     XGrabServer(display);
48561126Selan #else
48661126Selan     XSetErrorHandler(RoachErrors);
48761126Selan #endif
48861126Selan 
48961126Selan     /*
49061126Selan        Get children of root.
49161126Selan     */
49261126Selan     XQueryTree(display, rootWin, &dummy, &dummy, &children, &nChildren);
49361126Selan 
49461126Selan     /*
49561126Selan        For each mapped child, add the window rectangle to the covered
49661126Selan        region.
49761126Selan     */
49861126Selan     covered = XCreateRegion();
49961126Selan     for (wx=0; wx<nChildren; wx++) {
50061126Selan 	if (XEventsQueued(display, QueuedAlready)) return 1;
50161126Selan 	errorVal = 0;
50261126Selan 	XGetWindowAttributes(display, children[wx], &wa);
50361126Selan 	if (errorVal) continue;
50461126Selan 	if (wa.map_state == IsViewable) {
50561126Selan 	    XGetGeometry(display, children[wx], &dummy, &winX, &winY,
50661126Selan 		&winWidth, &winHeight, &borderWidth, &depth);
50761126Selan 	    if (errorVal) continue;
50861126Selan 	    rect.x = winX;
50961126Selan 	    rect.y = winY;
51061126Selan 	    rect.width = winWidth + (borderWidth * 2);
51161126Selan 	    rect.height = winHeight + (borderWidth * 2);
51261126Selan 	    XUnionRectWithRegion(&rect, covered, covered);
51361126Selan 	}
51461126Selan     }
515*61144Selan     XFree((char *)children);
51661126Selan 
51761126Selan #if GRAB_SERVER
51861126Selan     XUngrabServer(display);
51961126Selan #else
52061126Selan     XSetErrorHandler((ErrorHandler *)NULL);
52161126Selan #endif
52261126Selan 
52361126Selan     /*
52461126Selan        Subtract the covered region from the root window region.
52561126Selan     */
52661126Selan     visible = XCreateRegion();
52761126Selan     rect.x = 0;
52861126Selan     rect.y = 0;
52961126Selan     rect.width = display_width;
53061126Selan     rect.height = display_height;
53161126Selan     XUnionRectWithRegion(&rect, visible, visible);
53261126Selan     XSubtractRegion(visible, covered, visible);
53361126Selan     XDestroyRegion(covered);
53461126Selan 
53561126Selan     /*
53661126Selan        Save visible region globally.
53761126Selan     */
53861126Selan     if (rootVisible)
53961126Selan 	XDestroyRegion(rootVisible);
54061126Selan     rootVisible = visible;
54161126Selan 
54261126Selan 
54361126Selan     /*
54461126Selan        Mark all roaches visible.
54561126Selan     */
54661126Selan     for (wx=0; wx<curRoaches; wx++)
54761126Selan 	roaches[wx].hidden = 0;
54861126Selan 
54961126Selan     return 0;
55061126Selan }
55161126Selan 
55261126Selan /*
55361126Selan    Mark hidden roaches.
55461126Selan */
55561126Selan int
MarkHiddenRoaches()55661126Selan MarkHiddenRoaches()
55761126Selan {
55861126Selan     int rx;
55961126Selan     Roach *r;
56061126Selan     int nVisible;
56161126Selan 
56261126Selan     nVisible = 0;
56361126Selan     for (rx=0; rx<curRoaches; rx++) {
56461126Selan 	r = &roaches[rx];
56561126Selan 
56661126Selan 	if (!r->hidden) {
56761126Selan 	    if (r->intX > 0 && XRectInRegion(rootVisible, r->intX, r->intY,
56861126Selan 			    r->rp->width, r->rp->height) == RectangleOut) {
56961126Selan 		r->hidden = 1;
57061126Selan 	    }
57161126Selan 	    else {
57261126Selan 		nVisible++;
57361126Selan 	    }
57461126Selan 	}
57561126Selan     }
57661126Selan 
57761126Selan     return nVisible;
57861126Selan }
57961126Selan 
58061126Selan /*
58161126Selan    Allocate a color by name.
58261126Selan */
58361126Selan Pixel
AllocNamedColor(colorName,dfltPix)58461126Selan AllocNamedColor(colorName, dfltPix)
58561126Selan char *colorName;
58661126Selan Pixel dfltPix;
58761126Selan {
58861126Selan 	Pixel pix;
58961126Selan 	XColor scrncolor;
59061126Selan 	XColor exactcolor;
59161126Selan 
59261126Selan 	if (XAllocNamedColor(display, DefaultColormap(display, screen),
59361126Selan 		colorName, &scrncolor, &exactcolor)) {
59461126Selan 		pix = scrncolor.pixel;
59561126Selan 	}
59661126Selan 	else {
59761126Selan 		pix = dfltPix;
59861126Selan 	}
59961126Selan 
60061126Selan 	return pix;
60161126Selan }
60261126Selan 
603