Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
              
            
!

CSS

              
                #canvas {
	background-color: black;
	color: white;
	width:512px;
	height:480px;
	position: absolute;
	margin: auto;
	padding: 0;
	top: 0; left: 0; bottom: 0; right: 0;
}
body {
	background-color: #111;
}
              
            
!

JS

              
                Array.prototype.shuffle = function(){
	o = this;
    for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
};
function random(min, max, step){
	if(max === undefined){
		max = min;
		min = 0;
	}
	if(step === undefined)step = 1;
	return Math.round(Math.random()*(max-min))+min;
}
function coin(){
	return random(1) === 0;
}
function findFallSpeed(level){
	return 60-(level-1)*8
}
function findGoal(level){
	return 4*level;
}
function makeGarbage(map){
	map.shift();
	var garbage = [];
	for(var i = 0; i < mapWidth; i ++)garbage.push(8);
	garbage[random(garbage.length-1)] = 0;
	map.push(garbage);
}
function opponent(player){
	if(player.name === 'player1')return p2;
	if(player.name === 'player2')return p1;
}

function Player(id, playing, testing){
	if(playing === undefined)playing = false;
	if(testing === undefined)testing = false;
	this.score = 0;
	this.displayScore = 0;
	this.level = defaults.level;
	this.lines = 0;
	this.goal = findGoal(this.level);
	this.map = [];
	this.bag = [];
	this.bag.list = [];
	this.bag.initialized = false;
	this.held = undefined;
	this.canHold = true;
	this.name = 'player'+id;
	this.playing = playing;
	this.testing = testing;
	this.wins = 0;
	this.blink = true;
	this.blinkTimer = 0;
	this.blinkLimit = 30;
	this.garbage = 0;
	this.startGarbage = 4;
	this.ai = true;
	this.dead = false;
	this.deadTimer = 0;
	this.deadLimit = 0;
	this.idle = false;
	this.idleTimer = 0;
	this.idleLimit = 60;
	for(var i = 0; i < mapHeight; i ++){
		this.map.push([]);
		for(var j = 0; j < mapWidth; j ++){
			this.map[i][j] = 0;
		}
	}
	for(var i = 0; i < this.startGarbage; i ++)makeGarbage(this.map);
	this.killTile = function(){
		var x;
		var y;
		var clear = true;
		for(var i = 0; i < mapHeight; i ++){
			for(var j = 0; j < mapWidth; j ++){
				if(this.map[i][j] !== 0)clear = false;
			}
		}
		if(clear){
			this.dead = false;
			this.idle = true;
			return;
		}
		do {
			x = random(0, mapWidth-1);
			y = random(0, mapHeight-1);
		} while(this.map[y][x] === 0);
		this.map[y][x] = 0;
		if(this.name === 'player1')new Sprite(blockDestruction, (x-5)*blockSize, (y+6)*blockSize, blockSize, blockSize);
		if(this.name === 'player2')new Sprite(blockDestruction, (x+15)*blockSize, (y+6)*blockSize, blockSize, blockSize);
		display.flip();
	}
	this.reset = function(){
		for(var i = 0; i < mapHeight; i ++){
			for(var j = 0; j < mapWidth; j ++){
				this.map[i][j] = 0;
			}
		}
		this.idle = false;
		this.dead = false;
		this.held = undefined;
		this.fallDelayMax = findFallSpeed(this.level);
		this.garbage = 0;
		this.score = 0;
		this.displayScore = 0;
		this.level = defaults.level;
		this.lines = 0;
		this.goal  = findGoal(this.level);
		for(var i = 0; i < this.startGarbage; i ++)makeGarbage(this.map);
		bag.init(this);
		bag.draw(this);
		wasDown['left'] = false;
		wasDown['right']= false;
		wasDown['up']   = false;
		wasDown['down'] = false;
		wasDown['w'] = false;
		wasDown['a']= false;
		wasDown['s']   = false;
		wasDown['d'] = false;
		wasDown['space'] = false;
		wasDown['enter'] = false;
		this.canHold = true;
		display.flip();
	}
	this.update = function(){
		this.blinkTimer ++;
		if(this.blinkTimer >= this.blinkLimit){
			if(this.blink)this.blink = false;
			else this.blink = true;
			display.flip();
			this.blinkTimer = 0;
		}
		if(this.playing){
			if(this.idle){
				this.idleTimer ++;
				if(this.idleTimer >= this.idleLimit){
					this.idleTimer = 0;
					this.idle = false;
					if(!this.ai)this.playing = false;
					else this.reset();
				}
			}
			if(this.dead){
				this.deadTimer ++;
				if(this.deadTimer >= this.deadLimit){
					this.deadTimer = 0;
					this.killTile();
					this.killTile();
					this.killTile();
					this.killTile();
					this.killTile();
					this.killTile();
					this.killTile();
					this.killTile();
				}
			}
			if(!this.dead && !this.idle){
				if(this.shape.ai){
					this.shape.aiTimer ++;
					if(this.shape.aiTimer >= this.shape.aiLimit){
						this.shape.aiTimer = 0;
						this.shape.aiLimit = 0;
						//this.shape.aiLimit = random(5, 25);
						this.shape.goBestMove();
					}
				}
				if(this.shape.locking){
					this.shape.lockDelay --;
					if(this.shape.lockDelay < 0){
						this.shape.lockDelay = this.shape.lockDelayMax;
						this.shape.lock();
					}
				}
				this.shape.fallDelay --;
				if(this.shape.fallDelay < 0){
					this.shape.fallDelay = this.shape.fallDelayMax;
					this.shape.move(0, 1);
				}
				var beforeScore = Math.round(this.displayScore);
				this.displayScore += (this.score-this.displayScore)/1;
				var afterScore = Math.round(this.displayScore);
				if(beforeScore != afterScore)display.flip();
			}
		}
	}
}

var mapWidth  = 10;
var mapHeight = 22;
var frameRate = 100/6;
var defaults = {};
defaults.level = 1;
defaults.lockDelay = 10;
var p1 = new Player(1, true);
var p2 = new Player(2, true);

