DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T b

⟦0f2c62e17⟧ TextFile

    Length: 27424 (0x6b20)
    Types: TextFile
    Names: »board.c«

Derivation

└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
    └─⟦this⟧ »EUUGD18/X/Dragon/board.c« 

TextFile

/******************************************************************************
* Dragon - a version of Mah-Jongg for X Windows
*
* Author: Gary E. Barnes	March 1989
*
* board.c - Deals with the Mah-Jongg board.  Setup and execution.
******************************************************************************/

#define _BOARD_C_

#include "main.h"
#include "board.h"

extern long random();

\f


void Write_Game( file )
     FILE	*file;
/******************************************************************************
*   file    - Specifies a file open for write
*
* Called to write out the current game context for later rereading.
******************************************************************************/
{

    (void)fwrite( (char*)&Score, 1, sizeof(Score), file );
    (void)fwrite( (char*)&Board_Tiles[0][0], 1, sizeof(Board_Tiles), file );

} /* Write_Game */

\f


void Read_Game( file )
     FILE	*file;
/******************************************************************************
*   file    - Specifies a file open for reading
*
* Called to read in a new current game context.
******************************************************************************/
{

    Click1 = Board_Position_NULL;
    Click2 = Board_Position_NULL;
    (void)fread( (char*)&Score, 1, sizeof(Score), file );
    (void)fread( (char*)&Board_Tiles[0][0], 1, sizeof(Board_Tiles), file );

} /* Read_Game */

\f


static int Pick_Tile( Avail )
     char	*Avail;
/******************************************************************************
*   Avail	- Specifies an [NTILES] array of available tiles.  Unavailable
*		  slots contain NO_TILE.
*
* Called to pick a random tile from the Available tiles.
******************************************************************************/
{
    register char	*t;
    register int	k;

/*--Pick a random starting place. */

    k = (int)random() % NTILES;
    t = &Avail[k];

/*--Search until we find a non-NO_TILE slot. */

    while (*t == NO_TILE) {
	++t;
	if (++k == NTILES) {
	    t = &Avail[0];
	    k = 0;
	}
    }

/*--Return the tile we found and zap the slot. */

    k = *t;
    *t = NO_TILE;
    return k;

} /* Pick_Tile */

\f


void Set_Tile_Controls()
/******************************************************************************
* Called whenever the board has been reset or resized.  We recalculate all of
* the drawing controls for the tiles.
******************************************************************************/
{
    register Board_Position	bp;
    int				row, col;

/*--Now set up the control information for all of the tiles.  The special
 *  tiles are easy. */

    DEBUG_CALL(Set_Tile_Controls);
    if (Board_Tiles[SPEC4].level > 0) {
	Board_Tiles[SPEC4].x	 	= Board_Tile0_X + 6 * (Tile_Width + 1)
	  				  + (Tile_Width + 1) / 2 + 4 * Side_X;
	Board_Tiles[SPEC4].y	 	= Board_Tile0_Y + 3 * (Tile_Height + 1)
					  + (Tile_Height + 1) / 2 - 3 * Side_Y;
    }

    if (Board_Tiles[SPEC3].level > 0) {
	Board_Tiles[SPEC3].x	 	= Board_Tile0_X + 0 * (Tile_Width + 1);
	Board_Tiles[SPEC3].y	 	= Board_Tile0_Y + 3 * (Tile_Height + 1)
					  + (Tile_Height + 1) / 2;
    }

    if (Board_Tiles[SPEC2].level > 0) {
	Board_Tiles[SPEC2].x	 	= Board_Tile0_X + 13 * (Tile_Width+1);
	Board_Tiles[SPEC2].y	 	= Board_Tile0_Y +  3 * (Tile_Height+1)
					  + (Tile_Height + 1) / 2;
    }

    if (Board_Tiles[SPEC1].level > 0) {
	Board_Tiles[SPEC1].x	 	= Board_Tile0_X + 14 * (Tile_Width+1);
	Board_Tiles[SPEC1].y	 	= Board_Tile0_Y +  3 * (Tile_Height+1)
	      				  + (Tile_Height + 1) / 2;
    }

/*--Do the more regular tiles. */

    for (row = 0; row <= 7; ++row) {
	for (col = 12; col >= 1; --col) {
	    bp = &Board_Tiles[row][col];

/*--Skip any tiles that don't exist. */

	    if (bp->level == 0) { continue; }

/*--Set up the face x/y coordinates. */

	    bp->x = Board_Tile0_X + col * (Tile_Width + 1);
	    bp->y = Board_Tile0_Y + row * (Tile_Height + 1);

	}
    }
    DEBUG_RETURN(Set_Tile_Controls);

} /* Set_Tile_Controls */

