replace old tetris with twitch tetris
59
tetris/Background.js
Normal file
@ -0,0 +1,59 @@
|
||||
|
||||
var Background = function (config) {
|
||||
var x, y,
|
||||
curTile;
|
||||
|
||||
config = config || {};
|
||||
|
||||
this.originX = (config.x || 0) + FIELD_OFFSET_X;
|
||||
this.originY = (config.y || 0) + FIELD_OFFSET_Y;
|
||||
|
||||
this.width = 10;
|
||||
this.height = 20;
|
||||
|
||||
this.tiles = [];
|
||||
for (x = 0; x < this.width; x += 1) {
|
||||
for (y = 0; y < this.height; y += 1) {
|
||||
curTile = new Block({ empty: true, blockX: x, blockY: y });
|
||||
this.tiles.push(curTile);
|
||||
}
|
||||
}
|
||||
|
||||
this.backdrop = new jaws.Sprite({image: 'media/background/backdrop.png'});
|
||||
this.backdrop.x = 0;
|
||||
this.backdrop.y = 0;
|
||||
|
||||
this.topBar = new jaws.Sprite({image: 'media/background/topbar.png'});
|
||||
this.topBar.x = 181;
|
||||
this.topBar.y = 0;
|
||||
|
||||
this.fullRedrawNeeded = true;
|
||||
};
|
||||
|
||||
|
||||
Background.prototype.draw = function (lastPaused) {
|
||||
var i;
|
||||
|
||||
if (this.fullRedrawNeeded || lastPaused) {
|
||||
this.backdrop.draw();
|
||||
|
||||
for (i = 0; i < this.tiles.length; i += 1) {
|
||||
this.tiles[i].draw();
|
||||
}
|
||||
|
||||
this.fullRedrawNeeded = false;
|
||||
|
||||
} else {
|
||||
|
||||
this.topBar.draw();
|
||||
|
||||
// clear the swap group / previews
|
||||
jaws.context.fillstyle = "#000D00";
|
||||
jaws.context.fillRect(24, 42, 118, 60);
|
||||
jaws.context.fillRect(457, 18, 107, 341);
|
||||
|
||||
for (i = 0; i < this.tiles.length; i += 1) {
|
||||
this.tiles[i].drawIfInvalid();
|
||||
}
|
||||
}
|
||||
};
|
111
tetris/Block.js
Normal file
@ -0,0 +1,111 @@
|
||||
|
||||
// TODO: constants file???
|
||||
var BLOCK_WIDTH = 24;
|
||||
|
||||
function Block(config) {
|
||||
var parent, key;
|
||||
|
||||
config = config || {};
|
||||
|
||||
this.boX = (config.boardOriginX || 0) + FIELD_OFFSET_X;
|
||||
this.boY = (config.boardOriginY || 0) + FIELD_OFFSET_Y;
|
||||
this.blockX = config.blockX;
|
||||
this.blockY = config.blockY;
|
||||
|
||||
this.occupiedPositions = config.occupiedPositions;
|
||||
this.addOccupied(this.blockX, this.blockY);
|
||||
|
||||
Block.invalidSpaces[this.blockX + "," + this.blockY] = true;
|
||||
|
||||
config.x = this.boX + BLOCK_WIDTH * this.blockX;
|
||||
config.y = this.boY + BLOCK_WIDTH * this.blockY;
|
||||
|
||||
if (config.preview) {
|
||||
config.image = 'media/greyblock.png';
|
||||
} else if (config.empty) {
|
||||
config.image = 'media/emptyblock.png';
|
||||
}else {
|
||||
config.image = SHAPES[config.shape].image;
|
||||
}
|
||||
|
||||
parent = new jaws.Sprite(config);
|
||||
for (key in parent) {
|
||||
this[key] = parent[key];
|
||||
}
|
||||
}
|
||||
|
||||
Block.invalidSpaces = {};
|
||||
Block.allInvalidated = false;
|
||||
Block.invalidFlushed = function() {
|
||||
Block.invalidSpaces = {};
|
||||
Block.allInvalidated = false;
|
||||
};
|
||||
Block.invalidateAll = function() {
|
||||
Block.allInvalidated = true;
|
||||
};
|
||||
|
||||
Block.prototype.setColor = function(shape, preview) {
|
||||
if (preview) {
|
||||
this.setImage('media/greyblock.png');
|
||||
} else {
|
||||
this.setImage(SHAPES[shape].image);
|
||||
}
|
||||
Block.invalidSpaces[this.blockX + "," + this.blockY] = true;
|
||||
};
|
||||
|
||||
Block.prototype.moveBlock = function(dx, dy) {
|
||||
Block.invalidSpaces[this.blockX + "," + this.blockY] = true;
|
||||
this.removeOccupied(this.blockX, this.blockY);
|
||||
this.blockX += dx;
|
||||
this.blockY += dy;
|
||||
Block.invalidSpaces[this.blockX + "," + this.blockY] = true;
|
||||
this.addOccupied(this.blockX, this.blockY);
|
||||
this.x += dx * BLOCK_WIDTH;
|
||||
this.y += dy * BLOCK_WIDTH;
|
||||
};
|
||||
|
||||
Block.prototype.setPosition = function(blockX, blockY) {
|
||||
Block.invalidSpaces[this.blockX + "," + this.blockY] = true;
|
||||
this.removeOccupied(this.blockX, this.blockY);
|
||||
this.blockX = blockX;
|
||||
this.blockY = blockY;
|
||||
Block.invalidSpaces[this.blockX + "," + this.blockY] = true;
|
||||
this.addOccupied(this.blockX, this.blockY);
|
||||
this.x = this.boX + blockX * BLOCK_WIDTH;
|
||||
this.y = this.boY + blockY * BLOCK_WIDTH;
|
||||
};
|
||||
|
||||
Block.prototype.getX = function() { return this.blockX; };
|
||||
Block.prototype.getY = function() { return this.blockY; };
|
||||
|
||||
Block.prototype.isPosition = function(x, y) {
|
||||
return this.blockX === x && this.blockY === y;
|
||||
};
|
||||
|
||||
Block.prototype.drawIfInvalid = function() {
|
||||
if (Block.invalidSpaces[this.blockX + "," + this.blockY] || Block.allInvalidated || this.blockY < 0) {
|
||||
this.draw();
|
||||
}
|
||||
};
|
||||
|
||||
Block.prototype.kill = function() {
|
||||
Block.invalidSpaces[this.blockX + "," + this.blockY] = true;
|
||||
this.removeOccupied(this.blockX, this.blockY);
|
||||
};
|
||||
|
||||
Block.prototype.removeOccupied = function(x, y) {
|
||||
var posString = x + ',' + y;
|
||||
if (this.occupiedPositions && this.occupiedPositions[posString]) {
|
||||
this.occupiedPositions[posString] -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
Block.prototype.addOccupied = function(x, y) {
|
||||
var posString = x + ',' + y;
|
||||
if (this.occupiedPositions) {
|
||||
if (this.occupiedPositions[posString] === undefined) {
|
||||
this.occupiedPositions[posString] = 0;
|
||||
}
|
||||
this.occupiedPositions[posString] += 1;
|
||||
}
|
||||
};
|
13
tetris/Button.js
Normal file
@ -0,0 +1,13 @@
|
||||
function Button(config) {
|
||||
|
||||
var parent = new jaws.Sprite(config),
|
||||
key;
|
||||
|
||||
for (key in parent) {
|
||||
this[key] = parent[key];
|
||||
}
|
||||
}
|
||||
|
||||
Button.prototype.isClicked = function(x, y) {
|
||||
return this.rect().collidePoint(x, y);
|
||||
};
|
372
tetris/ControlGroup.js
Normal file
@ -0,0 +1,372 @@
|
||||
|
||||
/**
|
||||
* The blocks that can be moved nby the user
|
||||
* @param {Array} blocks - an array of [Block] of size 4 that can be operated on
|
||||
* @param {Char} shape - the block type: i, o, j, l, s, z, t
|
||||
* @param {function({Number}x, {Number}y)} isLegalCallback - a function that retursn true if a block can be moved
|
||||
* to the new position
|
||||
*/
|
||||
function ControlGroup(blocks, shape, isLegalCallback) {
|
||||
var i,
|
||||
newX, newY,
|
||||
shapeConf;
|
||||
|
||||
// place the blocks according to the shape
|
||||
shapeConf = SHAPES[shape];
|
||||
this.pos = shapeConf.pos;
|
||||
this.spin = shapeConf.spin;
|
||||
this.bottomed = false;
|
||||
|
||||
this.blocks = blocks;
|
||||
this.baseX = shapeConf.startX;
|
||||
this.baseY = shapeConf.startY;
|
||||
|
||||
this.shape = shape;
|
||||
this.kickOffsets = WALL_KICK_OFFSETS[shapeConf.kickType];
|
||||
this.dir = 0;
|
||||
|
||||
this.isIllegalStart = false;
|
||||
|
||||
this.isLegalCallback = isLegalCallback || function() {return true;};
|
||||
|
||||
this.lastWasSpin = false;
|
||||
|
||||
for (i = 0; i < blocks.length; i += 1) {
|
||||
newX = this.baseX + this.pos[i].x;
|
||||
newY = this.baseY + this.pos[i].y;
|
||||
// see if the block placement is illegal before placing
|
||||
if (!this.isLegalCallback(newX, newY)) {
|
||||
this.isIllegalStart = true;
|
||||
}
|
||||
this.blocks[i].setPosition(newX, newY);
|
||||
}
|
||||
|
||||
this.updateBottomedState();
|
||||
}
|
||||
|
||||
/**
|
||||
* if the position is legal
|
||||
* @param {Number} x
|
||||
* @param {Number} y
|
||||
* @returns {Boolean} true iff the position is legal to move to
|
||||
*/
|
||||
ControlGroup.prototype.isLegalPosition = function (x, y) {
|
||||
var i,
|
||||
blocks = this.blocks;
|
||||
|
||||
// if it's a currently occupied, it must be legal
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
if (blocks[i].isPosition(x, y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// if it's still not proven legal, then defer to the game to decide
|
||||
return this.isLegalCallback(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shift the block left or right
|
||||
* @param {Boolean} left - true to shift left false to shift right
|
||||
* @returns {Boolean} true iff the shift was successful
|
||||
*/
|
||||
ControlGroup.prototype.shift = function(left) {
|
||||
var dx = (left ? -1 : 1),
|
||||
i;
|
||||
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
if (!this.isLegalPosition(this.blocks[i].getX()+dx, this.blocks[i].getY())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.lastWasSpin = false;
|
||||
this.baseX += dx;
|
||||
|
||||
for (i = 0; i < this.blocks.length; i += 1) {
|
||||
this.blocks[i].moveBlock(dx, 0);
|
||||
}
|
||||
this.updateBottomedState();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
ControlGroup.prototype.updateBottomedState = function() {
|
||||
var i;
|
||||
|
||||
for (i = 0; i < this.blocks.length; i += 1) {
|
||||
if (!this.isLegalPosition(this.blocks[i].getX(), this.blocks[i].getY() + 1)) {
|
||||
this.bottomed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.bottomed = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Drop the block by one
|
||||
*/
|
||||
ControlGroup.prototype.drop = function() {
|
||||
var i;
|
||||
|
||||
// don't drop if bottomed
|
||||
if (this.bottomed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastWasSpin = false;
|
||||
this.baseY += 1;
|
||||
|
||||
for (i = 0; i < this.blocks.length; i += 1) {
|
||||
this.blocks[i].moveBlock(0, 1);
|
||||
}
|
||||
this.updateBottomedState();
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Boolean} true if the block is bottomed and another shoudl spawn
|
||||
*/
|
||||
ControlGroup.prototype.isBottomed = function() {
|
||||
return this.bottomed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Turns the block
|
||||
* @param {Boolean} cw - true for clockwise, false for counter-clockwise
|
||||
* @returns {Boolean} true iff the block was successfully turned
|
||||
*/
|
||||
ControlGroup.prototype.turn = function(cw) {
|
||||
var kick,
|
||||
newPos = null,
|
||||
direction = cw ? 'cw' : 'ccw',
|
||||
availableKicks = this.kickOffsets[this.dir][direction],
|
||||
i;
|
||||
|
||||
// for possible each kick offset
|
||||
for (i = 0; i < availableKicks.length; i += 1) {
|
||||
kick = availableKicks[i];
|
||||
newPos = this.tryTurn(cw, kick);
|
||||
if (newPos) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if there s still no valid rotation, fail
|
||||
if (!newPos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.lastWasSpin = true;
|
||||
|
||||
// must be legal at this point move the bocks
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
this.blocks[i].setPosition(newPos[i].x, newPos[i].y);
|
||||
}
|
||||
this.baseX += kick.x;
|
||||
this.baseY += kick.y;
|
||||
|
||||
// keep track of the direction
|
||||
if (cw) {
|
||||
this.dir += 1;
|
||||
if (this.dir === 4) {
|
||||
this.dir = 0;
|
||||
}
|
||||
} else {
|
||||
this.dir -= 1;
|
||||
if (this.dir === -1) {
|
||||
this.dir = 3;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateBottomedState();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given rotation and kick is valid.
|
||||
* @param {Boolean} cw - true if cw, false if ccw
|
||||
* @param {Object} kick - the kick offset x/y object to try
|
||||
* @returns {Array} and array of x/y objects if valid, null if not valid
|
||||
*/
|
||||
ControlGroup.prototype.tryTurn = function (cw, kick) {
|
||||
var newX, newY,
|
||||
oldX, oldY,
|
||||
i,
|
||||
newPos = [],
|
||||
curPos;
|
||||
|
||||
if (this.spin === 'block') {
|
||||
for (i = 0; i < this.blocks.length; i += 1) {
|
||||
newX = (cw ? -1 : 1) * (this.blocks[i].blockY - this.baseY) + this.baseX + kick.x;
|
||||
newY = (cw ? 1 : -1) * (this.blocks[i].blockX - this.baseX) + this.baseY + kick.y;
|
||||
|
||||
newPos[i] = {x: newX, y: newY};
|
||||
}
|
||||
} else {
|
||||
// point turning
|
||||
for (i = 0; i < this.blocks.length; i += 1) {
|
||||
oldX = this.blocks[i].blockX - this.baseX;
|
||||
oldY = this.blocks[i].blockY - this.baseY;
|
||||
|
||||
if (oldX >= 0) { oldX += 1; }
|
||||
if (oldY >= 0) { oldY += 1; }
|
||||
|
||||
newX = (cw ? -1 : 1) * oldY;
|
||||
newY = (cw ? 1 : -1) * oldX;
|
||||
|
||||
if (newX > 0) { newX -= 1; }
|
||||
if (newY > 0) { newY -= 1; }
|
||||
|
||||
newPos[i] = {x: newX + this.baseX + kick.x, y: newY + this.baseY + kick.y};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// for each block
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
curPos = newPos[i];
|
||||
if (!this.isLegalPosition(curPos.x, curPos.y)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return newPos;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the positions that the block will use when it falls
|
||||
* @returns {Object} {dist:{Number}, positions: {[Object]} array of hashs of {x: Number, y: Number}}
|
||||
*/
|
||||
ControlGroup.prototype.getFallPositions = function () {
|
||||
var res = [],
|
||||
dist = 0,
|
||||
i,
|
||||
curBlock,
|
||||
notDone = true;
|
||||
|
||||
while (notDone) {
|
||||
dist += 1;
|
||||
|
||||
// for each block
|
||||
for (i = 0; i < 4 && notDone; i += 1) {
|
||||
curBlock = this.blocks[i];
|
||||
// if it's not a legal position
|
||||
if (!this.isLegalPosition(curBlock.getX(), curBlock.getY() + dist)) {
|
||||
// back up one and stop dropping
|
||||
dist -= 1;
|
||||
notDone = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for each block
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
curBlock = this.blocks[i];
|
||||
res.push({x: curBlock.getX(), y: curBlock.getY() + dist});
|
||||
}
|
||||
|
||||
return {dist: dist, positions: res};
|
||||
};
|
||||
|
||||
/**
|
||||
* makes the block fall all the way to the bottom
|
||||
* forces the next cycle to be recognized as bottomed
|
||||
* @returns {Number} the distance fallen
|
||||
*/
|
||||
ControlGroup.prototype.fall = function() {
|
||||
var fall = this.getFallPositions(),
|
||||
positions = fall.positions,
|
||||
dist = fall.dist,
|
||||
i, curPos;
|
||||
|
||||
if (dist !== 0) {
|
||||
this.lastWasSpin = false;
|
||||
}
|
||||
|
||||
// for each block
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
curPos = positions[i];
|
||||
this.blocks[i].setPosition(curPos.x, curPos.y);
|
||||
}
|
||||
|
||||
this.bottomed = true;
|
||||
return dist;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the preview blocks to the approproriate positions
|
||||
* @param {[Block]} previews - the 4 blocks to be modified to be put into position as preview blocks
|
||||
*/
|
||||
ControlGroup.prototype.configurePreviewBlocks = function(previews) {
|
||||
var positions = this.getFallPositions().positions,
|
||||
i;
|
||||
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
previews[i].setPosition(positions[i].x, positions[i].y);
|
||||
}
|
||||
};
|
||||
|
||||
ControlGroup.prototype.getShape = function () {
|
||||
return this.shape;
|
||||
};
|
||||
|
||||
ControlGroup.prototype.getBlocks = function () {
|
||||
return this.blocks;
|
||||
};
|
||||
|
||||
/*
|
||||
* Gets the type of T spin that the group is in
|
||||
* @returns {String} 'mini' for a mini-t, 'normal' for a normal t, null for not a t spin
|
||||
*/
|
||||
ControlGroup.prototype.getTSpin = function() {
|
||||
var i,
|
||||
testPoints = [{x:-1,y:-1},{x:1,y:-1},{x:1,y:1},{x:-1,y:1}],
|
||||
count = 0,
|
||||
mini = false,
|
||||
curPoint;
|
||||
|
||||
if (!this.lastWasSpin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// make sure it's actually a t
|
||||
if (this.shape !== 't') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// t-spin mini tests
|
||||
if (this.dir === 0) {
|
||||
testPoints[0].miniCheck = true;
|
||||
testPoints[1].miniCheck = true;
|
||||
} else if (this.dir === 1) {
|
||||
testPoints[1].miniCheck = true;
|
||||
testPoints[2].miniCheck = true;
|
||||
} else if (this.dir === 2) {
|
||||
testPoints[2].miniCheck = true;
|
||||
testPoints[3].miniCheck = true;
|
||||
} else if (this.dir === 3) {
|
||||
testPoints[3].miniCheck = true;
|
||||
testPoints[0].miniCheck = true;
|
||||
}
|
||||
|
||||
// 3 point t test
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
curPoint = testPoints[i]
|
||||
if (!this.isLegalPosition(this.baseX + curPoint.x, this.baseY + curPoint.y)) {
|
||||
count += 1;
|
||||
} else if (curPoint.miniCheck) {
|
||||
mini = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count >= 3) {
|
||||
if (mini) {
|
||||
return 'mini';
|
||||
}
|
||||
return 'normal';
|
||||
}
|
||||
return null;
|
||||
};
|
301
tetris/Game.js
Normal file
@ -0,0 +1,301 @@
|
||||
function Game(inputMapping, autoRepeat, threshold) {
|
||||
var thisObject = this,
|
||||
i;
|
||||
|
||||
this.firstLoop = true;
|
||||
|
||||
this.blocks = [];
|
||||
this.controlGroup = null;
|
||||
|
||||
// make the preview blocks
|
||||
this.previewBlocks = [];
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
this.previewBlocks.push(new Block({blockX: -10, blockY: -10, preview: true}));
|
||||
}
|
||||
|
||||
this.scoreOutput = new TtyBlock("scoreDiv", 3);
|
||||
this.linesOutput = new TtyBlock("linesDiv", 3);
|
||||
this.levelOutput = new TtyBlock("levelDiv", 3);
|
||||
this.tickerOutput = new TtyBlock("tickerDiv", 5);
|
||||
this.scoreTracker = new ScoreTracker(this.scoreOutput, this.linesOutput, this.levelOutput, this.tickerOutput);
|
||||
|
||||
this.dropPeriod = this.scoreTracker.getLevelPeriod();
|
||||
this.timeToNextDrop = this.dropPeriod;
|
||||
|
||||
// TODO: find the official values for these constants
|
||||
this.keyChargeTime = threshold;
|
||||
this.keyRepeatTime = autoRepeat;
|
||||
|
||||
this.bottomTimer = null;
|
||||
this.bottomLockTime = 500;
|
||||
this.lastBottomedState = false;
|
||||
|
||||
this.lastTime = null;
|
||||
|
||||
this.gameLost = false;
|
||||
|
||||
// evenly distributed random piece generator
|
||||
this.previewLength = 5;
|
||||
this.randBag = new RandomBag(this.previewLength);
|
||||
// make the preview blocks
|
||||
this.previewGroups = [];
|
||||
for (i = 0; i < this.previewLength; i += 1) {
|
||||
this.previewGroups.push(new PreviewGroup(330, 70 * i + 35));
|
||||
}
|
||||
|
||||
this.swapGroup = null;
|
||||
this.swapAllowed = true;
|
||||
|
||||
// the currently occupied positions, number of blocks at a position
|
||||
// indexed by the position as a string
|
||||
this.occupiedPositions = {};
|
||||
|
||||
this.input = {
|
||||
shiftLeft: {
|
||||
autoRepeat: true,
|
||||
handler: function () {
|
||||
if (thisObject.controlGroup.shift(true)) {
|
||||
thisObject.resetLockCounter(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
shiftRight: {
|
||||
autoRepeat: true,
|
||||
handler: function() {
|
||||
if (thisObject.controlGroup.shift(false)) {
|
||||
thisObject.resetLockCounter(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
softDrop: {
|
||||
autoRepeat: true,
|
||||
preCharged: true,
|
||||
handler: function() {
|
||||
thisObject.dropBlock();
|
||||
thisObject.scoreTracker.softDrop();
|
||||
}
|
||||
},
|
||||
hardDrop: { handler: function() {
|
||||
var dist = thisObject.controlGroup.fall();
|
||||
thisObject.scoreTracker.hardDrop(dist);
|
||||
thisObject.lockBlocks();
|
||||
}},
|
||||
rotateLeft: { handler: function() {
|
||||
if (thisObject.controlGroup.turn(false)) {
|
||||
thisObject.resetLockCounter(true);
|
||||
}
|
||||
}},
|
||||
rotateRight: { handler: function() {
|
||||
if (thisObject.controlGroup.turn(true)) {
|
||||
thisObject.resetLockCounter(true);
|
||||
}
|
||||
}},
|
||||
swap: { handler: function() {
|
||||
thisObject.swap();
|
||||
}}
|
||||
};
|
||||
|
||||
this.inputMapping = inputMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* drops a new block into the game
|
||||
*/
|
||||
Game.prototype.newBlock = function (calledBySwap) {
|
||||
var thisObject = this,
|
||||
shape = this.randBag.popQueue(),
|
||||
newBlocks = [],
|
||||
curBlock,
|
||||
i;
|
||||
|
||||
this.dropPeriod = this.scoreTracker.getLevelPeriod();
|
||||
|
||||
// create some new blocks
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
curBlock = new Block({blockX: -10, blockY: -10, shape: shape, occupiedPositions: this.occupiedPositions});
|
||||
newBlocks.push(curBlock);
|
||||
this.blocks.push(curBlock);
|
||||
}
|
||||
|
||||
this.controlGroup = new ControlGroup(newBlocks, shape, function(x, y){
|
||||
return thisObject.isLegalPosition(x, y);
|
||||
});
|
||||
|
||||
if (this.controlGroup.isIllegalStart) {
|
||||
this.gameLost = true;
|
||||
}
|
||||
|
||||
if (!calledBySwap) {
|
||||
// the user is allowed to swap blocks again
|
||||
this.swapAllowed = true;
|
||||
}
|
||||
|
||||
this.updatePreviews(this.randBag.getQueue());
|
||||
};
|
||||
|
||||
/**
|
||||
* processes the input keys
|
||||
* @param {Number} dTime - the time in milliseconds since the last frame
|
||||
*/
|
||||
Game.prototype.processInput = function(dTime) {
|
||||
var curInput,
|
||||
keyName,
|
||||
curKeys,
|
||||
pressed,
|
||||
curInput,
|
||||
i;
|
||||
|
||||
for (actionType in this.inputMapping) {
|
||||
curKeys = this.inputMapping[actionType];
|
||||
curInput = this.input[actionType];
|
||||
pressed = false;
|
||||
for (i = 0; i < curKeys.length; i += 1) {
|
||||
if (jaws.pressed(curKeys[i])) {
|
||||
pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if the key is down
|
||||
if (pressed) {
|
||||
// if it is a 'press' frame
|
||||
if (!curInput.lastState) {
|
||||
curInput.handler();
|
||||
curInput.lastState = true;
|
||||
curInput.charged = (curInput.preCharged ? true : false);
|
||||
curInput.holdTime = 0;
|
||||
}
|
||||
// if it supports auto-repeat
|
||||
if (curInput.autoRepeat) {
|
||||
curInput.holdTime += dTime;
|
||||
|
||||
// if not charged and past the charge time
|
||||
if ((!curInput.charged) && (curInput.holdTime > this.keyChargeTime)) {
|
||||
// call the handler, and reset the hold time
|
||||
curInput.holdTime -= this.keyChargeTime;
|
||||
curInput.handler();
|
||||
curInput.charged = true;
|
||||
}
|
||||
// if charged and past the repeat time
|
||||
if (curInput.charged && (curInput.holdTime > this.keyRepeatTime)) {
|
||||
curInput.holdTime -= this.keyRepeatTime;
|
||||
curInput.handler();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// it was released
|
||||
curInput.lastState = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Game.prototype.update = function(time) {
|
||||
var curTime,
|
||||
dTime,
|
||||
i;
|
||||
|
||||
// if the first block needs to be made
|
||||
if (this.firstLoop) {
|
||||
this.firstLoop = false;
|
||||
|
||||
this.newBlock();
|
||||
|
||||
this.lastTime = time;
|
||||
}
|
||||
|
||||
curTime = time;
|
||||
dTime = curTime - this.lastTime;
|
||||
this.lastTime = curTime;
|
||||
|
||||
this.processInput(dTime);
|
||||
|
||||
if (!this.controlGroup.isBottomed()) {
|
||||
this.lastBottomedState = false;
|
||||
this.applyGravity(dTime);
|
||||
|
||||
} else {
|
||||
// if it has just touched hte bottom
|
||||
if (!this.lastBottomedState) {
|
||||
this.resetLockCounter(false);
|
||||
} else {
|
||||
this.bottomTimer -= dTime;
|
||||
|
||||
if (this.bottomTimer <= 0 || this.slideCount >= 15) {
|
||||
this.lockBlocks();
|
||||
}
|
||||
}
|
||||
this.lastBottomedState = true;
|
||||
}
|
||||
|
||||
// update the position of the preview blocks
|
||||
if (this.controlGroup) {
|
||||
// ask the control group to move the preview blocks
|
||||
this.controlGroup.configurePreviewBlocks(this.previewBlocks);
|
||||
} else {
|
||||
// if there is no contorl group, just move them off the screen
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
this.previewBlocks[i].setPosition(-10, -10);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the entire game scene
|
||||
*/
|
||||
Game.prototype.draw = function(dTime) {
|
||||
var i;
|
||||
|
||||
this.scoreOutput.draw(dTime);
|
||||
this.linesOutput.draw(dTime);
|
||||
this.levelOutput.draw(dTime);
|
||||
this.tickerOutput.draw(dTime);
|
||||
|
||||
// draw the preview blocks
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
this.previewBlocks[i].drawIfInvalid();
|
||||
}
|
||||
|
||||
// draw the swap block
|
||||
if (this.swapGroup) {
|
||||
this.swapGroup.draw();
|
||||
}
|
||||
|
||||
// draw the queue
|
||||
for (i = 0; i < this.previewGroups.length; i += 1) {
|
||||
this.previewGroups[i].draw();
|
||||
}
|
||||
|
||||
for (i = 0; i < this.blocks.length; i += 1) {
|
||||
this.blocks[i].drawIfInvalid();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true iff the given position can be moved into
|
||||
* @param {Number} x - the x position
|
||||
* @param {Number} y - the y position
|
||||
* @returns {Boolean} true iff the new position is legal
|
||||
*/
|
||||
Game.prototype.isLegalPosition = function (x, y) {
|
||||
// if there is a block in the way
|
||||
if (this.occupiedPositions[x+','+y]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if it's on the field
|
||||
if (x >= 10 || x < 0 || y >= 20) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* drops the controlled blocks by one
|
||||
*/
|
||||
Game.prototype.dropBlock = function (causedByGravity) {
|
||||
if (!causedByGravity) {
|
||||
this.timeToNextDrop = this.dropPeriod;
|
||||
}
|
||||
|
||||
this.controlGroup.drop();
|
||||
};
|
209
tetris/Game_Logic.js
Normal file
@ -0,0 +1,209 @@
|
||||
|
||||
/**
|
||||
* @returns {[Number]} the line numbers of all the completed rows
|
||||
*/
|
||||
Game.prototype.getRows = function () {
|
||||
var i,
|
||||
rows = [],
|
||||
res = [],
|
||||
curRow;
|
||||
|
||||
// initialize the rows to 0
|
||||
for (i = 0; i < 20; i += 1) {
|
||||
rows[i] = 0;
|
||||
}
|
||||
// for each block
|
||||
for (i = 0; i < this.blocks.length; i += 1) {
|
||||
// increment the appropriate row
|
||||
curRow = this.blocks[i].getY();
|
||||
rows[curRow] += 1;
|
||||
// if the row is full
|
||||
if (rows[curRow] === 10) {
|
||||
res.push(curRow);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the rows from the field
|
||||
*/
|
||||
Game.prototype.removeRows = function (rows) {
|
||||
var dropDist = {},
|
||||
i, j,
|
||||
remove = {},
|
||||
curBlock,
|
||||
curY;
|
||||
|
||||
// initialize drops to 0
|
||||
for (i = -4; i < 20; i += 1) {
|
||||
dropDist[i] = 0;
|
||||
}
|
||||
|
||||
// for each removed row
|
||||
for (i = 0; i < rows.length; i += 1) {
|
||||
remove[rows[i]] = true;
|
||||
|
||||
// every row above this should be dropped another spot
|
||||
for (j = -4; j < rows[i]; j += 1) {
|
||||
dropDist[j] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// for each block
|
||||
for (i = 0; i < this.blocks.length; i += 1) {
|
||||
curBlock = this.blocks[i];
|
||||
curY = curBlock.getY();
|
||||
|
||||
// if it is being removed
|
||||
if (remove[curY]) {
|
||||
// remove the block
|
||||
this.removeBlock(i);
|
||||
i -= 1;
|
||||
} else {
|
||||
// it is being dropped
|
||||
curBlock.setPosition(curBlock.getX(), curBlock.getY() + dropDist[curY]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Game.prototype.removeBlock = function(index) {
|
||||
this.blocks[index].kill();
|
||||
return this.blocks.splice(index, 1);
|
||||
};
|
||||
|
||||
Game.prototype.applyGravity = function (dTime) {
|
||||
this.timeToNextDrop -= dTime;
|
||||
|
||||
// drop until there is a positive time until the next drop time is positive, or the control group s bottomed out
|
||||
while (this.timeToNextDrop < 0 && (!this.controlGroup.isBottomed())) {
|
||||
this.dropBlock(true);
|
||||
this.timeToNextDrop += this.dropPeriod;
|
||||
}
|
||||
|
||||
// if it exited through bottoming, reset the drop period
|
||||
if (this.controlGroup.isBottomed()) {
|
||||
this.timeToNextDrop = this.dropPeriod;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the shapes of the preview along the side
|
||||
* @param {[Char]} queue - the queue of pieces
|
||||
*/
|
||||
Game.prototype.updatePreviews = function(queue) {
|
||||
var i;
|
||||
for (i = 0; i < queue.length; i += 1) {
|
||||
this.previewGroups[i].setShape(queue[i]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* called when the user attempts to swap a block
|
||||
*/
|
||||
Game.prototype.swap = function() {
|
||||
var i, j,
|
||||
newShape,
|
||||
oldShape = this.controlGroup.getShape(),
|
||||
oldBlocks = this.controlGroup.getBlocks(),
|
||||
newBlocks = [],
|
||||
thisObject = this;
|
||||
|
||||
// can only be called once per drop
|
||||
if (!this.swapAllowed) {
|
||||
return;
|
||||
}
|
||||
this.swapAllowed = false;
|
||||
|
||||
// Reset the locking
|
||||
this.resetLockCounter(false);
|
||||
|
||||
// remove the blocks
|
||||
// for each block on the field
|
||||
for (i = 0; i < this.blocks.length; i += 1) {
|
||||
// if the block is part of the control group, remove it
|
||||
for (j = 0; j < 4; j += 1) {
|
||||
if (oldBlocks[j] === this.blocks[i]) {
|
||||
this.removeBlock(i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there is a block waiting
|
||||
if (this.swapGroup) {
|
||||
newShape = this.swapGroup.getShape();
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
newBlocks.push(new Block({blockX:-10, blockY:-10, shape: newShape, occupiedPositions: this.occupiedPositions}));
|
||||
this.blocks.push(newBlocks[i]);
|
||||
}
|
||||
|
||||
this.controlGroup = new ControlGroup(newBlocks, newShape, function(x, y){
|
||||
return thisObject.isLegalPosition(x, y);
|
||||
});
|
||||
|
||||
this.swapGroup.setShape(oldShape);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is no block waiting
|
||||
this.swapGroup = new PreviewGroup(-100, 60);
|
||||
this.swapGroup.setShape(oldShape);
|
||||
this.newBlock(true);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* locks the currnt piece in, registers lines and makes a new block
|
||||
*/
|
||||
Game.prototype.lockBlocks = function() {
|
||||
// figure out if it a t-spin/t-spin mini
|
||||
var tSpinType = this.controlGroup.getTSpin(),
|
||||
scoreObject = {},
|
||||
rows;
|
||||
|
||||
if (tSpinType === 'mini') {
|
||||
scoreObject.miniT = true;
|
||||
} else if (tSpinType === 'normal') {
|
||||
scoreObject.normalT = true;
|
||||
}
|
||||
|
||||
// look for rows
|
||||
rows = this.getRows();
|
||||
scoreObject.lines = rows.length;
|
||||
if (rows.length > 0) {
|
||||
this.removeRows(rows);
|
||||
}
|
||||
|
||||
// apply the score
|
||||
this.scoreTracker.updateScore(scoreObject);
|
||||
|
||||
this.newBlock();
|
||||
this.resetLockCounter(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets the lock counter, and the slide counter if not soft
|
||||
* @param {Boolean} soft = true if a soft reset, and the slide counter should not be reset
|
||||
*/
|
||||
Game.prototype.resetLockCounter = function (soft) {
|
||||
if (soft) {
|
||||
this.slideCount += 1;
|
||||
} else {
|
||||
this.slideCount = 0;
|
||||
}
|
||||
this.bottomTimer = this.bottomLockTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the game is over and returns a score object
|
||||
* if it is. Otherwise, returns null
|
||||
*/
|
||||
Game.prototype.getResults = function() {
|
||||
if (this.gameLost || this.scoreTracker.gameWon()) {
|
||||
return this.scoreTracker.getResults();
|
||||
}
|
||||
return null;
|
||||
};
|
47
tetris/PreviewGroup.js
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
function PreviewGroup(baseX, baseY) {
|
||||
var i;
|
||||
|
||||
this.blocks = [];
|
||||
this.shape = null;
|
||||
|
||||
// create the blocks
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
this.blocks.push(new Block({
|
||||
boardOriginX: baseX,
|
||||
boardOriginY: baseY,
|
||||
blockX: 0,
|
||||
blockY: 0,
|
||||
shape: 'i'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the shape and color of the blocks
|
||||
* @param {Char} shape - the letter of the new shape
|
||||
* @param {Boolean} preview - true if it should have preview colors
|
||||
*/
|
||||
PreviewGroup.prototype.setShape = function(shape) {
|
||||
var shapeConfig = SHAPES[shape],
|
||||
i;
|
||||
|
||||
this.shape = shape;
|
||||
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
this.blocks[i].setPosition(shapeConfig.pos[i].x, shapeConfig.pos[i].y);
|
||||
this.blocks[i].setColor(shape, false);
|
||||
}
|
||||
};
|
||||
|
||||
PreviewGroup.prototype.getShape = function () {
|
||||
return this.shape;
|
||||
};
|
||||
|
||||
PreviewGroup.prototype.draw = function() {
|
||||
var i;
|
||||
for (i = 0; i < 4; i += 1) {
|
||||
this.blocks[i].draw();
|
||||
}
|
||||
};
|
||||
|
49
tetris/RandomBag.js
Normal file
@ -0,0 +1,49 @@
|
||||
function RandomBag(queueSize) {
|
||||
// start off empty
|
||||
this.available = [];
|
||||
this.queue = [];
|
||||
|
||||
// initialize by refilling the queue
|
||||
while (this.queue.length < queueSize) {
|
||||
this.queue.push(this.nextAvailable());
|
||||
}
|
||||
}
|
||||
|
||||
RandomBag.initialList = ['i', 'o', 'j', 'l', 'z', 's', 't'];
|
||||
|
||||
/**
|
||||
* Returns the letters of the queue
|
||||
* @returns {[Char]} the letters of the queue in order of oldest to newest
|
||||
*/
|
||||
RandomBag.prototype.getQueue = function () {
|
||||
return this.queue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Moves the queue forward by one
|
||||
* @returns {Char} the poped value
|
||||
*/
|
||||
RandomBag.prototype.popQueue = function () {
|
||||
var res = this.queue.shift();
|
||||
this.queue.push(this.nextAvailable());
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* gets the next letter for the queue, and updates the random bag state
|
||||
* @returns {Char} the next letter for the queue
|
||||
* @private
|
||||
*/
|
||||
RandomBag.prototype.nextAvailable = function() {
|
||||
var index, res;
|
||||
|
||||
// if the available needs to be rebuilt
|
||||
if (this.available.length === 0) {
|
||||
this.available = RandomBag.initialList.slice(0); // shallow copy
|
||||
}
|
||||
|
||||
index = Math.floor(Math.random()*this.available.length);
|
||||
res = this.available.splice(index, 1)[0];
|
||||
|
||||
return res;
|
||||
};
|
213
tetris/ScoreTracker.js
Normal file
@ -0,0 +1,213 @@
|
||||
function ScoreTracker(scoreOutput, linesOutput, levelOutput, tickerOutput) {
|
||||
this.level = 1;
|
||||
this.score = 0;
|
||||
this.linesRemaining = ScoreTracker.levelLines(this.level);
|
||||
|
||||
this.scoreOutput = scoreOutput;
|
||||
this.linesOutput = linesOutput;
|
||||
this.levelOutput = levelOutput;
|
||||
this.tickerOutput = tickerOutput;
|
||||
|
||||
this.curCombo = -1;
|
||||
this.lastWasBonus = false;
|
||||
this.backToBackCount = 0;
|
||||
|
||||
this.isGameWon = false;
|
||||
|
||||
this.outputScore();
|
||||
this.outputLines();
|
||||
this.outputLevel();
|
||||
}
|
||||
|
||||
ScoreTracker.levelLines = function (level) {
|
||||
return level*5;
|
||||
};
|
||||
|
||||
ScoreTracker.prototype.updateScore = function(config) {
|
||||
var linesCleared = 0,
|
||||
isBonus = false,
|
||||
scoreDiff = 0,
|
||||
tickerLines = [],
|
||||
i;
|
||||
|
||||
if (config.miniT) {
|
||||
// mini t spin, 1 for no lines, 2 for 1 line
|
||||
tickerLines.push("T Spin Mini");
|
||||
linesCleared += 1;
|
||||
scoreDiff += 100 * this.level;
|
||||
if (config.lines === 1) {
|
||||
linesCleared += 1;
|
||||
scoreDiff += 100 * this.level;
|
||||
}
|
||||
} else if (config.normalT) {
|
||||
// normal t spin, bonus for eveything but 0 lines
|
||||
switch (config.lines) {
|
||||
case 0:
|
||||
tickerLines.push("T Spin");
|
||||
linesCleared += 4;
|
||||
scoreDiff += 400 * this.level;
|
||||
break;
|
||||
case 1:
|
||||
tickerLines.push("T Spin Single");
|
||||
linesCleared += 8;
|
||||
isBonus = true;
|
||||
scoreDiff += 800 * this.level;
|
||||
break;
|
||||
case 2:
|
||||
tickerLines.push("T Spin Double");
|
||||
linesCleared += 12;
|
||||
isBonus = true;
|
||||
scoreDiff += 1200 * this.level;
|
||||
break;
|
||||
case 3:
|
||||
tickerLines.push("T SPIN TRIPLE");
|
||||
linesCleared += 16;
|
||||
isBonus = true;
|
||||
scoreDiff += 1600 * this.level;
|
||||
break;
|
||||
}
|
||||
} else if (config.lines > 0) {
|
||||
// plain old line clears
|
||||
switch (config.lines) {
|
||||
case 1:
|
||||
tickerLines.push("Single");
|
||||
linesCleared += 1;
|
||||
scoreDiff += 100 * this.level;
|
||||
break;
|
||||
case 2:
|
||||
tickerLines.push("Double");
|
||||
linesCleared += 3;
|
||||
scoreDiff += 300 * this.level;
|
||||
break;
|
||||
case 3:
|
||||
tickerLines.push("Triple");
|
||||
linesCleared += 5;
|
||||
scoreDiff += 500 * this.level;
|
||||
break;
|
||||
case 4:
|
||||
tickerLines.push("TETRIS");
|
||||
linesCleared += 8;
|
||||
isBonus = true;
|
||||
scoreDiff += 800 * this.level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// apply the combo
|
||||
if (linesCleared > 0) {
|
||||
this.curCombo += 1;
|
||||
linesCleared += Math.floor(this.curCombo * 0.5);
|
||||
scoreDiff += 50 * this.curCombo * this.level;
|
||||
if (this.curCombo >= 1) {
|
||||
tickerLines.push("Combo x" + this.curCombo);
|
||||
}
|
||||
} else {
|
||||
this.curCombo = -1;
|
||||
}
|
||||
|
||||
// apply back-to-back bonus
|
||||
if (this.lastWasBonus && isBonus) {
|
||||
tickerLines.push("Back-to-Back");
|
||||
this.backToBackCount += 1;
|
||||
linesCleared = Math.floor(linesCleared * 1.5);
|
||||
scoreDiff += this.backToBackCount * 0.5 * scoreDiff;
|
||||
} else {
|
||||
this.backToBackCount = 0;
|
||||
}
|
||||
// only update the last bonus state if a single through triple was gotten
|
||||
if (config.lines > 0) {
|
||||
this.lastWasBonus = isBonus;
|
||||
}
|
||||
|
||||
// apply the lines cleared
|
||||
this.linesRemaining -= linesCleared;
|
||||
if (this.linesRemaining <= 0) {
|
||||
if (this.level < 15) {
|
||||
this.level += 1;
|
||||
this.linesRemaining = ScoreTracker.levelLines(this.level);
|
||||
} else {
|
||||
this.isGameWon = true;
|
||||
}
|
||||
this.outputLevel();
|
||||
}
|
||||
|
||||
if (linesCleared > 0) {
|
||||
this.outputLines();
|
||||
}
|
||||
|
||||
|
||||
this.score += scoreDiff;
|
||||
this.outputScore();
|
||||
|
||||
if (tickerLines.length === 0) {
|
||||
this.tickerOutput.addLine("");
|
||||
} else {
|
||||
for (i = 0; i < tickerLines.length; i += 1) {
|
||||
this.tickerOutput.addLine(tickerLines[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ScoreTracker.prototype.softDrop = function() {
|
||||
this.score += 1;
|
||||
};
|
||||
|
||||
ScoreTracker.prototype.hardDrop = function(dist) {
|
||||
this.score += 2 * dist;
|
||||
};
|
||||
|
||||
ScoreTracker.prototype.getLinesRemaining = function() { return this.linesRemaining; };
|
||||
ScoreTracker.prototype.getScore = function() { return this.score; };
|
||||
ScoreTracker.prototype.getLevel = function() { return this.level; };
|
||||
|
||||
ScoreTracker.prototype.getLevelPeriod = function() {
|
||||
var periods = [
|
||||
1000,
|
||||
800,
|
||||
600,
|
||||
470,
|
||||
380,
|
||||
250,
|
||||
200,
|
||||
160,
|
||||
130,
|
||||
90,
|
||||
50,
|
||||
27,
|
||||
20,
|
||||
15,
|
||||
10
|
||||
],
|
||||
res = periods[(this.level < periods.length) ? this.level : periods.length - 1];
|
||||
return res;
|
||||
};
|
||||
|
||||
ScoreTracker.prototype.gameWon = function() {
|
||||
return this.isGameWon;
|
||||
};
|
||||
|
||||
ScoreTracker.prototype.getResults = function() {
|
||||
return {
|
||||
score: this.score,
|
||||
level: this.level,
|
||||
won: this.isGameWon
|
||||
};
|
||||
};
|
||||
|
||||
ScoreTracker.prototype.outputScore = function() {
|
||||
this.scoreOutput.addLine("Score:");
|
||||
this.scoreOutput.addLine("" + this.score);
|
||||
this.scoreOutput.addLine("");
|
||||
};
|
||||
|
||||
ScoreTracker.prototype.outputLines = function() {
|
||||
this.linesOutput.addLine("Lines:");
|
||||
this.linesOutput.addLine("" + this.linesRemaining);
|
||||
this.linesOutput.addLine("");
|
||||
};
|
||||
|
||||
ScoreTracker.prototype.outputLevel = function() {
|
||||
this.levelOutput.addLine("Level:");
|
||||
this.levelOutput.addLine("" + this.level);
|
||||
this.levelOutput.addLine("");
|
||||
};
|
94
tetris/Shapes.js
Normal file
@ -0,0 +1,94 @@
|
||||
var SHAPES = {
|
||||
i: {
|
||||
spin: 'corner',
|
||||
startX: 5,
|
||||
startY: 0,
|
||||
pos: [
|
||||
{ x: -2, y: -1 },
|
||||
{ x: -1, y: -1},
|
||||
{ x: 0, y: -1 },
|
||||
{ x: 1, y: -1 }
|
||||
],
|
||||
image: 'media/cyanblock.png',
|
||||
kickType: 'i_block'
|
||||
},
|
||||
o: {
|
||||
spin: 'corner',
|
||||
startX: 5,
|
||||
startY: -1,
|
||||
pos: [
|
||||
{ x: -1, y: 0 },
|
||||
{ x: 0, y: 0},
|
||||
{ x: -1, y: -1 },
|
||||
{ x: 0, y: -1 }
|
||||
],
|
||||
image: 'media/yellowblock.png',
|
||||
kickType: 'standard'
|
||||
},
|
||||
j: {
|
||||
spin: 'block',
|
||||
startX: 4,
|
||||
startY: -1,
|
||||
pos: [
|
||||
{ x: -1, y: -1 },
|
||||
{ x: -1, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 1, y: 0 }
|
||||
],
|
||||
image: 'media/blueblock.png',
|
||||
kickType: 'standard'
|
||||
},
|
||||
l: {
|
||||
spin: 'block',
|
||||
startX: 4,
|
||||
startY: -1,
|
||||
pos: [
|
||||
{ x: -1, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 1, y: 0 },
|
||||
{ x: 1, y: -1 }
|
||||
],
|
||||
image: 'media/orangeblock.png',
|
||||
kickType: 'standard'
|
||||
},
|
||||
s: {
|
||||
spin: 'block',
|
||||
startX: 4,
|
||||
startY: -1,
|
||||
pos: [
|
||||
{ x: -1, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: -1 },
|
||||
{ x: 1, y: -1 }
|
||||
],
|
||||
image: 'media/greenblock.png',
|
||||
kickType: 'standard'
|
||||
},
|
||||
z: {
|
||||
spin: 'block',
|
||||
startX: 4,
|
||||
startY: -1,
|
||||
pos: [
|
||||
{ x: -1, y: -1 },
|
||||
{ x: 0, y: -1 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 1, y: 0 }
|
||||
],
|
||||
image: 'media/redblock.png',
|
||||
kickType: 'standard'
|
||||
},
|
||||
t: {
|
||||
spin: 'block',
|
||||
startX: 4,
|
||||
startY: -1,
|
||||
pos: [
|
||||
{ x: -1, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: -1 },
|
||||
{ x: 1, y: 0 }
|
||||
],
|
||||
image: 'media/purpleblock.png',
|
||||
kickType: 'standard'
|
||||
}
|
||||
|
||||
};
|
84
tetris/TtyBlock.js
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
function TtyBlock (divName, numLines, rollOverLength, rollOverRemove) {
|
||||
var i;
|
||||
|
||||
this.elem = document.getElementById(divName);
|
||||
|
||||
// slow scorlling effect variables
|
||||
this.curPos = 0;
|
||||
this.cursorShown = false;
|
||||
|
||||
// TODO: make these random starting values
|
||||
this.timePassedType = 0;
|
||||
this.timePassedFlash = 0;
|
||||
|
||||
// time in ms
|
||||
this.typePeriod = 30;
|
||||
this.flashPeriod = 300;
|
||||
|
||||
this.lines = [];
|
||||
for (i = 0; i < numLines; i += 1) {
|
||||
this.lines.push("");
|
||||
}
|
||||
|
||||
this.rollOverLength = rollOverLength || 9;
|
||||
this.rollOverRemove = rollOverRemove || 3;
|
||||
|
||||
this.backlog = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
updates the text block
|
||||
*/
|
||||
TtyBlock.prototype.draw = function (dTime) {
|
||||
var i,
|
||||
outputString = "",
|
||||
lastLine;
|
||||
|
||||
this.timePassedType += dTime;
|
||||
|
||||
while (this.timePassedType > this.typePeriod) {
|
||||
this.curPos += 1;
|
||||
this.timePassedType -= this.typePeriod;
|
||||
}
|
||||
|
||||
lastLine = this.lines[this.lines.length-1];
|
||||
|
||||
if (this.curPos > lastLine.length) {
|
||||
this.timePassedFlash += dTime;
|
||||
while (this.timePassedFlash > this.flashPeriod) {
|
||||
this.cursorShown = !this.cursorShown;
|
||||
this.timePassedFlash -= this.flashPeriod;
|
||||
}
|
||||
}
|
||||
|
||||
// if I'm past the end of the last line, and there is a backlog, shift all the lines
|
||||
if (this.curPos > lastLine.length && this.backlog.length > 0) {
|
||||
this.lines.shift();
|
||||
lastLine = this.backlog.shift();
|
||||
this.lines.push(lastLine);
|
||||
this.curPos = 0;
|
||||
}
|
||||
|
||||
// print all of the lines but the last one
|
||||
for (i = 0; i < this.lines.length - 1; i += 1) {
|
||||
outputString += this.lines[i] + "<br/>";
|
||||
}
|
||||
outputString += lastLine.slice(0, Math.min(this.curPos, lastLine.length));
|
||||
if (this.cursorShown) {
|
||||
outputString += "_";
|
||||
}
|
||||
// rewirte for html gaurds
|
||||
outputString.replace('>', '>');
|
||||
this.elem.innerHTML = outputString;
|
||||
};
|
||||
|
||||
TtyBlock.prototype.addLine = function(str) {
|
||||
// if the backlog is too long, then remove the last 3 values
|
||||
if (this.backlog.length > this.rollOverLength) {
|
||||
this.backlog.splice(this.backlog.length - this.rollOverRemove, this.rollOverRemove);
|
||||
}
|
||||
|
||||
this.backlog.push(" > " + str);
|
||||
};
|
67
tetris/WallKicks.js
Normal file
@ -0,0 +1,67 @@
|
||||
var WALL_KICK_OFFSETS = {};
|
||||
|
||||
/*
|
||||
0 -> starting orientation
|
||||
1 -> 1 turn cw
|
||||
2 -> 2 turns
|
||||
3-> 1 turn ccw
|
||||
|
||||
non-I blocks
|
||||
L->2 ( 0, 0) (-1, 0) (-1,-1) ( 0,+2) (-1,+2)
|
||||
L->0 ( 0, 0) (-1, 0) (-1,-1) ( 0,+2) (-1,+2)
|
||||
2->R ( 0, 0) (-1, 0) (-1,+1) ( 0,-2) (-1,-2)
|
||||
2->L ( 0, 0) (+1, 0) (+1,+1) ( 0,-2) (+1,-2)
|
||||
R->0 ( 0, 0) (+1, 0) (+1,-1) ( 0,+2) (+1,+2)
|
||||
R->2 ( 0, 0) (+1, 0) (+1,-1) ( 0,+2) (+1,+2)
|
||||
0->L ( 0, 0) (+1, 0) (+1,+1) ( 0,-2) (+1,-2)
|
||||
0->R ( 0, 0) (-1, 0) (-1,+1) ( 0,-2) (-1,-2)
|
||||
|
||||
I block
|
||||
0->R ( 0, 0) (-2, 0) (+1, 0) (-2,-1) (+1,+2)
|
||||
0->L ( 0, 0) (-1, 0) (+2, 0) (-1,+2) (+2,-1)
|
||||
|
||||
R->2 ( 0, 0) (-1, 0) (+2, 0) (-1,+2) (+2,-1)
|
||||
R->0 ( 0, 0) (+2, 0) (-1, 0) (+2,+1) (-1,-2)
|
||||
|
||||
2->L ( 0, 0) (+2, 0) (-1, 0) (+2,+1) (-1,-2)
|
||||
2->R ( 0, 0) (+1, 0) (-2, 0) (+1,-2) (-2,+1)
|
||||
|
||||
L->0 ( 0, 0) (+1, 0) (-2, 0) (+1,-2) (-2,+1)
|
||||
L->2 ( 0, 0) (-2, 0) (+1, 0) (-2,-1) (+1,+2)
|
||||
*/
|
||||
|
||||
WALL_KICK_OFFSETS.standard = [
|
||||
{
|
||||
cw: [{x:0,y:0}, {x:-1,y:0}, {x:-1,y:-1}, {x:0,y:2}, {x:-1,y:2}],
|
||||
ccw: [{x:0,y:0}, {x:1,y:0}, {x:1,y:-1}, {x:0,y:2}, {x:1,y:2}]
|
||||
},{
|
||||
cw: [{x:0,y:0}, {x:1,y:0}, {x:1,y:1}, {x:0,y:-2}, {x:1,y:-2}],
|
||||
ccw: [{x:0,y:0}, {x:1,y:0}, {x:1,y:1}, {x:0,y:-2}, {x:1,y:-2}]
|
||||
},{
|
||||
cw: [{x:0, y:0}, {x:1,y:0}, {x:1,y:-1}, {x:0,y:2}, {x:1,y:2}],
|
||||
ccw: [{x:0, y:0}, {x:-1, y:0}, {x:-1,y:-1}, {x:0,y:2}, {x:-1,y:2}]
|
||||
},{
|
||||
cw: [{x:0,y:0}, {x:-1,y:0}, {x:-1,y:1}, {x:0,y:-2}, {x:-1,y:-2}],
|
||||
ccw: [{x:0,y:0}, {x:-1,y:0}, {x:-1,y:1}, {x:0,y:-2}, {x:-1,y:-2}]
|
||||
}
|
||||
];
|
||||
|
||||
WALL_KICK_OFFSETS.i_block = [
|
||||
{
|
||||
cw: [{x:0,y:0}, {x:-2,y:0}, {x:1,y:0}, {x:-2,y:1}, {x:1,y:-2}],
|
||||
ccw: [{x:0,y:0}, {x:-1,y:0}, {x:2,y:0}, {x:-1,y:-2}, {x:2,y:1}]
|
||||
},{
|
||||
cw: [{x:0,y:0}, {x:-1,y:0}, {x:2,y:0}, {x:-1,y:-2}, {x:2,y:1}],
|
||||
ccw: [{x:0,y:0}, {x:2,y:0}, {x:-1,y:0}, {x:2,y:-1}, {x:-1,y:2}]
|
||||
},{
|
||||
cw: [{x:0,y:0}, {x:2,y:0}, {x:-1,y:0}, {x:2,y:-1}, {x:-1,y:2}],
|
||||
ccw: [{x:0,y:0}, {x:1,y:0}, {x:-2,y:0}, {x:1,y:2}, {x:-2,y:-1}]
|
||||
},{
|
||||
cw: [{x:0,y:0}, {x:1,y:0}, {x:-2,y:0}, {x:1,y:2}, {x:-2,y:1}],
|
||||
ccw: [{x:0,y:0}, {x:-2,y:0}, {x:1,y:0}, {x:-2,y:1}, {x:1,y:-2}]
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
|
||||
|
66
tetris/about.html
Normal file
@ -0,0 +1,66 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>TwitchTetris</title>
|
||||
<link href="//fonts.googleapis.com/css?family=VT323" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="styles.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<table border="0" class="mainTable">
|
||||
<tr>
|
||||
<td class="menuCell">
|
||||
<div class="menu">
|
||||
<img class="menuLogo" src="media/background/logo.png"></img>
|
||||
<br/><br/>
|
||||
|
||||
<a href="index.html" class="bareLink">
|
||||
<div class="menuItem">Play TwitchTetris</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="controls.html" class="bareLink">
|
||||
<div class="menuItem">Controls Options</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="highScores.html" class="bareLink">
|
||||
<div class="menuItem">High Scores</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="about.html" class="bareLink">
|
||||
<div class="menuItem selectedMenuItem">About TwitchTetris</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="http://www.leighpauls.com" class="bareLink">
|
||||
<div class="menuItem">About the Developer</div>
|
||||
</a>
|
||||
<br/>
|
||||
|
||||
<div class="instructions">
|
||||
<b> > TwitchTetris --help </b>
|
||||
<p>Use the keyboard controls to rotate and move the blocks as they fall</p>
|
||||
<p>Place the blocks to form horizontal lines, which will be removed and make all the blocks above fall down. If the blocks reach the top of the screen, the game is over!</p>
|
||||
<p>Clear multiple lines at a time, or clear lines in consective moves to earn more points.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
<td class="contentCell">
|
||||
<div class="aboutText">
|
||||
<b> > man TwitchTeteris</b><br/>
|
||||
<p>TwitchTetris is an open-source implementation of Tetris, the classic falling-block game that we all know and love, designed for those who demand the best performace from their tetris game.</p>
|
||||
<p>TwitchTetris aims to be the fastest implementation of Tetris to be played within a browser. This is possible because it's implemented completely in Html5/JavaScript. It was developed out of the frustration of inconsistent performance of Flash plugins across multiple Operating Systems and Browsers, and the sad state of other available Html5 Tetris games. TwitchTetris should have the fastest reaction time to user input of any complete Tetris implementation, and should perform the most consistently over all modern browsers.</p>
|
||||
|
||||
<p>This project is still in the infancy of development. Please go to <a href="http://code.google.com/p/html5tetris/">the google code page</a> to report <i>any</i> bugs, problems, or suggestions that you have for the game. Contributions from other developers are also welcome.</p>
|
||||
|
||||
<p>While TwitchTetris is not deisgned to generate revenue, ads are displayed on this website in order to cover the potential cost incurring high traffic. There are no plans to expand advertising past one or 2 ads per page, and obtrusive ads which make users wait to play the game will never be shown on this website.</p>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
127
tetris/controls.html
Normal file
@ -0,0 +1,127 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>TwitchTetris</title>
|
||||
<link href="//fonts.googleapis.com/css?family=VT323" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="styles.css" rel="stylesheet" type="text/css" />
|
||||
<link href="controlsStyles.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="jawsjs.js" type="text/javascript"></script>
|
||||
<script src="cookie.js" type="text/javascript"></script>
|
||||
<script src="input.js" type="text/javascript"></script>
|
||||
<script src="controls.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body onload="onControlsLoad();">
|
||||
<table border="0" class="mainTable">
|
||||
<tr>
|
||||
<td class="menuCell">
|
||||
<div class="menu">
|
||||
<img class="menuLogo" src="media/background/logo.png"></img>
|
||||
<br/><br/>
|
||||
|
||||
<a href="index.html" class="bareLink">
|
||||
<div class="menuItem">Play TwitchTetris</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="controls.html" class="bareLink">
|
||||
<div class="menuItem selectedMenuItem">Controls Options</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="highScores.html" class="bareLink">
|
||||
<div class="menuItem">High Scores</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="about.html" class="bareLink">
|
||||
<div class="menuItem">About TwitchTetris</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="http://www.leighpauls.com" class="bareLink">
|
||||
<div class="menuItem">About the Developer</div>
|
||||
</a>
|
||||
<br/>
|
||||
|
||||
<div class="instructions">
|
||||
<b> > TwitchTetris --help </b>
|
||||
<p>Use the keyboard controls to rotate and move the blocks as they fall</p>
|
||||
<p>Place the blocks to form horizontal lines, which will be removed and make all the blocks above fall down. If the blocks reach the top of the screen, the game is over!</p>
|
||||
<p>Clear multiple lines at a time, or clear lines in consective moves to earn more points.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
<td class="contentCell">
|
||||
<canvas id="dummyCanvas" height="0px" width="0px"></canvas>
|
||||
<div class="controlsArea">
|
||||
<div class="enableCustomDiv">
|
||||
Do you want to use a custom control scheme?
|
||||
<br/>
|
||||
<br/>
|
||||
<form>
|
||||
<input type="radio" name="controlStyle" id="defaultRadio" onchange="setDefaultControls();"/>
|
||||
<label for="defaultRadio">Default Controls</label><br/>
|
||||
<input type="radio" name="controlStyle" id="customRadio" onchange="configureCustomControls();"/>
|
||||
<label for="customRadio">Custom Controls</label><br/>
|
||||
</form>
|
||||
<br/>
|
||||
<div id="instructionsDefault" class="withDisplay">
|
||||
To change your controls, select "Custom Controls" from above.
|
||||
</div>
|
||||
<div id="instructionsCustom" class="noDisplay">
|
||||
Click on the fields on the right to set your controls.
|
||||
</div>
|
||||
<div id="instructionsPending" class="noDisplay">
|
||||
Press a key to set this field...
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
</div>
|
||||
<div class="customControlsDiv">
|
||||
<div class="controlsUnit" id="rotateLeftDiv" onclick="controlsUnitClicked('rotateLeft');">
|
||||
Rotate Left: <span id="rotateLeftValue" class="controlsValue"></span>
|
||||
</div>
|
||||
<div class="controlsUnit" id="rotateRightDiv" onclick="controlsUnitClicked('rotateRight');">
|
||||
Rotate Right: <span id="rotateRightValue" class="controlsValue"></span>
|
||||
</div>
|
||||
<div class="controlsUnit" id="shiftLeftDiv" onclick="controlsUnitClicked('shiftLeft');">
|
||||
Shift Left: <span id="shiftLeftValue" class="controlsValue"></span>
|
||||
</div>
|
||||
<div class="controlsUnit" id="shiftRightDiv" onclick="controlsUnitClicked('shiftRight');">
|
||||
Shift Right: <span id="shiftRightValue" class="controlsValue"></span>
|
||||
</div>
|
||||
<div class="controlsUnit" id="softDropDiv" onclick="controlsUnitClicked('softDrop');">
|
||||
Soft Drop: <span id="softDropValue" class="controlsValue"></span>
|
||||
</div>
|
||||
<div class="controlsUnit" id="hardDropDiv" onclick="controlsUnitClicked('hardDrop');">
|
||||
Hard Drop: <span id="hardDropValue" class="controlsValue"></span>
|
||||
</div>
|
||||
<div class="controlsUnit" id="swapDiv" onclick="controlsUnitClicked('swap');">
|
||||
Swap Peice: <span id="swapValue" class="controlsValue"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="autoRepeatArea">
|
||||
Auto-Repeat Times:<br/>
|
||||
<button onclick="resetAutoRepeat();">Reset Auto-Repeat</button>
|
||||
|
||||
<div class="autoRepeatForm">
|
||||
<div class="autoRepeatDiv">
|
||||
AutoRepeat: <span id="autoRepeatValue"></span> ms <br/>
|
||||
<input id="autoRepeatRange" type="range" min="15" max="300" value="50" step="5" onchange="updateAutoRepeat();"/>
|
||||
</div>
|
||||
<div class="thresholdDiv">
|
||||
Repeat Charge: <span id="thresholdValue"></span> ms <br/>
|
||||
<input id="thresholdRange" type="range" min="15" max="500" value="200" step="5" onchange="updateThreshold();"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
202
tetris/controls.js
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
var controlsLoaded = false;
|
||||
var curControl = null;
|
||||
|
||||
function onControlsLoad() {
|
||||
jaws.start(InputMonitor);
|
||||
// check for an existing controls cookie
|
||||
var customControls = readCookie('customControls');
|
||||
|
||||
// these actions will trigger the controls configurations
|
||||
if (customControls === 'TRUE') {
|
||||
// if there is a cookie, set up the controls for it
|
||||
document.getElementById('customRadio').checked = true;
|
||||
configureCustomControls();
|
||||
} else {
|
||||
// if no cookie, assign defaults, create the cookie
|
||||
document.getElementById('defaultRadio').checked = true;
|
||||
setDefaultControls();
|
||||
}
|
||||
|
||||
configureAutoRepeat();
|
||||
|
||||
controlsLoaded = true;
|
||||
}
|
||||
|
||||
function setDefaultControls() {
|
||||
stopPollingInput();
|
||||
|
||||
document.getElementById('instructionsDefault').setAttribute('class', 'withDisplay');
|
||||
document.getElementById('instructionsCustom').setAttribute('class', 'noDisplay');
|
||||
document.getElementById('instructionsPending').setAttribute('class', 'noDisplay');
|
||||
|
||||
// set the cookies
|
||||
createCookie('customControls', 'FALSE', 1000);
|
||||
|
||||
// configure the gui to the default text
|
||||
document.getElementById('rotateLeftValue')
|
||||
.innerHTML = 'Z';
|
||||
document.getElementById('rotateRightValue')
|
||||
.innerHTML = 'X, UP';
|
||||
document.getElementById('shiftLeftValue')
|
||||
.innerHTML = 'LEFT';
|
||||
document.getElementById('shiftRightValue')
|
||||
.innerHTML = 'RIGHT';
|
||||
document.getElementById('softDropValue')
|
||||
.innerHTML = 'DOWN';
|
||||
document.getElementById('hardDropValue')
|
||||
.innerHTML = 'SPACE';
|
||||
document.getElementById('swapValue')
|
||||
.innerHTML = 'SHIFT, C';
|
||||
}
|
||||
|
||||
function configureCustomControls(fromCookie, fromThreshold) {
|
||||
stopPollingInput();
|
||||
|
||||
document.getElementById('instructionsDefault').setAttribute('class', 'noDisplay');
|
||||
document.getElementById('instructionsCustom').setAttribute('class', 'withDisplay');
|
||||
document.getElementById('instructionsPending').setAttribute('class', 'noDisplay');
|
||||
|
||||
if (controlsLoaded && !fromCookie) {
|
||||
// the cookies need to be created & initialized
|
||||
createCookie('rotateLeft', 'Z', 1000);
|
||||
createCookie('rotateRight', 'X', 1000);
|
||||
createCookie('shiftLeft', 'LEFT', 1000);
|
||||
createCookie('shiftRight', 'RIGHT', 1000);
|
||||
createCookie('softDrop', 'DOWN', 1000);
|
||||
createCookie('hardDrop', 'SPACE', 1000);
|
||||
createCookie('swap', 'C', 1000);
|
||||
|
||||
createCookie('customControls', 'TRUE', 1000);
|
||||
}
|
||||
|
||||
// assign all of the GUI elements based on the cookie
|
||||
document.getElementById('rotateLeftValue')
|
||||
.innerHTML = readCookie('rotateLeft');
|
||||
document.getElementById('rotateRightValue')
|
||||
.innerHTML = readCookie('rotateRight');
|
||||
document.getElementById('shiftLeftValue')
|
||||
.innerHTML = readCookie('shiftLeft');
|
||||
document.getElementById('shiftRightValue')
|
||||
.innerHTML = readCookie('shiftRight');
|
||||
document.getElementById('softDropValue')
|
||||
.innerHTML = readCookie('softDrop');
|
||||
document.getElementById('hardDropValue')
|
||||
.innerHTML = readCookie('hardDrop');
|
||||
document.getElementById('swapValue')
|
||||
.innerHTML = readCookie('swap');
|
||||
}
|
||||
|
||||
function controlsUnitClicked(controlName) {
|
||||
// if default controls, switch to custom
|
||||
if (readCookie('customControls') !== 'TRUE') {
|
||||
// if no cookie, assign defaults, create the cookie
|
||||
document.getElementById('customRadio').checked = true;
|
||||
configureCustomControls();
|
||||
}
|
||||
|
||||
document.getElementById('instructionsDefault').setAttribute('class', 'noDisplay');
|
||||
document.getElementById('instructionsCustom').setAttribute('class', 'noDisplay');
|
||||
document.getElementById('instructionsPending').setAttribute('class', 'withDisplay');
|
||||
|
||||
if (curControl !== null) {
|
||||
stopPollingInput();
|
||||
}
|
||||
curControl = {
|
||||
name: controlName,
|
||||
containerId: controlName + 'Div'
|
||||
};
|
||||
|
||||
startPollingInput();
|
||||
}
|
||||
|
||||
function startPollingInput() {
|
||||
document.getElementById(curControl.containerId).setAttribute('class', 'controlsUnit controlsUnitPending');
|
||||
|
||||
inputPolling = true;
|
||||
}
|
||||
|
||||
function stopPollingInput() {
|
||||
if (curControl !== null) {
|
||||
inputPolling = false;
|
||||
|
||||
document.getElementById(curControl.containerId).setAttribute('class', 'controlsUnit');
|
||||
curControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
function findWhereKeyUsed(key) {
|
||||
var cookies = ['rotateLeft',
|
||||
'rotateRight',
|
||||
'shiftLeft',
|
||||
'shiftRight',
|
||||
'softDrop',
|
||||
'hardDrop',
|
||||
'swap'],
|
||||
i;
|
||||
|
||||
for (i = 0; i < cookies.length; i += 1) {
|
||||
if (readCookie(cookies[i]) === key) {
|
||||
return cookies[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function reportKeyPressed(keyLower) {
|
||||
// should never fail this case...
|
||||
if (curControl !== null) {
|
||||
var key = keyLower.toUpperCase();
|
||||
|
||||
// if this key is used anywhere else
|
||||
var controlUsed = findWhereKeyUsed(key);
|
||||
if (controlUsed !== null) {
|
||||
// swap the two controls
|
||||
createCookie(controlUsed, readCookie(curControl.name), 1000);
|
||||
createCookie(curControl.name, key, 1000);
|
||||
} else {
|
||||
// set this key to the new value
|
||||
createCookie(curControl.name, key, 1000);
|
||||
}
|
||||
|
||||
configureCustomControls(true);
|
||||
|
||||
stopPollingInput();
|
||||
}
|
||||
}
|
||||
|
||||
function configureAutoRepeat() {
|
||||
var autoRepeat = readCookie('autoRepeat');
|
||||
if (autoRepeat === null) {
|
||||
autoRepeat = "50";
|
||||
createCookie('autoRepeat', autoRepeat, 1000);
|
||||
}
|
||||
var threshold = readCookie('threshold');
|
||||
if (threshold === null) {
|
||||
threshold = "200";
|
||||
createCookie("threshold", threshold, 1000);
|
||||
}
|
||||
|
||||
document.getElementById('autoRepeatRange').value = autoRepeat;
|
||||
document.getElementById('autoRepeatValue').innerHTML = autoRepeat;
|
||||
document.getElementById('thresholdRange').value = threshold;
|
||||
document.getElementById('thresholdValue').innerHTML = threshold;
|
||||
}
|
||||
|
||||
function updateAutoRepeat() {
|
||||
var newVal = document.getElementById('autoRepeatRange').value;
|
||||
document.getElementById('autoRepeatValue').innerHTML = newVal;
|
||||
createCookie('autoRepeat', newVal, 1000);
|
||||
}
|
||||
|
||||
function updateThreshold() {
|
||||
var newVal = document.getElementById('thresholdRange').value;
|
||||
document.getElementById('thresholdValue').innerHTML = newVal;
|
||||
createCookie('threshold', newVal, 1000);
|
||||
}
|
||||
|
||||
function resetAutoRepeat() {
|
||||
eraseCookie('autoRepeat');
|
||||
eraseCookie('threshold');
|
||||
configureAutoRepeat();
|
||||
}
|
113
tetris/controlsStyles.css
Normal file
@ -0,0 +1,113 @@
|
||||
.customControlsDiv
|
||||
{
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.controlsArea
|
||||
{
|
||||
padding: 5px;
|
||||
width: 580;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.controlsUnit
|
||||
{
|
||||
margin: 10px 5px;
|
||||
font-size: 16px;
|
||||
font-family: VT323;
|
||||
color: #008000;
|
||||
background-color: #000d00;
|
||||
border: 1px solid #008000;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.controlsUnit:hover
|
||||
{
|
||||
cursor: pointer;
|
||||
background-color: #008000;
|
||||
color: #000d00;
|
||||
}
|
||||
|
||||
.controlsUnitPending
|
||||
{
|
||||
cursor: pointer;
|
||||
background-color: #008000;
|
||||
color: #000d00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.enableCustomDiv
|
||||
{
|
||||
margin: 10px 5px;
|
||||
font-size: 16px;
|
||||
font-family: VT323;
|
||||
color: #008000;
|
||||
background-color: #000d00;
|
||||
border: 1px solid #008000;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
width: 300px;
|
||||
height: 245px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.controlsValue
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
|
||||
.noDisplay
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.withDisplay
|
||||
{
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.autoRepeatDiv
|
||||
{
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.thresholdDiv
|
||||
{
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.autoRepeatArea
|
||||
{
|
||||
margin: 10px 5px;
|
||||
font-size: 16px;
|
||||
font-family: VT323;
|
||||
color: #008000;
|
||||
background-color: #000d00;
|
||||
border: 1px solid #008000;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
width: 560px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
top: 265px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.autoRepeatForm
|
||||
{
|
||||
width: 350px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
}
|
24
tetris/cookie.js
Normal file
@ -0,0 +1,24 @@
|
||||
function createCookie(name,value,days) {
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime()+(days*24*60*60*1000));
|
||||
var expires = "; expires="+date.toGMTString();
|
||||
}
|
||||
else var expires = "";
|
||||
document.cookie = name+"="+value+expires+"; path=/";
|
||||
}
|
||||
|
||||
function readCookie(name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for(var i=0;i < ca.length;i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0)==' ') c = c.substring(1,c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function eraseCookie(name) {
|
||||
createCookie(name,"",-1);
|
||||
}
|
46
tetris/gameControls.js
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
// default input assignments
|
||||
var inputAssignments = {
|
||||
shiftLeft: ['left'],
|
||||
shiftRight: ['right'],
|
||||
softDrop: ['down'],
|
||||
rotateLeft: ['z'],
|
||||
rotateRight: ['x', 'up'],
|
||||
swap: ['shift', 'c'],
|
||||
hardDrop: ['space']
|
||||
};
|
||||
|
||||
var autoRepeatConfig = 50;
|
||||
var thresholdConfig = 200;
|
||||
|
||||
function loadGameControls() {
|
||||
var cookies = ['rotateLeft',
|
||||
'rotateRight',
|
||||
'shiftLeft',
|
||||
'shiftRight',
|
||||
'softDrop',
|
||||
'hardDrop',
|
||||
'swap'],
|
||||
i, curVal;
|
||||
|
||||
// if custom controls need to be loaded
|
||||
if (readCookie('customControls') === 'TRUE') {
|
||||
// for each input cookie
|
||||
for (i = 0; i < cookies.length; i += 1) {
|
||||
// print the controls to the table
|
||||
curVal = readCookie(cookies[i]);
|
||||
document.getElementById(cookies[i]).innerHTML = curVal;
|
||||
// pass the controls into the config object
|
||||
inputAssignments[cookies[i]] = [curVal.toLowerCase()];
|
||||
}
|
||||
}
|
||||
|
||||
var autoRepeat = readCookie('autoRepeat');
|
||||
if (autoRepeat !== null) {
|
||||
autoRepeatConfig = parseInt(autoRepeat);
|
||||
}
|
||||
var threshold = readCookie('threshold');
|
||||
if (threshold != null) {
|
||||
thresholdConfig = parseInt(threshold);
|
||||
}
|
||||
}
|
60
tetris/highScores.html
Normal file
@ -0,0 +1,60 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>TwitchTetris</title>
|
||||
<link href="//fonts.googleapis.com/css?family=VT323" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="styles.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="highScores.js"></script>
|
||||
<script src="json-minified.js"></script>
|
||||
</head>
|
||||
<body onload="highScoresOnLoad()">
|
||||
|
||||
<table border="0" class="mainTable">
|
||||
<tr>
|
||||
<td class="menuCell">
|
||||
<div class="menu">
|
||||
<img class="menuLogo" src="media/background/logo.png"></img>
|
||||
<br/><br/>
|
||||
|
||||
<a href="index.html" class="bareLink">
|
||||
<div class="menuItem">Play TwitchTetris</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="controls.html" class="bareLink">
|
||||
<div class="menuItem">Controls Options</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="highScores.html" class="bareLink">
|
||||
<div class="menuItem selectedMenuItem">High Scores</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="about.html" class="bareLink">
|
||||
<div class="menuItem">About TwitchTetris</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="http://www.leighpauls.com" class="bareLink">
|
||||
<div class="menuItem">About the Developer</div>
|
||||
</a>
|
||||
<br/>
|
||||
|
||||
<div class="instructions">
|
||||
<b> > TwitchTetris --help </b>
|
||||
<p>Use the keyboard controls to rotate and move the blocks as they fall</p>
|
||||
<p>Place the blocks to form horizontal lines, which will be removed and make all the blocks above fall down. If the blocks reach the top of the screen, the game is over!</p>
|
||||
<p>Clear multiple lines at a time, or clear lines in consective moves to earn more points.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
<td class="contentCell">
|
||||
<div class="scoreArea">
|
||||
Daily High Scores:
|
||||
<div class="highScoresArea" id="dailyScoreDiv"></div>
|
||||
<br/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
39
tetris/highScores.js
Normal file
@ -0,0 +1,39 @@
|
||||
function getXmlHttp() {
|
||||
if (window.XMLHttpRequest)
|
||||
{// code for IE7+, Firefox, Chrome, Opera, Safari
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
else
|
||||
{// code for IE6, IE5
|
||||
return new ActiveXObject("Microsoft.XMLHTTP");
|
||||
}
|
||||
}
|
||||
|
||||
function highScoresOnLoad() {
|
||||
// div called id=highScoreDiv
|
||||
var xmlhttp = getXmlHttp();
|
||||
xmlhttp.onreadystatechange=function()
|
||||
{
|
||||
if (xmlhttp.readyState==4 && xmlhttp.status==200)
|
||||
{
|
||||
var response = jsonParse(xmlhttp.responseText),
|
||||
dailyScoreList = response.dailyScores,
|
||||
dailyOutput,
|
||||
i;
|
||||
|
||||
dailyOutput= '<table class="highScoreTable"><tr class="highScoreTableHeader"><td>#</td><td>Name</td><td>Score</td></tr>';
|
||||
|
||||
for (i = 0; i < dailyScoreList.length; i += 1) {
|
||||
curScore = dailyScoreList[i];
|
||||
dailyOutput += '<tr><td>' + (i+1) + '</td><td>' + curScore.name + '</td><td>' + curScore.score + '</td></tr>';
|
||||
}
|
||||
|
||||
dailyOutput += '</table>';
|
||||
|
||||
document.getElementById("dailyScoreDiv").innerHTML = dailyOutput;
|
||||
}
|
||||
}
|
||||
|
||||
xmlhttp.open("POST", "/score/tables", true);
|
||||
xmlhttp.send();
|
||||
}
|
@ -1,393 +1,121 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body { font-family: Helvetica, sans-serif;background:black; }
|
||||
#tetris { margin: 1em auto; padding: 1em; border: 4px solid grey; border-radius: 10px; background-color: #F8F8F8; }
|
||||
#stats { display: inline-block; vertical-align: top; }
|
||||
#canvas { display: inline-block; vertical-align: top; background: black; box-shadow: 10px 10px 10px #999; border: 2px solid #333; }
|
||||
#menu { display: inline-block; vertical-align: top; position: relative; }
|
||||
#menu p { margin: 0.5em 0; text-align: center; }
|
||||
#menu p a { text-decoration: none; color: black; }
|
||||
#upcoming { display: block; margin: 0 auto; background-color: #E0E0E0; }
|
||||
#score { color: black; font-weight: bold; vertical-align: middle; }
|
||||
#rows { color: black; font-weight: bold; vertical-align: middle; }
|
||||
#stats { position: absolute; bottom: 0em; right: 1em; }
|
||||
@media screen and (min-width: 0px) and (min-height: 0px) { #tetris { font-size: 0.75em; width: 250px; } #menu { width: 100px; height: 200px; } #upcoming { width: 50px; height: 50px; } #canvas { width: 100px; height: 200px; } } /* 10px chunks */
|
||||
@media screen and (min-width: 400px) and (min-height: 400px) { #tetris { font-size: 1.00em; width: 350px; } #menu { width: 150px; height: 300px; } #upcoming { width: 75px; height: 75px; } #canvas { width: 150px; height: 300px; } } /* 15px chunks */
|
||||
@media screen and (min-width: 500px) and (min-height: 500px) { #tetris { font-size: 1.25em; width: 450px; } #menu { width: 200px; height: 400px; } #upcoming { width: 100px; height: 100px; } #canvas { width: 200px; height: 400px; } } /* 20px chunks */
|
||||
@media screen and (min-width: 600px) and (min-height: 600px) { #tetris { font-size: 1.50em; width: 550px; } #menu { width: 250px; height: 500px; } #upcoming { width: 125px; height: 125px; } #canvas { width: 250px; height: 500px; } } /* 25px chunks */
|
||||
@media screen and (min-width: 700px) and (min-height: 700px) { #tetris { font-size: 1.75em; width: 650px; } #menu { width: 300px; height: 600px; } #upcoming { width: 150px; height: 150px; } #canvas { width: 300px; height: 600px; } } /* 30px chunks */
|
||||
@media screen and (min-width: 800px) and (min-height: 800px) { #tetris { font-size: 2.00em; width: 750px; } #menu { width: 350px; height: 700px; } #upcoming { width: 175px; height: 175px; } #canvas { width: 350px; height: 700px; } } /* 35px chunks */
|
||||
@media screen and (min-width: 900px) and (min-height: 900px) { #tetris { font-size: 2.25em; width: 850px; } #menu { width: 400px; height: 800px; } #upcoming { width: 200px; height: 200px; } #canvas { width: 400px; height: 800px; } } /* 40px chunks */
|
||||
</style>
|
||||
</head>
|
||||
<head>
|
||||
<title>TwitchTetris | 3kh0</title>
|
||||
<link href="//fonts.googleapis.com/css?family=VT323" rel="stylesheet" type="text/css" />
|
||||
<link href="/favicon.ico" rel="icon">
|
||||
<link href="styles.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<body>
|
||||
<script src="cookie.js"></script>
|
||||
<script src="gameControls.js"></script>
|
||||
|
||||
<div id="tetris">
|
||||
<div id="menu">
|
||||
<p id="start"><a href="javascript:play();">Press Space to Play</a></p>
|
||||
<p><canvas id="upcoming"></canvas></p>
|
||||
<p>Score: <span id="score">00000</span></p>
|
||||
<p>Rows: <span id="rows">0</span></p>
|
||||
</div>
|
||||
<canvas id="canvas">
|
||||
Sorry, this example cannot be run because your browser does not support the <canvas> element
|
||||
</canvas>
|
||||
</div>
|
||||
<script src="jawsjs.js"></script>
|
||||
<script src="Block.js"></script>
|
||||
<script src="Shapes.js"></script>
|
||||
<script src="WallKicks.js"></script>
|
||||
<script src="ControlGroup.js"></script>
|
||||
<script src="Background.js"></script>
|
||||
<script src="RandomBag.js"></script>
|
||||
<script src="PreviewGroup.js"></script>
|
||||
<script src="ScoreTracker.js"></script>
|
||||
<script src="TtyBlock.js"></script>
|
||||
<script src="Game.js"></script>
|
||||
<script src="Game_Logic.js"></script>
|
||||
<script src="Button.js"></script>
|
||||
<script src="tetris.js"></script>
|
||||
<script src="/js/main.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
var Stats=function(){function s(a,g,d){var f,c,e;for(c=0;c<30;c++)for(f=0;f<73;f++)e=(f+c*74)*4,a[e]=a[e+4],a[e+1]=a[e+5],a[e+2]=a[e+6];for(c=0;c<30;c++)e=(73+c*74)*4,c<g?(a[e]=b[d].bg.r,a[e+1]=b[d].bg.g,a[e+2]=b[d].bg.b):(a[e]=b[d].fg.r,a[e+1]=b[d].fg.g,a[e+2]=b[d].fg.b)}var r=0,t=2,g,u=0,j=(new Date).getTime(),F=j,v=j,l=0,w=1E3,x=0,k,d,a,m,y,n=0,z=1E3,A=0,f,c,o,B,p=0,C=1E3,D=0,h,i,q,E,b={fps:{bg:{r:16,g:16,b:48},fg:{r:0,g:255,b:255}},ms:{bg:{r:16,g:48,b:16},fg:{r:0,g:255,b:0}},mb:{bg:{r:48,g:16,
|
||||
b:26},fg:{r:255,g:0,b:128}}};g=document.createElement("div");g.style.cursor="pointer";g.style.width="80px";g.style.opacity="0.9";g.style.zIndex="10001";g.addEventListener("click",function(){r++;r==t&&(r=0);k.style.display="none";f.style.display="none";h.style.display="none";switch(r){case 0:k.style.display="block";break;case 1:f.style.display="block";break;case 2:h.style.display="block"}},!1);k=document.createElement("div");k.style.backgroundColor="rgb("+Math.floor(b.fps.bg.r/2)+","+Math.floor(b.fps.bg.g/
|
||||
2)+","+Math.floor(b.fps.bg.b/2)+")";k.style.padding="2px 0px 3px 0px";g.appendChild(k);d=document.createElement("div");d.style.fontFamily="Helvetica, Arial, sans-serif";d.style.textAlign="left";d.style.fontSize="9px";d.style.color="rgb("+b.fps.fg.r+","+b.fps.fg.g+","+b.fps.fg.b+")";d.style.margin="0px 0px 1px 3px";d.innerHTML='<span style="font-weight:bold">FPS</span>';k.appendChild(d);a=document.createElement("canvas");a.width=74;a.height=30;a.style.display="block";a.style.marginLeft="3px";k.appendChild(a);
|
||||
m=a.getContext("2d");m.fillStyle="rgb("+b.fps.bg.r+","+b.fps.bg.g+","+b.fps.bg.b+")";m.fillRect(0,0,a.width,a.height);y=m.getImageData(0,0,a.width,a.height);f=document.createElement("div");f.style.backgroundColor="rgb("+Math.floor(b.ms.bg.r/2)+","+Math.floor(b.ms.bg.g/2)+","+Math.floor(b.ms.bg.b/2)+")";f.style.padding="2px 0px 3px 0px";f.style.display="none";g.appendChild(f);c=document.createElement("div");c.style.fontFamily="Helvetica, Arial, sans-serif";c.style.textAlign="left";c.style.fontSize=
|
||||
"9px";c.style.color="rgb("+b.ms.fg.r+","+b.ms.fg.g+","+b.ms.fg.b+")";c.style.margin="0px 0px 1px 3px";c.innerHTML='<span style="font-weight:bold">MS</span>';f.appendChild(c);a=document.createElement("canvas");a.width=74;a.height=30;a.style.display="block";a.style.marginLeft="3px";f.appendChild(a);o=a.getContext("2d");o.fillStyle="rgb("+b.ms.bg.r+","+b.ms.bg.g+","+b.ms.bg.b+")";o.fillRect(0,0,a.width,a.height);B=o.getImageData(0,0,a.width,a.height);try{performance&&performance.memory&&performance.memory.totalJSHeapSize&&
|
||||
(t=3)}catch(G){}h=document.createElement("div");h.style.backgroundColor="rgb("+Math.floor(b.mb.bg.r/2)+","+Math.floor(b.mb.bg.g/2)+","+Math.floor(b.mb.bg.b/2)+")";h.style.padding="2px 0px 3px 0px";h.style.display="none";g.appendChild(h);i=document.createElement("div");i.style.fontFamily="Helvetica, Arial, sans-serif";i.style.textAlign="left";i.style.fontSize="9px";i.style.color="rgb("+b.mb.fg.r+","+b.mb.fg.g+","+b.mb.fg.b+")";i.style.margin="0px 0px 1px 3px";i.innerHTML='<span style="font-weight:bold">MB</span>';
|
||||
h.appendChild(i);a=document.createElement("canvas");a.width=74;a.height=30;a.style.display="block";a.style.marginLeft="3px";h.appendChild(a);q=a.getContext("2d");q.fillStyle="#301010";q.fillRect(0,0,a.width,a.height);E=q.getImageData(0,0,a.width,a.height);return{domElement:g,update:function(){u++;j=(new Date).getTime();n=j-F;z=Math.min(z,n);A=Math.max(A,n);s(B.data,Math.min(30,30-n/200*30),"ms");c.innerHTML='<span style="font-weight:bold">'+n+" MS</span> ("+z+"-"+A+")";o.putImageData(B,0,0);F=j;if(j>
|
||||
v+1E3){l=Math.round(u*1E3/(j-v));w=Math.min(w,l);x=Math.max(x,l);s(y.data,Math.min(30,30-l/100*30),"fps");d.innerHTML='<span style="font-weight:bold">'+l+" FPS</span> ("+w+"-"+x+")";m.putImageData(y,0,0);if(t==3)p=performance.memory.usedJSHeapSize*9.54E-7,C=Math.min(C,p),D=Math.max(D,p),s(E.data,Math.min(30,30-p/2),"mb"),i.innerHTML='<span style="font-weight:bold">'+Math.round(p)+" MB</span> ("+Math.round(C)+"-"+Math.round(D)+")",q.putImageData(E,0,0);v=j;u=0}}}};
|
||||
</script>
|
||||
<script>
|
||||
|
||||
function get(id) { return document.getElementById(id); }
|
||||
function hide(id) { get(id).style.visibility = 'hidden'; }
|
||||
function show(id) { get(id).style.visibility = null; }
|
||||
function html(id, html) { get(id).innerHTML = html; }
|
||||
|
||||
function timestamp() { return new Date().getTime(); }
|
||||
function random(min, max) { return (min + (Math.random() * (max - min))); }
|
||||
function randomChoice(choices) { return choices[Math.round(random(0, choices.length-1))]; }
|
||||
|
||||
if (!window.requestAnimationFrame) {
|
||||
window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback, element) {
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
}
|
||||
}
|
||||
|
||||
var KEY = { ESC: 27, SPACE: 32, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 },
|
||||
DIR = { UP: 0, RIGHT: 1, DOWN: 2, LEFT: 3, MIN: 0, MAX: 3 },
|
||||
stats = new Stats(),
|
||||
canvas = get('canvas'),
|
||||
ctx = canvas.getContext('2d'),
|
||||
ucanvas = get('upcoming'),
|
||||
uctx = ucanvas.getContext('2d'),
|
||||
speed = { start: 0.6, decrement: 0.005, min: 0.1 },
|
||||
nx = 10,
|
||||
ny = 20,
|
||||
nu = 5;
|
||||
|
||||
var dx, dy,
|
||||
blocks,
|
||||
actions,
|
||||
playing,
|
||||
dt,
|
||||
current,
|
||||
next,
|
||||
score,
|
||||
vscore,
|
||||
rows,
|
||||
step;
|
||||
var i = { size: 4, blocks: [0x0F00, 0x2222, 0x00F0, 0x4444], color: 'cyan' };
|
||||
var j = { size: 3, blocks: [0x44C0, 0x8E00, 0x6440, 0x0E20], color: 'blue' };
|
||||
var l = { size: 3, blocks: [0x4460, 0x0E80, 0xC440, 0x2E00], color: 'orange' };
|
||||
var o = { size: 2, blocks: [0xCC00, 0xCC00, 0xCC00, 0xCC00], color: 'yellow' };
|
||||
var s = { size: 3, blocks: [0x06C0, 0x8C40, 0x6C00, 0x4620], color: 'green' };
|
||||
var t = { size: 3, blocks: [0x0E40, 0x4C40, 0x4E00, 0x4640], color: 'purple' };
|
||||
var z = { size: 3, blocks: [0x0C60, 0x4C80, 0xC600, 0x2640], color: 'red' };
|
||||
function eachblock(type, x, y, dir, fn) {
|
||||
var bit, result, row = 0, col = 0, blocks = type.blocks[dir];
|
||||
for(bit = 0x8000 ; bit > 0 ; bit = bit >> 1) {
|
||||
if (blocks & bit) {
|
||||
fn(x + col, y + row);
|
||||
}
|
||||
if (++col === 4) {
|
||||
col = 0;
|
||||
++row;
|
||||
<script>
|
||||
function onClickEvent(event) {
|
||||
if (Tetris.currentInstance) {
|
||||
Tetris.currentInstance.mouseClicked(event.layerX, event.layerY);
|
||||
}
|
||||
}
|
||||
}
|
||||
function occupied(type, x, y, dir) {
|
||||
var result = false
|
||||
eachblock(type, x, y, dir, function(x, y) {
|
||||
if ((x < 0) || (x >= nx) || (y < 0) || (y >= ny) || getBlock(x,y))
|
||||
result = true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<table border="0" class="mainTable">
|
||||
<tr>
|
||||
<td class="menuCell">
|
||||
<div class="menu">
|
||||
<img class="menuLogo" src="media/background/logo.png" />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
function unoccupied(type, x, y, dir) {
|
||||
return !occupied(type, x, y, dir);
|
||||
}
|
||||
var pieces = [];
|
||||
function randomPiece() {
|
||||
if (pieces.length == 0)
|
||||
pieces = [i,i,i,i,j,j,j,j,l,l,l,l,o,o,o,o,s,s,s,s,t,t,t,t,z,z,z,z];
|
||||
var type = pieces.splice(random(0, pieces.length-1), 1)[0];
|
||||
return { type: type, dir: DIR.UP, x: Math.round(random(0, nx - type.size)), y: 0 };
|
||||
}
|
||||
<a href="index.html" class="bareLink">
|
||||
<div class="menuItem selectedMenuItem">Play TwitchTetris</div>
|
||||
</a>
|
||||
<br />
|
||||
<a href="controls.html" class="bareLink">
|
||||
<div class="menuItem">Controls Options</div>
|
||||
</a>
|
||||
<br />
|
||||
<a href="highScores.html" class="bareLink">
|
||||
<div class="menuItem">High Scores</div>
|
||||
</a>
|
||||
<br />
|
||||
<a href="about.html" class="bareLink">
|
||||
<div class="menuItem">About TwitchTetris</div>
|
||||
</a>
|
||||
<br />
|
||||
<a href="http://www.leighpauls.com" class="bareLink">
|
||||
<div class="menuItem">About the Developer</div>
|
||||
</a>
|
||||
<br />
|
||||
|
||||
function run() {
|
||||
<div class="instructions">
|
||||
<b> > TwitchTetris --help </b>
|
||||
<p>Use the keyboard controls to rotate and move the blocks as they fall</p>
|
||||
<p>Place the blocks to form horizontal lines, which will be removed and make all the blocks above fall down. If the blocks reach the top of the screen, the game is over!</p>
|
||||
<p>Clear multiple lines at a time, or clear lines in consective moves to earn more points.</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="contentCell">
|
||||
<div class="gamePanel">
|
||||
<div class="gameElements">
|
||||
<div class="ttyOutput scoreOutput" id="scoreDiv"></div>
|
||||
<div class="ttyOutput linesOutput" id="linesDiv"></div>
|
||||
<div class="ttyOutput levelOutput" id="levelDiv"></div>
|
||||
<div class="ttyOutput tickerOutput" id="tickerDiv"></div>
|
||||
|
||||
showStats();
|
||||
addEvents();
|
||||
<div class="gameEndOutputHidden" id="gameEndContainer">
|
||||
<div style="padding: 5px;" id="gameEndDiv"></div>
|
||||
</div>
|
||||
|
||||
var last = now = timestamp();
|
||||
function frame() {
|
||||
now = timestamp();
|
||||
update(Math.min(1, (now - last) / 1000.0));
|
||||
draw();
|
||||
stats.update();
|
||||
last = now;
|
||||
requestAnimationFrame(frame, canvas);
|
||||
}
|
||||
<canvas class="gameCanvas" id="gameCanvas" width="600" height="500" onclick="onClickEvent(event)" class="gameCanvas">
|
||||
Your browser does not natively support Html5, or the Canvas Tag. Using this browser is slowing the progress of the web. Please get a modern browser, such as Google Chrome or Mozzila FireFox
|
||||
</canvas>
|
||||
</div>
|
||||
<div class="controlsTitle">
|
||||
<br />
|
||||
<b>Controls:</b><br />
|
||||
<table border="1" cellpadding="3" class="controlsTable">
|
||||
<tr class="controlsTableHeader">
|
||||
<td width="80">Move Block</td>
|
||||
<td width="80">Soft Drop</td>
|
||||
<td width="80">Rotate</td>
|
||||
<td width="80">Save Piece</td>
|
||||
<td width="80">Hard Drop</td>
|
||||
<td width="80">Pause</td>
|
||||
</tr>
|
||||
|
||||
resize();
|
||||
reset();
|
||||
frame();
|
||||
|
||||
}
|
||||
|
||||
function showStats() {
|
||||
stats.domElement.id = 'stats';
|
||||
get('menu').appendChild(stats.domElement);
|
||||
}
|
||||
|
||||
function addEvents() {
|
||||
document.addEventListener('keydown', keydown, false);
|
||||
window.addEventListener('resize', resize, false);
|
||||
}
|
||||
|
||||
function resize(event) {
|
||||
canvas.width = canvas.clientWidth;
|
||||
canvas.height = canvas.clientHeight;
|
||||
ucanvas.width = ucanvas.clientWidth;
|
||||
ucanvas.height = ucanvas.clientHeight;
|
||||
dx = canvas.width / nx;
|
||||
dy = canvas.height / ny;
|
||||
invalidate();
|
||||
invalidateNext();
|
||||
}
|
||||
|
||||
function keydown(ev) {
|
||||
var handled = false;
|
||||
if (playing) {
|
||||
switch(ev.keyCode) {
|
||||
case KEY.LEFT: actions.push(DIR.LEFT); handled = true; break;
|
||||
case KEY.RIGHT: actions.push(DIR.RIGHT); handled = true; break;
|
||||
case KEY.UP: actions.push(DIR.UP); handled = true; break;
|
||||
case KEY.DOWN: actions.push(DIR.DOWN); handled = true; break;
|
||||
case KEY.ESC: lose(); handled = true; break;
|
||||
}
|
||||
}
|
||||
else if (ev.keyCode == KEY.SPACE) {
|
||||
play();
|
||||
handled = true;
|
||||
}
|
||||
if (handled)
|
||||
ev.preventDefault();
|
||||
}
|
||||
function play() { hide('start'); reset(); playing = true; }
|
||||
function lose() { show('start'); setVisualScore(); playing = false; }
|
||||
|
||||
function setVisualScore(n) { vscore = n || score; invalidateScore(); }
|
||||
function setScore(n) { score = n; setVisualScore(n); }
|
||||
function addScore(n) { score = score + n; }
|
||||
function clearScore() { setScore(0); }
|
||||
function clearRows() { setRows(0); }
|
||||
function setRows(n) { rows = n; step = Math.max(speed.min, speed.start - (speed.decrement*rows)); invalidateRows(); }
|
||||
function addRows(n) { setRows(rows + n); }
|
||||
function getBlock(x,y) { return (blocks && blocks[x] ? blocks[x][y] : null); }
|
||||
function setBlock(x,y,type) { blocks[x] = blocks[x] || []; blocks[x][y] = type; invalidate(); }
|
||||
function clearBlocks() { blocks = []; invalidate(); }
|
||||
function clearActions() { actions = []; }
|
||||
function setCurrentPiece(piece) { current = piece || randomPiece(); invalidate(); }
|
||||
function setNextPiece(piece) { next = piece || randomPiece(); invalidateNext(); }
|
||||
|
||||
function reset() {
|
||||
dt = 0;
|
||||
clearActions();
|
||||
clearBlocks();
|
||||
clearRows();
|
||||
clearScore();
|
||||
setCurrentPiece(next);
|
||||
setNextPiece();
|
||||
}
|
||||
|
||||
function update(idt) {
|
||||
if (playing) {
|
||||
if (vscore < score)
|
||||
setVisualScore(vscore + 1);
|
||||
handle(actions.shift());
|
||||
dt = dt + idt;
|
||||
if (dt > step) {
|
||||
dt = dt - step;
|
||||
drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handle(action) {
|
||||
switch(action) {
|
||||
case DIR.LEFT: move(DIR.LEFT); break;
|
||||
case DIR.RIGHT: move(DIR.RIGHT); break;
|
||||
case DIR.UP: rotate(); break;
|
||||
case DIR.DOWN: drop(); break;
|
||||
}
|
||||
}
|
||||
|
||||
function move(dir) {
|
||||
var x = current.x, y = current.y;
|
||||
switch(dir) {
|
||||
case DIR.RIGHT: x = x + 1; break;
|
||||
case DIR.LEFT: x = x - 1; break;
|
||||
case DIR.DOWN: y = y + 1; break;
|
||||
}
|
||||
if (unoccupied(current.type, x, y, current.dir)) {
|
||||
current.x = x;
|
||||
current.y = y;
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function rotate() {
|
||||
var newdir = (current.dir == DIR.MAX ? DIR.MIN : current.dir + 1);
|
||||
if (unoccupied(current.type, current.x, current.y, newdir)) {
|
||||
current.dir = newdir;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
function drop() {
|
||||
if (!move(DIR.DOWN)) {
|
||||
addScore(10);
|
||||
dropPiece();
|
||||
removeLines();
|
||||
setCurrentPiece(next);
|
||||
setNextPiece(randomPiece());
|
||||
clearActions();
|
||||
if (occupied(current.type, current.x, current.y, current.dir)) {
|
||||
lose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dropPiece() {
|
||||
eachblock(current.type, current.x, current.y, current.dir, function(x, y) {
|
||||
setBlock(x, y, current.type);
|
||||
});
|
||||
}
|
||||
|
||||
function removeLines() {
|
||||
var x, y, complete, n = 0;
|
||||
for(y = ny ; y > 0 ; --y) {
|
||||
complete = true;
|
||||
for(x = 0 ; x < nx ; ++x) {
|
||||
if (!getBlock(x, y))
|
||||
complete = false;
|
||||
}
|
||||
if (complete) {
|
||||
removeLine(y);
|
||||
y = y + 1;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (n > 0) {
|
||||
addRows(n);
|
||||
addScore(100*Math.pow(2,n-1));
|
||||
}
|
||||
}
|
||||
|
||||
function removeLine(n) {
|
||||
var x, y;
|
||||
for(y = n ; y >= 0 ; --y) {
|
||||
for(x = 0 ; x < nx ; ++x)
|
||||
setBlock(x, y, (y == 0) ? null : getBlock(x, y-1));
|
||||
}
|
||||
}
|
||||
var invalid = {};
|
||||
|
||||
function invalidate() { invalid.court = true; }
|
||||
function invalidateNext() { invalid.next = true; }
|
||||
function invalidateScore() { invalid.score = true; }
|
||||
function invalidateRows() { invalid.rows = true; }
|
||||
|
||||
function draw() {
|
||||
ctx.save();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.translate(0.5, 0.5);
|
||||
drawCourt();
|
||||
drawNext();
|
||||
drawScore();
|
||||
drawRows();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawCourt() {
|
||||
if (invalid.court) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
if (playing)
|
||||
drawPiece(ctx, current.type, current.x, current.y, current.dir);
|
||||
var x, y, block;
|
||||
for(y = 0 ; y < ny ; y++) {
|
||||
for (x = 0 ; x < nx ; x++) {
|
||||
if (block = getBlock(x,y))
|
||||
drawBlock(ctx, x, y, block.color);
|
||||
}
|
||||
}
|
||||
ctx.strokeRect(0, 0, nx*dx - 1, ny*dy - 1);
|
||||
invalid.court = false;
|
||||
}
|
||||
}
|
||||
|
||||
function drawNext() {
|
||||
if (invalid.next) {
|
||||
var padding = (nu - next.type.size) / 2;
|
||||
uctx.save();
|
||||
uctx.translate(0.5, 0.5);
|
||||
uctx.clearRect(0, 0, nu*dx, nu*dy);
|
||||
drawPiece(uctx, next.type, padding, padding, next.dir);
|
||||
uctx.strokeStyle = 'black';
|
||||
uctx.strokeRect(0, 0, nu*dx - 1, nu*dy - 1);
|
||||
uctx.restore();
|
||||
invalid.next = false;
|
||||
}
|
||||
}
|
||||
|
||||
function drawScore() {
|
||||
if (invalid.score) {
|
||||
html('score', ("00000" + Math.floor(vscore)).slice(-5));
|
||||
invalid.score = false;
|
||||
}
|
||||
}
|
||||
|
||||
function drawRows() {
|
||||
if (invalid.rows) {
|
||||
html('rows', rows);
|
||||
invalid.rows = false;
|
||||
}
|
||||
}
|
||||
|
||||
function drawPiece(ctx, type, x, y, dir) {
|
||||
eachblock(type, x, y, dir, function(x, y) {
|
||||
drawBlock(ctx, x, y, type.color);
|
||||
});
|
||||
}
|
||||
|
||||
function drawBlock(ctx, x, y, color) {
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(x*dx, y*dy, dx, dy);
|
||||
ctx.strokeRect(x*dx, y*dy, dx, dy)
|
||||
}
|
||||
run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<tr>
|
||||
<td><span id="shiftLeft">Left</span>, <span id="shiftRight">Right</span></td>
|
||||
<td><span id="softDrop">Down</span></td>
|
||||
<td><span id="rotateLeft">Z</span>, <span id="rotateRight">X, Up</span></td>
|
||||
<td><span id="swap">C, Shift</span></td>
|
||||
<td><span id="hardDrop">Space</span></td>
|
||||
<td>Esc</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<br />
|
||||
<a href="controls.html" class="bareLink">
|
||||
<div class="menuItem">Controls Options</div>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
69
tetris/input.js
Normal file
@ -0,0 +1,69 @@
|
||||
var availKeys = [
|
||||
"backspace",
|
||||
"tab",
|
||||
"enter",
|
||||
"shift",
|
||||
"ctrl",
|
||||
"alt",
|
||||
"space",
|
||||
"pageup",
|
||||
"pagedown",
|
||||
"end",
|
||||
"home",
|
||||
"left",
|
||||
"up",
|
||||
"right",
|
||||
"down",
|
||||
"insert",
|
||||
"delete",
|
||||
"multiply",
|
||||
"add",
|
||||
"subtract",
|
||||
"decimalpoint",
|
||||
"divide",
|
||||
"numlock",
|
||||
"scrollock",
|
||||
"semicolon",
|
||||
"equalsign",
|
||||
"comma",
|
||||
"dash",
|
||||
"period",
|
||||
"forwardslash",
|
||||
"openbracket",
|
||||
"backslash",
|
||||
"closebracket",
|
||||
"singlequote",
|
||||
"numpad1","numpad2","numpad3","numpad4","numpad5","numpad6","numpad7","numpad8","numpad9",
|
||||
"0","1","2","3","4","5","6","7","8","9",
|
||||
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"
|
||||
];
|
||||
|
||||
var inputPolling = false;
|
||||
|
||||
function InputMonitor() {
|
||||
// give the program a global reference to me
|
||||
this.setup = function(){
|
||||
jaws.preventDefaultKeys(availKeys);
|
||||
};
|
||||
|
||||
// do nothing
|
||||
this.draw = function(){};
|
||||
|
||||
// polls the keys if appropriate
|
||||
this.update = function() {
|
||||
var i,
|
||||
pressed;
|
||||
|
||||
for (i = 0;
|
||||
i < availKeys.length && inputPolling;
|
||||
i += 1) {
|
||||
|
||||
pressed = jaws.pressed(availKeys[i]);
|
||||
if (pressed) {
|
||||
reportKeyPressed(availKeys[i]);
|
||||
inputPolling = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1520
tetris/jawsjs.js
Normal file
4
tetris/json-minified.js
Normal file
@ -0,0 +1,4 @@
|
||||
window.jsonParse=function(){var r="(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)",k='(?:[^\\0-\\x08\\x0a-\\x1f"\\\\]|\\\\(?:["/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';k='(?:"'+k+'*")';var s=new RegExp("(?:false|true|null|[\\{\\}\\[\\]]|"+r+"|"+k+")","g"),t=new RegExp("\\\\(?:([^u])|u(.{4}))","g"),u={'"':'"',"/":"/","\\":"\\",b:"\u0008",f:"\u000c",n:"\n",r:"\r",t:"\t"};function v(h,j,e){return j?u[j]:String.fromCharCode(parseInt(e,16))}var w=new String(""),x=Object.hasOwnProperty;return function(h,
|
||||
j){h=h.match(s);var e,c=h[0],l=false;if("{"===c)e={};else if("["===c)e=[];else{e=[];l=true}for(var b,d=[e],m=1-l,y=h.length;m<y;++m){c=h[m];var a;switch(c.charCodeAt(0)){default:a=d[0];a[b||a.length]=+c;b=void 0;break;case 34:c=c.substring(1,c.length-1);if(c.indexOf("\\")!==-1)c=c.replace(t,v);a=d[0];if(!b)if(a instanceof Array)b=a.length;else{b=c||w;break}a[b]=c;b=void 0;break;case 91:a=d[0];d.unshift(a[b||a.length]=[]);b=void 0;break;case 93:d.shift();break;case 102:a=d[0];a[b||a.length]=false;
|
||||
b=void 0;break;case 110:a=d[0];a[b||a.length]=null;b=void 0;break;case 116:a=d[0];a[b||a.length]=true;b=void 0;break;case 123:a=d[0];d.unshift(a[b||a.length]={});b=void 0;break;case 125:d.shift();break}}if(l){if(d.length!==1)throw new Error;e=e[0]}else if(d.length)throw new Error;if(j){var p=function(n,o){var f=n[o];if(f&&typeof f==="object"){var i=null;for(var g in f)if(x.call(f,g)&&f!==n){var q=p(f,g);if(q!==void 0)f[g]=q;else{i||(i=[]);i.push(g)}}if(i)for(g=i.length;--g>=0;)delete f[i[g]]}return j.call(n,
|
||||
o,f)};e=p({"":e},"")}return e}}();
|
BIN
tetris/media/background/backdrop.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
tetris/media/background/endconsole.png
Normal file
After Width: | Height: | Size: 822 B |
BIN
tetris/media/background/logo.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
tetris/media/background/topbar.png
Normal file
After Width: | Height: | Size: 453 B |
BIN
tetris/media/blueblock.png
Normal file
After Width: | Height: | Size: 146 B |
BIN
tetris/media/buttons/continue.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
tetris/media/buttons/restart.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
tetris/media/cyanblock.png
Normal file
After Width: | Height: | Size: 142 B |
BIN
tetris/media/emptyblock.png
Normal file
After Width: | Height: | Size: 170 B |
BIN
tetris/media/greenblock.png
Normal file
After Width: | Height: | Size: 145 B |
BIN
tetris/media/greyblock.png
Normal file
After Width: | Height: | Size: 146 B |
BIN
tetris/media/orangeblock.png
Normal file
After Width: | Height: | Size: 143 B |
BIN
tetris/media/purpleblock.png
Normal file
After Width: | Height: | Size: 144 B |
BIN
tetris/media/redblock.png
Normal file
After Width: | Height: | Size: 141 B |
BIN
tetris/media/yellowblock.png
Normal file
After Width: | Height: | Size: 144 B |
69
tetris/scoreScreen.html
Normal file
@ -0,0 +1,69 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>TwitchTetris</title>
|
||||
<link href="//fonts.googleapis.com/css?family=VT323" rel="stylesheet" type="text/css" />
|
||||
|
||||
<link href="styles.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="scoreScreen.js"></script>
|
||||
<script src="json-minified.js"></script>
|
||||
</head>
|
||||
<body onload="scoreScreenOnLoad()">
|
||||
|
||||
<table border="0" class="mainTable">
|
||||
<tr>
|
||||
<td class="menuCell">
|
||||
<div class="menu">
|
||||
<img class="menuLogo" src="media/background/logo.png"></img>
|
||||
<br/><br/>
|
||||
|
||||
<a href="index.html" class="bareLink">
|
||||
<div class="menuItem">Play TwitchTetris</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="controls.html" class="bareLink">
|
||||
<div class="menuItem">Controls Options</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="highScores.html" class="bareLink">
|
||||
<div class="menuItem selectedMenuItem">High Scores</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="about.html" class="bareLink">
|
||||
<div class="menuItem">About TwitchTetris</div>
|
||||
</a>
|
||||
<br/>
|
||||
<a href="http://www.leighpauls.com" class="bareLink">
|
||||
<div class="menuItem">About the Developer</div>
|
||||
</a>
|
||||
<br/>
|
||||
|
||||
<div class="instructions">
|
||||
<b> > TwitchTetris --help </b>
|
||||
<p>Use the keyboard controls to rotate and move the blocks as they fall</p>
|
||||
<p>Place the blocks to form horizontal lines, which will be removed and make all the blocks above fall down. If the blocks reach the top of the screen, the game is over!</p>
|
||||
<p>Clear multiple lines at a time, or clear lines in consective moves to earn more points.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
<td class="contentCell">
|
||||
<div class="scoreArea" id="scoreDiv"></div>
|
||||
<div id="applyNameDiv" class="applyNameHidden">
|
||||
Congratulations! You have made a top Score!<br/>
|
||||
Enter your name and hit ENTER to submit your name!</br>
|
||||
<label for="nameInput">Name: </label>
|
||||
<input type="text" id="nameInput" onkeydown="nameKeyDown(event)" />
|
||||
</div>
|
||||
<br/><br/>
|
||||
<a href="index.html" class="bareLink" onclick="trySubmitName()">
|
||||
<div class="playAgainButton">
|
||||
Play Again
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
88
tetris/scoreScreen.js
Normal file
@ -0,0 +1,88 @@
|
||||
function $_GET(q) {
|
||||
var s = window.location.search;
|
||||
var re = new RegExp('&'+q+'(?:=([^&]*))?(?=&|$)','i');
|
||||
return (s=s.replace(/^\?/,'&').match(re)) ? (typeof s[1] == 'undefined' ? '' : decodeURIComponent(s[1])) : undefined;
|
||||
}
|
||||
|
||||
function getXmlHttp() {
|
||||
if (window.XMLHttpRequest)
|
||||
{// code for IE7+, Firefox, Chrome, Opera, Safari
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
else
|
||||
{// code for IE6, IE5
|
||||
return new ActiveXObject("Microsoft.XMLHTTP");
|
||||
}
|
||||
}
|
||||
|
||||
function scoreScreenOnLoad() {
|
||||
var sessionRef = $_GET('tempRef');
|
||||
|
||||
var xmlhttp = getXmlHttp();
|
||||
xmlhttp.onreadystatechange=function()
|
||||
{
|
||||
if (xmlhttp.readyState==4 && xmlhttp.status==200)
|
||||
{
|
||||
var response = jsonParse(xmlhttp.responseText),
|
||||
ranked = false;
|
||||
output = '<br/><br/><div class="resTitle">GOOD GAME!</div><br/><br/>';
|
||||
|
||||
output += '<table class="resultsTable">';
|
||||
|
||||
output += '<tr><td class="resultsLeft">Score:</td><td class="resultsRight">' + response.userScore + '</td></tr>';
|
||||
if (response.dailyRank > 0) {
|
||||
output += '<tr><td class="resultsLeft">Daily Rank:</td><td class="resultsRight">'
|
||||
+ response.dailyRank + '</td></tr>';
|
||||
ranked = true;
|
||||
|
||||
}
|
||||
if (response.totalRank > 0) {
|
||||
output += '<tr><td class="resultsLeft">Total Rank:</td><td class="resultsRight">'
|
||||
+ response.totalRank + '</td></tr>';
|
||||
ranked = true;
|
||||
}
|
||||
output += '</table><br/><br/><br/>';
|
||||
|
||||
document.getElementById("scoreDiv").innerHTML = output;
|
||||
|
||||
// if ranked, prompt for a name
|
||||
if (ranked) {
|
||||
document.getElementById("applyNameDiv").setAttribute('class', 'applyNameVisible');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmlhttp.open("POST", "/score/postGame?tempRef="+sessionRef, true);
|
||||
xmlhttp.send();
|
||||
}
|
||||
|
||||
function nameKeyDown(e) {
|
||||
var keycode;
|
||||
if (window.event) { //IE
|
||||
keycode = e.keyCode;
|
||||
} else {
|
||||
keycode = e.which;
|
||||
}
|
||||
if (keycode === 13) { // if the enter key
|
||||
applyName();
|
||||
}
|
||||
}
|
||||
|
||||
function applyName() {
|
||||
var sessionRef = $_GET('tempRef');
|
||||
var name = document.getElementById("nameInput").value;
|
||||
|
||||
if (name.length < 1 || sessionRef.length < 1) return;
|
||||
|
||||
document.getElementById("applyNameDiv").setAttribute('class', 'applyNameHidden');
|
||||
|
||||
var xmlhttp = getXmlHttp();
|
||||
xmlhttp.open("POST", "/score/apply?tempRef="+sessionRef+"&name="+name, true);
|
||||
xmlhttp.send();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function trySubmitName() {
|
||||
applyName();
|
||||
}
|
289
tetris/styles.css
Normal file
@ -0,0 +1,289 @@
|
||||
/** PAGE ELEMENTS **/
|
||||
|
||||
.menuCell
|
||||
{
|
||||
background-color: #001d00;
|
||||
vertical-align: top;
|
||||
height: 600px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
|
||||
.instructions
|
||||
{
|
||||
margin: 0 5;
|
||||
font-size: 15px;
|
||||
font-family: VT323;
|
||||
color: #008000;
|
||||
background-color: #000d00;
|
||||
border: 1px solid #008000;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
width: 140;
|
||||
}
|
||||
|
||||
.menu
|
||||
{
|
||||
width: 160;
|
||||
}
|
||||
|
||||
.menuLogo
|
||||
{
|
||||
width: 150px;
|
||||
margin: 5 5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menuItem
|
||||
{
|
||||
font-family: VT323;
|
||||
margin: 0 auto;
|
||||
border: 1px solid #008000;
|
||||
border-radius: 5px;
|
||||
background-color: #000d00;
|
||||
color: #008000;
|
||||
padding: 5px;
|
||||
width: 140px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.scoreArea
|
||||
{
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.playAgainButton
|
||||
{
|
||||
font-family: VT323;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
border: 1px solid #008000;
|
||||
border-radius: 5px;
|
||||
background-color: #000d00;
|
||||
color: #008000;
|
||||
padding: 5px;
|
||||
width: 140px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
a.bareLink:link
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
a.bareLink:active
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
a.bareLink:visited
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.selectedMenuItem
|
||||
{
|
||||
color: #000d00;
|
||||
background-color: #008000;
|
||||
}
|
||||
|
||||
.menuItem:hover
|
||||
{
|
||||
color: #c0c0c0;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.aboutText
|
||||
{
|
||||
margin: 5 5;
|
||||
font-size: 16px;
|
||||
font-family: VT323;
|
||||
color: #008000;
|
||||
background-color: #000d00;
|
||||
border: 1px solid #008000;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
width: 580;
|
||||
}
|
||||
|
||||
.contentCell
|
||||
{
|
||||
background-color: #001d00;
|
||||
color: #c0c0c0;
|
||||
font-family: VT323;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.applyNameHidden
|
||||
{
|
||||
visibility: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.applyNameVisible
|
||||
{
|
||||
visibility: visible;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Page table */
|
||||
.mainTable
|
||||
{
|
||||
margin: 0 auto;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #000000;
|
||||
|
||||
}
|
||||
|
||||
/* panel containing the game and controls */
|
||||
.gamePanel
|
||||
{
|
||||
width: 601px;
|
||||
position: relative;
|
||||
top: 0; left: 0;
|
||||
}
|
||||
|
||||
/* controls table */
|
||||
.controlsTitle
|
||||
{
|
||||
text-align: center;
|
||||
}
|
||||
.controlsTableHeader
|
||||
{
|
||||
color: #c0c0c0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.controlsTable
|
||||
{
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #008000;
|
||||
color: #008000;
|
||||
background-color: #000d00;
|
||||
}
|
||||
.controlsTable td
|
||||
{
|
||||
border-color: #008000;
|
||||
}
|
||||
|
||||
|
||||
.gameCanvas
|
||||
{
|
||||
border: 1px solid #008000;
|
||||
}
|
||||
|
||||
.resTitle
|
||||
{
|
||||
font-size: 26px;
|
||||
text-align: center;
|
||||
}
|
||||
.resultsTable
|
||||
{
|
||||
margin: 0 auto;
|
||||
font-size: 26px;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #008000;
|
||||
color: #008000;
|
||||
background-color: #000d00;
|
||||
}
|
||||
.resultsTable td
|
||||
{
|
||||
border: 1px solid #008000;
|
||||
padding: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.highScoreTable
|
||||
{
|
||||
margin: 0 auto;
|
||||
font-size: 18px;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #008000;
|
||||
color: #008000;
|
||||
background-color: #000d00;
|
||||
}
|
||||
.highScoreTable td
|
||||
{
|
||||
border: 1px solid #008000;
|
||||
width: 200px;
|
||||
}
|
||||
.highScoreTableHeader
|
||||
{
|
||||
color: #c0c0c0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.resultsLeft
|
||||
{
|
||||
text-align: right;
|
||||
}
|
||||
.resultsRight
|
||||
{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/** GAME ELEMENTS **/
|
||||
.gameElements
|
||||
{
|
||||
position: relative;
|
||||
height: 500px;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.gameCanvas
|
||||
{
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ttyOutput
|
||||
{
|
||||
font-family: VT323;
|
||||
font-size:20px;
|
||||
position: absolute;
|
||||
height: 0px; width: 200px;
|
||||
color: #008800;
|
||||
line-height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
.scoreOutput
|
||||
{
|
||||
top: 140px; left: 20px;
|
||||
}
|
||||
.linesOutput
|
||||
{
|
||||
top: 235px; left: 20px;
|
||||
}
|
||||
.levelOutput
|
||||
{
|
||||
top: 328; left: 20px;
|
||||
}
|
||||
.tickerOutput
|
||||
{
|
||||
top: 372; left: 435;
|
||||
}
|
||||
|
||||
.gameEndOutputHidden
|
||||
{
|
||||
top: 60px; left: 195px;
|
||||
width: 210px; height: 216px;
|
||||
background-image: url('media/background/endconsole.png');
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.gameEndOutputVisible
|
||||
{
|
||||
font-family: VT323;
|
||||
font-size:20px;
|
||||
position: absolute;
|
||||
color: #008800;
|
||||
line-height: 100%;
|
||||
z-index: 2;
|
||||
top: 60px; left: 195px;
|
||||
width: 210px; height: 216px;
|
||||
background-image: url('media/background/endconsole.png');
|
||||
}
|
249
tetris/tetris.js
Normal file
@ -0,0 +1,249 @@
|
||||
|
||||
FIELD_OFFSET_X = 180;
|
||||
FIELD_OFFSET_Y = 12;
|
||||
|
||||
function TetrisControl() {
|
||||
var tetris = new Tetris(this);
|
||||
|
||||
this.setup = function () {
|
||||
tetris.setup();
|
||||
};
|
||||
this.update = function () {
|
||||
tetris.update();
|
||||
};
|
||||
this.draw = function () {
|
||||
tetris.draw();
|
||||
};
|
||||
|
||||
this.restart = function() {
|
||||
// create a new Tetris object
|
||||
tetris = new Tetris(this);
|
||||
|
||||
// emulate an initial setup condition and the first loop
|
||||
tetris.setup();
|
||||
tetris.update();
|
||||
};
|
||||
}
|
||||
|
||||
function Tetris(controller) {
|
||||
var background = null,
|
||||
game = null,
|
||||
timeOffset = 0,
|
||||
|
||||
lastEscapeState = false,
|
||||
startPauseTime = 0,
|
||||
paused = false,
|
||||
lastPaused = false,
|
||||
|
||||
gameOver = false,
|
||||
|
||||
mouseClick = null,
|
||||
|
||||
self = this,
|
||||
|
||||
continueButton = null,
|
||||
restartButton = null,
|
||||
|
||||
lastTime = null,
|
||||
dTime = null,
|
||||
|
||||
gameEndTty = new TtyBlock('gameEndDiv', 10, 20, 1);
|
||||
|
||||
|
||||
this.setup = function () {
|
||||
// find the keys to stop
|
||||
var stoppedKeys = [],
|
||||
curAction, i;
|
||||
for (curAction in inputAssignments) {
|
||||
stoppedKeys = stoppedKeys.concat(inputAssignments[curAction]);
|
||||
}
|
||||
jaws.preventDefaultKeys(stoppedKeys);
|
||||
|
||||
|
||||
Tetris.currentInstance = self;
|
||||
game = new Game(inputAssignments, autoRepeatConfig, thresholdConfig);
|
||||
|
||||
continueButton = new Button({image: 'media/buttons/continue.png', x: 250, y: 150});
|
||||
restartButton = new Button({image: 'media/buttons/restart.png', x: 250, y: 200});
|
||||
|
||||
background = new Background();
|
||||
|
||||
timeOffset = (new Date()).getTime();
|
||||
};
|
||||
|
||||
this.update = function() {
|
||||
var realTime = (new Date()).getTime(),
|
||||
escapePressed = jaws.pressed('esc'),
|
||||
scoreObject;
|
||||
|
||||
if (lastTime === null) {
|
||||
dTime = 0;
|
||||
lastTime = realTime;
|
||||
} else {
|
||||
dTime = realTime - lastTime;
|
||||
lastTime = realTime;
|
||||
}
|
||||
|
||||
if (!paused && !gameOver) {
|
||||
// see if the game should be pased
|
||||
if (escapePressed && (!lastEscapeState)) {
|
||||
// go into pause mode
|
||||
startPauseTime = realTime;
|
||||
paused = true;
|
||||
} else {
|
||||
game.update(realTime - timeOffset);
|
||||
// see if the game is over
|
||||
scoreObject = game.getResults();
|
||||
if (scoreObject) {
|
||||
gameOver = true;
|
||||
|
||||
// make the game end visible
|
||||
document.getElementById('gameEndContainer').setAttribute('class', 'gameEndOutputVisible');
|
||||
gameEndTty.addLine('GOOD GAME!!!');
|
||||
gameEndTty.addLine('');
|
||||
gameEndTty.addLine('');
|
||||
if (scoreObject.won) {
|
||||
gameEndTty.addLine('You Win!');
|
||||
} else {
|
||||
gameEndTty.addLine('Better Luck Next Time');
|
||||
}
|
||||
gameEndTty.addLine('');
|
||||
gameEndTty.addLine('');
|
||||
|
||||
/*
|
||||
gameEndTty.addLine('Re-directing you to');
|
||||
gameEndTty.addLine('the score screen...');
|
||||
*/
|
||||
|
||||
gameEndTty.addLine('Your score was:');
|
||||
gameEndTty.addLine(scoreObject.score.toString());
|
||||
gameEndTty.addLine('');
|
||||
gameEndTty.addLine('');
|
||||
|
||||
//sendScoreRequest(scoreObject.score);
|
||||
|
||||
window.setTimeout(function() {
|
||||
document.getElementById('gameEndContainer').setAttribute('class', 'gameEndOutputHidden');
|
||||
controller.restart();
|
||||
}, 6000);
|
||||
}
|
||||
}
|
||||
} else if (paused) {
|
||||
// see if the escape key was hit
|
||||
if (escapePressed && (!lastEscapeState)) {
|
||||
// change the time offset
|
||||
timeOffset += realTime - startPauseTime;
|
||||
paused = false;
|
||||
}
|
||||
// see if any buttons were pressed
|
||||
if (mouseClick) {
|
||||
if (continueButton.isClicked(mouseClick.x, mouseClick.y)) {
|
||||
// change the time offset
|
||||
timeOffset += realTime - startPauseTime;
|
||||
paused = false;
|
||||
}
|
||||
if (restartButton.isClicked(mouseClick.x, mouseClick.y)) {
|
||||
// restart the game
|
||||
controller.restart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: nothing???
|
||||
}
|
||||
|
||||
lastEscapeState = escapePressed;
|
||||
mouseClick = null;
|
||||
};
|
||||
|
||||
this.draw = function() {
|
||||
|
||||
if (!paused && !gameOver) {
|
||||
|
||||
// draw the game
|
||||
background.draw(lastPaused);
|
||||
if (lastPaused) {
|
||||
lastPaused = false;
|
||||
Block.invalidateAll();
|
||||
}
|
||||
game.draw(dTime);
|
||||
Block.invalidFlushed();
|
||||
|
||||
} else if (paused) {
|
||||
// draw the game
|
||||
background.draw();
|
||||
game.draw(dTime);
|
||||
|
||||
//draw the pause menu
|
||||
continueButton.draw();
|
||||
restartButton.draw();
|
||||
lastPaused = true;
|
||||
} else {
|
||||
// continue to draw the game for game over
|
||||
// draw the game
|
||||
background.draw();
|
||||
game.draw(dTime);
|
||||
}
|
||||
|
||||
gameEndTty.draw(dTime);
|
||||
};
|
||||
|
||||
this.mouseClicked = function(x, y) {
|
||||
mouseClick = {x: x, y: y};
|
||||
};
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
loadGameControls();
|
||||
|
||||
jaws.assets.add('media/blueblock.png');
|
||||
jaws.assets.add('media/cyanblock.png');
|
||||
jaws.assets.add('media/greenblock.png');
|
||||
jaws.assets.add('media/orangeblock.png');
|
||||
jaws.assets.add('media/purpleblock.png');
|
||||
jaws.assets.add('media/redblock.png');
|
||||
jaws.assets.add('media/yellowblock.png');
|
||||
|
||||
jaws.assets.add('media/greyblock.png');
|
||||
jaws.assets.add('media/emptyblock.png');
|
||||
|
||||
jaws.assets.add('media/buttons/continue.png');
|
||||
jaws.assets.add('media/buttons/restart.png');
|
||||
|
||||
jaws.assets.add('media/background/backdrop.png');
|
||||
jaws.assets.add('media/background/topbar.png');
|
||||
|
||||
jaws.start(TetrisControl);
|
||||
};
|
||||
|
||||
var redirCode;
|
||||
|
||||
function redirectToScore() {
|
||||
window.location.replace('/scoreScreen.html?tempRef=' + redirCode);
|
||||
}
|
||||
|
||||
function sendScoreRequest(score) {
|
||||
var xmlhttp;
|
||||
if (window.XMLHttpRequest)
|
||||
{// code for IE7+, Firefox, Chrome, Opera, Safari
|
||||
xmlhttp=new XMLHttpRequest();
|
||||
}
|
||||
else
|
||||
{// code for IE6, IE5
|
||||
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
|
||||
}
|
||||
xmlhttp.onreadystatechange=function()
|
||||
{
|
||||
if (xmlhttp.readyState==4 && xmlhttp.status==200)
|
||||
{
|
||||
redirCode = xmlhttp.responseText;
|
||||
|
||||
setTimeout('redirectToScore();', 4000);
|
||||
}
|
||||
}
|
||||
|
||||
// World's 3rd most piss-poor obfustication technique
|
||||
// A serious real-time/replay game monitor is needed
|
||||
xmlhttp.open("POST", "/score/reportScore?gthbyu="+(score*17), true);
|
||||
xmlhttp.send();
|
||||
}
|