/* $XConsortium: main.c,v 1.19 94/03/28 18:34:05 gildea Exp $ */ /* Puzzle - (C) Copyright 1987, 1988 Don Bennett. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, * provided that the above copyright notice appear in all copies and that * both that copyright notice and this permission notice appear in * supporting documentation. */ #define DEBUG #define USE_PICTURE #define SERVER_BUG /** Puzzle ** ** Don Bennett, HP Labs ** ** this is the interface code for the puzzle program. **/ #include #include #include #include #include #include "ac.cursor" #include "ac_mask" #define max(x,y) ((x)>(y)?(x):(y)) #define min(x,y) ((x)>(y)?(y):(x)) #define abs(x) ((x)>0?(x):-(x)) #define PUZZLE_BORDER_WIDTH 2 #define TITLE_WINDOW_HEIGHT 25 #define BOUNDARY_HEIGHT 3 #define BOX_WIDTH 10 #define BOX_HEIGHT 10 #define MIN_TILE_HEIGHT 30 #define MIN_TILE_WIDTH 30 #define MAX_STEPS 1000 #define DEFAULT_SPEED 5 #define TITLE_TILES 0 #define TITLE_TEXT 1 #define TITLE_ANIMATED 2 int BoxWidth = BOX_WIDTH; int BoxHeight = BOX_HEIGHT; int PuzzleSize = 4; int PuzzleWidth=4, PuzzleHeight=4; char defaultPuzzleSize[] = "4x4"; int TileHeight, TileWidth; int TextXStart; int TitleWinHeight, BoundaryHeight, TileWinHeight; int FgPixel, BgPixel; Display *dpy; int screen; GC gc, rect_gc; Colormap PuzzleColormap; typedef struct { Window root; int x,y; unsigned int width, height; unsigned int border_width; unsigned int depth; } WindowGeom; WindowGeom PuzzleWinInfo; Window PuzzleRoot, TitleWindow=0, TileWindow, ScrambleWindow, SolveWindow; char *ProgName; char *TitleFontName = "8x13"; char *TileFontName = "8x13bold"; XFontStruct *TitleFontInfo, *TileFontInfo; extern int OutputLogging; extern int *position; extern int space_x, space_y; int UsePicture = 0; int UseDisplay = 0; int CreateNewColormap = 0; char *PictureFileName; long PictureWidth; long PictureHeight; Pixmap PicturePixmap; int TilesPerSecond; int MoveSteps; int VertStepSize[MAX_STEPS]; int HoriStepSize[MAX_STEPS]; #define LEFT 0 #define RIGHT 1 #define UP 2 #define DOWN 3 #define indx(x,y) (((y)*PuzzleWidth) + (x)) #define isdigit(x) ((x)>= '0' && (x) <= '9') #define ulx(x,y) ((x)*TileWidth) #define llx(x,y) ((x)*TileWidth) #define urx(x,y) (((x)+1)*TileWidth - 1) #define lrx(x,y) (((x)+1)*TileWidth - 1) #define uly(x,y) ((y)*TileHeight) #define ury(x,y) ((y)*TileHeight) #define lly(x,y) (((y)+1)*TileHeight - 1) #define lry(x,y) (((y)+1)*TileHeight - 1) /* * PuzzlePending - XPending entry point fo the other module. */ PuzzlePending() { return(XPending(dpy)); } /* * SetupDisplay - eastablish the connection to the X server. */ SetupDisplay(server) char *server; { dpy = XOpenDisplay(server); if (dpy == NULL) { fprintf(stderr, "%s: unable to open display '%s'\n", ProgName, XDisplayName (server)); exit(1); } screen = DefaultScreen(dpy); #ifdef DEBUG XSynchronize(dpy,1); #endif /* DEBUG */ } XQueryWindow(window,frame) Window window; WindowGeom *frame; { XGetGeometry(dpy, window, &(frame->root), &(frame->x), &(frame->y), &(frame->width), &(frame->height), &(frame->border_width), &(frame->depth)); } RectSet(W,x,y,w,h,pixel) Window W; int x,y; unsigned int w,h; unsigned long pixel; { XSetForeground(dpy, rect_gc, pixel); XFillRectangle(dpy, W, rect_gc, x, y, w, h); } MoveArea(W,src_x,src_y,dst_x,dst_y,w,h) Window W; int src_x, src_y, dst_x, dst_y; unsigned int w, h; { XCopyArea(dpy,W,W,gc,src_x,src_y,w,h,dst_x,dst_y); } /** RepaintTitle - puts the program title in the title bar **/ RepaintTitle(method) int method; { int Twidth,Theight; int i,j, startColor,color2,tinyBoxSize; int Tx, Ty; /* * applications painting their own title is out of style, * so don't just leave it there; */ tinyBoxSize = 5; Twidth = PuzzleWinInfo.width*3/4; Tx = (PuzzleWinInfo.width-Twidth)/2; TextXStart = Tx; if (method == TITLE_TEXT) { Twidth = XTextWidth(TitleFontInfo,ProgName,strlen(ProgName)); Theight = TitleFontInfo->ascent + TitleFontInfo->descent; Tx = (PuzzleWinInfo.width-Twidth)/2; Ty = (TitleWinHeight-Theight)/2 + TitleFontInfo->ascent; XSetFont(dpy, gc, TitleFontInfo->fid); XDrawString(dpy, TitleWindow, gc,Tx, Ty, ProgName,strlen(ProgName)); XFlush(dpy); } else if (method == TITLE_TILES) { for (i=0,startColor=0; i1; i--) { swapWith = rand()%i; tmp = permute[swapWith]; permute[swapWith] = permute[i]; permute[i] = tmp; } counter = 0; for (i=0,startColor=0; iascent + TileFontInfo->descent; y_offset = (TileHeight - height)/2 + TileFontInfo->ascent; XSetFont(dpy, gc, TileFontInfo->fid); counter = 0; for (i=0; i sizehints.min_width) sizehints.width = width; } if(HeightValue & flags) { sizehints.flags |= USSize; if (height > sizehints.min_height) sizehints.height = height; } if(XValue & flags) { if(XNegative & flags) x = DisplayWidth(dpy, DefaultScreen(dpy)) + x - sizehints.width; sizehints.flags |= USPosition; sizehints.x = x; } if(YValue & flags) { if(YNegative & flags) y = DisplayHeight(dpy, DefaultScreen(dpy)) + y -sizehints.height; sizehints.flags |= USPosition; sizehints.y = y; } tileHeight = (sizehints.height-TitleWinHeight-BoundaryHeight)/PuzzleHeight; sizehints.height = tileHeight*PuzzleHeight+TitleWinHeight+BoundaryHeight; tileWidth = sizehints.width/PuzzleWidth; sizehints.width = tileWidth * PuzzleWidth; } } /*******************************************************************/ /** create the puzzle main window and set its standard properties **/ /*******************************************************************/ xswa.event_mask = ExposureMask; visual.visualid = CopyFromParent; PuzzleRoot = XCreateSimpleWindow(dpy, RootWindow(dpy,screen), sizehints.x, sizehints.y, sizehints.width, sizehints.height, PUZZLE_BORDER_WIDTH, FgPixel,FgPixel); XSetStandardProperties(dpy, PuzzleRoot,"puzzle","Puzzle", None, argv, argc, &sizehints); if (CreateNewColormap) XSetWindowColormap(dpy, PuzzleRoot, PuzzleColormap); xgcv.foreground = FgPixel; xgcv.background = BgPixel; xgcv.line_width = 1; gc = XCreateGC(dpy, PuzzleRoot, GCForeground|GCBackground|GCLineWidth, &xgcv); /*********************************/ /** load the arrow-cross cursor **/ /*********************************/ { Pixmap ACPixmap, ACMask; Cursor ACCursor; XColor FGcolor, BGcolor; FGcolor.red = 0; FGcolor.green = 0; FGcolor.blue = 0; BGcolor.red = 0xffff; BGcolor.green = 0xffff; BGcolor.blue = 0xffff; ACPixmap = XCreateBitmapFromData(dpy,RootWindow(dpy,screen), (char *) ac_bits, ac_width, ac_height); ACMask = XCreateBitmapFromData(dpy,RootWindow(dpy,screen), (char *) ac_mask_bits, ac_mask_width, ac_mask_height); ACCursor = XCreatePixmapCursor(dpy,ACPixmap,ACMask, &FGcolor,&BGcolor, ac_x_hot, ac_y_hot); if (ACCursor == (Cursor) 0) error("Unable to store ArrowCrossCursor."); XDefineCursor(dpy,PuzzleRoot,ACCursor); } /*****************************************/ /** allocate the fonts we will be using **/ /*****************************************/ TitleFontInfo = XLoadQueryFont(dpy,TitleFontName); TileFontInfo = XLoadQueryFont(dpy,TileFontName); if (TitleFontInfo == NULL) error("Opening title font.\n"); if (TileFontInfo == NULL) error("Opening tile font.\n"); XSelectInput(dpy, PuzzleRoot, ExposureMask|VisibilityChangeMask); XMapWindow(dpy,PuzzleRoot); } static short old_height = -1; static short old_width = -1; SizeChanged() { XQueryWindow(PuzzleRoot,&PuzzleWinInfo); if (PuzzleWinInfo.width == old_width && PuzzleWinInfo.height == old_height) return(0); else return(1); } Reset() { int Box_x,Box_y; int TileBgPixel; /** TileWindow is that portion of PuzzleRoot that contains ** the sliding pieces; **/ if (UsePicture) TileBgPixel = BlackPixel(dpy,screen); else TileBgPixel = WhitePixel(dpy,screen); #ifdef SERVER_BUG /* seems I need to do this, or the next title window will be obscured * by the old title window! This must be a server bug, right? */ if (TitleWindow) XUnmapWindow(dpy,TitleWindow); /* if (TitleWindow) XDestroyWindow(dpy,TitleWindow); */ #endif /* SERVER_BUG */ XDestroySubwindows(dpy,PuzzleRoot); /** fix the dimensions of PuzzleRoot so the height and width ** of the TileWindow will work out to be multiples of PuzzleSize; **/ /** If we're dealing with a picture, the tile region can be no larger ** than the picture! **/ #ifdef USE_PICTURE if (UsePicture) { int tmp; tmp = PuzzleWinInfo.height - TitleWinHeight - BoundaryHeight; if (tmp > PictureHeight) PuzzleWinInfo.height = PictureHeight+TitleWinHeight+BoundaryHeight; if (PuzzleWinInfo.width > PictureWidth) PuzzleWinInfo.width = PictureWidth; } #endif /* USE_PICTURE */ TileHeight=(PuzzleWinInfo.height-TitleWinHeight-BoundaryHeight)/PuzzleHeight; /* PuzzleWinInfo.height = TileHeight*PuzzleHeight+TitleWinHeight+BoundaryHeight; */ TileWidth = PuzzleWinInfo.width/PuzzleWidth; /* PuzzleWinInfo.width = TileWidth * PuzzleWidth; */ /** fixup the size of PuzzleRoot **/ /* XResizeWindow(dpy,PuzzleRoot,PuzzleWinInfo.width,PuzzleWinInfo.height); */ old_width = PuzzleWinInfo.width; old_height = PuzzleWinInfo.height; TileWinHeight = PuzzleWinInfo.height - TitleWinHeight; TitleWindow = XCreateSimpleWindow(dpy, PuzzleRoot, 0,0, PuzzleWinInfo.width, TitleWinHeight, 0,0,BgPixel); TileWindow = XCreateSimpleWindow(dpy, PuzzleRoot, 0,TitleWinHeight+BoundaryHeight, PuzzleWinInfo.width, TileWinHeight, 0,0,TileBgPixel); rect_gc = XCreateGC(dpy,TileWindow,0,0); XCopyGC(dpy, gc, -1, rect_gc); XMapWindow(dpy,TitleWindow); XMapWindow(dpy,TileWindow); XSync(dpy,0); RepaintBar(); RepaintTitle(TITLE_TEXT); /** locate the two check boxes **/ Box_x = TextXStart/2 - BoxWidth/2; Box_y = TitleWinHeight/2 - BoxHeight/2; ScrambleWindow = XCreateSimpleWindow(dpy, TitleWindow, Box_x, Box_y, BoxWidth, BoxHeight, 1,FgPixel,BgPixel); Box_x = PuzzleWinInfo.width - Box_x - BoxWidth; SolveWindow = XCreateSimpleWindow(dpy, TitleWindow, Box_x,Box_y, BoxWidth,BoxHeight, 1,FgPixel,BgPixel); XMapWindow(dpy,ScrambleWindow); XMapWindow(dpy,SolveWindow); XSync(dpy,0); XSelectInput(dpy, TitleWindow, ButtonPressMask|ExposureMask); XSelectInput(dpy, TileWindow, ButtonPressMask|ExposureMask| VisibilityChangeMask); XSelectInput(dpy, ScrambleWindow,ButtonPressMask|ExposureMask); XSelectInput(dpy, SolveWindow, ButtonPressMask|ExposureMask); RepaintTiles(); RepaintTitle(TITLE_ANIMATED); CalculateSpeed(); CalculateStepsize(); XSync(dpy,0); } /* * Sets the global variable MoveSteps based on speed * specified on the command line; */ #if defined(USG) && !defined(Cray) /* tv_usec never changes */ #define MIN_DELTA_T 100L #else /* don't divide by zero */ #define MIN_DELTA_T 1L #endif /** delta-t in miliseconds **/ #define DeltaT(tv2,tv1) \ ( ((tv2.tv_sec - tv1.tv_sec )*1000L) \ +((tv2.tv_usec - tv1.tv_usec)/1000L)) CalculateSpeed() { struct timeval tv1, tv2; int i, x, y; long timePerTile; static int firstCall = 1; long delta; if (!firstCall) return; firstCall = 0; x = space_x * TileWidth; y = space_y * TileHeight; timePerTile = (long)(1000/TilesPerSecond); XSync(dpy,0); X_GETTIMEOFDAY(&tv1); tv2 = tv1; MoveSteps = 0; delta = 0L; while (delta < timePerTile) { MoveArea(TileWindow,x,y,x+1,y,TileWidth,TileHeight); RectSet(TileWindow,x,y,1,TileHeight,FgPixel); XSync(dpy,0); X_GETTIMEOFDAY(&tv2); delta = DeltaT(tv2,tv1); delta = max(MIN_DELTA_T, delta); if (delta >= 0) MoveSteps++; /* crock for broken systems */ } /* * now, see how long this takes without all the extra b.s. * and compensate; */ XSync(dpy,0); X_GETTIMEOFDAY(&tv1); for (i=0; i 0) for (i=0; i= 0) { VertStepSize[i]++; error -= MoveSteps; } error += rem; } for (i=0; i 0) for (i=0; i= 0) { HoriStepSize[i]++; error -= MoveSteps; } error += rem; } /** This code is a little screwed up and I don't want to fix it ** right now, so just do a little hack to make sure the total ** distance comes out right; **/ sum = 0; for (i=0; istate != VisibilityUnobscured) AbortSolving(); } ProcessExpose(event) XExposeEvent *event; { int loop = 1; int reset = 0, title = 0, tiles = 0, bar = 0; loop = 1; while (loop) { if (event->count == 0) { if (event->window == TitleWindow) title++; else if (event->window == TileWindow) tiles++; else if (event->window == PuzzleRoot) bar++; } loop = XCheckMaskEvent(dpy, ExposureMask, (XEvent *)event); } if (SizeChanged()) reset++; if (reset) Reset(); else { if (title) RepaintTitle(TITLE_TILES); if (tiles) RepaintTiles(); if (bar) RepaintBar(); } } ProcessButton(event) XButtonEvent *event; { Window w; w = event->window; if (w == TileWindow) { if (SolvingStatus()) AbortSolving(); else SlidePieces(event); } else if (w == ScrambleWindow) { AbortSolving(); Scramble(); RepaintTiles(); } else if (w == SolveWindow) Solve(); else if ((w == TitleWindow) && (*event).button == Button2) exit(0); } ProcessInput() { XEvent event; while(1) { GetNextEvent(&event); ProcessEvent(&event); } } ProcessEvents() { XEvent event; while(XPending(dpy)) { GetNextEvent(&event); ProcessEvent(&event); } } GetNextEvent(event) XEvent *event; { if (!XCheckMaskEvent(dpy,VisibilityChangeMask,event) && !XCheckMaskEvent(dpy,ExposureMask,event)) XNextEvent(dpy,event); } ProcessEvent(event) XEvent *event; { switch(event->type) { case ButtonPress: ProcessButton(&event->xbutton); break; case Expose: ProcessExpose(&event->xexpose); break; case VisibilityNotify: ProcessVisibility(&event->xvisibility); break; default: break; } } main(argc,argv) int argc; char *argv[]; { int i; char *ServerName, *Geometry; char *puzzle_size = NULL; char *option; ProgName = argv[0]; ServerName = ""; Geometry = ""; TilesPerSecond = -1; /********************************/ /** parse command line options **/ /********************************/ for (i=1; i= argc) usage (); ServerName = argv[i]; continue; case 'g': /* -geometry geom */ if (++i >= argc) usage (); Geometry = argv[i]; continue; case 's': /* -size WxH or -speed n */ if (arg[2] == 'i') { if (++i >= argc) usage (); puzzle_size = argv[i]; continue; } else if (arg[2] == 'p') { if (++i >= argc) usage (); TilesPerSecond = atoi (argv[i]); continue; } else usage (); break; case 'p': /* -picture filename */ if (++i >= argc) usage (); UsePicture++; PictureFileName = argv[i]; continue; case 'c': /* -colormap */ CreateNewColormap++; continue; default: usage (); } /* end switch */ } else usage (); } /* end for */ SetupDisplay (ServerName); if (!Geometry) { Geometry = XGetDefault (dpy, ProgName, "Geometry"); } if (!puzzle_size) { option = XGetDefault (dpy, ProgName, "Size"); puzzle_size = option ? option : defaultPuzzleSize; } if (TilesPerSecond <= 0) { option = XGetDefault (dpy, ProgName, "Speed"); TilesPerSecond = option ? atoi (option) : DEFAULT_SPEED; } if (!UsePicture) { option = XGetDefault (dpy, ProgName, "Picture"); if (option) { UsePicture++; PictureFileName = option; } } if (!CreateNewColormap) { option = XGetDefault (dpy, ProgName, "Colormap"); if (option) { CreateNewColormap++; } } sscanf (puzzle_size, "%dx%d", &PuzzleWidth, &PuzzleHeight); if (PuzzleWidth < 4 || PuzzleHeight < 4) { fprintf (stderr, "%s: Puzzle size must be at least 4x4\n", ProgName); exit (1); } PuzzleSize = min((PuzzleWidth/2)*2,(PuzzleHeight/2)*2); Setup (Geometry,argc,argv); ProcessInput(); exit (0); } static char *help_message[] = { "where options include:", " -display host:dpy X server to use", " -geometry geom geometry of puzzle window", " -size WxH number of squares in puzzle", " -speed number tiles to move per second", " -picture filename image to use for tiles", " -colormap create a new colormap", NULL}; usage() { char **cpp; fprintf (stderr, "usage: %s [-options ...]\n\n", ProgName); for (cpp = help_message; *cpp; cpp++) { fprintf (stderr, "%s\n", *cpp); } fprintf (stderr, "\n"); exit (1); } error(str) char *str; { fprintf(stderr,"Error %s\n",str); exit(1); } /** ** Output Routines - **/ resetLogging() { } flushLogging() { } saveLoggingState() { } LogMoveSpace(first_x,first_y,last_x,last_y,dir) int first_x,first_y,last_x,last_y,dir; { displayLogMoveSpace(first_x,first_y,last_x,last_y,dir); } #ifdef UNDEFINED /** this stuff really isn't worth it; **/ static int prevDir = -1; static int prevFirstX, prevFirstY, prevLastX, prevLastY; resetLogging() { prevDir = -1; } flushLogging() { if (prevDir != -1) displayLogMoveSpace(prevFirstX,prevFirstY,prevLastX,prevLastY,prevDir); prevDir = -1; } saveLoggingState(fx,fy,lx,ly,dir) int fx,fy,lx,ly,dir; { prevDir = dir; prevFirstX = fx; prevFirstY = fy; prevLastX = lx; prevLastY = ly; } LogMoveSpace(first_x,first_y,last_x,last_y,dir) int first_x,first_y,last_x,last_y,dir; { if (prevDir == -1) /** we don't already have something to move **/ saveLoggingState(first_x,first_y,last_x,last_y,dir); else if (prevDir == dir) { /** we're going in the same direction **/ prevLastX = last_x; prevLastY = last_y; } else { flushLogging(); saveLoggingState(first_x,first_y,last_x,last_y,dir); } } #endif /* UNDEFINED */ displayLogMoveSpace(first_x,first_y,last_x,last_y,dir) int first_x,first_y,last_x,last_y,dir; { int min_x,min_y,max_x,max_y; int x,y,w,h,dx,dy,x2,y2; int i, clear_x, clear_y; max_x = max(first_x,last_x); min_x = min(first_x,last_x); max_y = max(first_y,last_y); min_y = min(first_y,last_y); x = ulx(min_x,0); y = uly(0,min_y); w = (max_x - min_x + 1)*TileWidth; h = (max_y - min_y + 1)*TileHeight; dx = x; dy = y; x2 = x; y2 = y; switch(dir) { case UP: clear_x = llx(max_x,0); clear_y = lly(0,max_y) + 1; for (i=0; i