The Tile Game for DOORS

tilegameNote 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!

The code demonstrates many features of DXL that are not well documented.

//  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