Created
November 28, 2019 21:36
-
-
Save thaenor/493b5052ccc19e1f57b4f31d5da18e77 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: | |
// * touch controls | |
// * allow late piece rotation | |
// * code cleanup | |
//--------------------------------------------------// | |
// PAGE OBJECT & LOGIC // | |
//--------------------------------------------------// | |
var Page = { | |
IsSetup: false, | |
//body: document.getElementsByTagName('body')[0], | |
body: document.getElementById('montetris'), | |
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, | |
var bodyW = parseInt(document.getElementById('montetris').style.width, 10), | |
bodyH = parseInt(document.getElementById('montetris').style.height, 10), | |
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'; | |
//this.cvs.style.top = '10px'; | |
}, | |
// performs the page setup | |
Initialize: function(){ | |
// if page has not been setup, do initial setup | |
if (this.IsSetup === false){ | |
//document.body.appendChild(Page.cvs); | |
document.getElementById('montetris').appendChild(Page.cvs) | |
this.body.style.overflow = 'hidden'; | |
this.body.style.backgroundColor = 'rgb(57,53,54)'; | |
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(245,176,39)', 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(242,242,242)', 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(245,176,39)', 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,255,255)', 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(242,242,242)', 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(255,255,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(245,176,39)', 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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment