Tetris for DOORS

Tetris for DOORS

Aim of the game is to fill as many squares as possible within the time allowed. You score a point for every square that is filled and a bonus of 50 points for each full row of squares.

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

//  Tetris for DOORS

/*
	Aim of the game is to fill as many squares as possible within the
	time allowed. You score a point for every square that is filled
	and a bonus of 50 points for each full row of squares.

	Use the cursor keys to control movement of the shapes as follows:

		LEFT	Move shape to the left
		RIGHT	Moves shape to the right
		UP		Rotate shape
		DOWN	Move shape down

	Limitation:
	There is no way in DXL to make the shapes fall automatically and
	capture keyboard input at the same time, so you have to move the
	shapes down yourself.

	To do:
		Store high scores in conf file.

		Allow shapes to be moved sideways underneath another shape.

		Make full rows disappear and move other rows down.

	Enjoy!

    smartDXL.com	17 May 2005

*/

DB  dbMain     = null
DBE dbeGrid    = null

/************************************
	Grid
************************************/
const int SQUARE_SIZE  = 25
const int SQUARE_EDGE  = 2
const int BOARD_WIDTH  = 12
const int BOARD_HEIGHT = 20

Array gridColours = create(BOARD_WIDTH, BOARD_HEIGHT)  // Colour of each square

/************************************
	Shapes
************************************/
const int NUM_COORDS = 4

const int SHAPE_LINE[NUM_COORDS * 2]      = { -2,  0, -1,  0,  0,  0,  1,  0 }
const int SHAPE_SQUARE[NUM_COORDS * 2]    = { -1,  1,  0,  1,  0,  0, -1,  0 }
const int SHAPE_S[NUM_COORDS * 2]         = {  1,  1,  0,  1,  0,  0, -1,  0 }
const int SHAPE_Z[NUM_COORDS * 2]         = { -1,  1,  0,  1,  0,  0,  1,  0 }
const int SHAPE_RIGHT[NUM_COORDS * 2]     = { -1,  0,  0,  0,  1,  0,  1, -1 }
const int SHAPE_LEFT[NUM_COORDS * 2]      = { -1, -1, -1,  0,  0,  0,  1,  0 }
const int SHAPE_TRIANGLE[NUM_COORDS * 2]  = { -1,  0,  0,  0,  1,  0,  0,  1 }

const int LINE_SHAPE     = 0
const int SQUARE_SHAPE   = 1
const int S_SHAPE        = 2
const int Z_SHAPE        = 3
const int RIGHT_SHAPE    = 4
const int LEFT_SHAPE     = 5
const int TRIANGLE_SHAPE = 6

const int NUM_SHAPES     = 7

/************************************
	Current shape and its position and colour
************************************/
int shapeColour                 = 0		// Colour of current shape
int shapeX                      = 0		// X-Coordinate of current shape
int shapeY                      = 0		// Y-Coordinate of current shape
int shapeCoords[NUM_COORDS * 2] = {}    // Geometry of the current shape

bool firstTime = true                   // Display welcome message if true

/************************************
	Scoring
************************************/
const int TIME_LIMIT   = 120            // seconds

int score     = 0
int timeStart = 0

/************************************
	startTimer
************************************/
void startTimer()
{
	Date d = null

	d = today
	timeStart = intOf(d)
}

/************************************
	setShape

	Update the coordinates of the current shape from the given array.
************************************/
void setShape(int coords[NUM_COORDS * 2])
{
	int i  = 0

	for (i = 0; i < NUM_COORDS * 2; i++)
	{
		shapeCoords[i] = coords[i]
	}
}

