Webtris E-mail

Unfortunately, your browser does not support modern web standards.
Please use one of the supported browsers listed below, they're free to download and use.

Supported browsers: Firefox, Safari, Chrome, Opera, and Konqueror.


Source (click to expand)

// Constants
const dir0 = 0;
const dir90 = 1;
const dir180 = 2;
const dir270 = 3;
const dir360 = 4;
const backColour = '#000000';
const version = 'v0.2';

const cellWidth = 16;
const cellHeight = 16;
const blockWidth = cellWidth * 4;
const blockHeight = cellHeight * 4;
const blockSize = blockWidth;

const defaultDelayGravity = 60;

const sNone = 0;
const sTitleScreen = 1;
const sPlaying = 2;
const sPaused = 3;
const sScoring = 4;
const sGameOver = 5;

// Cache objects
var objCanvas = document.getElementById('canvas');
var objBuffer = document.getElementById('buffer');
var objBlockBuffer = document.getElementById('blockbuffer');
var objFrameBuffer = document.getElementById('framebuffer');
var ctxCanvas = objCanvas.getContext('2d');
var ctxBuffer = objBuffer.getContext('2d');
var ctxBlockBuffer = objBlockBuffer.getContext('2d');
var ctxFrameBuffer = objFrameBuffer.getContext('2d');

// Gamve variables 
var fps = 0;                        // The actual fps the game is running at
var fpsTarget = 30;				    // Targeted fps
var fpsLimit = true;			    // Attempt to limit the frames per second
var framePrev = 0;				    // The time the previous frame started
var frameStart = 0;				    // The time the current frame started
var frameCount = 0;				    // The number of frames rendered so far
var frameDelta = 0;				    // The time difference between this and the previous frame
var frameDelay = 1000 / this.fpsTarget;
var gameStart = 0;					// The time the game started
var state = sNone;                  // The current game state
var testing = false;                // When true, display the test info

var quit = false;					// When true the game is over
var score = 0;						// The current player's score
var scoreMult = 0;                  // Score multiplier for clearing more than one row at the same time
var level = 1;                      // The current play level
var rowsCleared = 0;                // Total number of rows cleared for the session

// Returns a random number between 0 and n - 1
function rnd(n) {
    return Math.ceil(Math.random() * n) - 1;
}

// Fill the rectangle with the current colour
function fillRect(x, y, w, h, ctx) {               
    ctx.fillRect(x, y, w, h);
}

// Outline a rectangle
function drawRect(x, y, w, h, ctx) {
    ctx.strokeRect(x, y, w, h);
}

//=================================================================
// cRect
//=================================================================
function cRect(x, y, w, h) {
	this.x = 0;
	this.y = 0;
	this.w = 0;
	this.h = 0;
	if (typeof(x) != 'undefined') this.x = x;
	if (typeof(y) != 'undefined') this.y = y;
	if (typeof(w) != 'undefined') this.w = w;
	if (typeof(h) != 'undefined') this.h = h;
}