\f


static void Pick1( bp, Avail )
     register Board_Position	 bp;
     char			*Avail;
{
    bp->tiles[0] = Pick_Tile( Avail );
    bp->level = 1;
}

static void Pick2( bp, Avail )
     register Board_Position	 bp;
     char			*Avail;
{
    bp->tiles[0] = Pick_Tile( Avail );
    bp->tiles[1] = Pick_Tile( Avail );
    bp->level = 2;
}

static void Pick3( bp, Avail )
     register Board_Position	 bp;
     char			*Avail;
{
    bp->tiles[0] = Pick_Tile( Avail );
    bp->tiles[1] = Pick_Tile( Avail );
    bp->tiles[2] = Pick_Tile( Avail );
    bp->level = 3;
}

static void Pick4( bp, Avail )
     register Board_Position	 bp;
     char			*Avail;
{
    bp->tiles[0] = Pick_Tile( Avail );
    bp->tiles[1] = Pick_Tile( Avail );
    bp->tiles[2] = Pick_Tile( Avail );
    bp->tiles[3] = Pick_Tile( Avail );
    bp->level = 4;
}

\f


void Setup_New_Game()
/******************************************************************************
* Called to generate an all-new game.
******************************************************************************/
{
    register Board_Position	bp;
    char			Avail[NTILES];
    int				row, col, i;

/*--Clear the board. */

    DEBUG_CALL(Setup_New_Game);
    bp = &Board_Tiles[0][0];
    for (row = 0; row < NROWS; ++row) {
	for (col = 0; col < NCOLS; ++col) {
	    bp->tiles[0] = NO_TILE;
	    bp->tiles[1] = NO_TILE;
	    bp->tiles[2] = NO_TILE;
	    bp->tiles[3] = NO_TILE;
	    bp->level = 0;
	}
    }

/*--Mark all tiles as available. */

    i = 0;
    for (row = 0; row < 4; ++row) {
	Avail[i++] = row + 1;
	Avail[i++] = row + 5;
	for (col = 8; col < NFACES; ++col) {
	    Avail[i++] = 1 + col % NFACES;
	}
    }
    if (i != NTILES) { (void)fprintf( stderr, "NTILES gak!\n" ); }

/*--Fill in the "odd" tile slots. */

    Pick1( &Board_Tiles[SPEC1], Avail );
    Pick1( &Board_Tiles[SPEC2], Avail );
    Pick1( &Board_Tiles[SPEC3], Avail );
    Pick1( &Board_Tiles[SPEC4], Avail );

    for (col = 1; col <= 12; ++col) {
	Pick1( &Board_Tiles[0][col], Avail );
	Pick1( &Board_Tiles[7][col], Avail );
    }
    for (row = 1; row <= 6; ++row) {
	Pick1( &Board_Tiles[row][ 3], Avail );
	Pick1( &Board_Tiles[row][10], Avail );
    }
    for (row = 2; row <= 5; ++row) {
	Pick1( &Board_Tiles[row][ 2], Avail );
	Pick1( &Board_Tiles[row][11], Avail );
    }
    for (row = 3; row <= 4; ++row) {
	Pick1( &Board_Tiles[row][ 1], Avail );
	Pick1( &Board_Tiles[row][12], Avail );
    }

/*--Now do the next square at level 2. */

    for (col = 4; col <= 9; ++col) {
	Pick2( &Board_Tiles[1][col], Avail );
	Pick2( &Board_Tiles[6][col], Avail );
    }
    for (row = 2; row <= 5; ++row) {
	Pick2( &Board_Tiles[row][4], Avail );
	Pick2( &Board_Tiles[row][9], Avail );
    }

/*--Now do the next square at level 3. */

    for (col = 5; col <= 8; ++col) {
	Pick3( &Board_Tiles[2][col], Avail );
	Pick3( &Board_Tiles[5][col], Avail );
    }
    for (row = 3; row <= 4; ++row) {
	Pick3( &Board_Tiles[row][5], Avail );
	Pick3( &Board_Tiles[row][8], Avail );
    }

/*--Now do the final square at level 4. */

    for (row = 3; row <= 4; ++row) {
	for (col = 6; col <= 7; ++col) {
	    Pick4( &Board_Tiles[row][col], Avail );
	}
    }

/*--Now set up the control information for all of the tiles. */

    Set_Tile_Controls();
    Score = NTILES;
    DEBUG_RETURN(Setup_New_Game);

} /* Setup_New_Game */