/************************************
	getNextShape

	Get the next shape and its colour.
************************************/
bool getNextShape()
{
	int shape = 0
	int c     = 0

	// initial start position for new shape
	shapeX   = BOARD_WIDTH / 2
	shapeY   = 0

	// see if the square at the start position is full
	c = (int get(gridColours, shapeX, shapeY))

	// empty squares are black
	if (c != realColor_Black)
	{
		// game over
		return(false)
	}

	// randomly select the next shape
	shape = random(NUM_SHAPES)

	if (shape == LINE_SHAPE)
	{
		setShape(SHAPE_LINE)
		shapeColour = realColor_Red
	}
	else if (shape == SQUARE_SHAPE)
	{
		setShape(SHAPE_SQUARE)
		shapeColour = realColor_Pink
	}
	else if (shape == S_SHAPE)
	{
		setShape(SHAPE_S)
		shapeColour = realColor_Blue
	}
	else if (shape == Z_SHAPE)
	{
		setShape(SHAPE_Z)
		shapeColour = realColor_Yellow
	}
	else if (shape == RIGHT_SHAPE)
	{
		setShape(SHAPE_RIGHT)
		shapeColour = realColor_Sea_Green
	}
	else if (shape == LEFT_SHAPE)
	{
		setShape(SHAPE_LEFT)
		shapeColour = realColor_Orange
	}
	else if (shape == TRIANGLE_SHAPE)
	{
		setShape(SHAPE_TRIANGLE)
		shapeColour = realColor_Thistle
	}

	return(true)
}

/************************************
	paintSquare
************************************/
void paintSquare(int x, int y, int col)
{
	int xCoord = 0
	int yCoord = 0

	xCoord = (x * SQUARE_SIZE)
	yCoord = (y * SQUARE_SIZE)

	realColour(dbeGrid, col)
	rectangle(dbeGrid, xCoord, yCoord, SQUARE_SIZE, SQUARE_SIZE)
	realColour(dbeGrid, realColor_Black)
	box(dbeGrid, xCoord, yCoord, SQUARE_SIZE, SQUARE_SIZE)
}

/************************************
	paintShape
************************************/
void paintShape(int col)
{
	int x = 0
	int y = 0
	int i = 0

	// loop through shape coordinates and draw the squares
	for (i = 0; i < NUM_COORDS; i++)
	{
		x = shapeX + shapeCoords[i * 2]
		y = shapeY + shapeCoords[i * 2 + 1]

		paintSquare(x, y, col)
		put(gridColours, col, x, y)
	}
}

/************************************
	inShape
************************************/
bool inShape(int x, int y)
{
	int j   = 0
	int myX = 0
	int myY = 0

	// square isn't empty - see if it is part of current shape
	for (j = 0; j < NUM_COORDS; j++)
	{
		myX = shapeX + shapeCoords[j * 2]
		myY = shapeY + shapeCoords[j * 2 + 1]

		if (x == myX && y == myY)
		{
			return(true)
		}
	}

	return(false)
}

/************************************
	getNewCoords

	Return the coordinates of the resulting shape, if the current shape was
	to be rotated by 90 degrees clockwise.
************************************/
void getNewCoords(int coords[NUM_COORDS * 2])
{
	int x   = 0
	int y   = 0
	int i   = 0

	for (i = 0; i < NUM_COORDS; i++)
	{
		x = shapeCoords[i * 2]
		y = shapeCoords[i * 2 + 1]

		coords[i * 2 + 1] = x

		coords[i * 2] = - y
	}
}

/************************************
	canRotate

	Returns TRUE if the current shape can be rotated clockwise by 90 degrees
	without going off the grid or onto a square that is already full.
************************************/
bool canRotate()
{
	int x   = 0
	int y   = 0
	int i   = 0
	int c   = 0
	int newCoords[NUM_COORDS * 2]     = {}

	// find the proposed coordinates after the rotation
	getNewCoords(newCoords)

	// loop through shape coordinates and check the squares
	for (i = 0; i < NUM_COORDS; i++)
	{
		x = shapeX + newCoords[i * 2]
		y = shapeY + newCoords[i * 2 + 1]

		// see if the square is off the board
		if (x >= BOARD_WIDTH || x < 0 || y >= BOARD_HEIGHT)
		{
			return(false)
		}

		// see if the square is full
		c = (int get(gridColours, x, y))

		// empty squares are black
		if (c != realColor_Black)
		{
			if (!inShape(x, y))
			{
				// square if already full
				return(false)
			}
		}
	}

	return(true)
}

/************************************
	canMoveTo

	Returns TRUE if the current shape can moved to the new position
	without going off the grid or onto a square that is already full.
************************************/
bool canMoveTo(int newX, int newY)
{
	int x   = 0
	int y   = 0
	int i   = 0
	int c   = 0

	// loop through shape coordinates and check the squares
	for (i = 0; i < NUM_COORDS; i++)
	{
		x = newX + shapeCoords[i * 2]
		y = newY + shapeCoords[i * 2 + 1]

		// see if the square is off the board
		if (x >= BOARD_WIDTH || x < 0 || y >= BOARD_HEIGHT)
		{
			return(false)
		}

		// see if the square is full
		c = (int get(gridColours, x, y))

		// empty squares are black
		if (c != realColor_Black)
		{
			if (!inShape(x, y))
			{
				// square is already full
				return(false)
			}
		}
	}

	return(true)
}