var sprites = [];
var paused = false;
function oob(x, y, map, top){
	if(top === undefined)top = false;
	if(!top)return x < 0 || x > map[0].length-1 || y < 0 || y > map.length-1;
	return x < 0 || x > map[0].length-1 || y > map.length-1;
}
var display = {};
display.width  = 32;
display.height = 30;
display.flip = function(){
	ctx.fillStyle = 'black';
	ctx.fill();
	for(var i = 1; i < display.height-9; i ++){
		for(var j = 0; j < display.width; j ++){
			ctx.drawImage(bg, j*blockSize, i*blockSize, blockSize, blockSize);
		}
	}
	var gridOffsetX = 0;
	var gridOffsetY = 1;
	var x;
	var y;
	for(var i = 0; i < display.height; i ++){
		gridY = i+gridOffsetY;
		if(gridY < mapHeight){
			for(var j = 0; j < display.width; j ++){
				gridX = j+gridOffsetX;
				if(gridX < mapWidth){	
					x = blockSize
					y = blockSize*6;	
					if(p1.playing){
						k = p1.map[gridY][gridX];
						if(paused)k = 0;
						if(k > 0 && !p1.dead && !p1.idle){
							for(var l = 0; l < p1.shape.destinations.length; l ++){
								var t = p1.shape.destinations[l];
								if(gridX === t.x && gridY === t.y){
									ctx.drawImage(blockDestination, t.x*blockSize+x, t.y*blockSize+y, blockSize, blockSize);
								}
							}
						}
						drawBlock(gridX*blockSize+x, gridY*blockSize+y, k);
						if(k == 0 && !paused && !p1.dead && !p1.idle){
							for(var l = 0; l < p1.shape.destinations.length; l ++){
								var t = p1.shape.destinations[l];
								if(gridX === t.x && gridY === t.y){
									ctx.drawImage(blockDestination, t.x*blockSize+x, t.y*blockSize+y, blockSize, blockSize);
								}
							}
						}
					} else {
						k = 0;
						drawBlock(gridX*blockSize+x, gridY*blockSize+y, k);
					}
					x = blockSize*21
					y = blockSize*6;
					if(p2.playing){
						if(p2.shape != undefined){
							k = p2.map[gridY][gridX];
							if(paused)k = 0;
							if(k > 0 && !p2.dead && !p2.idle){
								for(var l = 0; l < p2.shape.destinations.length; l ++){
									var t = p2.shape.destinations[l];
									if(gridX === t.x && gridY === t.y){
										ctx.drawImage(blockDestination, t.x*blockSize+x, t.y*blockSize+y, blockSize, blockSize);
									}
								}
							}
							drawBlock(gridX*blockSize+x, gridY*blockSize+y, k);
							if(k == 0 && !paused && !p2.dead && !p2.idle){
								for(var l = 0; l < p2.shape.destinations.length; l ++){
									var t = p2.shape.destinations[l];
									if(gridX === t.x && gridY === t.y){
										ctx.drawImage(blockDestination, t.x*blockSize+x, t.y*blockSize+y, blockSize, blockSize);
									}
								}
							}
						}
					} else {
						k = 0;
						drawBlock(gridX*blockSize+x, gridY*blockSize+y, k);
					}
				}
			}
		}
	}
	ctx.drawImage(matrix, 0, blockSize*6, (mapWidth+2)*blockSize, (mapHeight+gridOffsetY)*blockSize);
	ctx.drawImage(matrix, blockSize*20, blockSize*6, (mapWidth+2)*blockSize, (mapHeight+gridOffsetY)*blockSize);
	x = (mapWidth+2)*blockSize;

	drawString(208, 352, 'WINS');
	drawString(208, 400, 'LEVEL');
	drawString(208, 448, 'GOAL');

	ctx.drawImage(box, 0, 0, 96, 96);
	ctx.drawImage(box, blockSize*6, 0, 96, 96);
	if(p1.playing && p1.bag.initialized)drawBlockSample(blockSize, blockSize, p1.bag.next);
	if(p1.held !== undefined){
		drawBlockSample(blockSize*7, blockSize, p1.held);
	}
	drawString(blockSize, blockSize, 'NEXT');
	drawString(blockSize*7, blockSize, 'HOLD');
	drawNumber(blockSize, 464, Math.round(p1.displayScore), 8);
	drawNumber(208, 368, p1.wins, 2);
	drawNumber(208, 416, p1.level, 2);
	drawNumber(208, 464, p1.goal-p1.lines, 2);

	
	ctx.drawImage(box, (display.width-6)*blockSize, 0, 96, 96);
	ctx.drawImage(box, (display.width-12)*blockSize, 0, 96, 96);
	if(p2.playing && p2.bag.initialized)drawBlockSample((display.width-5)*blockSize, blockSize, p2.bag.next);
	if(p2.held !== undefined){
		drawBlockSample((display.width-11)*blockSize, blockSize, p2.held);
	}
	drawString((display.width-5)*blockSize, blockSize, 'NEXT');
	drawString((display.width-11)*blockSize, blockSize, 'HOLD');
	drawNumber((display.width-9)*blockSize, 464, Math.round(p2.displayScore), 8);
	drawNumber(272, 368, p2.wins, 2);
	drawNumber(272, 416, p2.level, 2);
	drawNumber(272, 464, p2.goal-p2.lines, 2);

	if(!paused){
		for(var i = 0; i < sprites.length; i ++){
			var t = sprites[i];
			ctx.drawImage(t.sheet, t.spriteX, t.spriteY, t.width, t.height, t.x+6*blockSize, t.y, t.width, t.height);
		}
	} else {
		drawString(blockSize*3, blockSize*17, 'PAUSED');
		drawString(blockSize*23, blockSize*17, 'PAUSED');
	}
	if(p1.ai)drawString(blockSize*4, blockSize*10, 'DEMO');
	if((p1.ai || !p1.playing) && p1.blink && !paused){
		drawString(blockSize, blockSize*17, 'PUSH SPACE');
	}
	if(p2.ai)drawString(blockSize*24, blockSize*10, 'DEMO');
	if((p2.ai || !p2.playing) && p2.blink && !paused){
		drawString(blockSize*21, blockSize*17, 'PUSH ENTER');
	}
	if((p1.dead || p1.idle) && !p1.ai)drawString(blockSize, blockSize*17, 'GAME OVER!');
	if((p2.dead || p2.idle) && !p2.ai)drawString(blockSize*21, blockSize*17, 'GAME OVER!');
	drawString(blockSize*13, 0, 'TETRIS');
}