//=================================================================
// cBlock
//=================================================================
function cBlock(colour, src) {
	this.colour = colour;
	this.data = new Array(new Array(new Array(),new Array(),new Array(),new Array()),
						  new Array(new Array(),new Array(),new Array(),new Array()),
						  new Array(new Array(),new Array(),new Array(),new Array()),
					      new Array(new Array(),new Array(),new Array(),new Array()));
	this.bounds = new Array(new Array(),new Array(),new Array(),new Array());

	// Is this shape 2x2, 3x3 or 4x4
	var size = 0;
	for (var y = 0; y < 4; y++)
		for (var x = 0; x < 4; x++)
			if (src[y][x] == 1) {
				if (y > size) size = y;
				if (x > size) size = x;
			}
    size++;

	// Calculate the four rotations for the shape
	for (var dir = dir0; dir < dir360; dir++) {
        var data = this.data[dir];

        // Zero the data array for this rotation
		for (var y = 0; y < 4; y++)
			for (var x = 0; x < 4; x++)
                data[y][x] = 0;

		for (var y = 0; y < size; y++)
			for (var x = 0; x < size; x++) {
				switch (dir) {
				case dir0: data[y][x] = src[y][x]; break;
				case dir90: data[y][x] = src[(size - 1) - x][y]; break;
				case dir180: data[y][x] = src[(size - 1) - y][(size - 1) - x]; break;
				case dir270: data[y][x] = src[x][(size - 1) - y]; break;
				}
	    	}
    }

	// Calculate the dimensions for each rotation
	for (var dir = dir0; dir < dir360; dir++) {
		this.bounds[dir] = new cRect();
        var bounds = this.bounds[dir];
        bounds.x = size;
        bounds.y = size;
		for (var y = 0; y < size; y++)
			for (var x = 0; x < size; x++)
				if (this.data[dir][y][x] != 0) {
					if (x < bounds.x) bounds.x = x;
					if (y < bounds.y) bounds.y = y;
					if (x > bounds.w) bounds.w = x;
					if (y > bounds.h) bounds.h = y;
				}

		// Add 1 to width and height because X and Y start counting at 0
		bounds.w += 1;
		bounds.h += 1;

		// Calculate the shape dimensions in terms of block size, used when drawing the shape
		bounds.w -= bounds.x;
		bounds.h -= bounds.y;
		bounds.x *= cellWidth;
		bounds.y *= cellHeight;
		bounds.w *= cellWidth;
		bounds.h *= cellHeight;
	}

	this.draw = function(dx, dy, dir, ctx) {
		ctx.fillStyle = this.colour;
        var data = this.data[dir];
		for (var y = 0; y < 4; y++)
			for (var x = 0; x < 4; x++) {
				if (data[y][x] == 1)
					fillRect(dx + (x * cellWidth), dy + (y * cellHeight), cellWidth, cellHeight, ctx);
			}
	}

	// Draws all the rotations for testing
	this.test = function(x, y) {
		for (var dir = dir0; dir < dir360; dir++)
			this.draw(x + (dir * this.w), y, dir, ctxBuffer);
	}

    // Render the four rotations of the shape and snapshot it
    this.genFrames = function(y) {
        for (var dir = dir0; dir < dir360; dir++)
            this.draw(dir * blockWidth, y, dir, ctxFrameBuffer);
    }

    // Draws the buffered image data for the shape
    this.drawFrame = function(dx, dy, dir, ctx) {
        //ctx.drawImage(objFrameBuffer, blockWidth * dir, 0, blockWidth, blockHeight, dx, dy, blockWidth, blockHeight);
        this.draw(dx, dy, dir, ctx);
        //clipX = dx;
        //clipY = dy;
        //clipW = 8;
        //clipH = 8;
        //ctx.putImageData(this.frames[dir], dx, dy);
    }
}

//=================================================================
// blocks
//=================================================================
var blocks = new function() {
	this.count = 7;
	this.block = new Array();
	this.block[0] = new cBlock('#FF0000', new Array(new Array(0,0,0,0), new Array(1,1,1,1), new Array(0,0,0,0), new Array(0,0,0,0)));
	this.block[1] = new cBlock('#FFFFFF', new Array(new Array(1,0,0,0), new Array(1,1,1,0), new Array(0,0,0,0), new Array(0,0,0,0)));
	this.block[2] = new cBlock('#FF00FF', new Array(new Array(0,0,1,0), new Array(1,1,1,0), new Array(0,0,0,0), new Array(0,0,0,0)));
	this.block[3] = new cBlock('#0000FF', new Array(new Array(1,1,0,0), new Array(1,1,0,0), new Array(0,0,0,0), new Array(0,0,0,0)));
	this.block[4] = new cBlock('#008000', new Array(new Array(0,1,1,0), new Array(1,1,0,0), new Array(0,0,0,0), new Array(0,0,0,0)));
	this.block[5] = new cBlock('#A52A2A', new Array(new Array(0,1,0,0), new Array(1,1,1,0), new Array(0,0,0,0), new Array(0,0,0,0)));
	this.block[6] = new cBlock('#00FFFF', new Array(new Array(1,1,0,0), new Array(0,1,1,0), new Array(0,0,0,0), new Array(0,0,0,0)));

    // Generate the shape frames
    fillRect(0, 0, blockWidth * dir360, blockHeight * this.count, ctxFrameBuffer);
    for (var i = 0; i < this.count; i++)
        this.block[i].genFrames(i * blockHeight);

	// Draws all the rotations of all blocks for testing
	this.test = function(x, y) {
		for (var i = 0; i < this.count; i++)
			this.block[i].test(x, y + (i * this.block[i].h));
	}
}

//=================================================================
// board
//=================================================================
var board = new function() {
	this.cols = 12;
	this.rows = 20;
    this.x = 8;
    this.y = 8;
	this.w = this.cols * cellWidth;
	this.h = this.rows * cellHeight;
    this.cells = new Array();
    this.scoreTime = 0;
    this.scoreRate = 0.25 * 1000;
    this.gameOverTime = 0;
    this.gameOverRate = 2 * 1000;

    this.drawFrame = function() {
        ctxBuffer.strokeStyle = "#A0A0A0";
        drawRect(this.x - 2, this.y - 2, this.w + 4, this.h + 4, ctxBuffer);
    }

    // Draw text centered into the board
    this.centerText = function(text, y) {
        ctxBuffer.fillText(text, this.x + ((board.w - ctxBuffer.measureText(text).width) / 2), this.y + y);
    }

    // Initialise the array holding dropped blocks
    for (var y = 0; y < this.rows; y++)
        this.cells[y] = new Array();

	// Fill the canvas with the backColour
	this.clearBuffer = function() {
		ctxBuffer.fillStyle = backColour;
	  	ctxBuffer.clearRect(this.x, this.y, this.w, this.h);
	  	fillRect(this.x, this.y, this.w, this.h, ctxBuffer);
	}

    this.clearBlockBuffer = function() {
	ctxBlockBuffer.fillStyle = backColour;
	ctxBlockBuffer.clearRect(this.x, this.y, this.w, this.h);
	fillRect(this.x, this.y, this.w, this.h, ctxBlockBuffer);
    }

    // Fill the board with 0's
    this.clearBoard = function() {
        for (var y = 0; y < this.rows; y++)
            for (var x = 0; x < this.cols; x++)
                this.cells[y][x] = 0;
    }
    this.clearBoard();

    // Draw 1's and 0's over the board cells on screen to
    // test how the buffer is working
    this.test = function() {
        ctxBuffer.fillStyle = '#FFFFFF';
        ctxBuffer.font = "10px sans-serif";
        for (var y = 0; y < this.rows; y++)
            for (var x = 0; x < this.cols; x++)
		        ctxBuffer.fillText(this.cells[y][x], this.x + (x * cellWidth), this.y + (y * cellHeight) + 9);  
    }

    // Setup a test for a nearly full board
    this.testFull = function() {
        for (var y = 4; y < this.rows; y++)
            for (var x = 0; x < this.cols; x++)
                this.cells[y][x] = 1;
    }

    // Display paused in the middle of the screen
    this.gameOver = function(time) {     
        if (this.gameOverTime == 0) {
            this.gameOverTime = time;
            return;
        }

        // A little delay for the game over message to sink in before returning to the title screen
	    var delta = time - this.gameOverTime;
	    if (delta < this.gameOverRate) return;
        this.gameOverTime = 0;
        setState(sTitleScreen);
    }

    // Returns -1 if no rows are full, or the index of the first full row
    this.anyRowFull = function() {
        var hit = 0;
        for (var y = 0; y < this.rows; y++) {
            hit = 0;
            for (var x = 0; x < this.cols; x++)
                if (this.cells[y][x] != 0) hit++;
            if (hit == this.cols) return y;
        }
        return -1;
    }

    // Called during a scoring loop
    this.score = function(time) {
        // If this is the first time through scoring then start the timer
        if (this.scoreTime == 0) {
            this.scoreTime = time;
            scoreMult = 0;
            return true;
        } 

        // A little delay to make each row being removed take more than one frame
	    var delta = time - this.scoreTime;
	    if (delta < this.scoreRate) return true;
        this.scoreTime = time;

        // Increment the score multiplier once through each loop in each scoring session
        scoreMult++;

        // Calculate the score so far
        score += scoreMult * 10;

        // Fetch the first row that is full
        var row = this.anyRowFull();

        // Copy the top part of the board, up to the row that is to be removed
        clip = ctxBlockBuffer.getImageData(this.x, this.y, this.w, row * cellHeight);

        // Clear the top row of the board
        ctxBlockBuffer.fillStyle = "#000000";
        fillRect(this.x, this.y, this.w, cellHeight, ctxBlockBuffer);

        // Paste the clipped rect one line down to erase the full row
        ctxBlockBuffer.putImageData(clip, this.x, this.y + cellHeight);

        // Move all the rows of blocks down one line above the specified row
        for (var y = row; y > 0; y--)
            for (var x = 0; x < this.cols; x++)
                this.cells[y][x] = this.cells[y - 1][x];

        // Clear the cells in the first row
        for (var x = 0; x < this.cols; x++)
            this.cells[0][x] = 0;

        if (this.anyRowFull() == -1) {
            this.scoreTime = 0;
            return false;
        } else return true;
        return (this.anyRowFull() >= 0);
    }
}