/************************************
	checkForFullRows

	When a complete row is full it is painted white.
************************************/
void checkForFullRows()
{
	int  x       = 0
	int  y       = 0
	int  c       = 0
	bool fullRow = true

	for (y = 0; y < BOARD_HEIGHT; y++)
	{
		fullRow = true

		for (x = 0; x < BOARD_WIDTH; x++)
		{
			c = (int get(gridColours, x, y))

			// empty squares are black
			if (c == realColor_Black)
			{
				fullRow = false
				break
			}
		}

		if (fullRow)
		{
			// row is full
			for (x = 0; x < BOARD_WIDTH; x++)
			{
				paintSquare(x, y, realColor_White)
				put(gridColours, realColor_White, x, y)
			}
		}
	}
}

/************************************
	drawWelcomeMessage
************************************/
void drawWelcomeMessage()
{
	realColor(dbeGrid, realColor_White)
	font(dbeGrid, 4, HeadingsFont)
	draw(dbeGrid, 110, 40, "Welcome to")

	font(dbeGrid, 1, HeadingsFont)
	draw(dbeGrid, 73, 70, "Tetris for DOORS")

	paintSquare(4, 4, realColor_Red)
	paintSquare(5, 4, realColor_Red)
	paintSquare(6, 4, realColor_Red)
	paintSquare(7, 4, realColor_Red)

	realColor(dbeGrid, realColor_White)
	font(dbeGrid, 2, TextFont)
	draw(dbeGrid, 20, 150, "The aim of the game is to fill as many squares as")
	draw(dbeGrid, 20, 165, "possible within the time allowed. You score one")
	draw(dbeGrid, 20, 180, "point for each square that is filled and a bonus")
	draw(dbeGrid, 20, 195, "of 50 points for each full row.")

	draw(dbeGrid, 20, 220, "Unfortunately there is no way in DXL to make the")
	draw(dbeGrid, 20, 235, "shapes fall automatically and capture keyboard")
	draw(dbeGrid, 20, 250, "input at the same time so you have to control")
	draw(dbeGrid, 20, 265, "downward movement with the keyboard.")

	paintSquare(2, 11, realColor_Blue)
	paintSquare(2, 12, realColor_Blue)
	paintSquare(3, 12, realColor_Blue)
	paintSquare(3, 13, realColor_Blue)

	paintSquare(8, 12, realColor_Yellow)
	paintSquare(9, 12, realColor_Yellow)
	paintSquare(7, 13, realColor_Yellow)
	paintSquare(8, 13, realColor_Yellow)

	realColor(dbeGrid, realColor_White)
	draw(dbeGrid, 50, 380, "Use the cursor keys as follows:")

	draw(dbeGrid, 50, 400, "LEFT");  draw(dbeGrid, 110, 400, "Move shape to the left")
	draw(dbeGrid, 50, 420, "RIGHT"); draw(dbeGrid, 110, 420, "Moves shape to the right")
	draw(dbeGrid, 50, 440, "UP");    draw(dbeGrid, 110, 440, "Rotate shape")
	draw(dbeGrid, 50, 460, "DOWN");  draw(dbeGrid, 110, 460, "Move shape down")

	draw(dbeGrid, 90, 490, "HIT any key to begin ...")
}

/************************************
	newGame
************************************/
void newGame()
{
	int x = 0
	int y = 0

	// paint the grid background in black
	realBackground(dbeGrid, realColor_Black)

	// loop through and initialise all squares on the grid
	for (x = 0; x < BOARD_WIDTH; x++)
	{
		for (y = 0; y < BOARD_HEIGHT; y++)
		{
			// draw grid lines
			//realColor(dbeGrid, realColor_Grey66)
			//box(dbeGrid, x * SQUARE_SIZE, y * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)

			// all squares are black to start with
			put(gridColours, realColor_Black, x, y)
		}
	}

	// show instructions the first time we initialise the grid
	if (firstTime)
	{
		drawWelcomeMessage()
	}
	else
	{
		// get the first shape
		if (getNextShape())
		{
			paintShape(shapeColour)
		}
	}

	// reset scoring
	score = 0
	startTimer()
}

