// TODO:
// * touch controls
// * allow late piece rotation
// * code cleanup
//--------------------------------------------------//
// PAGE OBJECT & LOGIC //
//--------------------------------------------------//
var Page = {
IsSetup: false,
body: document.getElementsByTagName("body")[0],
cvs: document.createElement("canvas"),
ctx: 0,
unitSize: 0,
AreaArr: [],
// calculates the unit size, canvas bounds, and canvas positioning
WindowChanged: function () {
// Calulcate the unitSize based on window width and height.
// The minimum of these calculations will be used.
var bodyW = document.documentElement.clientWidth,
bodyH = document.documentElement.clientHeight,
newUnitW = (bodyW - (bodyW % 80)) / 16,
newUnitH = (bodyH - (bodyH % 100)) / 20,
newUnitMin = Math.max(Math.min(newUnitW, newUnitH), 20);
// if the calcUnitMin != unitSize, update unitSize, recalculate
// all DrawAreaObjs, and update the canvas element bounds
this.unitSize = newUnitMin;
// store Right-most & Bottom-most points for canvas bounds
var rightLimit = 0,
bottomLimit = 0;
for (var i = 0; i < Page.AreaArr.length; i++) {
Page.AreaArr[i].CalculateBounds();
var newRightLimit = Page.AreaArr[i].left + Page.AreaArr[i].W,
newBottomLimit = Page.AreaArr[i].top + Page.AreaArr[i].H;
rightLimit = Math.max(newRightLimit, rightLimit);
bottomLimit = Math.max(newBottomLimit, bottomLimit);
}
this.cvs.width = rightLimit;
this.cvs.height = bottomLimit;
// left pos uses Game.W because ideally that area is centered
var topPos = (bodyH - bottomLimit) / 2,
leftPos = bodyW / 2 - this.Game.W / 2,
rightOffset = bodyW - (leftPos + rightLimit) - this.unitSize * 0.5;
// if default canvas positioning extends beyond screen, adjust it
if (rightOffset < 0) {
leftPos = Math.max(this.unitSize * 0.5, leftPos + rightOffset);
}
this.cvs.style.left = leftPos + "px";
this.cvs.style.top = topPos + "px";
},
// performs the page setup
Initialize: function () {
// if page has not been setup, do initial setup
if (this.IsSetup === false) {
document.body.appendChild(Page.cvs);
this.body.style.overflow = "hidden";
this.body.style.backgroundColor = "rgb(19,21,25)";
this.cvs.style.position = "absolute";
this.ctx = this.cvs.getContext("2d");
this.IsSetup = true;
}
this.WindowChanged();
// dirty all draw areas
for (var i = 0; i < Page.AreaArr.length; i++) {
Page.AreaArr[i].IsDirty = true;
}
},
// redraws canvas visuals whenever the page is marked as dirty
Update: function () {
for (var i = 0; i < Page.AreaArr.length; i++) {
if (Page.AreaArr[i].IsDirty) {
Page.AreaArr[i].Draw();
Page.AreaArr[i].IsDirty = false;
}
}
}
};
// Definition for Area objects. Bounds are in UNITS
function DrawAreaObj(Left, Top, Width, Height, DrawFunction) {
// bounds in UNITS
this.leftBase = Left;
this.topBase = Top;
this.widthBase = Width;
this.heightBase = Height;
// bounds in PIXELS
this.left = 0;
this.top = 0;
this.W = 0;
this.H = 0;
// dirty flag (clean yourself up flag, you're better than that)
this.IsDirty = false;
// bounds recalculated and area dirtied when unitSize changes
this.CalculateBounds = function () {
this.left = this.leftBase * Page.unitSize;
this.top = this.topBase * Page.unitSize;
this.W = this.widthBase * Page.unitSize;
this.H = this.heightBase * Page.unitSize;
this.IsDirty = true;
};
// draw function as passed in by the callee
this.Draw = DrawFunction;
// push this area into the area arr
Page.AreaArr.push(this);
}
Page.Game = new DrawAreaObj(0, 0, 10, 20, function () {
// unitSize minus a couple pixels of separation
var uDrawSize = Page.unitSize - 2,
drawL,
drawT;
// redraws the background elements for game area
Page.ctx.fillStyle = "rgb(28,30,34)";
Page.ctx.fillRect(this.left, this.top, this.W, this.H);
// draw the static unit blocks
for (var i = 0; i < GM.StaticUnits.length; i++) {
for (var j = 0; j < GM.StaticUnits[i].length; j++) {
// get the unit value for this index pair
var uValue = GM.StaticUnits[i][j];
// if this unit value is not 0, draw the unit
if (uValue !== 0) {
drawL = i * Page.unitSize + 1;
drawT = j * Page.unitSize + 1;
// fill this square with color based on player alive status
Page.ctx.fillStyle = GM.IsAlive ? uValue : "rgb(34,36,42)";
Page.ctx.fillRect(drawL, drawT, uDrawSize, uDrawSize);
}
}
}
// draw the current active projection and piece (if exists)
if (GM.Pc.Cur !== 0 && GM.IsAlive) {
var projColor = ColorWithAlpha(GM.Pc.Cur.color, 0.1);
for (var k = 0; k < GM.Pc.Cur.UO.arr.length; k++) {
drawL = (GM.Pc.Cur.x + GM.Pc.Cur.UO.arr[k].x) * Page.unitSize + 1;
drawT = (GM.Pc.Cur.y + GM.Pc.Cur.UO.arr[k].y) * Page.unitSize + 1;
Page.ctx.fillStyle = GM.Pc.Cur.color;
Page.ctx.fillRect(drawL, drawT, uDrawSize, uDrawSize);
// also draw the projection (if one exists)
if (GM.IsAlive && GM.Pc.ProjY !== 0) {
drawT += GM.Pc.ProjY * Page.unitSize;
Page.ctx.fillStyle = projColor;
Page.ctx.fillRect(drawL, drawT, uDrawSize, uDrawSize);
}
}
}
// if the player is dead, draw the game over text
if (!GM.IsAlive) {
DrawText(
"GAME OVER",
"rgb(255,255,255)",
"500",
"center",
uDrawSize,
this.W / 2,
this.H / 4
);
}
});
Page.UpcomingA = new DrawAreaObj(10.5, 2.6, 2.5, 2.5, function () {
var uDrawSize = Math.floor(Page.unitSize / 2),
pcA = GM.Pc.Upcoming[0];
// next box background
Page.ctx.fillStyle = "rgb(28,30,34)";
Page.ctx.fillRect(this.left, this.top, this.W, this.H);
// draw the upcoming piece (if one exists)
if (pcA !== 0) {
Page.ctx.fillStyle = pcA.color;
var totalL = 0,
totalT = 0,
countedL = [],
countedT = [];
// calculate average positions of units in order to center
for (var i = 0; i < pcA.UO.arr.length; i++) {
var curX = pcA.UO.arr[i].x,
curY = pcA.UO.arr[i].y;
if (countedL.indexOf(curX) < 0) {
countedL.push(curX);
totalL += curX;
}
if (countedT.indexOf(curY) < 0) {
countedT.push(curY);
totalT += curY;
}
}
var avgL = uDrawSize * (totalL / countedL.length + 0.5),
avgT = uDrawSize * (totalT / countedT.length + 0.5),
offsetL = this.left + this.W / 2,
offsetT = this.top + this.H / 2;
console.log(avgL + ", " + avgT);
// now draw the upcoming piece, using avg vars to center
for (var j = 0; j < pcA.UO.arr.length; j++) {
var drawL = Math.floor(offsetL - avgL + pcA.UO.arr[j].x * uDrawSize),
drawT = Math.floor(offsetT - avgT + pcA.UO.arr[j].y * uDrawSize);
Page.ctx.fillRect(drawL, drawT, uDrawSize - 1, uDrawSize - 1);
}
}
});
Page.UpcomingB = new DrawAreaObj(10.5, 5.2, 2.5, 2.5, function () {
var uDrawSize = Math.floor(Page.unitSize / 2),
pcB = GM.Pc.Upcoming[1];
// next box background
Page.ctx.fillStyle = "rgb(28,30,34)";
Page.ctx.fillRect(this.left, this.top, this.W, this.H);
// draw the upcoming piece (if one exists)
if (pcB !== 0) {
Page.ctx.fillStyle = pcB.color;
var totalL = 0,
totalT = 0,
countedL = [],
countedT = [];
// calculate average positions of units in order to center
for (var i = 0; i < pcB.UO.arr.length; i++) {
var curX = pcB.UO.arr[i].x,
curY = pcB.UO.arr[i].y;
if (countedL.indexOf(curX) < 0) {
countedL.push(curX);
totalL += curX;
}
if (countedT.indexOf(curY) < 0) {
countedT.push(curY);
totalT += curY;
}
}
var avgL = uDrawSize * (totalL / countedL.length + 0.5),
avgT = uDrawSize * (totalT / countedT.length + 0.5),
offsetL = this.left + this.W / 2,
offsetT = this.top + this.H / 2;
console.log(avgL + ", " + avgT);
// now draw the upcoming piece, using avg vars to center
for (var j = 0; j < pcB.UO.arr.length; j++) {
var drawL = Math.floor(offsetL - avgL + pcB.UO.arr[j].x * uDrawSize),
drawT = Math.floor(offsetT - avgT + pcB.UO.arr[j].y * uDrawSize);
Page.ctx.fillRect(drawL, drawT, uDrawSize - 1, uDrawSize - 1);
}
}
});
Page.UpcomingC = new DrawAreaObj(10.5, 7.8, 2.5, 2.5, function () {
var uDrawSize = Math.floor(Page.unitSize / 2),
pcC = GM.Pc.Upcoming[2];
// next box background
Page.ctx.fillStyle = "rgb(28,30,34)";
Page.ctx.fillRect(this.left, this.top, this.W, this.H);
// draw the upcoming piece (if one exists)
if (pcC !== 0) {
Page.ctx.fillStyle = pcC.color;
var totalL = 0,
totalT = 0,
countedL = [],
countedT = [];
// calculate average positions of units in order to center
for (var i = 0; i < pcC.UO.arr.length; i++) {
var curX = pcC.UO.arr[i].x,
curY = pcC.UO.arr[i].y;
if (countedL.indexOf(curX) < 0) {
countedL.push(curX);
totalL += curX;
}
if (countedT.indexOf(curY) < 0) {
countedT.push(curY);
totalT += curY;
}
}
var avgL = uDrawSize * (totalL / countedL.length + 0.5),
avgT = uDrawSize * (totalT / countedT.length + 0.5),
offsetL = this.left + this.W / 2,
offsetT = this.top + this.H / 2;
console.log(avgL + ", " + avgT);
// now draw the upcoming piece, using avg vars to center
for (var j = 0; j < pcC.UO.arr.length; j++) {
var drawL = Math.floor(offsetL - avgL + pcC.UO.arr[j].x * uDrawSize),
drawT = Math.floor(offsetT - avgT + pcC.UO.arr[j].y * uDrawSize);
Page.ctx.fillRect(drawL, drawT, uDrawSize - 1, uDrawSize - 1);
}
}
});
Page.ScoreBarHigh = new DrawAreaObj(10.5, 0, 4.5, 1, function () {
// draw the score area back bar
Page.ctx.fillStyle = "rgb(28,30,34)";
Page.ctx.fillRect(this.left, this.top, this.W, this.H);
// Draw the trophy symbol
var miniUnit, left, top, width, height;
miniUnit = Page.unitSize * 0.01;
Page.ctx.fillStyle = "rgb(255,232,96)";
// trophy base
left = Math.floor(this.left + miniUnit * 33);
top = Math.floor(this.top + this.H - miniUnit * 28);
width = Math.floor(miniUnit * 30);
height = Math.floor(miniUnit * 12);
Page.ctx.fillRect(left, top, width, height);
// trophy trunk
left = Math.floor(this.left + miniUnit * 42);
top = Math.floor(this.top + this.H - miniUnit * 50);
width = Math.floor(miniUnit * 12);
height = Math.floor(miniUnit * 32);
Page.ctx.fillRect(left, top, width, height);
// trophy bowl
left = Math.floor(this.left + miniUnit * 48);
top = Math.floor(this.top + this.H - miniUnit * 68);
Page.ctx.arc(left, top, miniUnit * 24, 0, Math.PI);
Page.ctx.fill();
// draw the player's current score
text = ("00000000" + GM.ScoreHigh).slice(-7);
left = this.left + this.W - 4;
top = this.top + Page.unitSize * 0.8;
size = Math.floor(Page.unitSize * 0.8) + 0.5;
DrawText(text, "rgb(255,232,96)", "500", "right", size, left, top);
});
Page.ScoreBarCur = new DrawAreaObj(10.5, 1.1, 4.5, 1, function () {
// draw the score area back bar
Page.ctx.fillStyle = "rgb(28,30,34)";
Page.ctx.fillRect(this.left, this.top, this.W, this.H);
// draw the player's current level
var text, left, top, size, miniUnit;
miniUnit = Page.unitSize * 0.01;
text = ("00" + GM.Level).slice(-2);
left = this.left + Math.floor(miniUnit * 50);
top = this.top + Page.unitSize * 0.8;
size = Math.floor(Page.unitSize * 0.5);
DrawText(text, "rgb(128,128,128)", "900", "center", size, left, top);
// draw the player's current score
text = ("00000000" + GM.ScoreCur).slice(-7);
left = this.left + this.W - 4;
top = this.top + Page.unitSize * 0.8;
size = Math.floor(Page.unitSize * 0.8) + 0.5;
DrawText(text, "rgb(255,255,255)", "500", "right", size, left, top);
});
//--------------------------------------------------//
// GAME MANAGER OBJECT & LOGIC //
//--------------------------------------------------//
var GM = {
//-- VARS ---------*/
// timers
TimeCur: 0,
TimeEvent: 0,
TickRate: 0,
// player status & score
IsAlive: 0,
Level: 0,
PiecesRemaining: 0,
// score count and current piece score modifiers
ScoreHigh: 0,
ScoreCur: 0,
ScoreBonus: 0,
DifficultFlag: 0,
// array of grid squares
StaticUnits: [],
/*-- FCNS ---------*/
// Set up intial game var values
Initialize: function () {
// reset current piece vars
this.Pc.Next = this.Pc.Cur = this.Pc.ProjY = 0;
// populate the GM's static unit array with 0's (empty)
for (var i = 0; i < 10; i++) {
this.StaticUnits[i] = [];
for (var j = 0; j < 20; j++) {
this.StaticUnits[i][j] = 0;
}
}
// reset timer
this.TimeCur = this.TimeEvent = 0;
this.TickRate = 500;
// set up level values for level 1
this.PiecesRemaining = 10;
this.Level = 1;
// reset the score and set player to alive
this.ScoreCur = 0;
this.IsAlive = true;
},
// updates time each frame and executing logic if a tick has passed
Update: function () {
this.TimeCur = new Date().getTime();
if (this.TimeCur >= this.TimeEvent) {
if (GM.Pc.Cur === 0 && this.IsAlive) {
this.Pc.Generate();
} else {
this.Pc.DoGravity();
this.Pc.ProjY = this.Pc.TryProject();
Page.Game.IsDirty = true;
}
this.RefreshTimer();
}
},
// reset the tick timer (generates a new TimeEvent target)
RefreshTimer: function () {
this.TimeEvent = new Date().getTime() + this.TickRate;
},
// called when a piece is spawned, advances level if needed
PieceSpawned: function () {
this.PiecesRemaining--;
if (this.PiecesRemaining <= 0) {
this.AdvanceLevel();
}
},
// advance level, recalculate TickRate, reset pieces remaining
AdvanceLevel: function () {
this.Level++;
this.TickRate = Math.floor(555 * Math.exp(this.Level / -10));
this.PiecesRemaining = Math.floor(5000 / this.TickRate);
Page.ScoreBarCur.IsDirty = true;
},
// check specified rows to see if any can be cleared
CheckUnits: function (checkRowsRaw) {
var scoreMult = 0,
pieceScore = 0,
checkRows = [];
// add the scoreBonus for dropping
if (this.ScoreBonus > 0) {
pieceScore += this.ScoreBonus;
}
// sort the rows
for (var a = 0; a < 20; a++) {
if (checkRowsRaw.indexOf(a) >= 0) {
checkRows.push(a);
}
}
for (var i = 0; i < checkRows.length; i++) {
var hasGap = false,
checkIndex = checkRows[i];
for (var j = 0; j < GM.StaticUnits.length; j++) {
if (GM.StaticUnits[j][checkIndex] === 0) {
hasGap = true;
break;
}
}
if (hasGap === false) {
for (var k = 0; k < GM.StaticUnits.length; k++) {
GM.StaticUnits[k].splice(checkIndex, 1);
GM.StaticUnits[k].unshift(0);
}
pieceScore += 100 + 200 * scoreMult;
if (scoreMult > 2) {
pieceScore += 100;
}
scoreMult++;
}
}
if (this.DifficultFlag === 1) {
pieceScore = Math.floor(pieceScore * 1.5);
this.DifficultFlag = 0;
}
if (pieceScore > 0) {
this.ScoreCur += pieceScore;
Page.ScoreBarCur.IsDirty = true;
this.ScoreBonus = 0;
if (scoreMult > 3) {
this.DifficultFlag = 1;
}
}
},
GameOver: function () {
Page.Game.IsDirty = Page.ScoreBarCur.IsDirty = true;
if (this.ScoreCur > this.ScoreHigh) {
this.ScoreHigh = this.ScoreCur;
Page.ScoreBarHigh.IsDirty = true;
console.log(this.ScoreHigh);
}
this.IsAlive = false;
}
};
//--------------------------------------------------//
// PIECE OBJECT BUILDER //
//--------------------------------------------------//
// PcObj is used to create new piece object instances based on the
// passed in parameters. PcObj is called by predefined templates
GM.PcObj = function (color, rotCount, units) {
this.x = 5;
this.y = 0;
this.color = color;
this.UO = {};
// rotate this piece by advancing to next unit obj of linked list
this.Rotate = function () {
this.UO = this.UO.nextUO;
};
// set up the piece unit object linked list to define rotations
this.SetUO = function (rotCount, units) {
var linkedListUO = [];
linkedListUO[0] = { nextUO: 0, arr: [] };
linkedListUO[0].arr = units;
for (var i = 0; i < rotCount; i++) {
var nextI = i + 1 < rotCount ? i + 1 : 0;
linkedListUO[i] = { nextUO: 0, arr: [] };
if (i > 0) {
linkedListUO[i - 1].nextUO = linkedListUO[i];
}
for (var j = 0; j < units.length; j++) {
var unX, unY;
if (i === 0) {
unX = units[j].x;
unY = units[j].y;
} else {
unX = linkedListUO[i - 1].arr[j].y * -1;
unY = linkedListUO[i - 1].arr[j].x;
}
linkedListUO[i].arr[j] = { x: unX, y: unY };
}
}
linkedListUO[rotCount - 1].nextUO = linkedListUO[0];
this.UO = linkedListUO[0];
};
this.SetUO(rotCount, units);
};
//--------------------------------------------------//
// PIECE TYPE TEMPLATES //
//--------------------------------------------------//
// Templates create a new piece object instance based on
// their color, rotation count, and unit block definitions.
// O - Square piece definition
GM.O = function () {
return new GM.PcObj("rgb(255,232,51)", 1, [
{ x: -1, y: 0 },
{ x: 0, y: 0 },
{ x: -1, y: 1 },
{ x: 0, y: 1 }
]);
};
// I - Line piece definition
GM.I = function () {
return new GM.PcObj("rgb(51,255,209)", 2, [
{ x: -2, y: 0 },
{ x: -1, y: 0 },
{ x: 0, y: 0 },
{ x: 1, y: 0 }
]);
};
// S - Right facing zigzag piece definition
GM.S = function () {
return new GM.PcObj("rgb(106,255,51)", 2, [
{ x: 0, y: 0 },
{ x: 1, y: 0 },
{ x: -1, y: 1 },
{ x: 0, y: 1 }
]);
};
// Z - Left facing zigzag piece definition
GM.Z = function () {
return new GM.PcObj("rgb(255,51,83)", 2, [
{ x: -1, y: 0 },
{ x: 0, y: 0 },
{ x: 0, y: 1 },
{ x: 1, y: 1 }
]);
};
// L - Right facing angle piece definition
GM.L = function () {
return new GM.PcObj("rgb(255,129,51)", 4, [
{ x: -1, y: 0 },
{ x: 0, y: 0 },
{ x: 1, y: 0 },
{ x: -1, y: -1 }
]);
};
// J - Left facing angle piece definition
GM.J = function () {
return new GM.PcObj("rgb(64,100,255)", 4, [
{ x: -1, y: 0 },
{ x: 0, y: 0 },
{ x: 1, y: 0 },
{ x: 1, y: -1 }
]);
};
// T - Hat shaped piece definition
GM.T = function () {
return new GM.PcObj("rgb(160,62,255)", 4, [
{ x: -1, y: 0 },
{ x: 0, y: 0 },
{ x: 1, y: 0 },
{ x: 0, y: -1 }
]);
};
//--------------------------------------------------//
// ACTIVE PIECE CONTROLLER //
//--------------------------------------------------//
// Controls the generation, movement, and placement of piece
// objects. Monitors the current piece and upcoming piece
GM.Pc = {
//-- VARS ---------*/
// current piece, projected Y pos of cur piece
Cur: 0,
ProjY: 0,
// upcoming pieces
Upcoming: [0, 0, 0],
//-- FCNS ---------*/
// push upcoming piece to current & randomize new upcoming piece
Generate: function () {
// push upcoming piece to current and push down other upcomings
this.Cur = this.Upcoming[0];
this.Upcoming[0] = this.Upcoming[1];
this.Upcoming[1] = this.Upcoming[2];
// check if the player lost
if (this.Cur !== 0) {
var spawnCollisions = this.CheckCollisions(0, 0, 0);
if (spawnCollisions > 0) {
GM.GameOver();
this.Freeze();
}
}
// if player is alive, generate random upcoming piece
if (GM.IsAlive !== 0) {
var randInt = Math.floor(Math.random() * 7);
switch (randInt) {
case 0:
this.Upcoming[2] = GM.O();
break;
case 1:
this.Upcoming[2] = GM.I();
break;
case 2:
this.Upcoming[2] = GM.S();
break;
case 3:
this.Upcoming[2] = GM.Z();
break;
case 4:
this.Upcoming[2] = GM.L();
break;
case 5:
this.Upcoming[2] = GM.J();
break;
case 6:
this.Upcoming[2] = GM.T();
break;
default:
break;
}
// if a current piece was set, inform the GM
if (this.Cur !== 0) {
GM.PieceSpawned();
Page.Game.IsDirty = true;
}
Page.UpcomingA.IsDirty = Page.UpcomingB.IsDirty = Page.UpcomingC.IsDirty = true;
}
},
// freeze the current piece's position and rotation
Freeze: function () {
if (GM.IsAlive) {
var affectedRows = [];
for (var i = 0; i < this.Cur.UO.arr.length; i++) {
var staticX = this.Cur.x + this.Cur.UO.arr[i].x,
staticY = this.Cur.y + this.Cur.UO.arr[i].y;
if (staticY >= 0 && staticY <= GM.StaticUnits[0].length) {
GM.StaticUnits[staticX][staticY] = this.Cur.color;
}
if (affectedRows.indexOf(staticY) < 0) {
affectedRows.push(staticY);
}
}
GM.CheckUnits(affectedRows);
this.Generate();
}
},
// apply gravity to the current piece, checking for collisions
DoGravity: function () {
if (this.Cur !== 0) {
var collisions = this.CheckCollisions(0, 0, 1);
if (collisions === 0) {
this.Cur.y++;
} else {
this.Freeze();
}
}
GM.RefreshTimer();
},
// attempt to rotate the current piece, returns bool
TryRotate: function () {
if (this.Cur !== 0) {
var collisions = this.CheckCollisions(1, 0, 0);
if (collisions === 0) {
this.Cur.Rotate();
return true;
}
}
return false;
},
// attempt to move current piece base on given XY, returns bool
TryMove: function (moveX, moveY) {
if (this.Cur !== 0) {
var collisions = this.CheckCollisions(0, moveX, moveY);
if (collisions === 0) {
this.Cur.x += moveX;
this.Cur.y += moveY;
if (moveY > 0) {
GM.RefreshTimer();
GM.ScoreBonus++;
}
return true;
}
}
return false;
},
// attempt to drop the current piece until it collides, returns bool
TryDrop: function () {
var squaresDropped = 0;
if (this.Cur !== 0) {
while (this.TryMove(0, 1) === true && squaresDropped < 22) {
squaresDropped++;
}
}
if (squaresDropped > 0) {
GM.ScoreBonus += 2 * squaresDropped;
this.Freeze();
return true;
} else {
return false;
}
},
// attempt to find (and return) projected drop point of current piece
TryProject: function () {
var squaresDropped = 0;
if (this.Cur !== 0) {
while (
this.CheckCollisions(0, 0, squaresDropped) === 0 &&
squaresDropped < 22
) {
squaresDropped++;
}
}
return squaresDropped - 1;
},
// return collision count OR -1 if test piece out of bounds
CheckCollisions: function (doRot, offsetX, offsetY) {
var unitArr,
collisionCount = 0;
if (doRot === 1) {
unitArr = this.Cur.UO.nextUO.arr;
} else {
unitArr = this.Cur.UO.arr;
}
for (var i = 0; i < unitArr.length; i++) {
var testX = this.Cur.x + unitArr[i].x + offsetX,
testY = this.Cur.y + unitArr[i].y + offsetY,
limitX = GM.StaticUnits.length,
limitY = GM.StaticUnits[0].length;
if (testX < 0 || testX >= limitX || testY >= limitY) {
return -1;
} else if (testY > 0) {
if (GM.StaticUnits[testX][testY] !== 0) {
collisionCount++;
}
}
}
return collisionCount;
}
};
//--------------------------------------------------//
// EVENT LISTENERS //
//--------------------------------------------------//
// Event for keyboard calls the corresponding manipulation functions
// in GM.Pc based on user inputs. If manipulation is successful,
// the page is marked as dirty.
document.addEventListener(
"keydown",
function (evt) {
var key = event.keyCode || event.which;
if (GM.IsAlive) {
switch (key) {
// Up arrow OR W = rotate
case 38:
case 87:
Page.Game.IsDirty = GM.Pc.TryRotate();
break;
// Left arrow OR A = move left
case 37:
case 65:
Page.Game.IsDirty = GM.Pc.TryMove(-1, 0);
break;
// Right arrow OR D = move right
case 39:
case 68:
Page.Game.IsDirty = GM.Pc.TryMove(1, 0);
break;
// Down arrow OR S = move down
case 40:
case 83:
Page.Game.IsDirty = GM.Pc.TryMove(0, 1);
break;
// Spacebar to drop the current piece
case 32:
Page.Game.IsDirty = GM.Pc.TryDrop();
break;
default:
break;
}
//if board was dirtied, cast fresh projection for current piece
if (Page.Game.IsDirty) {
GM.Pc.ProjY = GM.Pc.TryProject();
}
}
// if player not alive, reset the game
else {
Init();
}
},
false
);
// Window resize event calls Page function to update the canvas
// size/position, area bounds within the canvas, and the unitSize
window.onresize = function (event) {
Page.WindowChanged();
};
//--------------------------------------------------//
// INITIALAZATION AND GAME LOOP //
//--------------------------------------------------//
// Called on page load / game reset, Init fcn initializes
// the Page and GM objects, then starts the main game loop.
function Init() {
// initialize the page object
Page.Initialize();
// initialize the GM object
GM.Initialize();
}
Init();
// Main game loop. Updates GM object to check if tick can be
// performed. Then, if the page is dirty, performs a Draw.
function Loop() {
// always update Page
Page.Update();
// only need to update GM if the player is alive
if (GM.IsAlive) {
GM.Update();
}
window.requestAnimationFrame(Loop);
}
Loop();
//--------------------------------------------------//
// HELPER FUNCTIONS //
//--------------------------------------------------//
function DrawText(text, color, weight, alignment, size, left, top) {
Page.ctx.font = weight + " " + size + 'px "Jura", sans-serif';
Page.ctx.textAlign = alignment;
Page.ctx.fillStyle = color;
Page.ctx.fillText(text, left, top);
}
function ColorWithAlpha(color, alpha) {
var retColor = "rgba" + color.substring(3, color.length - 1);
retColor += "," + alpha + ")";
return retColor;
}