//  Tile Game

/*
    The Tile Game for DOORS.
    
    Note the arrangement of tiles - in numerical order, left to right,
    starting from the top left.
    
    Start game by clicking on start and then slide tiles back into the
    original positions to win.
    
    Slide a tile by clicking on it.
    
    There is a time limit set so click quick!
    
    smartDXL.com	 10 May 2005
*/


DB  dbMain       = null
DBE dbeCanvas    = null
DBE dbeStart     = null

int GRID_SIZE    = 3
int TILE_SIZE    = 50
int MARGIN       = 4

int numPositions = GRID_SIZE * GRID_SIZE

int gridPosition[numPositions]

int emptyPosition = numPositions - 1

bool gameOver = true

Date    d         = null
int     timeStart = 0
int     timeNow   = 0
int     timeLimit = numPositions * 3

/******************************************************************************
	startTimer
******************************************************************************/
void startTimer()
{
	d = today
	timeStart = intOf(d)
}


/******************************************************************************
	checkTimer
******************************************************************************/
void checkTimer()
{
	d = today
	timeNow = intOf(d)
	
	if (timeNow - timeStart > timeLimit)
	{
		gameOver = true
		infoBox("GAME OVER")
	}
}


/******************************************************************************
	isSolution
******************************************************************************/
bool isSolution()
{
	int pos = 0
	
	if (emptyPosition == numPositions - 1)
	{
		for (pos = 0; pos < numPositions - 1; pos++)
		{
			if (gridPosition[pos] != pos + 1)
			{
				return(false)
			}
		}
		return(true)
	}
	return(false)
}


/******************************************************************************
	initialiseGrid
	
	Arrange the tiles in the grid in numerical order with the last grid
	position being empty.
******************************************************************************/
void initialiseGrid()
{
	int pos  = 0
	
	for (pos = 0; pos < numPositions; pos++)
	{
		gridPosition[pos] = pos + 1
	}
	
	emptyPosition = numPositions - 1
}


/******************************************************************************
	drawTile
******************************************************************************/
void drawTile(int pos)
{
	int x    = 0
	int y    = 0
	int xAdj = 0
	int yAdj = 0
	
	
	// coordinates for grid around tile
	x = (pos % GRID_SIZE) * TILE_SIZE
	y = (pos / GRID_SIZE) * TILE_SIZE
	
	// Adjustment to coordinates for number on tile
	xAdj = (TILE_SIZE/2) - width(dbeCanvas, gridPosition[pos] "") / 2
	yAdj = (TILE_SIZE/2) + height(dbeCanvas, gridPosition[pos] "") / 2
	
	//print("draw tile at pos[" pos "] x=" x " y=" y "\n")
	
	// draw grid lines around tile
	realColor(dbeCanvas, realColor_Black)
	box(dbeCanvas, x, y, TILE_SIZE, TILE_SIZE)
	
	// draw the tile
	realColor(dbeCanvas, realColor_White)
	rectangle(dbeCanvas, x + (MARGIN / 2), y + (MARGIN / 2), TILE_SIZE - MARGIN, TILE_SIZE - MARGIN)
	
	// draw number on the tile
	font(dbeCanvas, 1, HeadingsFont)
	realColor(dbeCanvas, realColor_Red)
	draw(dbeCanvas, x + xAdj, y + yAdj, gridPosition[pos] "")
}


/******************************************************************************
	moveTile
	
	move tile from oldPos to emptyPosition
******************************************************************************/
void moveTile(int oldPos)
{
	//print("Move from " oldPos " to " emptyPosition "\n")
	gridPosition[emptyPosition] = gridPosition[oldPos]
	emptyPosition = oldPos
}


/******************************************************************************
	doDrawCanvas
	
	Redraw the canvas.
******************************************************************************/
void doDrawCanvas(DBE dbeCanvas)
{
	int pos  = 0
	

	// fill background
	realBackground(dbeCanvas, realColor_Grey66)
	
	// draw tiles
	for (pos = 0; pos < numPositions; pos++)
	{
		if (pos != emptyPosition)
		{
			drawTile(pos)
		}
	}
}


