xxxxxxxxxx
334
let blockSize = 20; //pixel size of each block unit
let counterBlockDown = 0; //time counter for moving block down every time interval
let counterLineClear = 0; //time counter for removing line every time interval
let boardWidth = 15; //number of block units that can fit horiontally
let boardHeight = 27; //number of block units that can fit vertically
let myBlock; //block object
let generateBlock = true; //generate new block only when this is true. this will be false when block is already generated and is in motion.
let fixBlock = false; //Once this is true, block is immobilized
let score = 0,
level = 1,
lines = 0,
scorePerLine = 1;
let blockSpeed = 3;
let blockTypeArr = ["I", "J", "L", "O", "S", "T", "Z"]; //Block Type Array
let specialTypeArr = ["R", "bomb"];
//displayGameBackground(score, level, lines)
//GAME
function startGame() {
displayGameBackground(score, level, lines); //display background interface
if (generateBlock) {
//If new block is needed, create a random shaped block
let blockType = random(0, 10);
if (blockType <= 11 - level * 0.5)
myBlock = new block(5, -4, random(blockTypeArr), blockSpeed);
else myBlock = new block(5, -4, random(specialTypeArr), blockSpeed);
myBlock.shapeType(myBlock.type);
generateBlock = false;
}
if (myBlock != null) {
myBlock.drawBlock();
myBlock.blockDown();
lineClear();
displayBoard();
}
scorePerLine = int(level * 0.5) + 1;
level = int(lines / 10) + 1;
blockSpeed = int(level * 1.25) + 2;
}
//block object for tetris blocks
class block {
constructor(_x, _y, _type, speed) {
this.x = _x;
this.y = _y;
this.type = _type;
this.shapeArr = [];
this.speed = speed;
}
/*this class method will fill shapeArr based on the
type of the block. shapeArr is a 4*4 array, in which
0: empty, 1: block*/
shapeType(_type) {
let shapeArr = [];
/*example L block
0 0 0 0
0 1 0 0
0 1 0 0
0 1 1 0*/
for (let i = 0; i < 4; i++) {
shapeArr[i] = [0, 0, 0, 0];
}
if (_type == "I") {
shapeArr[0][1] = 1;
shapeArr[1][1] = 1;
shapeArr[2][1] = 1;
shapeArr[3][1] = 1;
} else if (_type == "J") {
shapeArr[1][2] = 1;
shapeArr[2][2] = 1;
shapeArr[3][1] = 1;
shapeArr[3][2] = 1;
} else if (_type == "L") {
shapeArr[1][1] = 1;
shapeArr[2][1] = 1;
shapeArr[3][1] = 1;
shapeArr[3][2] = 1;
} else if (_type == "O") {
shapeArr[2][1] = 1;
shapeArr[2][2] = 1;
shapeArr[3][1] = 1;
shapeArr[3][2] = 1;
} else if (_type == "S") {
shapeArr[2][1] = 1;
shapeArr[2][2] = 1;
shapeArr[3][0] = 1;
shapeArr[3][1] = 1;
} else if (_type == "T") {
shapeArr[2][1] = 1;
shapeArr[3][0] = 1;
shapeArr[3][1] = 1;
shapeArr[3][2] = 1;
} else if (_type == "Z") {
shapeArr[2][1] = 1;
shapeArr[2][2] = 1;
shapeArr[3][2] = 1;
shapeArr[3][3] = 1;
} else if (_type == "R") {
//Random 4*4 block
for (let i = 0; i < 4; i++) {
shapeArr[i] = [
int(random(0, 1) + 0.5),
int(random(0, 1) + 0.5),
int(random(0, 1) + 0.5),
int(random(0, 1) + 0.5),
];
}
} else if (_type == "bomb") {
for (let i = 0; i < 4; i++) {
shapeArr[3][2] = 2;
}
}
this.shapeArr = shapeArr;
}
//Checks if block can be moved in a given direction
validMove(dX, dY, dR) {
if (dR == 0) {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.y + i >= 0 && this.x + j >= 0) {
if (
/*ignores all empty spaces. 1s must always be within the
boundary of game field and must not overlap with non-empty,
or 1s, when moved*/
this.shapeArr[i][j] != 0 &&
(board[this.y + i + dY][this.x + j + dX] != 0 ||
this.y + i + dY >= boardHeight ||
this.x + j + dX < 0 ||
this.x + j + dX >= boardWidth)
) {
if (this.shapeArr[i][j] == 2) {
this.explode();
}
return false;
}
}
}
}
} else {
this.rotate();
if (this.y >= 0) {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (
/*ignores all empty spaces. 1s must always be within the
boundary of game field and must not overlap with non-empty,
or 1s, when moved*/
this.shapeArr[i][j] == 1 &&
(board[this.y + i][this.x + j] != 0 ||
this.y + i >= boardHeight ||
this.x + j < 0 ||
this.x + j >= boardWidth)
) {
return false;
}
if (this.shapeArr[i][j] == 2) return false; //bomb cannot be rotated
}
}
} else return false;
}
return true;
}
//Move block down for every time interval
blockDown() {
if (this.validMove(0, 1, 0)) {
if (millis() - counterBlockDown > 1000 / this.speed) {
this.y++;
counterBlockDown = millis();
}
} else {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.y + i < 0 && this.shapeArr[i][j] == 1) {
gameOver = true;
break;
}
}
}
/*The code waits for the user key input. As long as there is an
input, fixBlock will remain false, meaning the user can still
move the block left/right even when it cannot go down further*/
if (millis() - counterBlockDown > 1000 / this.speed) {
fixBlock = true;
counterBlockDown = millis();
}
//update game filed only when block is "fixed"
if (fixBlock) {
//Once block cannot be moved down further, update game field
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.shapeArr[i][j] == 1) board[this.y + i][this.x + j] = 1;
}
}
blockSettleMusic.play();
//generate new block
generateBlock = true;
//reset the fixBlock for the new block
fixBlock = false;
}
}
}
//Move Block left or right. left when direction is -1, right when direction is 1
moveLeftRight(dX) {
if (this.validMove(dX, 0, 0)) {
this.x += dX;
moveBlockMusic.play();
}
if (!this.validMove(0, 1, 0)) counterBlockDown = millis();
}
/*rotate block 90 degrees clockwise.
from: https://www.geeksforgeeks.org/rotate-a-matrix-by-90-degree-in-clockwise
-direction-without-using-any-extra-space*/
rotate() {
for (let i = 0; i < 2; i++) {
for (let j = i; j < 4 - i - 1; j++) {
// Swap elements of each cycle
// in clockwise direction
let temp = this.shapeArr[i][j];
this.shapeArr[i][j] = this.shapeArr[3 - j][i];
this.shapeArr[3 - j][i] = this.shapeArr[3 - i][3 - j];
this.shapeArr[3 - i][3 - j] = this.shapeArr[j][3 - i];
this.shapeArr[j][3 - i] = temp;
}
}
}
//rotate block with condition check
rotateBlock() {
if (!this.validMove(0, 0, 1)) {
for (let i = 0; i < 3; i++) this.rotate();
}
}
/*explode bomb block. if bomb is at (x,y), it will destroy every row
from x-2 to x+2*/
explode() {
let go = true;
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.shapeArr[i][j] == 2 && go) {
go = false;
for (let k = this.y + i - 2; k <= this.y + i + 2; k++) {
if (k >= 0 && k < boardHeight) {
for (let l = 0; l < boardWidth; l++) {
board[k][l] = -1;
}
}
}
}
}
}
}
//draw blocks. for each 1s in the shapeArr, block image will be placed
drawBlock() {
blockImg = blockImg.get(0, 0, 20, 20);
push();
translate(25, 30);
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.y + i >= 0 && this.x + j >= 0) {
if (this.shapeArr[i][j] == 1)
image(blockImg, blockSize * (this.x + j), blockSize * (this.y + i));
if (this.shapeArr[i][j] == 2)
image(
bombBlockImg,
blockSize * (this.x + j),
blockSize * (this.y + i)
);
}
}
}
pop();
}
}
//visually display the gameboard.
function displayBoard() {
blockImg = blockImg.get(0, 0, 20, 20);
push();
translate(25, 30);
for (let i = 0; i < boardHeight; i++) {
for (let j = 0; j < boardWidth; j++) {
if (board[i][j] == 1) image(blockImg, blockSize * j, blockSize * i);
else if (board[i][j] == -1) {
image(greyBlockImg, blockSize * j, blockSize * i);
removeLine(i);
}
}
}
pop();
}
//Check if there is a line cleared
function lineClear() {
for (let i = 0; i < boardHeight; i++) {
if (board[i][0] == 1) {
for (let j = 0; j < boardWidth; j++) {
if (board[i][j] == 1) {
if (j == boardWidth - 1) {
for (let k = 0; k < boardWidth; k++) board[i][k] = "-1";
counterLineClear = millis();
}
} else break;
}
}
}
}
function removeLine(index) {
if (millis() - counterLineClear > 400) {
for (let i = index; i > 1; i--) {
for (let j = 0; j < boardWidth; j++) {
board[i][j] = board[i - 1][j];
}
}
lineClearMusic.play();
lines++;
score += scorePerLine;
counterLineClear = millis();
}
}