/************************************
	gameOver
************************************/
void gameOver()
{
	int y = 0
	int c = 0

	// count full rows for bonus points
	for (y = 0; y < BOARD_HEIGHT; y++)
	{
		c = (int get(gridColours, 0, y))

		// all squares in a full row are white
		if (c == realColor_White)
		{
			score += 50
		}
	}

	infoBox("GAME OVER\nYou scored " score " points")

	// start again
	newGame()
}

/************************************
	checkTimer
************************************/
void checkTimer()
{
	Date d       = null
	int  timeNow = null

	d = today
	timeNow = intOf(d)

	if (timeNow - timeStart > TIME_LIMIT)
	{
		gameOver()
	}
}

/************************************
	moveRight
************************************/
void moveRight()
{
	if (canMoveTo(shapeX + 1, shapeY))
	{
		paintShape(realColor_Black)
		shapeX++
		paintShape(shapeColour)
	}
}

/************************************
	moveLeft
************************************/
void moveLeft()
{
	if (canMoveTo(shapeX - 1, shapeY))
	{
		paintShape(realColor_Black)
		shapeX--
		paintShape(shapeColour)
	}
}

/************************************
	moveDown
************************************/
void moveDown()
{
	if (canMoveTo(shapeX, shapeY + 1))
	{
		paintShape(realColor_Black)
		shapeY++
		paintShape(shapeColour)

		// if the shape is resting on a full square then
		// it has reached the bottom and cannot be moved so get the next shape
		if (!canMoveTo(shapeX, shapeY + 1))
		{
			score += 4

			checkForFullRows()

			if (getNextShape())
			{
				paintShape(shapeColour)
			}
			else
			{
				gameOver()
			}
		}
	}
	else
	{
		score += 4

		checkForFullRows()

		if (getNextShape())
		{
			paintShape(shapeColour)
		}
		else
		{
			gameOver()
		}
	}
}

/************************************
	rotate
************************************/
void rotate()
{
	int i = 0
	int newCoords[NUM_COORDS * 2]     = {}

	if (canRotate)
	{
		paintShape(realColor_Black)

		getNewCoords(newCoords)

		for (i = 0; i < NUM_COORDS * 2; i++)
		{
			shapeCoords[i] = newCoords[i]
		}

		paintShape(shapeColour)

		if (!canMoveTo(shapeX, shapeY + 1))
		{
			score += 4

			checkForFullRows()

			if (getNextShape())
			{
				paintShape(shapeColour)
			}
			else
			{
				gameOver()
			}
		}
	}
}

/************************************
	doDrawCanvas
************************************/
void doDrawCanvas(DBE dbe)
{
	; // do nothing
}

/************************************
	doKeyboard

	Callback from keyboard.
************************************/
void doKeyboard(DBE cnv, char lastKey, bool ctrl, int x, int y)
{
	if (firstTime)
	{
		firstTime = false
		startTimer()

		// hide welcome message by painting the grid background in black
		realBackground(dbeGrid, realColor_Black)

		// get the first shape
		if (getNextShape())
		{
			paintShape(shapeColour)
		}
	}
	else
	{
		// check elapsed time since game started
		checkTimer()
	}

	if (lastKey == keyLeft)
	{
		moveLeft()
	}
	else if (lastKey == keyRight)
	{
		moveRight()
	}
	else if (lastKey == keyUp)
	{
		rotate()
	}
	else if (lastKey == keyDown)
	{
		moveDown()
	}
}

/************************************
	doCloseAction

	Clean up data structures and destroy the dialog box.
************************************/
void doCloseAction(DB db)
{
	delete(gridColours)

	hide(dbMain)
	destroy(dbMain)
	dbMain = null
}

/************************************
	MAIN
************************************/
dbMain = create("Tetris", styleCentered | styleFixed)

// magic number 12's make up for the pixels that are lost under the window frame
dbeGrid = canvas(dbMain,
                   (BOARD_WIDTH  * SQUARE_SIZE + 12),
                   (BOARD_HEIGHT * SQUARE_SIZE + 12),
                   doDrawCanvas)

close(dbMain, false, doCloseAction)

realize dbMain

set(dbeGrid, doKeyboard)

newGame()

show dbMain