\f


/*ARGSUSED*/
void Restart_Game( w, event, params, num_params )
     Widget	w;
     XEvent	*event;
     String	*params;
     Cardinal	*num_params;
/******************************************************************************
* Called when the RESTART button is pressed.  Restart the game.
******************************************************************************/
{
    int				row;
    int				col;
    register Board_Position	bp;

/*--Reset levels and remove hilites. */

    DEBUG_CALL(Restart_Game);
    Click1 = Board_Position_NULL;
    Click2 = Board_Position_NULL;
    Score = NTILES;
    bp = &Board_Tiles[0][0];
    for (row = 0; row < NROWS; ++row) {
	for (col = 0; col < NCOLS; ++bp,++col) {
	    if      (bp->tiles[3] != NO_TILE) { bp->level = 4; }
	    else if (bp->tiles[2] != NO_TILE) { bp->level = 3; }
	    else if (bp->tiles[1] != NO_TILE) { bp->level = 2; }
	    else if (bp->tiles[0] != NO_TILE) { bp->level = 1; }
	    else { bp->level = 0; }
	}
    }

/*--Finish setting up and then redraw everything. */

    Set_Tile_Controls();
    XClearArea( XtDisplay(Board), XtWindow(Board), 0, 0, 0, 0, TRUE );
    DEBUG_RETURN(Restart_Game);

} /* Restart_Game */

\f


static void Set_Tile_Draw( row, col )
     int	row;
     int	col;
/******************************************************************************
*   row	- Specifies the row of the tile
*   col - Specifies the column of the tile
*
* Called to set the "draw" flag on a tile.  We also recursively set the
* draw flag on anyone that needs to be redrawn because we are being redrawn.
******************************************************************************/
{
    register Board_Position	bp = &Board_Tiles[row][col];

/*--If we don't exist or if we are already being redrawn then stop. */

    DEBUG_CALL(Set_Tile_Draw);
    if (bp->level == 0 || bp->draw) {
	return;
    }

/*--Redraw us.  Redraw anyone to our left that has a height greater than ours
 *  because their shadow/tile-face overlaps us. */

    bp->draw = TRUE;
    if (col > 0 &&
	Board_Tiles[row][col-1].level > bp->level) {
	Set_Tile_Draw( row, col-1 );
    }

/*--Redraw anyone below us that has a level greater than ours because their
 *  shadow/tile-face overlaps us. */

    if (row < 7 &&
	Board_Tiles[row+1][col].level > bp->level) {
	Set_Tile_Draw( row+1, col );
    }

/*--Redraw anyone below-to-the-left of us. */

    if (row < 7 &&
	col > 0 &&
	Board_Tiles[row+1][col-1].level > 0) {
	Set_Tile_Draw( row+1, col-1 );
    }

/*--Redraw anyone above-to-the-left of us that has a level greater than ours
 *  because their tile-face overlaps our tile-edge. */

    if (row > 0 && col > 0 &&
	Board_Tiles[row-1][col-1].level != bp->level) {
	Set_Tile_Draw( row-1, col-1 );
    }

/*--If we are certain specific tiles then we may need to set specific other
 *  tiles. */

    if (row == 3 || row == 4) {
	if (col == 6 || col == 7) {
	    Set_Tile_Draw( SPEC4row, SPEC4col );
	} else if (col == 1) {
	    Set_Tile_Draw( SPEC3row, SPEC3col );
	}
    }
    DEBUG_RETURN(Set_Tile_Draw);

} /* Set_Tile_Draw */