var blockIndex = 1;
var blockSize = 16;

var blockDestruction = new Image();
blockDestruction.src = 'https://i.imgur.com/3K3A2SG.png';
var blockDestination = new Image();
blockDestination.src = 'https://i.imgur.com/JUR9rFL.png';

var blockSamples = new Image();
blockSamples.src = 'https://i.imgur.com/g548Q3O.png';
var blockSamplesCoords = {
	'I':{x:0,  y:0, width:4, height:4},
	'O':{x:4,  y:0, width:4, height:4},
	'J':{x:8,  y:0, width:4, height:4},
	'L':{x:12, y:0, width:4, height:4},
	'Z':{x:16, y:0, width:4, height:4},
	'T':{x:20, y:0, width:4, height:4},
	'S':{x:24, y:0, width:4, height:4},
}
function drawBlockSample(x, y, name){
	ctx.drawImage(
		blockSamples, 
		blockSamplesCoords[name].x*blockSize, 
		blockSamplesCoords[name].y*blockSize, 
		blockSamplesCoords[name].width*blockSize, 
		blockSamplesCoords[name].height*blockSize, 
		x, 
		y, 
		blockSamplesCoords[name].width*blockSize, 
		blockSamplesCoords[name].height*blockSize
		);
}

var matrix = new Image();
matrix.src = 'https://i.imgur.com/wGcmvNU.png';

var box = new Image();
box.src = 'https://i.imgur.com/p0fWLYe.png';

var chars = new Image();
chars.src = 'https://i.imgur.com/87f3UkH.png';
var charsCoords = {};
var charsOrder = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ! ';
var x = 0;
var y = 0;
var charsWidth = 160;
var charsHeight= 72;
for(var i = 0; i < charsOrder.length; i ++){
	if(x > charsWidth/blockSize-1){
		x = 0;
		y ++;
	}
	var c = charsOrder.charAt(i);
	charsCoords[c] = {};
	charsCoords[c].x = x;
	charsCoords[c].y = y;
	x ++;
}
function drawString(x, y, string){
	for(var i = 0; i < string.length; i ++){
		var c = string.charAt(i);
		if(c != ' '){
			ctx.drawImage(
				chars, 
				charsCoords[c].x*blockSize, 
				charsCoords[c].y*(blockSize+2), 
				blockSize, 
				blockSize,
				x+blockSize*i, 
				y, 
				blockSize, 
				blockSize
				);
		}
	}
}
function drawNumber(x, y, number, place){
	string = number.toString();
	if(string.length > place){
		string = '';
		for(var i = 0; i < place; i ++)string += '0';
	}
	while(string.length < place)string = '0'+string;
	for(var i = 0; i < string.length; i ++){
		var c = string.charAt(i);
		ctx.drawImage(chars, charsCoords[c].x*blockSize, charsCoords[c].y*(blockSize+2), blockSize, blockSize, x+blockSize*i, y, blockSize, blockSize)
	}
}

var bg = new Image();
bg.src = 'https://i.imgur.com/zeQpekQ.png';

var blocks = new Image();
blocks.src = 'https://i.imgur.com/GrIgPHU.png';
function drawBlock(x, y, index){
	ctx.drawImage(blocks, blockSize*index, 0, blockSize, blockSize, x, y, blockSize, blockSize)
}

var canvas = document.createElement('canvas');
canvas.id = 'canvas';
canvas.width = blockSize*display.width;
canvas.height = blockSize*display.height;
document.body.appendChild(canvas);

var ctx = canvas.getContext('2d');
ctx.rect(0, 0, canvas.width, canvas.height);

var origin = {x:3, y:0};