/******************************************************************************
	doMouse
	
	Callback from user mouse click on the canvas.
	
	Move a tile and redraw the canvas.
	
******************************************************************************/
void doMouse(DBE dbeCanvas, int but, bool ctrl, int x, int y)
{
	const int LEFT_MOUSE  = 1
	const int RIGHT_MOUSE = 3
	
	int xEmpty    = 0
	int yEmpty    = 0
	int oldPos    = 0
	
	// only react if user is still in the game
	if (gameOver)
	{
		return
	}
	
	// only react to a left mouse click
	if (but != LEFT_MOUSE)
	{
		return
	}
	
	// corrdinates of emptyPosition
	xEmpty = (emptyPosition % GRID_SIZE) * TILE_SIZE + (MARGIN / 2)
	yEmpty = (emptyPosition / GRID_SIZE) * TILE_SIZE + (MARGIN / 2)
	
	
	// mouse click is valid if it is on tile next to the empty position
	if ((x < xEmpty - MARGIN) && 
	    (x > xEmpty - TILE_SIZE) && 
	     y > yEmpty && 
	     y < yEmpty + TILE_SIZE)
	{
		// click in tile to left of empty position
		oldPos = emptyPosition - 1
		//print("Tile to left = " oldPos "\n")
	}
	else if ((x > xEmpty + TILE_SIZE + MARGIN) && 
	         (x < xEmpty + TILE_SIZE + TILE_SIZE) && 
	          y > yEmpty && 
	          y < yEmpty + TILE_SIZE)
	{
		// click in tile to right of empty position
		oldPos = emptyPosition + 1
		//print("Tile to right = " oldPos "\n")
	}
	else if ((y < yEmpty - MARGIN) && 
	         (y > yEmpty - TILE_SIZE) && 
	          x > xEmpty && 
	          x < xEmpty + TILE_SIZE)
	{
		// click in tile above empty position
		oldPos = emptyPosition - GRID_SIZE
		//print("Tile above = " oldPos "\n")
	}
	else if ((y > yEmpty + TILE_SIZE + MARGIN) && 
	         (y < yEmpty + TILE_SIZE + TILE_SIZE) && 
	          x > xEmpty && 
	          x < xEmpty + TILE_SIZE)
	{
		// click in tile below empty position
		oldPos = emptyPosition + GRID_SIZE
		//print("Tile below = " oldPos "\n")
	}
	else
	{
		return
	}
	
	// move the tile to the empty position
	moveTile(oldPos)
	doDrawCanvas(dbeCanvas)
	
	if (isSolution())
	{
		infoBox("CONGRATULATIONS!")
	}
	else
	{
		checkTimer()
	}
}


/******************************************************************************
	doShuffleGrid
	
	Scatters the tiles in the grid. 
******************************************************************************/
void doShuffleGrid(DB db)
{
	int iterations = 0
	int i          = 0
	int newPos     = 0
	
	iterations = GRID_SIZE * 20
	
	for (i = 0; i < iterations; i++)
	{
		if (i % 2 == 0)
		{
			// move up/down for even iterations
			if (emptyPosition < GRID_SIZE)
			{
				// empty position is in the top row so we can only move down
				newPos = emptyPosition + GRID_SIZE
			}
			else if (emptyPosition >= numPositions - GRID_SIZE)
			{
				// empty position is in the bottom row so we can only move up
				newPos = emptyPosition - GRID_SIZE
			}
			else
			{
				// randomly select to move up or down
				if (random(2) == 0)
				{
					newPos = emptyPosition - GRID_SIZE
				}
				else
				{
					newPos = emptyPosition + GRID_SIZE
				}
			}	
		}
		else
		{
			// move left/right for odd iterations
			if (emptyPosition % GRID_SIZE == 0)
			{
				// empty position is in the left-most column so we can only move right
				newPos = emptyPosition + 1
			}
			else if (emptyPosition == GRID_SIZE - 1)
			{
				// empty position is in the right-most column of top row so we can only move left
				newPos = emptyPosition - 1
			}
			else if ((emptyPosition % GRID_SIZE) + 1 == GRID_SIZE)
			{
				// empty position is in the right-most column so we can only move left
				newPos = emptyPosition - 1
			}
			else
			{
				// randomly select to move left or right
				if (random(2) == 0)
				{
					newPos = emptyPosition - 1
				}
				else
				{
					newPos = emptyPosition + 1
				}
			}
		}
		
		// move the tile to the new position
		moveTile(newPos)
	}
	
	doDrawCanvas(dbeCanvas)
	gameOver = false
	startTimer()
}


/******************************************************************************
	doCloseAction
	
	Clean up data structures when closing the dialog.
	
******************************************************************************/
void doCloseAction(DB db)
{
	hide(dbMain)
	destroy(dbMain)
	dbMain = null
}


/******************************************************************************
	MAIN
******************************************************************************/
initialiseGrid()

dbMain = create("Tile Puzzle", styleCentered | styleFixed)

dbeCanvas = canvas(dbMain, 
                   (GRID_SIZE * TILE_SIZE) + (GRID_SIZE * MARGIN), 
                   (GRID_SIZE * TILE_SIZE) + (GRID_SIZE * MARGIN), 
                   doDrawCanvas)

dbeStart = apply(dbMain, "Start", doShuffleGrid)                  
close(dbMain, true, doCloseAction)

realize dbMain

set(dbeCanvas, doMouse)

doDrawCanvas(dbeCanvas)

show dbMain