\f


static void Remove_Tile( bp, row, col )
     register Board_Position	bp;
     int			row;
     int			col;
/******************************************************************************
* Called to remove the top tile of the indicated Board_Position.
******************************************************************************/
{

/*--If the tile just went away then clear the area and allow the window
 *  background to shine through. */

    DEBUG_CALL(Remove_Tiles);
    if (bp->level == 1) {
	if (Tile_Control & SHADOW) {
	    XClearArea( XtDisplay(Board), XtWindow(Board),
		        bp->x, bp->y - Side_Y - Shadow_Y,
		        Tile_Width + Side_X + 2 + Shadow_X,
		        Tile_Height + Side_Y + 2 + Shadow_Y,
		        FALSE );
	} else {
	    XClearArea( XtDisplay(Board), XtWindow(Board),
		        bp->x, bp->y - Side_Y,
		        Tile_Width + Side_X + 2,
		        Tile_Height + Side_Y + 2,
		        FALSE );
	}
    } else {
	int	sidex = Side_X * bp->level;
	int	sidey = Side_Y * bp->level;
	if (Tile_Control & SHADOW) {
	    XClearArea( XtDisplay(Board), XtWindow(Board),
		        bp->x + sidex, bp->y - sidey - Shadow_Y,
		        Tile_Width + 2 + Shadow_X,
		        Tile_Height+ 2 + Shadow_Y,
		        FALSE );
	} else {
	    XClearArea( XtDisplay(Board), XtWindow(Board),
		        bp->x + sidex, bp->y - sidey,
		        Tile_Width + 2,
		        Tile_Height+ 2,
		        FALSE );
	}
	Set_Tile_Draw( row, col );
    }
    --bp->level;

/*--Schedule the surrounding tiles for redrawing. */

    if (col == SPEC1col) {
	if (row == SPEC4row) {
	    Set_Tile_Draw( 3, 6 );
	    Set_Tile_Draw( 3, 7 );
	    Set_Tile_Draw( 4, 6 );
	    Set_Tile_Draw( 4, 7 );
	    return;
	} else if (row == SPEC3row) {
	    Set_Tile_Draw( 3, 1 );
	    Set_Tile_Draw( 4, 1 );
	    return;
	} else if (row == SPEC2row) {
	    Set_Tile_Draw( SPEC1row, SPEC1col );
	    Set_Tile_Draw( 3, 12 );
	    Set_Tile_Draw( 4, 12 );
	    return;
	} else {
	    Set_Tile_Draw( SPEC2row, SPEC2col );
	    Set_Tile_Draw( 3, 12 );
	    Set_Tile_Draw( 4, 12 );
	    return;
	}
    }
    if (col == 1 && (row == 3 || row == 4)) {
	Set_Tile_Draw( SPEC3row, SPEC3col );
    }
    if (col == 12 && (row == 3 || row == 4)) {
	Set_Tile_Draw( SPEC2row, SPEC2col );
    }
    if (row > 0) {
	Set_Tile_Draw( row - 1, col + 1 );
	Set_Tile_Draw( row - 1, col     );
	if (col > 0 &&
	    Board_Tiles[row-1][col].level == 0) {
	    Set_Tile_Draw( row - 1, col - 1 );
	}
    }
    Set_Tile_Draw( row, col+1 );
    if (col > 0) {
	Set_Tile_Draw( row,     col - 1 );
    }
    if (row < 7) {
	Set_Tile_Draw( row + 1, col     );
	if (col > 0) {
	    Set_Tile_Draw( row + 1, col - 1 );
	}
    }
    DEBUG_RETURN(Remove_Tile);

} /* Remove_Tile */

\f


static void Touch_Tile( bp, row, col, event )
     register Board_Position	 bp;
     register XButtonEvent	*event;