//=================================================================
// player
//=================================================================
var player = new function() {
	this.x = 0;							// X position of the block
	this.y = 0;							// Y position of the block
    this.shape = 0;						// The block the player is currently using
	this.dir = dir0;					// The direction the block is facing
	this.nextShape = 0;					// The next block the player will have
	this.nextDir = dir0;				// The direction of the next block the player will have
    this.shapeTime = 0;                 // The time the new shape was assigned
    this.shapeDelay = 0.5 * 1000;       // Number of seconds before fast fall affects a new shape

	this.fallTime = new Date().getTime();		// The time the last fall occurred
	this.fallRate = 2 * 1000;
	this.fallRateMax = 10;				// The fastest rate at which the block can fall
	this.fallFast = false;				// When true, the block will fall at max speed

    this.drawStats = function(time, x, y) {
        ctxBuffer.fillStyle = "#A0A0A0";
        fillRect(x, y, blockWidth, 128, ctxBuffer);

        ctxBuffer.shadowBlur = 0;
        ctxBuffer.shadowOffsetX = 0;
        ctxBuffer.shadowOffsetY = 0;
        ctxBuffer.shadowColor = "#FFFFFF";
		ctxBuffer.fillStyle = "#000000";
        ctxBuffer.font = "10px sans-serif";

        ctxBuffer.fillText("Next Block", x, y + 8);
        fillRect(x, y + 10, blockWidth, blockHeight, ctxBuffer);
        blocks.block[this.nextShape].draw(x, y + 10, this.nextDir, ctxBuffer); 

		ctxBuffer.fillStyle = "#000000";
        ctxBuffer.fillText("Score", x, y + blockHeight + 10 + 8);

        var s = parseInt(score);
        w = ctxBuffer.measureText(s).width;
        ctxBuffer.fillText(s, x + blockWidth - w, y + blockHeight + 20 + 8);

        ctxBuffer.fillText("FPS", x, y + blockHeight + 20 + 20 + 8);
        var fps = Math.round(frameCount / ((time - gameStart) / 1000));
        var s = parseInt(fps);
        w = ctxBuffer.measureText(s).width;
        ctxBuffer.fillText(s, x + blockWidth - w, y + blockHeight + 20 + 20 + 8);
    }

    // Assign the NextBlock to the player, then randomly select a new block
	this.selectNextShape = function() {
        this.shape = this.nextShape;
        this.shapeTime = new Date().getTime();
        this.dir = this.nextDir;
        this.nextShape = rnd(7);
        this.nextDir = rnd(4);

        // Position the block at the top of the board, centered horizontally
        this.y = -blocks.block[this.shape].bounds[this.dir].y;
        this.x = ((board.w - blockWidth) / 2);

        // Reset fall speed and start the fall timer over    
        this.fallFast = false;
        this.fallTime = new Date().getTime();

        // Make sure that it's actually possible to place the block, if not, then it's game over
        if (!this.canMove(this.x, this.y, this.dir))
            setState(sGameOver);
	}

    // Draw the player
	this.draw = function() {
		blocks.block[this.shape].drawFrame(board.x + this.x, board.y + this.y, this.dir, ctxBuffer);
	}

	// Gravity is always affecting the block, but it is variable depending on the level
	// so that Y position is incremented faster
	this.fall = function(time) {
        if (state == sPlaying) {
		    var delta = time - this.fallTime;
            if (delta > (this.fallFast && ((time - this.shapeTime) > this.shapeDelay) ? this.fallRateMax : this.fallRate))
			    if (this.canMove(this.x, this.y + cellHeight, this.dir)) {
				    this.y += cellHeight;
				    this.fallTime = time;
				} else this.endMove();
        }
	}

	// Rotate the block
	this.rotate = function() {
        if ((state == sPlaying) && (this.canMove(this.x, this.y, (this.dir + 1) % dir360)))
            this.dir = (this.dir + 1) % dir360;
	}
	
	// Move the block to the left
	this.left = function() {
		if ((state == sPlaying) && (this.canMove(this.x - cellWidth, this.y, this.dir)))
			this.x -= cellWidth;
	}

	// Move the block to the right
	this.right = function() {
		if ((state == sPlaying) && (this.canMove(this.x + cellWidth, this.y, this.dir)))
			this.x += cellWidth;
	}

    // Returns true if the block can be moved to the specified location
	this.canMove = function(x, y, dir) {
        var bounds = blocks.block[this.shape].bounds[dir];

        // If the block has hit the bottom row of the board then return true
        if (y > (board.h - bounds.y - bounds.h)) return false;

        // Make sure that the block doesn't hang over the edge of the board
        if (x < -bounds.x) return false;
        if (x > board.w - (bounds.x + bounds.w)) return false;

        // Make sure that a rotation at the top of the screen doesn't overlap the block
        if ((y + bounds.y) < 0) return false;

        // Compare the board against the block to see if there are any overlaps
        var boardX = x / cellWidth;
        var boardY = y / cellHeight;
        var data = blocks.block[this.shape].data[dir];
        for (var y = Math.max(boardY, 0); y < Math.min(boardY + 4, board.rows); y++)
            for (var x = Math.max(boardX, 0); x < Math.min(boardX + 4, board.cols); x++)
                if ((board.cells[y][x] != 0) && (data[y - boardY][x - boardX] != 0))
                    return false;

        // Nothing hit, OK to make the move
		return true;
	}

    // If a move has been found to result in the block being unable to fall further than
    // it's last position, then the block is frozen and a new round can begin.
	this.endMove = function() {
        // Draw the block onto the block buffer
        this.draw();
		blocks.block[this.shape].draw(board.x + this.x, board.y + this.y, this.dir, ctxBlockBuffer);

        // Fill in the cells on the board that are occupied by block pieces
        var boardX = this.x / cellWidth;
        var boardY = this.y / cellHeight;
        var data = blocks.block[this.shape].data[this.dir];
        for (var y = Math.max(boardY, 0); y < Math.min(boardY + 4, board.rows); y++)
            for (var x = Math.max(boardX, 0); x < Math.min(boardX + 4, board.cols); x++)
                board.cells[y][x] = board.cells[y][x] || data[y - boardY][x - boardX];

        // If there are any full rows on the board, then animate removing them
        if (board.anyRowFull() >= 0) {
            setState(sScoring);
        } else this.selectNextShape();
	}
}