var colors = {'red':1, 'orange':2, 'yellow':3, 'green':4, 'cyan':5, 'blue':6, 'purple':7};
var shapeNames = ['O', 'T', 'J', 'L', 'I', 'S', 'Z'];
var bag = {};
bag.init = function(player){
	player.bag.initialized = true;
	player.bag.list = [];
	player.bag.list.length = 0;
	for(var i = 0; i < shapeNames.length; i ++)player.bag.list.push(shapeNames[i]);
	player.bag.list.shuffle();
	for(var i = 0; i < random(0, shapeNames.length-1); i ++)player.bag.list.shift();
	player.bag.next = player.bag.list[0];
}
bag.draw = function(player){
	new Shape(player.bag.next, player);
	player.bag.list.shift();
	if(player.bag.list.length === 0){
		for(var i = 0; i < shapeNames.length; i ++)player.bag.list.push(shapeNames[i]);
		player.bag.list.shuffle();
	}
	player.bag.next = player.bag.list[0];
}
var shapes = {
	'O':{tiles:[[{x:1, y:0}, {x:2, y:0}, {x:1, y:1}, {x:2, y:1}]], color:'orange'},
	'T':{tiles:[
		[{x:1, y:0}, {x:0, y:1}, {x:1, y:1}, {x:2, y:1}],
		[{x:1, y:0}, {x:1, y:1}, {x:2, y:1}, {x:1, y:2}],
		[{x:0, y:1}, {x:1, y:1}, {x:2, y:1}, {x:1, y:2}],
		[{x:1, y:0}, {x:0, y:1}, {x:1, y:1}, {x:1, y:2}],
		], color:'blue'},
	'J':{tiles:[
		[{x:0, y:0}, {x:0, y:1}, {x:1, y:1}, {x:2, y:1}],
		[{x:1, y:0}, {x:2, y:0}, {x:1, y:1}, {x:1, y:2}],
		[{x:0, y:1}, {x:1, y:1}, {x:2, y:1}, {x:2, y:2}],
		[{x:1, y:0}, {x:1, y:1}, {x:0, y:2}, {x:1, y:2}],
		], color:'yellow'},
	'L':{tiles:[
		[{x:2, y:0}, {x:0, y:1}, {x:1, y:1}, {x:2, y:1}],
		[{x:1, y:0}, {x:1, y:1}, {x:1, y:2}, {x:2, y:2}],
		[{x:0, y:1}, {x:1, y:1}, {x:2, y:1}, {x:0, y:2}],
		[{x:0, y:0}, {x:1, y:0}, {x:1, y:1}, {x:1, y:2}],
		], color:'green'},
	'I':{tiles:[
		[{x:0, y:1}, {x:1, y:1}, {x:2, y:1}, {x:3, y:1}],
		[{x:2, y:0}, {x:2, y:1}, {x:2, y:2}, {x:2, y:3}],
		[{x:0, y:2}, {x:1, y:2}, {x:2, y:2}, {x:3, y:2}],
		[{x:1, y:0}, {x:1, y:1}, {x:1, y:2}, {x:1, y:3}],
		], color:'red'},
	'S':{tiles:[
		[{x:1, y:0}, {x:2, y:0}, {x:0, y:1}, {x:1, y:1}],
		[{x:1, y:0}, {x:1, y:1}, {x:2, y:1}, {x:2, y:2}],
		[{x:1, y:1}, {x:2, y:1}, {x:0, y:2}, {x:1, y:2}],
		[{x:0, y:0}, {x:0, y:1}, {x:1, y:1}, {x:1, y:2}],
		], color:'purple'},
	'Z':{tiles:[
		[{x:0, y:0}, {x:1, y:0}, {x:1, y:1}, {x:2, y:1}],
		[{x:2, y:0}, {x:1, y:1}, {x:2, y:1}, {x:1, y:2}],
		[{x:0, y:1}, {x:1, y:1}, {x:1, y:2}, {x:2, y:2}],
		[{x:1, y:0}, {x:0, y:1}, {x:1, y:1}, {x:0, y:2}],
		], color:'cyan'},
};
function Sprite(sheet, x, y, width, height, interval, loop){
	var self = this;
	if(interval === undefined)interval = 'none';
	if(loop === undefined)loop = false;
	this.x = x;
	this.y = y;
	this.spriteX = this.width*this.index;
	this.spriteY = 0;
	this.sheet = sheet;
	this.width = width;
	this.height= height;
	this.index = 0;
	this.frames = this.sheet.width/this.width;
	this.loop  = loop;
	this.kill = function(){
		for(var i = 0; i < sprites.length; i ++){
			var t = sprites[i];
			if(t.width === this.width && t.height === this.height && t.index === this.index && t.frames === this.frames){
				sprites.splice(i, 1);
				clearInterval(this.interval);
				return;
			}
		}
	}
	this.update = function(){
		this.spriteX = this.width*this.index;
		this.index ++;
		if(this.index == this.frames){
			if(loop)this.index = 0;
			else this.kill();
		}
	}
	sprites.push(this);
	this.update();
}
function calculateScore(map){
	var score = 0;
	var aggregateHeight = 0;
	var completeLines = 0;
	var holes = 0;
	var holeDepth = 0;
	var bumpiness = 0;
	var column = [];
	var segments = [];
	var tiles = [];
	var chainMap = [];
	for(var i = 0; i < mapHeight; i ++){
		chainMap.push([]);
		for(var j = 0; j < mapWidth; j ++)chainMap[i].push({filled:false});
	}
	for(var i = 0; i < mapHeight; i ++){
		for(var j = 0; j < mapWidth; j ++){
			if(map[i][j] === 0)break;
			if(j === mapWidth-1){
				completeLines ++;
				// map.splice(i, 1);
				// var temp = [];
				// map.unshift(temp);
				// for(var k = 0; k < mapWidth; k ++)temp.push(0);
			}
		}
	}
	if(completeLines > 0){
		for(var i = 0; i < mapHeight; i ++){
			for(var j = 0; j < mapWidth; j ++){
				var t;
				if(oob(j, i, map))t = 0;
				else t = map[i][j];
				if(t != 0 && !chainMap[i][j].filled){
					floodFill(j, i, map, chainMap, tiles);
					segments.push(tiles)
					tiles = [];
					tiles.length = 0;
				}
			}
		}
		for(var i = 0; i < segments.length; i ++){
			var s = segments[i];
			for(var j = 0; j < s.length; j ++){
				var t = s[j];
				map[t.y][t.x] = 0;
			}
			var done = false;
			var diters = 0;
			while(!done){
				diters ++;
				for(var j = 0; j < s.length; j ++){
					t = s[j];
					t.y ++;
				}
				for(var j = 0; j < s.length; j ++){
					t = s[j];
					if(t.y > mapHeight-1 || map[t.y][t.x] != 0){
						done = true;
					}
				}
				if(done){
					for(var j = 0; j < s.length; j ++){
						t = s[j];
						t.y --;
					}
				}
			}
			for(var j = 0; j < s.length; j ++){
				var t = s[j];
				map[t.y][t.x] = t.id;
			}
		}
	}
	for(var j = 0; j < mapWidth; j ++){
		column[j] = {};
		column[j].height = 0;
		for(var i = 0; i < mapHeight; i ++){
			if(map[i][j] > 0){
				column[j].height = mapHeight-i;
				aggregateHeight += column[j].height;
				break;
			}
		}
	}
	for(var j = 1; j < mapWidth; j ++){
		bumpiness += Math.abs(column[j].height-column[j-1].height);
	}
	for(var j = 0; j < mapWidth; j ++){
		column[j].holes = 0;
		column[j].holeDepth = 0;
		var streak = 0;
		var depth = 0;
		for(var i = mapHeight-1; i >= 0; i --){
			if(map[i][j] > 0){
				column[j].holes += streak;
				streak = 0;
			} else {
				streak ++;
			}
		}
		if(column[j].holes > 0){
			for(var i = mapHeight-1; i >= 0; i --){
				if(map[i][j] == 0){
					column[j].holeDepth = Math.abs(column[j].height-(mapHeight - 1 - i)-1);
					break;
				}
			}
		}
		holes += column[j].holes;
		holeDepth += column[j].holeDepth;
	}
	//score = -0.66569*aggregateHeight + -0.46544*bumpiness + 0.99275*completeLines + -0.24077*holes;
	if(aggregateHeight >= mapHeight-4 || holes > 0)score = -aggregateHeight + -bumpiness + completeLines + -100000*holes;
	else score = -aggregateHeight + -bumpiness + 100*completeLines + -10000*holes;
	for(var j = 0; j < mapWidth; j ++)if(map[0][j] > 0)score = -999999999;
	return {total:score, aggregateHeight:aggregateHeight, bumpiness:bumpiness, completeLines:completeLines, holes:holes, holeDepth:holeDepth};
}
function floodFill(x, y, map, chainMap, tiles){
	chainMap[y][x].filled = true;
	tiles.push({x:x, y:y, id:map[y][x]});
	for(var i = -1; i <= 1; i ++){
		var ry = y+i;
		for(var j = -1; j <= 1; j ++){
			var rx = x+j;
			if(Math.abs(x-rx) + Math.abs(y-ry) === 1 && !oob(rx, ry, map) && map[ry][rx] !== 0 && !chainMap[ry][rx].filled){
				floodFill(rx, ry, map, chainMap, tiles);
			}
		}
	}
}
function Shape(type, owner){
	owner.shape = this;
	this.x = origin.x;
	this.y = origin.y;
	this.width = 0;
	this.height = 0;
	this.type = type;
	this.rotationIndex = 0;
	this.initializing = true;
	this.destinations = [];
	this.segments = [];
	this.tiles = [];
	this.chainMap = [];
	this.fallDelayMax = findFallSpeed(owner.level);
	this.fallDelay = this.fallDelayMax;
	this.lockDelayMax = defaults.lockDelay;
	this.lockDelay = this.lockDelayMax;
	this.locked = false;
	this.locking = false;
	this.o = opponent(owner);
	this.ai = owner.ai;
	this.aiTimer = 0;
	this.aiLimit = 5;
	this.aiRotates = 0;
	this.aiRotatesMax = 4;
	this.bestMove = undefined;
	this.findBest = function(type, map, tetris, lookahead){
		if(lookahead === undefined)lookahead = false;
		this.clear();
		var moves = [];
		var valid = false;
		var startX = this.x;
		var startY = this.y;
		var tempMap = [];
		for(var i = 0; i < mapHeight; i ++){
			tempMap.push([]);
			for(var j = 0; j < mapWidth; j ++){
				tempMap[i].push(map[i][j]);
			}
		}
		var beforeScore = calculateScore(tempMap);
		if(beforeScore.aggregateHeight > 7*mapWidth){
			tetris = false;
			//console.log('Stack is too tall; temporarily deactivating TETRIS mode')
		}
		if(beforeScore.holes > 0){
			tetris = false;
			//console.log('There are holes in the stack; temporarily deactivating TETRIS mode')
		}
		for(var j = 0; j < shapes[type].tiles.length; j ++){
			this.x = -2;
			while(this.x < mapWidth){
				var destinations = [];
				destinations.length = 0;
				valid = true;
				for(var i = 0; i < shapes[type].tiles[j].length; i ++){
					var t = shapes[type].tiles[j][i];
					var x = t.x+this.x;
					var y = t.y+this.y;
					if(oob(x, y, tempMap) || (tetris && x == mapWidth-1) || tempMap[y][x] > 0){
						valid = false;
						break;
					}
				}
				if(valid){
					var airborne = true;
					var tx = this.x;
					var ty = this.y;
					while(airborne){
						ty += 1;
						for(var i = 0; i < shapes[type].tiles[j].length; i ++){
							var t = shapes[type].tiles[j][i];
							if(t.y+ty < 0)continue;
							if(t.y+ty > mapHeight-1 || tempMap[t.y+ty][t.x+tx] !== 0){
								airborne = false;
								ty -= 1;
								break;
							}
						}
					}
					for(var i = 0; i < shapes[type].tiles[j].length; i ++){
						var t = shapes[type].tiles[j][i];
						var x = t.x+tx;
						var y = t.y+ty;
						tempMap[y][x] = colors[shapes[type].color];
						destinations.push({x:x, y:y});
					}
					prospect = {spots:destinations, x:tx, y:ty, rotation:j, score:calculateScore(tempMap)};
					moves.push(prospect);
					for(var i = 0; i < destinations.length; i ++){
						var t = destinations[i];
						tempMap[t.y][t.x] = 0;
					}
				}
				this.x ++;
			}
		}
		if(lookahead){
			for(var i = 0; i < moves.length; i ++){
				var m = moves[i];
				for(var j = 0; j < m.spots; j ++){
					var s = m.spots[j];
					tempMap[s.y][s.x] = 8;
					m.score += findBest(owner.bag.next, tempMap, tetris, false).score;
					tempMap[s.y][s.x] = 0;
				}
			}
		}
		var bestMove = {};
		var scores = [];
		for(var i = 0; i < moves.length; i ++){
			var m = moves[i];
			var score = m.score.total;
			scores.push(score);
		}
		scores.sort(function(a, b){return b-a});
		for(var i = 0; i < moves.length; i ++){
			if(moves[i].score.total === scores[0])bestMove = {x:moves[i].x, y:moves[i].y, rotation:moves[i].rotation, score:moves[i].score};
		}
		this.x = startX;
		this.y = startY;
		this.restore();
		if(bestMove.score.holes > beforeScore.holes && tetris){
			//console.log('Best move creates a hole; temporarily deactivating TETRIS mode.')
			return this.findBest(type, map, false, lookahead)
		}
		return bestMove;
	}
	this.goBestMove = function(){
		var moved = false;
		var lookahead = owner.name === 'player1';
		if(this.bestMove === undefined){
			this.bestMove = this.findBest(this.type, owner.map, true, lookahead);
		}
		if(!owner.testing){
			if(this.rotationIndex != this.bestMove.rotation){
				this.rotate(1);
				moved = true;
			}
			if(this.x < this.bestMove.x)moved = this.move( 1, 0);
			else
			if(this.x > this.bestMove.x)moved = this.move(-1, 0);
			if(this.x === this.bestMove.x && this.rotationIndex == this.bestMove.rotation){
				this.dash( 0, 1);
				this.lock();
				moved = true;
			}
			if(!moved){
				if(this.aiRotates >= this.aiRotatesMax){
					this.bestMove = undefined;
				} else {
					this.rotate(1);
					this.aiRotates ++;
					//console.log("Move blocked, rotating")
				}
			}
		} else {
			this.clear();
			this.x = this.bestMove.x;
			this.rotationIndex = this.bestMove.rotation;
			this.restore();
			this.update();
			this.dash( 0, 1);
			this.lock();
		}

	}
	this.check = function(){
		// for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
		// 	var t = shapes[this.type].tiles[this.rotationIndex][i];
		// 	var x = t.x+this.x;
		// 	var y = t.y+this.y;
		// 	if(oob(x, y, owner.map) || owner.map[y][x] > 0){
		// 		this.lock();
		// 	}
		// }
	}
	this.clear = function(){
		for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
			var t = shapes[this.type].tiles[this.rotationIndex][i];
			var x = t.x+this.x;
			var y = t.y+this.y;
			if(!oob(x, y, owner.map)){
				owner.map[y][x] = 0;
			}
		}
	}
	this.restore = function(){
		for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
			var t = shapes[this.type].tiles[this.rotationIndex][i];
			var x = t.x+this.x;
			var y = t.y+this.y;
			if(!oob(x, y, owner.map)){
				owner.map[y][x] = colors[shapes[this.type].color];
			}
		}
	}
	this.update = function(){
		this.clear();
		var tx = this.x;
		var ty = this.y;
		this.destinations = [];
		this.destinations.length = 0;
		var valid = true;
		while(valid){
			ty += 1;
			for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
				var t = shapes[this.type].tiles[this.rotationIndex][i];
				if(t.y+ty < 0)continue;
				if(t.y+ty > mapHeight-1 || owner.map[t.y+ty][t.x+tx] !== 0){
					valid = false;
					ty -= 1;
					break;
				}
			}
		}
		for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
			var t = shapes[this.type].tiles[this.rotationIndex][i];
			var x = t.x+this.x;
			var y = t.y+this.y;
			if(!oob(x, y, owner.map)){
				owner.map[y][x] = colors[shapes[this.type].color];
			}
			this.destinations.push({x:t.x+tx, y:t.y+ty});
		}
		this.finalX = tx;
		this.finalY = ty;
		display.flip();
	}
	this.hold = function(){
		if(owner.canHold){
			this.lockDelay = this.lockDelayMax;
			for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
				var t = shapes[this.type].tiles[this.rotationIndex][i];
				var x = t.x+this.x;
				var y = t.y+this.y;
				if(!oob(x, y, owner.map)){
					owner.map[y][x] = 0;
				}
			}
			if(owner.held === undefined){
				owner.held = this.type;
				bag.draw(owner);
			} else {
				new Shape(owner.held, owner);
				owner.held = this.type;
			}
			owner.canHold = false;
			display.flip();
		}
	}
	this.size = function(){
		this.width = 0;
		this.height = 0;
		var x1 = 0;
		var x2 = 0;
		var y1 = 0;
		var y2 = 0;
		for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
			var t = shapes[this.type].tiles[this.rotationIndex][i];
			if(t.x < x1)x1 = t.x;
			if(t.x > x2)x2 = t.x;
			if(t.y < y1)y1 = t.y;
			if(t.y > y2)y2 = t.y;
		}
		this.width = Math.abs(x1-x2);
		this.height = Math.abs(y1-y2);
	}
	this.rotate = function(difference){
		if(difference === undefined)difference = 1;
		var targetIndex = this.rotationIndex+difference;
		if(targetIndex >= shapes[this.type].tiles.length)targetIndex = 0;
		if(targetIndex < 0)targetIndex = shapes[this.type].tiles.length-1;
		for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
			var t = shapes[this.type].tiles[this.rotationIndex][i];
			var x = t.x+this.x;
			var y = t.y+this.y;
			if(!oob(x, y, owner.map)){
				owner.map[y][x] = 0;
			}
		}
		var ox = 0;
		var oy = 0;
		var iters = 0;
		var outside = true;
		var valid = true;
		while(outside){
			outside = false;
			for(var i = 0; i < shapes[this.type].tiles[targetIndex].length; i ++){
				var t = shapes[this.type].tiles[targetIndex][i];
				if(oob(t.x+this.x+ox, t.y+this.y+oy, owner.map) || (t.y+this.y+oy !== -1 && owner.map[t.y+this.y+oy][t.x+this.x+ox] !== 0)){
					outside = true;
					break;
				}
			}
			if(!outside){
				valid = true;
				break;
			}
			if(iters === 0){
				ox = 0;
				oy = -1;
			}else
			if(iters === 1){
				ox = 0;
				oy = 1;
			}else
			if(iters === 2){
				ox = -1;
				oy = 0;
			}else
			if(iters === 3){
				ox = 1;
				oy = 0;
			}else
			if(iters === 4){
				ox = -2;
				oy = 0;
			}else
			if(iters === 5){
				ox = 2;
				oy = 0;
			}else{
				valid = false;
				break;
			}
			iters ++;
		}
		if(valid){
			this.lockDelay = this.lockDelayMax;
			this.locking = false;
			this.x += ox;
			this.y += oy;
			this.rotationIndex = targetIndex;
			this.size();
		}
		this.update();
		wasDown['up'] = false;
		wasDown['w'] = false;
	}
	this.dash = function(dx, dy){
		if(dx === 0 && dy > 0){
			for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
				var t = shapes[this.type].tiles[this.rotationIndex][i];
				var x = t.x+this.x;
				var y = t.y+this.y;
				if(!oob(x, y, owner.map)){
					owner.map[y][x] = 0;
				}
			}
			owner.score += Math.abs(this.y-this.finalY)*2;
			this.x = this.finalX;
			this.y = this.finalY;
			this.update();
		} else {
			while(true)if(!this.move(dx, dy))break;
			if(!this.locked)this.update();
		}
	}
	this.move = function(dx, dy, points){
		if(points === undefined)points = 0;
		var moved = false;
		if(this.locked){
			return;
		}
		var tx = this.x + dx;
		var ty = this.y + dy;
		var valid = true;
		this.clear();
		for(var i = 0; i < shapes[this.type].tiles[this.rotationIndex].length; i ++){
			var t = shapes[this.type].tiles[this.rotationIndex][i];
			if(oob(t.x+tx, t.y+ty, owner.map, true) || (t.y+this.y !== -1 && owner.map[t.y+ty][t.x+tx] !== 0)){
				if(dy === 1 && (t.y+ty > mapHeight-1 || owner.map[t.y+ty][t.x+tx] !== 0)){
					this.locking = true;
					wasDown['down'] = false;
					wasDown['s'] = false;
				}
				valid = false;
				break;
			}
		}
		if(valid){
			if(dx === 0 && dy === 1)owner.score += points;
			this.x = tx;
			this.y = ty;
			moved = true;
			this.lockDelay = this.lockDelayMax;
			this.locking = false;
			this.locked = false;
		}
		this.update();
		if(!(dx === 0 && dy === 0))if(this.initializing)this.initializing = false;
		return moved;
	}
	this.lock = function(){
		this.locked = true;
		this.locking = false;
		var gameOver = false;
		var chain = 0;
		var checking = true;
		var iters = 0;
		while(checking){
			iters ++;
			checking = false;
			for(var i = 0; i < mapHeight; i ++){
				var k = 0;
				for(var j = 0; j < mapWidth; j ++){
					var t;
					if(oob(j, i, owner.map))t = 0;
					else t = owner.map[i][j];
					if(t != 0){
						if(i === 0){
							gameOver = true;
						}
					}
					if(t != 0 && k === j)k ++;
					else k = 0;
					if(j === mapWidth-1 && k > 0){
						chain ++;
						owner.goal --;
						owner.map.splice(i, 1);
						var temp = [];
						owner.map.unshift(temp);
						for(var l = 0; l < mapWidth; l ++){
							temp.push(0);
							if(owner.name === 'player1')new Sprite(blockDestruction, (l-5)*blockSize, (i+6)*blockSize, blockSize, blockSize);
							if(owner.name === 'player2')new Sprite(blockDestruction, (l+15)*blockSize, (i+6)*blockSize, blockSize, blockSize);
						}
						checking = true;
					}
				}
			}
			if(chain > 0){
				this.segments = [];
				this.segments.length = 0;
				this.tiles = [];
				this.tiles.length = 0;
				this.chainMap = [];
				this.chainMap.length = 0;
				for(var i = 0; i < mapHeight; i ++){
					this.chainMap.push([]);
					for(var j = 0; j < mapWidth; j ++)this.chainMap[i].push({filled:false});
				}
				for(var i = 0; i < mapHeight; i ++){
					for(var j = 0; j < mapWidth; j ++){
						var t;
						if(oob(j, i, owner.map))t = 0;
						else t = owner.map[i][j];
						if(t != 0 && !this.chainMap[i][j].filled){
							floodFill(j, i, owner.map, this.chainMap, this.tiles);
							this.segments.push(this.tiles)
							this.tiles = [];
							this.tiles.length = 0;
						}
					}
				}
				for(var i = 0; i < this.segments.length; i ++){
					var s = this.segments[i];
					for(var j = 0; j < s.length; j ++){
						var t = s[j];
						owner.map[t.y][t.x] = 0;
					}
					var done = false;
					var diters = 0;
					while(!done){
						diters ++;
						for(var j = 0; j < s.length; j ++){
							t = s[j];
							t.y ++;
						}
						for(var j = 0; j < s.length; j ++){
							t = s[j];
							if(t.y > mapHeight-1 || owner.map[t.y][t.x] != 0){
								done = true;
							}
						}
						if(done){
							for(var j = 0; j < s.length; j ++){
								t = s[j];
								t.y --;
							}
						}
					}
					for(var j = 0; j < s.length; j ++){
						var t = s[j];
						owner.map[t.y][t.x] = t.id;
					}
				}
			}
		}
		if(gameOver){
			gameOver = false;
			owner.dead = true;
			if(this.o.playing && !(this.o.ai && !owner.ai) && !(owner.ai && !this.o.ai)){
				this.o.dead = true;
				this.o.wins ++;
			}
			return;
		} else {
			if(chain === 1)owner.score += 100 * owner.level;
			if(chain === 2)owner.score += 300 * owner.level;
			if(chain === 3)owner.score += 500 * owner.level;
			if(chain === 4)owner.score += 800 * owner.level;
			if(chain >=  5)owner.score += 1000 * (chain-4) * owner.level;
			if(this.o.playing){
				if(chain >= 2){
					if((owner.ai && this.o.ai) || (!owner.ai && !this.o.ai)){
						this.o.garbage += chain-1;
					}
				}
			}
			if(owner.goal <= 0){
				owner.goal = findGoal(owner.level) - owner.goal;
				owner.level ++;
				this.fallDelayMax = findFallSpeed(owner.level);
			}
			while(owner.garbage > 0){
				owner.garbage --;
				makeGarbage(owner.map);
			}
			display.flip();
			owner.canHold = true;
			bag.draw(owner);
			wasDown['left'] = false;
			wasDown['right']= false;
			wasDown['up']   = false;
			wasDown['down'] = false;
			wasDown['w'] = false;
			wasDown['a']= false;
			wasDown['s']   = false;
			wasDown['d'] = false;
		}
	}
	this.size();
	this.check();
	this.update();
}
keyDown = {};
keyDown['left'] = false;
keyDown['right']= false;
keyDown['up']   = false;
keyDown['down'] = false;
keyDown['p'] 	= false;
keyDown['w'] 	= false;
keyDown['a']	= false;
keyDown['s']   	= false;
keyDown['q'] 	= false;
keyDown['\''] 	= false;
keyDown['leftShift']= false;
keyDown['rightShift']= false;
keyDown['space']= false;
keyDown['enter']= false;
wasDown = {};
wasDown['left'] = false;
wasDown['right']= false;
wasDown['up']   = false;
wasDown['down'] = false;
wasDown['p'] 	= false;
wasDown['w'] 	= false;
wasDown['a']	= false;
wasDown['s']   	= false;
wasDown['d'] 	= false;
wasDown['q'] 	= false;
wasDown['\''] 	= false;
wasDown['leftShift']= false;
wasDown['rightShift']= false;
wasDown['space']= false;
wasDown['enter']= false;
function keydown(e){
	switch(e.keyCode){
		case 37:
			if(!wasDown['left'] && !keyDown['left'])wasDown['left'] = true;
			keyDown['left'] = true;
			break;
		case 39:
			if(!wasDown['right'] && !keyDown['right'])wasDown['right'] = true;
			keyDown['right'] = true;
			break;
		case 38:
		 	if(!wasDown['up'] && !keyDown['up'])wasDown['up'] = true;
			keyDown['up'] = true;
			break;
		case 40:
			if(!wasDown['down'] && !keyDown['down'])wasDown['down'] = true;
			keyDown['down'] = true;
			break;
		case 65:
			if(!wasDown['a'] && !keyDown['a'])wasDown['a'] = true;
			keyDown['a'] = true;
			break;
		case 68:
			if(!wasDown['d'] && !keyDown['d'])wasDown['d'] = true;
			keyDown['d'] = true;
			break;
		case 87:
		 	if(!wasDown['w'] && !keyDown['w'])wasDown['w'] = true;
			keyDown['w'] = true;
			break;
		case 83:
			if(!wasDown['s'] && !keyDown['s'])wasDown['s'] = true;
			keyDown['s'] = true;
			break;
		case 80:
			if(!wasDown['p'] && !keyDown['p'])wasDown['p'] = true;
			keyDown['p'] = true;
			break;
		case 81:
			if(!wasDown['q'] && !keyDown['q'])wasDown['q'] = true;
			keyDown['q'] = true;
			break;
		case 222:
			if(!wasDown['\''] && !keyDown['\''])wasDown['\''] = true;
			keyDown['\''] = true;
			break;
		case 13:
			if(!wasDown['enter'] && !keyDown['enter'])wasDown['enter'] = true;
			keyDown['enter'] = true;
			break;
		case 16:
			if (e.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT){
				keyDown['leftShift'] = true;
			} else if (e.location === KeyboardEvent.DOM_KEY_LOCATION_RIGHT){
				keyDown['rightShift'] = true;
			}
			break;
		case 32:
			if(!wasDown['space'] && !keyDown['space'])wasDown['space'] = true;
			keyDown['space'] = true;
			break;
	}
	if(!paused){
		if(wasDown['space']){
			if(p1.ai){
				p1.ai = false;
				p1.reset();
			} else {
				p1.shape.dash( 0, 1);
			}
			wasDown['space'] = false;
		}
		if(wasDown['enter']){
			if(p2.ai){
				p2.ai = false;
				p2.reset();
			} else {
				p2.shape.dash( 0, 1);
			}
			wasDown['enter'] = false;
		}
		if(p1.playing && !p1.ai){
			if(wasDown['a'])p1.shape.move(-1, 0);
			if(wasDown['d'])p1.shape.move( 1, 0);
			if(wasDown['w'])p1.shape.rotate();
			if(wasDown['s']){
				if(p1.shape.locking)p1.shape.lock();
				else p1.shape.move( 0, 1);
			}
			if(keyDown['leftShift']){
				p1.shape.hold();
			}
		}
		if(p2.playing && !p2.ai){
			if(wasDown['left'])p2.shape.move(-1, 0);
			if(wasDown['right'])p2.shape.move( 1, 0);
			if(wasDown['up'])p2.shape.rotate();
			if(wasDown['down']){
				if(p2.shape.locking)p2.shape.lock();
				else p2.shape.move( 0, 1, 1);
			}
			if(keyDown['rightShift'])p2.shape.hold();
		}
	}
	if(wasDown['p']){
		if(!paused){
			paused = true;
		} else {
			paused = false;
		}
		display.flip();
		wasDown['p'] = false;
	}
}
function keyup(e){
	switch(e.keyCode){
		case 37:
			keyDown['left'] = false;
			wasDown['left'] = false;
			break;
		case 39:
			keyDown['right'] = false;
			wasDown['right'] = false;
			break;
		case 38:
			keyDown['up'] = false;
			wasDown['up'] = false;
			break;
		case 40:
			keyDown['down'] = false;
			wasDown['down'] = false;
			break;
		case 65:
			keyDown['a'] = false;
			wasDown['a'] = false;
			break;
		case 68:
			keyDown['d'] = false;
			wasDown['d'] = false;
			break;
		case 87:
			keyDown['w'] = false;
			wasDown['w'] = false;
			break;
		case 83:
			keyDown['s'] = false;
			wasDown['s'] = false;
			break;
		case 80:
			keyDown['p'] = false;
			wasDown['p'] = false;
			break;
		case 81:
			keyDown['q'] = false;
			wasDown['q'] = false;
			break;
		case 222:
			keyDown['\''] = false;
			wasDown['\''] = false;
			break;
		case 16:
			if(keyDown['leftShift'])keyDown['leftShift'] = false;
			if(keyDown['rightShift'])keyDown['rightShift'] = false;
			break;
		case 13:
			keyDown['enter'] = false;
			wasDown['enter'] = false;
			break;
		case 32:
			keyDown['space'] = false;
			wasDown['space'] = false;
			break;
	}
}
if(p1.playing){
	bag.init(p1);
	bag.draw(p1);
}
if(p2.playing){
	bag.init(p2);
	bag.draw(p2);
}
document.onkeydown = keydown;
document.onkeyup   = keyup;
function onEnterFrame(){
	if(!paused){
		p1.update();
		p2.update();
	}
	var updated = false;
	for(var i = 0; i < sprites.length; i ++){
		var sprite = sprites[i];
		sprite.update();
		updated = true;
	}
	if(updated)display.flip();
}
var enterFrame = setInterval(onEnterFrame, frameRate);
              
            
!
999px

Console