/******************************************************************************
* Called when we click on a specific tile.  We decide what to do.  For a
* single click we hilite the tile unless we already have two tiles hilited.
* For a "double" click with two tiles hilited we will remove both of the
* tiles.
******************************************************************************/
{

/*--If there is no Click1 then this guy becomes it. */

    DEBUG_CALL(Touch_Tile);
    if (Click1 == Board_Position_NULL) {
	Click1 = bp;
	Click1_Row = row;
	Click1_Col = col;
	Hilite_Tile( row, col );
	DEBUG_RETURN(Touch_Tile);
	return;
    }

/*--If there is no Click2 then this guy becomes it unless he is already Click1.
 */

    if (Click1 != bp) {
	if (Click2_Row == row &&
	    Click2_Col == col &&
	    Click2_Time + Dragon_Resources.Double_Click_Time >= event->time) {
	    Click2 = bp;
	}
	if( Click2 == Board_Position_NULL) {
	    Click2 = bp;
	    Click2_Row = row;
	    Click2_Col = col;
	    Click2_Time = event->time;
	    Hilite_Tile( row, col );
	    DEBUG_RETURN(Touch_Tile);
	    return;
	}

/*--If this guy is not one Click1 and not Click2 then we have an error. */

	if (Click2 != bp) {
	    XBell( XtDisplay(Board), 0 );
	    DEBUG_RETURN(Touch_Tile);
	    return;
	}
    }

/*--If he double-clicks then remove both tiles. */

    if (Click2 != Board_Position_NULL &&
	Click2_Time + Dragon_Resources.Double_Click_Time >= event->time) {
	One_Button_Hint = FALSE;
	Remove_Tile( Click1, Click1_Row, Click1_Col );
	Click1 = Board_Position_NULL;
	Remove_Tile( Click2, Click2_Row, Click2_Col );
	Click2 = Board_Position_NULL;
	Score -= 2;
	Draw_All_Tiles();
	DEBUG_RETURN(Touch_Tile);
	return;
    }

/*--2nd click on any tile means turn-it-off. */

    if (Click1 == bp) {
	int	s;
	Hilite_Tile( Click1_Row, Click1_Col );
	Click1 = Click2;
	s = Click1_Row;
	Click1_Row = Click2_Row;
	Click2_Row = s;
	s = Click1_Col;
	Click1_Col = Click2_Col;
	Click2_Col = s;;
	Click2 = Board_Position_NULL;
    } else {
	Click2 = Board_Position_NULL;
	Hilite_Tile( Click2_Row, Click2_Col );
    }
    Click2_Time = event->time;
    DEBUG_RETURN(Touch_Tile);

} /* Touch_Tile */

\f


/*ARGSUSED*/
void Tile_Remove( w, event, params, num_params )
     Widget		w;
     XButtonEvent	*event;
     String		*params;
     Cardinal		*num_params;
/******************************************************************************
* Called when the remove-selected-tile-pair mouse button is pressed.
******************************************************************************/
{

    DEBUG_CALL(Tile_Remove);
    if (Click1 != Board_Position_NULL &&
	Click2 != Board_Position_NULL) {
	Click2_Time = event->time;
	Touch_Tile( Click2, Click2_Row, Click2_Col, event );
    }
    DEBUG_RETURN(Tile_Remove);

} /* Tile_Remove */

\f


static Boolean Touch( bp, event )
     register Board_Position	 bp;
     register XButtonEvent	*event;
/******************************************************************************
* Return TRUE if this XButtonEvent touched this Board_Position.
******************************************************************************/
{
    int		face_x = bp->x + bp->level * Side_X;
    int		face_y = bp->y - bp->level * Side_Y;

/*--Does this tile exist? */

    DEBUG_CALL(Touch);
    if (bp->level == 0) {
	DEBUG_RETURN(Touch);
	return FALSE;
    }

/*--Did we touch the face? */

    if (event->x >= face_x && event->x <= face_x + Tile_Width + 1 &&
	event->y >= face_y && event->y <= face_y + Tile_Height + 1) {
	DEBUG_RETURN(Touch);
	return TRUE;
    }

/*--Did we touch the side? */

    if (event->x >= bp->x && event->x <= bp->x + Tile_Width + 1 &&
	event->y >= bp->y && event->y <= bp->y + Tile_Height + 1) {
	DEBUG_RETURN(Touch);
	return TRUE;
    }

/*--Guess not. */

    DEBUG_RETURN(Touch);
    return FALSE;

} /* Touch */

\f