//=================================================================
// game
//=================================================================

// Set the current game state.  To improve performance, changing the state 
// to titlescreen, paused or game over will simply render the screen once
// then update it once per frame, rather than repeatedly drawing it each time
function setState(newState) {
    if (newState != state) {        
        state = newState;
	    switch (state) {
	    case sTitleScreen:
            var x = board.x;
            var y = board.y;
            board.clearBuffer();
		    ctxBuffer.fillStyle = "#FFFFFF";
            ctxBuffer.font = "24px sans-serif";
            board.centerText("Webtris v0.2", 40);
            ctxBuffer.font = "12px sans-serif";
            board.centerText("(c) 2009 By Neil Burlock", 55);

            ctxBuffer.font = "14px sans-serif";
            board.centerText("A HTML5 demonstration", 100);

            ctxBuffer.fillStyle = "#B0B0B0";
            ctxBuffer.font = "10px sans-serif";
            board.centerText("Press any key to play", 150);
            board.centerText("W to rotate", 165);
            board.centerText("A & D to move", 180);
            board.centerText("S to fall fast", 195);
            board.centerText("Escape to quit", 210);
            board.centerText("P to toggle pause", 240);
            board.centerText("T to toggle debug", 255);
            break;

        case sGameOver:
		    ctxBuffer.fillStyle = "#FFFFFF";
            ctxBuffer.font = "30px sans-serif";
            board.centerText("Game Over", ((board.h - 10) / 2));
            break;

        case sPaused:
		    ctxBuffer.fillStyle = "#FFFFFF";
            ctxBuffer.font = "30px sans-serif";
            board.centerText("Paused", ((board.h - 10) / 2));
            break;
        }       
    }
}

// Initialise the game
function initGame() {        
    initSession();
    setState(sTitleScreen);
}

// Initialise a play session
function initSession() {
	quit = false;
	score = 0;
	delayCount = 0;
	level = 1;
	setState(sPlaying);
	rowscleared = 0;
    player.nextShape = rnd(7);
    player.nextDir = rnd(4);
	board.clearBoard();
    board.clearBuffer();
    board.clearBlockBuffer();
	player.selectNextShape();
}

function mainLoop () {
	frameStart = new Date().getTime();
	frameDelta = frameStart - framePrev;
                    
    switch (state) {
    case sTitleScreen:
        break;

    case sPaused:
        break;

    case sPlaying:        
        ctxBuffer.drawImage(objBlockBuffer, 0, 0);
        if (testing) board.test();
	    player.fall(frameStart);
        if (state == sPlaying) player.draw();
        break;

    case sScoring:                    
        if (!board.score(frameStart)) {
            player.selectNextShape();
            setState(sPlaying);
        }
        break;

    case sGameOver:
        board.gameOver(frameStart);
        break;
    }
    player.drawStats(frameStart, board.x + board.w + 6, board.y);
    
    // Delay to the start of the next frame
	frameCount++;
	if (fpsLimit) {
		frameDelay = fpsTarget;
		if (frameDelta > frameDelay) {
			frameDelay = Math.max(20, frameDelay - (frameDelta - frameDelay))
		}
		this.framePrev = frameStart;
		setTimeout(mainLoop, frameDelay);
	} else {
		setTimeout(mainLoop, 1);
	}

    // Canvas flip
    ctxCanvas.drawImage(objBuffer, 0, 0);
}

// bind keyboard events to game functions
function bindKeys() {
    // For each of these keyboard events, return false to suppress normal browser
    // events, such as scrolling the page when the cursor keys are pressed.  A
    // single key press will trigger all three events, so the same key will still
    // have to be suppressed in events that aren't used.

    const keyEscape = 27;
    const keyLeft = 37;
    const keyUp = 38;
    const keyRight = 39;
    const keyDown = 40;
    const keyP = 112;
    const keyT = 116;
    const keySpace = 32;
    const keyW = 119;
    const keyA = 97;
    const keyS = 115;
    const keyD = 100;

    // The down key needs to act like a toggle, so use keyup & keydown
	document.onkeydown = function(e) {
		e = e || window.event;
		switch (e.keyCode) {
		case keyDown: // Bring block home
            player.fallFast = true;
            return false;
			break;
        default:
		    switch (e.which) {
		    case 83: // Bring block home
                player.fallFast = true;
                return false;
			    break;
            }
		}
	}
	document.onkeyup = function(e) {
		e = e || window.event;
		switch (e.keyCode) {
		case keyDown: // Bring block home
			player.fallFast = false;
            return false;
			break;
        case keyEscape: // Quit the current game
            if (state == sPlaying) setState(sTitleScreen);
            break;
        default:
		    switch (e.which) {
		    case 83: // Bring block home
                player.fallFast = false;
                return false;
			    break;
            }
		}
	}

    // The rest of the keyboard events can be handled as presses
	document.onkeypress = function(e) {
		e = e || window.event;
		switch (e.keyCode) {
		case keyUp: // Rotate block
			player.rotate();
            return false;
			break;
		case keyLeft: // Move block left
			player.left();
            return false;
			break;
		case keyRight: // Move block right
			player.right();
            return false;
			break;
        case keyDown: // Do nothing
            return false;
            break;
        default:
            switch (e.which) {
		    case keyW: // Rotate block
			    player.rotate();
                return false;
			    break;
		    case keyA: // Move block left
			    player.left();
                return false;
			    break;
		    case keyD: // Move block right
			    player.right();
                return false;
			    break;
		    case keyEscape: // Quit
                return false;
			    break;
		    case keyP: // Pause
                setState(state == sPaused ? sPlaying : sPaused);
                return false;
			    break;
            case keyT:// Test mode
                testing = !testing;
                break;
            default:
                //alert(e.which);
                if (state == sTitleScreen) {
                    initSession();
                }
            }
		}
	}
}

function init() {
	initGame();
	bindKeys();
    board.drawFrame();
    gameStart = new Date().getTime();
	mainLoop();		
}

setTimeout(init, 10);