2024-10-24 21:07:28 +02:00

575 lines
14 KiB
JavaScript

// Stolen from https://codepen.io/loktar00/pen/BaGqXY
// However has modifications to work good as a theme
const VERSION = "1.0";
const REMOTE_UPDATE_PATH =
"https://git.javalsai.dynv6.net/UwU/haxxor-theme/raw/branch/main/"; // Change this to your remote repository!
("use strict");
function createTetris() {
let keyMap = {};
var tetrominos = [
{
// box
colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
data: [
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0],
],
},
{
// stick
colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
data: [
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
],
},
{
// z
colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
data: [
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 0, 1, 1],
[0, 0, 0, 0],
],
},
{
// T
colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
data: [
[0, 0, 0, 0],
[0, 1, 1, 1],
[0, 0, 1, 0],
[0, 0, 0, 0],
],
},
{
// s
colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
data: [
[0, 0, 0, 0],
[0, 1, 1, 0],
[1, 1, 0, 0],
[0, 0, 0, 0],
],
},
{
// backwards L
colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
data: [
[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0],
],
},
{
// L
colors: ["rgb(00, 00, 00)", "#47ba23", "rgb(00, 00, 00)"],
data: [
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 1, 0],
[0, 0, 0, 0],
],
},
];
var Tetris = function (x, y, width, height) {
this.posX = x || 0;
this.posY = y || 0;
this.width = width || window.innerWidth;
this.height = height || window.innerHeight;
this.bgCanvas = document.createElement("canvas");
this.fgCanvas = document.createElement("canvas");
this.bgCanvas.width = this.fgCanvas.width = this.width;
this.bgCanvas.height = this.fgCanvas.height = this.height;
this.bgCtx = this.bgCanvas.getContext("2d");
this.fgCtx = this.fgCanvas.getContext("2d");
this.bgCanvas.style.left = this.posX + "px";
this.bgCanvas.style.top = this.posY + "px";
// Stay in background
this.bgCanvas.style.zIndex = 1;
this.fgCanvas.style.zIndex = 1;
this.bgCanvas.style.position = "absolute";
this.fgCanvas.style.position = "absolute";
this.fgCanvas.style.left = this.posX + "px";
this.fgCanvas.style.top = this.posY + "px";
document.body.insertBefore(this.fgCanvas, document.body.firstChild);
document.body.insertBefore(this.bgCanvas, document.body.firstChild);
this.init();
};
Tetris.prototype.init = function () {
this.curPiece = {
data: null,
colors: [0, 0, 0],
x: 0,
y: 0,
};
this.lastMove = Date.now();
this.curSpeed = 50 + Math.random() * 50;
this.unitSize = 20;
this.linesCleared = 0;
this.level = 0;
this.loseBlock = 0;
// init the board
this.board = [];
this.boardWidth = Math.floor(this.width / this.unitSize);
this.boardHeight = Math.floor(this.height / this.unitSize);
var board = this.board,
boardWidth = this.boardWidth,
boardHeight = this.boardHeight,
halfHeight = boardHeight / 2,
curPiece = this.curPiece,
x = 0,
y = 0;
// init board
for (x = 0; x <= boardWidth; x++) {
board[x] = [];
for (y = 0; y <= boardHeight; y++) {
board[x][y] = {
data: 0,
colors: ["rgb(0,0,0)", "rgb(0,0,0)", "rgb(0,0,0)"],
};
if (Math.random() > 0.15 && y > halfHeight) {
board[x][y] = {
data: 1,
colors:
tetrominos[Math.floor(Math.random() * tetrominos.length)].colors,
};
}
}
}
// collapse the board a bit
for (x = 0; x <= boardWidth; x++) {
for (y = boardHeight - 1; y > -1; y--) {
if (board[x][y].data === 0 && y > 0) {
for (var yy = y; yy > 0; yy--) {
if (board[x][yy - 1].data) {
board[x][yy].data = 1;
board[x][yy].colors = board[x][yy - 1].colors;
board[x][yy - 1].data = 0;
board[x][yy - 1].colors = [
"rgb(0,0,0)",
"rgb(0,0,0)",
"rgb(0,0,0)",
];
}
}
}
}
}
var self = this;
window.addEventListener("keyup", function(e) {
keyMap[e.code] = e.type == 'keydown';
});
window.addEventListener("keydown", function (e) {
keyMap[e.code] = e.type == 'keydown';
switch (e.code) {
case "ArrowLeft":
if (keyMap["ShiftLeft"] || keyMap["ShiftRight"]) {
if (self.checkMovement(curPiece, -2, 0)) {
curPiece.x -= 2;
};
break
}
if (self.checkMovement(curPiece, -1, 0)) {
curPiece.x -= 1;
}
break;
case "ArrowRight":
if (keyMap["ShiftLeft"] || keyMap["ShiftRight"]) {
if (self.checkMovement(curPiece, 2, 0)) {
curPiece.x += 2;
}
break;
}
if (self.checkMovement(curPiece, 1, 0)) {
curPiece.x += 1;
}
break;
case "ArrowDown":
if (self.checkMovement(curPiece, 0, 1)) {
curPiece.y++;
}
break;
case "ArrowUp":
case "Space":
curPiece.data = self.rotateTetrimono(curPiece);
break;
}
});
// render the board
this.checkLines();
this.renderBoard();
// assign the first tetri
this.newTetromino();
this.update();
};
Tetris.prototype.update = function () {
var curPiece = this.curPiece;
if (!this.checkMovement(curPiece, 0, 1)) {
if (curPiece.y < -1) {
// you lose
this.loseScreen();
return true;
} else {
this.fillBoard(curPiece);
this.newTetromino();
}
} else {
if (Date.now() > this.lastMove) {
this.lastMove = Date.now() + this.curSpeed;
if (this.checkMovement(curPiece, 0, 0.5)) {
curPiece.y += 0.5;
} else {
this.fillBoard(curPiece);
this.newTetromino();
}
}
}
this.render();
var self = this;
requestAnimationFrame(function () {
self.update();
});
};
// render only the board.
Tetris.prototype.renderBoard = function () {
var canvas = this.bgCanvas,
ctx = this.bgCtx,
unitSize = this.unitSize,
board = this.board,
boardWidth = this.boardWidth,
boardHeight = this.boardHeight;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var x = 0; x <= boardWidth; x++) {
for (var y = 0; y <= boardHeight; y++) {
if (board[x][y].data !== 0) {
var bX = x * unitSize,
bY = y * unitSize;
//ctx.shadowBlur = 5;
//ctx.shadowColor = "rgba(0,255,0,.6)";
ctx.fillStyle = board[x][y].colors[0];
ctx.fillRect(bX, bY, unitSize, unitSize);
ctx.fillStyle = board[x][y].colors[1];
ctx.fillRect(bX + 2, bY + 2, unitSize - 4, unitSize - 4);
ctx.fillStyle = board[x][y].colors[2];
ctx.fillRect(bX + 4, bY + 4, unitSize - 8, unitSize - 8);
}
}
}
};
// Render the current active piece
Tetris.prototype.render = function () {
var canvas = this.fgCanvas,
ctx = this.fgCtx,
unitSize = this.unitSize,
curPiece = this.curPiece;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var x = 0; x < 4; x++) {
for (var y = 0; y < 4; y++) {
if (curPiece.data[x][y] === 1) {
var xPos = (curPiece.x + x) * unitSize,
yPos = (curPiece.y + y) * unitSize;
if (yPos > -1) {
ctx.shadowBlur = 10;
ctx.shadowColor = "rgba(0,255,0,.6)";
ctx.fillStyle = curPiece.colors[0];
ctx.fillRect(xPos, yPos, unitSize, unitSize);
ctx.fillStyle = curPiece.colors[1];
ctx.fillRect(xPos + 2, yPos + 2, unitSize - 4, unitSize - 4);
ctx.fillStyle = curPiece.colors[2];
ctx.fillRect(xPos + 4, yPos + 4, unitSize - 8, unitSize - 8);
}
}
}
}
};
// Make sure we can mov where we want.
Tetris.prototype.checkMovement = function (curPiece, newX, newY) {
var piece = curPiece.data,
posX = curPiece.x,
posY = curPiece.y,
board = this.board,
boardWidth = this.boardWidth,
boardHeight = this.boardHeight;
for (var x = 0; x < 4; x++) {
for (var y = 0; y < 4; y++) {
if (piece[x][y] === 1) {
if (!board[posX + x + newX]) {
board[posX + x + newX] = [];
}
if (!board[posX + x + newX][y + posY + newY]) {
board[posX + x + newX][y + posY + newY] = {
data: 0,
};
}
if (
posX + x + newX >= boardWidth ||
posX + x + newX < 0 ||
board[posX + x + newX][y + posY + newY].data == 1
) {
return false;
}
if (posY + y + newY > boardHeight) {
return false;
}
}
}
}
return true;
};
// checks for completed lines and clears them
Tetris.prototype.checkLines = function () {
var board = this.board,
boardWidth = this.boardWidth,
boardHeight = this.boardHeight,
linesCleared = this.linesCleared,
level = this.level,
y = boardHeight + 1;
while (y--) {
var x = boardWidth,
lines = 0;
while (x--) {
if (board[x][y].data === 1) {
lines++;
}
}
if (lines === boardWidth) {
linesCleared++;
level = Math.round(linesCleared / 20) * 20;
var lineY = y;
while (lineY) {
for (x = 0; x <= boardWidth; x++) {
if (lineY - 1 > 0) {
board[x][lineY].data = board[x][lineY - 1].data;
board[x][lineY].colors = board[x][lineY - 1].colors;
}
}
lineY--;
}
y++;
}
}
};
// Lose animation
Tetris.prototype.loseScreen = function () {
var ctx = this.bgCtx,
unitSize = this.unitSize,
boardWidth = this.boardWidth,
boardHeight = this.boardHeight,
y = boardHeight - this.loseBlock;
for (var x = 0; x < boardWidth; x++) {
var bX = x * unitSize,
bY = y * unitSize;
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(bX, bY, unitSize, unitSize);
ctx.fillStyle = "#54a13c";
ctx.fillRect(bX + 2, bY + 2, unitSize - 4, unitSize - 4);
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillRect(bX + 4, bY + 4, unitSize - 8, unitSize - 8);
}
if (this.loseBlock <= boardHeight + 1) {
this.loseBlock++;
var self = this;
requestAnimationFrame(function () {
self.loseScreen();
});
} else {
this.init();
}
};
// adds the piece as part of the board
Tetris.prototype.fillBoard = function (curPiece) {
var piece = curPiece.data,
posX = curPiece.x,
posY = curPiece.y,
board = this.board;
for (var x = 0; x < 4; x++) {
for (var y = 0; y < 4; y++) {
if (piece[x][y] === 1) {
board[x + posX][y + posY].data = 1;
board[x + posX][y + posY].colors = curPiece.colors;
}
}
}
this.checkLines();
this.renderBoard();
};
// rotate a piece
Tetris.prototype.rotateTetrimono = function (curPiece) {
var rotated = [];
for (var x = 0; x < 4; x++) {
rotated[x] = [];
for (var y = 0; y < 4; y++) {
rotated[x][y] = curPiece.data[3 - y][x];
}
}
if (
!this.checkMovement(
{
data: rotated,
x: curPiece.x,
y: curPiece.y,
},
0,
0
)
) {
rotated = curPiece.data;
}
return rotated;
};
// assign the player a new peice
Tetris.prototype.newTetromino = function () {
var pieceNum = Math.floor(Math.random() * tetrominos.length),
curPiece = this.curPiece;
curPiece.data = tetrominos[pieceNum].data;
curPiece.colors = tetrominos[pieceNum].colors;
curPiece.x = Math.floor(
Math.random() * (this.boardWidth - curPiece.data.length + 1)
);
curPiece.y = -4;
};
let boardDiv = 20 * Math.round(window.innerWidth / 20),
boards = 1,
bWidth = boardDiv / boards,
tetrisInstances = [];
let second_nav = 0;
if (document.getElementsByClassName("secondary-nav")[0] !== undefined)
second_nav =
document.getElementsByClassName("secondary-nav")[0].clientHeight;
let barsHeight = second_nav + document.getElementById("navbar").clientHeight;
let bheight = determineFullHeight() - barsHeight;
console.log(
determineFullHeight(),
document.getElementsByClassName("full height")[0].scrollHeight,
second_nav,
document.getElementById("navbar").clientHeight
);
for (var w = 0; w < boards; w++) {
tetrisInstances.push(
new Tetris(
20 * Math.round((w * bWidth) / 20),
barsHeight,
bWidth,
bheight
)
);
}
}
function determineFullHeight() {
if (document.getElementsByClassName("full height")[0] !== undefined) {
return document.getElementsByClassName("full height")[0].clientHeight; // This is guaranteed to work very fine
} else {
let footer_size = 0;
if (document.getElementsByClassName("page-footer")[0] !== undefined)
footer_size =
document.getElementsByClassName("page-footer")[0].clientHeight;
return document.documentElement.scrollHeight - footer_size; // Fallback response, might not work as well
}
}
function main() {
if (!isActive()) {
return;
}
getUpdated();
setTimeout(createTetris, 50); // Sometimes page isnt completely rendered when tetris is supposed to be loaded, so 50ms timeout is enough to load page on most systems
}
async function getUpdated() {
try {
let remote_version = (await fetch(REMOTE_UPDATE_PATH + "VERSION")).text;
if (remote_version != VERSION) {
console.log(
`New version available in remote:\nRemote: v${remote_version}\nCurrent: v${VERSION}`
);
}
} catch {
console.log("Failed to get updates, is updates repository offline?");
}
}
function isActive() {
let style = getComputedStyle(document.body);
return style.getPropertyValue("--is-haxxorman") == "true";
}
document.addEventListener("DOMContentLoaded", main);