/*ARGSUSED*/
void Tile_Press( w, event, params, num_params )
	      Widget		 w;
     register XButtonEvent	*event;
	      String		*params;
	      Cardinal		*num_params;
/******************************************************************************
* Called when the Board receives a BtnDown event.
******************************************************************************/
{
    register Board_Position	bp;
    int		x;
    int		y;
    int		row;
    int		col;

/*--Figure out a rough row/col coordinate for the click. */

    DEBUG_CALL(Tile_Press);
    y = event->y - Board_Tile0_Y;
    if (y < 0) { return; }
    row = y / (Tile_Height + 1);
    if (row > 7) { return; }
    x = event->x - Board_Tile0_X;
    if (x < 0) { return; }
    col = x / (Tile_Width + 1);
    if (col < 0 || row > 14) { goto Touched; }

/*--See if we are a special tile. */

    if (col == 0) {
	if (Touch( bp = &Board_Tiles[SPEC3], event )) {
	    Touch_Tile( bp, SPEC3row, SPEC3col, event );
	    goto Touched;
	}
	goto Touched;
    } else if (col == 13) {
	if (Touch( bp = &Board_Tiles[SPEC2], event )) {
	    Touch_Tile( bp, SPEC2row, SPEC2col, event );
	    goto Touched;
	}
	if (Touch( bp = &Board_Tiles[4][12], event )) {
	    Touch_Tile( bp, 4, 12, event );
	    goto Touched;
	}
	if (Touch( bp = &Board_Tiles[3][12], event )) {
	    Touch_Tile( bp, 3, 12, event );
	    goto Touched;
	}
	goto Touched;
    } else if (col == SPEC1col) {
	if (Touch( bp = &Board_Tiles[SPEC1], event )) {
	    Touch_Tile( bp, SPEC1row, SPEC1col, event );
	    goto Touched;
	}
	if (Touch( bp = &Board_Tiles[SPEC2], event )) {
	    Touch_Tile( bp, SPEC2row, SPEC2col, event );
	    goto Touched;
	}
	goto Touched;
    } else if ((row == 3 || row == 4) && (col == 6 || col == 7)) {
	if (Touch( bp = &Board_Tiles[SPEC4], event )) {
	    Touch_Tile( bp, SPEC4row, SPEC4col, event );
	    goto Touched;
	}
    }

/*--See if the x/y falls exactly into somebody else's tile face. */

    if (col > 0 && row < 7) {
	if (Touch( bp = &Board_Tiles[row+1][col-1], event )) {
	    Touch_Tile( bp, row+1, col-1, event );
	    goto Touched;
	}
    }
    if (row < 7) {
	if (Touch( bp = &Board_Tiles[row+1][col], event )) {
	    Touch_Tile( bp, row+1, col, event );
	    goto Touched;
	}
    }
    if (col > 0) {
	if (Touch( bp = &Board_Tiles[row][col-1], event )) {
	    Touch_Tile( bp, row, col-1, event );
	    goto Touched;
	}
    }

/*--We don't have a touch on a neighbor so it must be us. */

    if (Touch( bp = &Board_Tiles[row][col], event )) {
	Touch_Tile( bp, row, col, event );
	goto Touched;
    }

  Touched :
    DEBUG_RETURN(Tile_Press);

} /* Tile_Press */

\f


static Boolean Tile_Not_Free( row, col )
     int	row;
     int	col;
/******************************************************************************
* Returns TRUE if the tile has neither a left nor a right side free.
******************************************************************************/
{

/*--The 4 in the center can be covered by SPEC4. */

    if (row == 3 || row == 4) {
	if ((col == 6 || col == 7) &&
	    Board_Tiles[SPEC4].level > 0) { return TRUE; }
	else if (col == 1 &&
		 Board_Tiles[SPEC3].level > 0 &&
		 Board_Tiles[row][col+1].level > 0) { return TRUE; }
	else if (col == 12 &&
		 Board_Tiles[SPEC2].level > 0 &&
		 Board_Tiles[row][col-1].level > 0) { return TRUE; }
    }
	
/*--If a tile has a neighbor then he isn't free. */

    if (Board_Tiles[row][col-1].level >= Board_Tiles[row][col].level &&
	Board_Tiles[row][col+1].level >= Board_Tiles[row][col].level) {
	return TRUE;
    }

/*--Check the special tiles. */

    if (col == SPEC1col) {

/*--Tiles 1, 3, and 4 are always free. */

	if (row != SPEC2row) { return FALSE; }

/*--Tile 2 is free if tile 1 is gone or if its two normal neighbors are gone.*/

	if (Board_Tiles[SPEC1].level > 0 &&
	    (Board_Tiles[3][12].level > 0 ||
	     Board_Tiles[4][12].level > 0)) { return TRUE; }
    }
    return FALSE;

} /* Tile_Not_Free */

