policychanges/src/main.js

865 lines
24 KiB
JavaScript

var utils = require('./utils.js');
var consts = require('./consts.js');
var shapes = require('./shapes.js');
var views = require('./views.js');
var canvas = require('./canvas.js');
var inputs = require('./input.js');
var openers = require('./openers.js');
// import * as utils from './utils.js';
// import * as consts from './const.js';
// import * as shapes from './shapes.js';
// import * as views from './views.js';
// import * as canvas from './canvas.js';
// import * as inputs from './input.js';
// import * as openers from './openers.js';
//import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r125/build/three.module.js';
/**
Init game matrix
*/
var initMatrix = function(rowCount, columnCount) {
var result = [];
for (var i = 0; i < rowCount; i++) {
var row = [];
result.push(row);
for (var j = 0; j < columnCount; j++) {
row.push(0);
}
}
return result;
};
/**
Clear game matrix
*/
var clearMatrix = function(matrix) {
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix[i].length; j++) {
matrix[i][j] = 0;
}
}
};
/**
Check all full rows in game matrix
return rows number array. eg: [18,19];
*/
var checkFullRows = function(matrix) {
var rowNumbers = [];
for (var i = 0; i < matrix.length; i++) {
var row = matrix[i];
var full = true;
for (var j = 0; j < row.length; j++) {
full = full && row[j] !== 0;
}
if (full) {
rowNumbers.push(i);
}
}
return rowNumbers;
};
/**
Remove one row from game matrix.
copy each previous row data to next row which row number less than row;
*/
var removeOneRow = function(matrix, row) {
var colCount = matrix[0].length;
for (var i = row; i >= 0; i--) {
for (var j = 0; j < colCount; j++) {
if (i > 0) {
matrix[i][j] = matrix[i - 1][j];
} else {
matrix[i][j] = 0;
}
}
}
};
/**
Remove rows from game matrix by row numbers.
*/
var removeRows = function(matrix, rows) {
for (var i in rows) {
removeOneRow(matrix, rows[i]);
}
};
/**
Check game data to determin wether the game is over
*/
var checkGameOver = function(matrix) {
var firstRow = matrix[0];
for (var i = 0; i < firstRow.length; i++) {
if (firstRow[i] !== 0) {
return true;
};
}
return false;
};
/**
Calculate the extra rewards add to the score
*/
var calcRewards = function(rows, tspinType) {
if(tspinType == 2)
rows*=2+1;
if (rows && rows.length > 1) {
return Math.pow(2, rows.length - 1) * 100;
}
return 0;
};
/**
Calculate game score
*/
var calcScore = function(rows) {
if (rows && rows.length) {
return rows.length * 100;
}
return 0;
};
/**
Calculate time interval by level, the higher the level,the faster shape moves
*/
var calcIntervalByLevel = function(level) {
return consts.DEFAULT_INTERVAL - (level - 1) * 60;
};
// Default max scene size
var defaults = {
maxHeight: 700,
maxWidth: 560
};
/**
Tetris main object definition
*/
function Tetris(id) {
this.id = id;
this.init();
}
Tetris.prototype = {
init: function(options) {
var cfg = this.config = utils.extend(options, defaults);
this.interval = consts.DEFAULT_INTERVAL;
views.init(this.id, cfg.maxWidth, cfg.maxHeight);
canvas.init(views.scene, views.preview, views.hold);
inputs.init();
this.createSettings();
// if true no openers. just random tetrinos
this.gameState = consts.DEFAULT_GAMESTATE;
this.isTimerOn = false;
this.currentOpener = 0;
this.matrix = initMatrix(consts.ROW_COUNT, consts.COLUMN_COUNT);
this.eventTimer = new Date();
this.debugTimer = new Date();
this.gamepadEnabled = false;
this.reset();
this._initEvents();
//this._fireShape();
// ewww
this._recurseGameState();
},
toggleTimer: function() {
document.getElementById("Timer").value = (this.isTimerOn = !this.isTimerOn) ? "Seconds:" : "Timer Off";
},
toggleGamepad: function(){
document.getElementById("enablegamepad").value = ((this.gamepadEnabled = !this.gamepadEnabled) ? "Disable Gamepad" : "Enable Gamepad");
},
// Gamestate 1
setFreePlay: function()
{
this.gameState = consts.GAMESTATES[0];
document.getElementById("Timer").value = "Timer Off";
document.getElementById("Time").value = "";
document.getElementById("besttime").value = "";
this.isTimerOn = false;
this.hintQueue = [];
this.shapeQueue = [];
this.hintMino = 0;
this._restartHandler();
this.currentOpener = 0;
},
// Gamestate 2
setCurrentOpener(opener)
{
document.getElementById("besttime").value = "";
this.gameState = consts.GAMESTATES[1];
this.currentOpener = opener;
this._restartHandler();
},
// Gamestate 3
setDoTest: function()
{
if(this.gameState != consts.GAMESTATES[1]) return;
// set game state to do test
this.gameState = consts.GAMESTATES[2];
this._restartHandler();
},
// Gamestate 4
setGameStateSequenceEditor: function()
{
document.getElementById("side").display = "none";
// change to editor gamestate
this.gameState = consts.GAMESTATES[3];
this.hintQueue = [];
this.shapeQueue = [];
this.hintMino = 0;
this._restartHandler();
this.currentOpener = 0;
this.pushHoldStack();
},
createSettings: function () {
var list = document.getElementById("settings");
var settings = inputs.settingsList;
settings.forEach(function(item) {
var option = document.createElement('option');
option.text = item;
option.id = item;
list.add(option);
});
},
updateSettingTextBox: function() {
// var setting = inputs.settingsList[document.getElementById("settings").selectedIndex-1];
// var doKeyToAlpha = inputs.keyboardShiftEvents.included(setting) || inputs.keyboardKeyEvents.included(setting);
// if(doKeyToAlpha)
// document.getElementById("setting_value").value = inputs.settingsMap.get(setting).fromCharCode();
// else
// document.getElementById("setting_value").value = inputs.settingsMap.get(setting);
document.getElementById("setting_value").value =
inputs.settingsMap.get(inputs.settingsList[document.getElementById("settings").selectedIndex-1]);
},
addOpener: function() {
var newOpener = document.createElement('li');
//<li style="font-size:12px;padding-left:1em";><a href="#" id="setMrTSpinsSTDreversedVar">Mr. T-Spin's STD (reversed) </a></li>
//newOpener.text = "New Sequence";
//newOpener.id = this.currentMinoInx;
newOpener.style="color:powderblue;text-decoration:underline;font-size:12px;padding-left:1em";
newOpener.appendChild(document.createTextNode("New Sequence: " + this.currentMinoInx));
document.getElementById("Openers").appendChild(newOpener);
// Print Sequence Data to console
// console.log("this.hintQueue = new Array(");
// this.shapeQueue.slice().reverse().forEach( function(shape, idx, arr) { console.log("shapes.getShape("+shape.nType()+ ((idx === arr.length-1) ? "));" : "),") ); } );
var shapes = [];
this.shapeQueue.slice().reverse().forEach( function(shape, idx) { shapes.push(shape.x); shapes.push(shape.y); shapes.push(shape.state); } );
// console.log("var hintDataList = [" + shapes.join(",") + "];");
// console.log("this.createHintQueue(hintDataList);");
var sequenceCode = [];
var shapeArrayStr = [];
this.shapeQueue.slice().reverse().forEach( function(shape, idx, arr) { shapeArrayStr += "shapes.getShape("+shape.nType() + ((idx === arr.length-1) ? "));" : "), ") } );
sequenceCode += "case :\n\tthis.shapeQueue = new Array(" + shapeArrayStr + "\nbreak;" + "\n\n";
sequenceCode += "case :\n\tthis.hintQueue = new Array(" + shapeArrayStr;
sequenceCode += "\n\nvar hintDataList = [" + shapes.join(",") + "];" + "\nthis.createHintQueue(hintDataList);" + "\nbreak;";
prompt("Generated Code:", sequenceCode);
//openers.addSequence(this.shapeQueue);
//this.setFreePlay();
//this.gameState = consts.GAMESTATES[1];
this.currentMinoInx = 0;
//this.setCurrentOpener(99999);//this.currentMinoInx);
clearMatrix(this.matrix);
this.currentOpener = 9999;
this.shapeQueue = [];
this.hintQueue = [];
//this._recurseGameState();
},
setSettings: function() {
var newVal = document.getElementById("setting_value").value;
var key = inputs.settingsList[document.getElementById("settings").selectedIndex-1];
utils.setCookie(key, newVal, 30);
inputs.settingsMap.set(key, newVal);
},
//Reset game
reset: function() {
this.numlefts = 0;
this.running = false;
this.isGameOver = false;
this.level = 1;
this.score = 0;
this.lines = 0;
// beginning of frame
this.startTime = new Date().getTime();
this.currentTime = this.startTime;
this.prevTime = this.startTime;
this.sequencePrevTime = this.startTime;
//todo:get rid of extra
this.levelTime = this.startTime;
this.prevInputTime = this.startTime;
// current tetrino index gets set to 0 at the end of opener sequence
this.currentMinoInx = 0;
this.shapeQueue = [];
this.hintQueue = [];
this.holdStack = [];
this.shape = shapes.getShape(0);
// gets set to false after mino has been popped from hold stack; set back to true on mino dropped
this.canPopFromHoldStack = false;
// manipulation counter for srs extended piece lockdown
this.manipulationCounter = 0;
// timer for srs extened piece lockdown
this.lockdownTimer = 0;
this.landed = false;
this.isSequenceCompleted = false;
clearMatrix(this.matrix);
views.setLevel(this.level);
views.setScore(this.score);
views.setGameOver(this.gameState == consts.GAMESTATES[0] && this.isGameOver);
openers.reset();
shapes.resetMinoRNG();
this._draw();
},
//Start game
start: function() {
this.running = true;
window.requestAnimationFrame(utils.proxy(this._refresh, this));
},
//Pause game
pause: function() {
this.running = false;
this.currentTime = new Date().getTime();
this.prevTime = this.currentTime;
},
pushHoldStack: function()
{
if(this.gameState == consts.GAMESTATES[3]) {
while(this.holdStack.length < 7)
this.holdStack.unshift(utils.deepClone(shapes.getShape(this.currentMinoInx++%7)));
this.shape = this.holdStack.pop();
this._draw();
return;
}
// 1 shape hold queue
if(this.holdStack.length > 0) {
this.canPopFromHoldStack = false;
this.shapeQueue.unshift(utils.deepClone(this.shape));
this.shape = utils.deepClone(this.holdStack.pop());
this.shape.resetOrigin();
this._draw();
}else if(this.holdStack.length < 4) {
this.holdStack.push(utils.deepClone(this.shape));
this.shape = this.shapeQueue.shift();
this.canPopFromHoldStack = false;
this.shape.resetOrigin();
this._draw();
}
/* 4 shape hold queue
if(this.holdStack.length < 4) {
this.holdStack.push(utils.deepClone(this.shape));
this.shape = this.shapeQueue.shift();
this.canPopFromHoldStack = false;
this.shape.resetOrigin();
this._draw();
}*/
},
popHoldStack: function()
{
if(this.gameState == consts.GAMESTATES[3]) {
if(this.holdStack.length < 7)
while(this.holdStack.length < 7)
this.holdStack.unshift(utils.deepClone(shapes.getShape(this.currentMinoInx++%7)));
// piece needs to be able to be placed
if(this.shape.canDown(this.matrix)) return;
this.shape.copyTo(this.matrix);
this.shapeQueue.unshift(utils.deepClone(this.shape));
this.shape = utils.deepClone(this.holdStack.pop());
this._check();
this._draw();
return;
}
// todo: disable if 1 shape hold queue
if(this.holdStack.length >= 1 && this.canPopFromHoldStack)
{
this.canPopFromHoldStack = false;
this.shapeQueue.unshift(utils.deepClone(this.shape));
this.shape = this.holdStack.pop();
this.shape.resetOrigin();
this._draw();
}
},
// Restart game
_restartHandler: function() {
this.reset();
this.start();
//this._fireShape();
this._recurseGameState();
},
// Bind game events
_initEvents: function() {
setInterval(() => {this._processTick();}, 1);
setInterval(() => {this.lockDownTimer++;}, 100 );
views.btnRestart.addEventListener('click', utils.proxy(this._restartHandler, this), false);
},
// Process freeplay queue
_processFreeplayQueue: function() {
while(this.shapeQueue.length <= 4)
{
this.preparedShape = shapes.randomShape();
this.shapeQueue.push(this.preparedShape);
}
this.shape = this.shapeQueue.shift();// || shapes.randomShape();
this.currentMinoInx++;
},
// Process opener trainer queue
_processOpenerTrainerQueue: function() {
while(this.shapeQueue.length <= 4)
{
this.preparedShape = utils.deepClone(openers.getNextMino(this.currentOpener));
this.shapeQueue.push(this.preparedShape);
}
while(this.hintQueue.length <= 4)
{
this.preparedShape = utils.deepClone(openers.getNextHint(this.currentOpener));
this.hintQueue.push(this.preparedShape);
}
this.hintMino = this.hintQueue.shift();
this.shape = this.shapeQueue.shift();
this.currentMinoInx++;
// Opener sequence completed
if(this.currentMinoInx > openers.getLength()) {
//new Audio("./dist/sound/Affirm2.ogg").play();
if(this.isTimerOn) {
var besttime = document.getElementById("besttime").value;
var deltaTime = new Date().getTime() - this.sequencePrevTime;
if(besttime == "" || deltaTime/1000.0 < parseFloat(besttime)) {
document.getElementById("besttime").value = (deltaTime/1000.0).toString();
}
}
this.hintQueue = [];
this.shapeQueue = [];
this.isSequenceCompleted = true;
// Recursion warning
if(this.currentOpener < 1000) // getting real hacky
this._restartHandler();
else clearMatrix(this.matrix);
// this.reset();
// this.start();
return;
}
},
// Process sequence editor
_processSequenceEditor: function () {
return;
},
// Fill next queue and set next shape
_fireShape: function() {
//todo:should be in shapes.js
this.landed = false;
this.manipulationCounter = 0;
this._draw();
},
_recurseGameState: function (){
switch(this.gameState) {
case consts.GAMESTATES[0]:
this._processFreeplayQueue();
this._fireShape();
break;
case consts.GAMESTATES[1]:
this._processOpenerTrainerQueue();
this._fireShape();
break;
case consts.GAMESTATES[2]:
this._processOpenerTrainerQueue();
this._fireShape();
case consts.GAMESTATES[3]:
this._processSequenceEditor();
break;
default:
break;
}
},
// lockdown timer with centisecond resolution
resetLockdown: function() {
if(this.shape.canDown(this.matrix) == false)
this.landed = true;
this.lockDownTimer = 0;
if(this.landed)
this.manipulationCounter++;
},
// Return if the piece can be shifted or rotated
isPieceLocked: function() {
if(this.manipulationCounter > 15) return true;
if(this.lockDownTimer >= 5) return true;
return false;
},
// Draw game data
_draw: function() {
canvas.drawScene();
canvas.drawShape(this.shape);
canvas.drawHoldShape(this.holdStack);
canvas.drawPreviewShape(this.shapeQueue);
if(this.gameState != consts.GAMESTATES[2])
canvas.drawHintShape(this.hintMino);
if(this.shape != undefined) {
let clone = Object.assign(Object.create(Object.getPrototypeOf(this.shape)), this.shape);
var bottomY = clone.bottomAt(this.matrix);
canvas.drawGhostShape(clone, bottomY);
}
canvas.drawMatrix(this.matrix);
},
// tick input data -- wont have better than 4-15ms resolution since javascript is single theaded and any ARR or DAS below 15ms will likely be broken
_processTick: async function() {
if(this.isTimerOn) {
var deltaPlayTime = new Date().getTime() - this.sequencePrevTime;
document.getElementById("Time").value = (deltaPlayTime/1000).toString();
}
// Don't process game related events if game over
if(this.isGameOver) return;
if(this.gamepadEnabled && inputs.gamepadEnabled()) {
inputs.updateGamepad();
inputs.processGamepadDPad();
inputs.processGamepadInput();
while((inputs.gamepadQueue != undefined && inputs.gamepadQueue.length >= 1)){
var curkey = inputs.gamepadQueue.shift();
if(inputs.settingsMap.get("Gamepad Left").includes(curkey)) {
this.shape.goLeft(this.matrix);
this.resetLockdown();
this._draw();
}
else if(inputs.settingsMap.get("Gamepad Right").includes(curkey)) {
this.shape.goRight(this.matrix);
this.resetLockdown();
this._draw();
}
else if(inputs.settingsMap.get("Gamepad Rotateccw").includes(curkey)) {
this.shape.rotate(this.matrix);
this.resetLockdown();
this._draw();
}
else if(inputs.settingsMap.get("Gamepad Rotate").includes(curkey)) {
this.shape.rotateClockwise(this.matrix);
this.resetLockdown();
this._draw();
}
else if(inputs.settingsMap.get("Gamepad Down").includes(curkey)) {
this.shape.goDown(this.matrix);
this._draw();
}
else if(inputs.settingsMap.get("Gamepad Harddrop").includes(curkey)) {
// if editor
if(this.gameState == consts.GAMESTATES[3]) {
this.popHoldStack();
this._draw();
}else {
this.shape.goBottom(this.matrix);
this.lockDownTimer = 5000;
this._update();
}
}
else if(inputs.settingsMap.get("Gamepad Hold").includes(curkey)) {
this.pushHoldStack();
this._draw();
}
else if(inputs.settingsMap.get("Gamepad Pophold").includes(curkey)) {
this.popHoldStack();
this._draw();
}
else if(inputs.settingsMap.get("Gamepad Reset").includes(curkey)) {
this._restartHandler();
return;
}
}
inputs.saveButtons();
inputs.gamepadClear();
}
// Do keyboard
inputs.processKeys();
inputs.processKeyShift();
// Keyboard inputs
while((inputs.inputQueue != undefined && inputs.inputQueue.length >= 1)){
var curkey = inputs.inputQueue.shift();
if(inputs.settingsMap.get("Keyboard Left").includes(curkey)) {
this.shape.goLeft(this.matrix);
this.resetLockdown();
this._draw();
}
else if(inputs.settingsMap.get("Keyboard Right").includes(curkey)) {
this.shape.goRight(this.matrix);
this.resetLockdown();
this._draw();
}
else if(inputs.settingsMap.get("Keyboard Down").includes(curkey)) {
this.shape.goDown(this.matrix);
this._draw();
}
else if(this.gameState == consts.GAMESTATES[3] && inputs.settingsMap.get("Keyboard Up").includes(curkey)) {
this.shape.goUp(this.matrix);
this._draw();
}
else if(inputs.settingsMap.get("Keyboard Rotateccw").includes(curkey)) {
this.shape.rotate(this.matrix);
this.resetLockdown();
this._draw();
}
else if(inputs.settingsMap.get("Keyboard Rotate").includes(curkey)) {
this.shape.rotateClockwise(this.matrix);
this.resetLockdown();
this._draw();
}
else if(inputs.settingsMap.get("Keyboard Harddrop").includes(curkey)) {
// if editor
if(this.gameState == consts.GAMESTATES[3]) {
this.popHoldStack();
this._draw();
}else {
this.shape.goBottom(this.matrix);
this.lockDownTimer = 5000;
this._update();
}
}
else if(inputs.settingsMap.get("Keyboard Hold").includes(curkey)) {
this.pushHoldStack();
this._draw();
}
else if(inputs.settingsMap.get("Keyboard Pophold").includes(curkey)) {
this.popHoldStack();
this._draw();
}
else if(inputs.settingsMap.get("Keyboard Hold").includes(curkey)) {
if(document.getElementById("divbg").style.display == "none")
document.getElementById("divbg").style.display = "initial";
else
document.getElementById("divbg").style.display="none";
}
if(inputs.settingsMap.get("Keyboard Reset").includes(curkey)) {
this._restartHandler();
return;
}
}
inputs.inputQueue = [];
inputs.saveKeyboardKeys();
inputs.saveButtons();
},
// Refresh game canvas
_refresh: function() {
if (!this.running)
return;
this.currentTime = new Date().getTime();
var deltaLevelTime = this.currentTime - this.prevTime;
if (deltaLevelTime > this.interval) { // every .6 seconds?
this._update();
this._checkLevel(this.prevTime = this.currentTime);
}
// Draw Frame
if (!this.isGameOver) {
window.requestAnimationFrame(utils.proxy(this._refresh, this));
}
},
// check if the current piece is in the same location as the hint piece
_checkHint: function() {
if(this.gameState == consts.GAMESTATES[0])
return;
if(!this.shape.isSameSRS(this.hintMino))
{
//new Audio('./dist/Failed.ogg').play();
this._restartHandler();
// Restart
return 1;
}
},
// Update game data
_update: function() {
switch(this.gameState) {
case consts.GAMESTATES[0]:
case consts.GAMESTATES[1]:
case consts.GAMESTATES[2]:
if(this.shape == undefined) break;
if (this.shape.canDown(this.matrix)) {
this.shape.goDown(this.matrix);
} else if(this.isPieceLocked()){
this.canPopFromHoldStack = true;
this.shape.copyTo(this.matrix);
this._check();
if(this._checkHint()) return;
//this._fireShape();
this._recurseGameState();
new Audio('./dist/sound/Blop2.ogg').play();
}
this._draw();
this.isGameOver = checkGameOver(this.matrix);
// if game over and gamestate is free play
views.setGameOver(this.gameState == consts.GAMESTATES[0] && this.isGameOver);
if (this.isGameOver)
views.setFinalScore(this.score);
break;
case consts.GAMESTATES[3]:
break;
default:
break;
}
},
// 0 - none, 1 - mini, 2 - tspin
_tSpinType: function(tPiece, matrix) {
var side1 = 0;
var side2 = 0;
var side3 = 0;
var side4 = 0;
side1X = tPiece.x;
side1Y = tPiece.y;
side2X = tPiece.x + 2;
side2Y = tPiece.y;
side3X = tPiece.x;
side3Y = tPiece.y + 2;
side4X = tPiece.x + 2;
side4Y = tPiece.y + 2;
if(matrix[side4Y] != undefined && matrix[side3Y] != undefined) {
if(matrix[side1Y][side1X] != 0)
side1 = 1;
if(matrix[side2Y][side2X] != 0)
side2 = 1;
// TODO: figure out why this occasionally is undefined
if(matrix[side3Y][side3X] != 0)
side3 = 1;
if(matrix[side4Y][side4X] != 0)
side4 = 1;
}
console.log("sides: " + side1+side2+side3+side4);
// if Sides A and B + (C or D) are touching a Surface
//considered a T-Spin
if((side1+side2+side3+side4) >= 3)
return 2;
//if Sides C and D + (A or B) are touching a Surface
//considered a Mini T-Spin
if((side1 || side2) && (side3 && side4))
return 1;
return 0;
},
// Check and update game data
_check: function() {
var rows = checkFullRows(this.matrix);
if (rows.length) {
var tspinType;
if(this.shape.flag === 'T')
tspinType = this._tSpinType(this.shape, this.matrix);
removeRows(this.matrix, rows);
console.log("type: " + tspinType);
var score = calcScore(rows);
var reward = calcRewards(rows, tspinType);
this.score += score + reward;
this.lines += rows.length;
views.setScore(this.score);
views.setReward(reward);
views.setLines(this.lines);
}
},
// Check and update game level
_checkLevel: function() {
var currentTime = new Date().getTime();
if (currentTime - this.levelTime > consts.LEVEL_INTERVAL) {
this.level += 1;
this.interval = calcIntervalByLevel(this.level);
views.setLevel(this.level);
this.levelTime = currentTime;
}
},
}
window.Tetris = Tetris;
// export {Tetris};