\f


/*ARGSUSED*/
void Tile_Release( w, event, params, num_params )
     Widget	w;
     XEvent	*event;
     String	*params;
     Cardinal	*num_params;
/******************************************************************************
* Called when the Board receives a BtnUp event.
******************************************************************************/
{
    extern int	Cheating;

/*--If there is a Click2 and if the tile type does not match with Click1 then
 *  unhilite Click2. */

    DEBUG_CALL(Tile_Release);
    if (!Cheating &&
	Click1 != Board_Position_NULL &&
	Click2 != Board_Position_NULL) {
	int		tile1, tile2;

	tile1 = Click1->tiles[Click1->level-1];
	tile2 = Click2->tiles[Click2->level-1];
	if (/* Do tile faces match for those types that must match exactly? */
	    ((tile1 > 8 || tile2 > 8) && tile1 != tile2) ||
	    /* Are both tiles seasons? */
	    (tile1 <= 4 && tile2 > 4) ||
	    /* Are both tiles flowers? */
	    (tile1 >= 5 && tile1 <= 8 && (tile2 < 5 || tile2 > 8))) {
	    /* They don't match. */
	    if (Dragon_Resources.Sticky_Tile) {
		/* Simply remove tile 2 from selected tiles. */
		Hilite_Tile( Click2_Row, Click2_Col );
	    } else {
		/* Remove tile 1 from selection and make tile 2 => tile 1.*/
		Hilite_Tile( Click1_Row, Click1_Col );
 		Click1 	    = Click2;
 		Click1_Row  = Click2_Row;
 		Click1_Col  = Click2_Col;
		Click2_Col  = 0;	/* Prevent dbl-clk removing 1 tile. */
	    }
	    Click2      = Board_Position_NULL;
	    Click2_Time = 0;
	}
    }

/*--If this tile has a left or a right neighbor then he isn't allowed. */

    if (!Cheating) {
	if (Click2 != Board_Position_NULL &&
	    Tile_Not_Free( Click2_Row, Click2_Col)) {
	    Hilite_Tile( Click2_Row, Click2_Col );
	    Click2 = Board_Position_NULL;
	    Click2_Time = 0;
	}
	if (Click1 != Board_Position_NULL &&
	    Tile_Not_Free( Click1_Row, Click1_Col)) {
	    Hilite_Tile( Click1_Row, Click1_Col );
	    Click1 = Board_Position_NULL;
	}
    }

    DEBUG_RETURN(Tile_Release);

} /* Tile_Release */

\f


static void Next_Tile( Click, row, col )
     int	 Click;
     int	*row;
     int	*col;
/******************************************************************************
* Returns the "next" tile past row/col that exists and is "free".  Returns 0,0
* when we run out of tiles.
******************************************************************************/
{
    int		tile1, tile2;

/*--Loop until we give up.  Advance the column.  Advance the row on column
 *  overflow.  Give up on row overflow. */

    DEBUG_CALL(Next_Tile);
    for (;;) {
	++*col;
	if (*col > 14) {
	    *col = 1;
	    ++*row;
	    if (*row > 7) {
		*row = 0;
		*col = 0;
		break;
	    }
	}

/*--Check this tile.  If it doesn't exist or isn't free then ignore it. */

	if (Board_Tiles[*row][*col].level == 0) { continue; }
	if (Tile_Not_Free( *row, *col )) { continue; }

/*--If moving Click1 then return now. */

	if (Click == 1) { break; }

/*--Continue the search if this tile does not match Click1. */

	tile1 =  Click1->tiles[Click1->level-1];
	tile2 = Board_Tiles[*row][*col].tiles[Board_Tiles[*row][*col].level-1];
	if (/* Do tile faces match for those types that must match exactly? */
	    ((tile1 > 8 || tile2 > 8) && tile1 != tile2) ||
	    /* Are both tiles seasons? */
	    (tile1 <= 4 && tile2 > 4) ||
	    /* Are both tiles flowers? */
	    (tile1 >= 5 && tile1 <= 8 && (tile2 < 5 || tile2 > 8))) {
	    /* They don't match. */
	    continue;
	}
	break;
    }
    DEBUG_RETURN(Next_Tile);

} /* Next_Tile */

\f


/*ARGSUSED*/
void Hints( w, event, params, num_params )
     Widget		w;
     XButtonEvent	*event;
     String		*params;
     Cardinal		*num_params;
/******************************************************************************
* If Click1 not present then search for the "first" remaining tile otherwise
* use Click1 as our current "base" tile.
* If Click1 present but not Click2 then search for any match for Click1.
* If Click2 not present either then search for the first remaining tile past 
* Click1 otherwise search for the first remaining tile past Click2.
* Keep searching for a new Click2 until we hit a matching tile or until we
* run out.  Exit on match with new tile as Click2.
* Advance Click1 and start a new search for Click2.  If we run out on Click1
* then remove Click1.
******************************************************************************/
{

/*--If we have a Click1 but no Click2 then search for a Click2. */

    if (Click1 != Board_Position_NULL &&
	Click2 == Board_Position_NULL) {
	One_Button_Hint = TRUE;
	Click2_Row = 0;
	Click2_Col = 0;
	for (;;) {
	    Next_Tile( 2, &Click2_Row, &Click2_Col );
	    if (Click2_Col == 0) {
		One_Button_Hint = FALSE;
		Hilite_Tile( Click1_Row, Click1_Col );
		Click1 = Board_Position_NULL;
		DEBUG_RETURN(Hints);
		return;
	    }
	    if (Click2_Row != Click1_Row ||
		Click2_Col != Click1_Col) {
		Click2 = &Board_Tiles[Click2_Row][Click2_Col];
		Hilite_Tile( Click2_Row, Click2_Col );
		DEBUG_RETURN(Hints);
		return;
	    }
	}
    }

/*--Find a Click1 to work with if we don't already have one. */

    DEBUG_CALL(Hints);
    if (Click1 == Board_Position_NULL) {
	Click1_Row = 0;
	Click1_Col = 0;
	Next_Tile( 1, &Click1_Row, &Click1_Col );
	if (Click1_Col == 0) { 
	    DEBUG_RETURN(Hints);
	    return;
	}
	Hilite_Tile( Click1_Row, Click1_Col );
	Click1 = &Board_Tiles[Click1_Row][Click1_Col];
    }

/*--Find our starting position for Click2 if we don't have one. */

    if (Click2 == Board_Position_NULL) {
	Click2_Row = Click1_Row;
	Click2_Col = Click1_Col;
    } else {
	Hilite_Tile( Click2_Row, Click2_Col );
	Click2 = Board_Position_NULL;
    }

/*--Loop until we get something. */

    for (;;) {
	Next_Tile( 2, &Click2_Row, &Click2_Col );
	if (Click2_Col != 0) {
	    if (Click2_Row != Click1_Row ||
		Click2_Col != Click1_Col) {
		Click2 = &Board_Tiles[Click2_Row][Click2_Col];
		Hilite_Tile( Click2_Row, Click2_Col );
		DEBUG_RETURN(Hints);
		return;
	    }
	} else {
	    Hilite_Tile( Click1_Row, Click1_Col );
	    Click1 = Board_Position_NULL;
	    if (One_Button_Hint) {
		One_Button_Hint = FALSE;
		return;
	    }
	    Next_Tile( 1, &Click1_Row, &Click1_Col );
	    if (Click1_Col == 0) {
		DEBUG_RETURN(Hints);
		return;
	    }
	    Hilite_Tile( Click1_Row, Click1_Col );
	    Click1 = &Board_Tiles[Click1_Row][Click1_Col];
	    Click2_Row = Click1_Row;
	    Click2_Col = Click1_Col;
	}
    }

} /* Hints */