mirror of
https://github.com/KeyZox71/knl_meowscendence.git
synced 2025-10-14 02:54:44 +02:00
「✨」 feat(src/front): taskbar to access profile and settings for user management is done :D and also its very pretty :3
This commit is contained in:
@ -12,7 +12,7 @@
|
||||
|
||||
|
||||
<!--body class="bg-gray-100 dark:bg-neutral-950 h-screen flex flex-col"-->
|
||||
<body class="bg-neutral-950 dark:bg-[url(https://api.kanel.ovh/random)] bg-center bg-cover h-screen flex flex-col">
|
||||
<body class="bg-[url(https://y-syo.me/res/bg.jpg)] dark:bg-[url(https://api.kanel.ovh/random)] bg-center bg-cover h-screen flex flex-col">
|
||||
<!--script src="https://kanel.ovh/oneko.js"></script-->
|
||||
<!--script src="./static/ts/oneko.js"></script-->
|
||||
|
||||
@ -35,15 +35,22 @@
|
||||
<div id="app" class="flex-1 flex items-center justify-center">
|
||||
</div>
|
||||
|
||||
<div id="taskbar-menu" class="absolute bottom-13 left-0"></div>
|
||||
|
||||
<div class="border-t-2 border-neutral-300 dark:border-neutral-800 sticky bottom-0">
|
||||
<nav class="bg-neutral-200 dark:bg-neutral-900 shadow-md border-t-2 border-neutral-400 dark:border-neutral-700 px-4 sm:px-6 lg:px-8 flex justify-start h-12 items-center space-x-6 font-[Kubasta]">
|
||||
<a id="profile-button" class="text-neutral-900 hover:text-neutral-700 dark:text-white dark:hover:text-neutral-400" href="/login" data-link>login</a>
|
||||
<a class="text-neutral-900 hover:text-neutral-700 dark:text-white dark:hover:text-neutral-400" href="/" data-link>home</a>
|
||||
<nav class="bg-neutral-200 dark:bg-neutral-900 shadow-md border-t-2 border-neutral-400 dark:border-neutral-700 flex justify-between h-12 items-center content-center space-x-6 font-[Kubasta]">
|
||||
<div class="flex px-4 items-center content-center space-x-2">
|
||||
<button id="profile-button" class="taskbar-button flex flex-row justify-center items-center"><img class="object-scale-down mr-2 h-5 w-5" src="https://api.kanel.ovh/id?id=65" /> start</button>
|
||||
<div class="text-neutral-700 dark:text-neutral-400">|</div>
|
||||
<a class="taskbar-button" href="https://rusty.42angouleme.fr/">rusty</a>
|
||||
<a class="taskbar-button" href="/tetris" data-link>tetris</a>
|
||||
</div>
|
||||
<div class="reverse-border m-1.5 h-8/10 content-center">
|
||||
<span id="taskbar-clock" class="text- neutral-900 dark:text-white px-4">12:37</span>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="module" src="/static/ts/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -47,3 +47,32 @@
|
||||
active:border-t-neutral-400 dark:active:border-t-neutral-700 active:border-l-neutral-400 dark:active:border-l-neutral-700 active:border-r-neutral-100 dark:active:border-r-neutral-500 active:border-b-neutral-100 dark:active:border-b-neutral-500
|
||||
;
|
||||
}
|
||||
|
||||
.taskbar-button {
|
||||
@apply shadow-2x1
|
||||
bg-neutral-200 hover:bg-neutral-300 active:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:active:bg-neutral-700
|
||||
text-neutral-900 dark:text-white
|
||||
px-4 py-0.5
|
||||
content-center text-center
|
||||
delay-0 duration-150 transition-colors
|
||||
|
||||
border-2 border-t-neutral-100 dark:border-t-neutral-500 border-l-neutral-100 dark:border-l-neutral-500 border-r-neutral-400 dark:border-r-neutral-700 border-b-neutral-400 dark:border-b-neutral-700
|
||||
active:border-t-neutral-400 dark:active:border-t-neutral-700 active:border-l-neutral-400 dark:active:border-l-neutral-700 active:border-r-neutral-100 dark:active:border-r-neutral-500 active:border-b-neutral-100 dark:active:border-b-neutral-500
|
||||
;
|
||||
}
|
||||
|
||||
.menu-default-button {
|
||||
@apply w-46 h-12
|
||||
text-neutral-900 dark:text-white
|
||||
bg-neutral-200 hover:bg-neutral-300
|
||||
dark:bg-neutral-800 dark:hover:bg-neutral-700
|
||||
;
|
||||
}
|
||||
|
||||
.menu-default-label {
|
||||
@apply w-46 h-12
|
||||
text-neutral-900 dark:text-white
|
||||
bg-neutral-200
|
||||
dark:bg-neutral-800
|
||||
;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { oneko } from "./oneko.ts";
|
||||
import Profile from "./views/Profile.ts";
|
||||
let profile_view = new Profile;
|
||||
|
||||
export async function isLogged(): boolean {
|
||||
export async function isLogged(): Promise<boolean> {
|
||||
let uuid_req = await fetch("http://localhost:3001/me", {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
@ -9,63 +11,11 @@ export async function isLogged(): boolean {
|
||||
{
|
||||
let uuid = await uuid_req.json();
|
||||
document.cookie = `uuid=${uuid.user};max-age=${60*60*24*7}`;
|
||||
|
||||
const old_button = document.getElementById("profile-button");
|
||||
|
||||
const dropdown = document.createElement("div");
|
||||
dropdown.classList.add("relative", "inline-block", "group");
|
||||
dropdown.id = "profile-button";
|
||||
const button_dropdown = dropdown.appendChild(document.createElement("button"));
|
||||
button_dropdown.innerHTML = uuid.user;
|
||||
button_dropdown.classList.add("text-neutral-900", "group-hover:text-neutral-700", "dark:text-white", "dark:group-hover:text-neutral-400");
|
||||
|
||||
const menu_div = dropdown.appendChild(document.createElement("div"));
|
||||
menu_div.classList.add("float:right", "hidden", "absolute", "left-0", "bottom-full", "dark:bg-neutral-800", "dark:text-white", "min-w-[160px]", "shadow-lg", "z-10", "group-hover:block");
|
||||
|
||||
const profile_a = menu_div.appendChild(document.createElement("a"));
|
||||
const settings_a = menu_div.appendChild(document.createElement("a"));
|
||||
const logout_button = menu_div.appendChild(document.createElement("button"));
|
||||
|
||||
profile_a.text = "profile";
|
||||
profile_a.classList.add("block", "no-underline", "px-4", "py-3");
|
||||
profile_a.href = "/profile";
|
||||
profile_a.setAttribute("data-link", "");
|
||||
|
||||
settings_a.text = "settings";
|
||||
settings_a.classList.add("block", "no-underline", "px-4", "py-3");
|
||||
settings_a.href = "/settings";
|
||||
settings_a.setAttribute("data-link", "");
|
||||
|
||||
logout_button.innerHTML = "logout";
|
||||
logout_button.classList.add("block", "no-underline", "px-4", "py-3");
|
||||
logout_button.id = "logout-button";
|
||||
//document.getElementById("logout-button")?.addEventListener("click", async () => {
|
||||
logout_button.addEventListener("click", async () => {
|
||||
let req = await fetch("http://localhost:3001/logout", {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
});
|
||||
if (req.status === 200)
|
||||
isLogged();
|
||||
else
|
||||
console.error("logout failed");
|
||||
});
|
||||
|
||||
old_button.replaceWith(dropdown);
|
||||
return true;
|
||||
}
|
||||
else // 401
|
||||
{
|
||||
document.cookie = `uuid=;max-age=0`;
|
||||
const old_button = document.getElementById("profile-button");
|
||||
const login_button = document.createElement("a");
|
||||
login_button.id = "profile-button";
|
||||
login_button.text = "login";
|
||||
login_button.classList.add("text-neutral-900", "hover:text-neutral-700", "dark:text-white", "dark:hover:text-neutral-400");
|
||||
login_button.href = "/login";
|
||||
login_button.setAttribute("data-link", "");
|
||||
|
||||
old_button.replaceWith(login_button);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -90,8 +40,6 @@ const routes = [
|
||||
|
||||
{ path: "/login", view: () => import("./views/LoginPage.ts") },
|
||||
{ path: "/register", view: () => import("./views/RegisterPage.ts") },
|
||||
|
||||
{ path: "/profile", view: () => import("./views/Profile.ts") },
|
||||
];
|
||||
|
||||
const router = async () => {
|
||||
@ -117,12 +65,18 @@ const router = async () => {
|
||||
view.run();
|
||||
};
|
||||
|
||||
document.getElementById("profile-button")?.addEventListener("click", () => {profile_view.run();});
|
||||
window.addEventListener("popstate", router);
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
isLogged();
|
||||
|
||||
document.body.addEventListener("click", e=> {
|
||||
if (!e.target.closest("#taskbar-menu") && !e.target.matches("#profile-button"))
|
||||
{
|
||||
profile_view.open = false;
|
||||
document.getElementById("taskbar-menu").innerHTML = "";
|
||||
}
|
||||
if (e.target.matches("[data-link]"))
|
||||
{
|
||||
e.preventDefault();
|
||||
@ -144,3 +98,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
|
||||
oneko();
|
||||
|
||||
function updateClock()
|
||||
{
|
||||
const days = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
||||
const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
||||
const clock = document.getElementById("taskbar-clock");
|
||||
const now = new Date();
|
||||
let hours = now.getHours();
|
||||
let minutes = now.getMinutes();
|
||||
|
||||
hours = hours < 10 ? "0" + hours : hours;
|
||||
minutes = minutes < 10 ? "0" + minutes : minutes;
|
||||
|
||||
clock.innerHTML = `${days[now.getDay()]} ${now.getDate()} ${months[now.getMonth()]} ` + hours + ":" + minutes;
|
||||
}
|
||||
|
||||
setInterval(updateClock, 5000);
|
||||
updateClock();
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Aview from "./Aview.ts"
|
||||
import { dragElement } from "./drag.js"
|
||||
import { setOnekoState } from "../oneko.ts"
|
||||
import { isLogged, navigationManager } from "../main.ts"
|
||||
|
||||
@ -13,22 +14,30 @@ export default class extends Aview {
|
||||
|
||||
async getHTML() {
|
||||
return `
|
||||
<form method="dialog" class="text-center p-10 bg-white dark:bg-neutral-800 rounded-xl shadow space-y-4 flex flex-col">
|
||||
<h1 class="text-4xl font-bold text-blue-600">login</h1>
|
||||
<div id="window" class="absolute default-border">
|
||||
<div id="window-header" class="bg-linear-to-r from-orange-200 to-orange-300 flex flex-row min-w-75 justify-between px-2">
|
||||
<span class="font-[Kubasta]">login.ts</span>
|
||||
<div>
|
||||
<button> - </button>
|
||||
<button> □ </button>
|
||||
<a href="/" data-link> × </a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 border rounded-md w-full px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required></input>
|
||||
<input type="password" id="password" placeholder="password" class="bg-white text-neutral-900 border w-full px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required></input>
|
||||
<form method="dialog" class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 space-y-4 reverse-border">
|
||||
<h1 class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome back ! please login.</h1>
|
||||
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
|
||||
<input type="password" id="password" placeholder="password" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
|
||||
<p id="login-error-message" class="hidden text-red-700 dark:text-red-500"></p>
|
||||
<button id="login-button" type="submit" class="bg-blue-600 text-white hover:bg-blue-500 w-full py-2 rounded-md transition-colors">login</button>
|
||||
|
||||
<a class="text-gray-400 dark:text-gray-600 underline" href="/register" data-link>
|
||||
register
|
||||
</a>
|
||||
</br>
|
||||
<button id="login-button" type="submit" class="default-button w-full">login</button>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async run() {
|
||||
dragElement(document.getElementById("window"));
|
||||
const login = async () => {
|
||||
const username = (document.getElementById("username") as HTMLInputElement).value;
|
||||
const password = (document.getElementById("password") as HTMLInputElement).value;
|
||||
|
@ -6,36 +6,81 @@ export default class extends Aview {
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
if (!isLogged())
|
||||
navigationManager("/login");
|
||||
this.setTitle("profile");
|
||||
}
|
||||
|
||||
async getHTML() {
|
||||
return `
|
||||
<div id="main-window" class="text-center p-10 bg-white dark:bg-neutral-800 rounded-xl shadow space-y-4">
|
||||
<div id="main-window" class="default-border shadow-2x1 bg-neutral-200 dark:bg-neutral-800">
|
||||
<div class="flex flex-row items-stretch">
|
||||
<div class="inline-block bg-linear-to-b from-orange-200 to-orange-300 min-h-84 w-6 relative">
|
||||
<!--div class="absolute bottom-1 left-full whitespace-nowrap origin-bottom-left -rotate-90 font-bold">knl_meowscendence</div-->
|
||||
<div class="absolute bottom-1 left-full whitespace-nowrap origin-bottom-left -rotate-90 font-bold">girls kissing :3</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<div id="profile-items" class="flex flex-col items-center">
|
||||
</div>
|
||||
<div id="menu-bottom-div" class="hidden mt-auto flex flex-col items-center">
|
||||
<hr class="my-2 w-32 reverse-border">
|
||||
<button id="menu-logout" class="menu-default-button">logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
open: boolean = false;
|
||||
|
||||
async run() {
|
||||
const uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
|
||||
const userdata_req = await fetch(`http://localhost:3002/users/${uuid}`, {
|
||||
let uuid: String;
|
||||
if (this.open)
|
||||
{
|
||||
this.open = false;
|
||||
document.getElementById("taskbar-menu").innerHTML = "";
|
||||
return ;
|
||||
}
|
||||
this.open = true;
|
||||
document.getElementById("taskbar-menu").innerHTML = await this.getHTML();
|
||||
|
||||
async function getMainHTML() {
|
||||
if (!(await isLogged()))
|
||||
{
|
||||
document.getElementById("menu-bottom-div").classList.add("hidden");
|
||||
return `
|
||||
<a class="menu-default-button inline-flex items-center justify-center" href="/login" data-link>login</a>
|
||||
<a class="menu-default-button inline-flex items-center justify-center" href="/register" data-link>register</a>
|
||||
`;
|
||||
}
|
||||
document.getElementById("menu-bottom-div").classList.remove("hidden");
|
||||
|
||||
uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
|
||||
return `
|
||||
<span class="menu-default-label inline-flex items-center justify-center">hi, ${uuid} !</span>
|
||||
<hr class="my-2 w-32 reverse-border">
|
||||
<button class="menu-default-button">profile</button>
|
||||
<button class="menu-default-button">settings</button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
document.getElementById("profile-items").innerHTML = await getMainHTML();
|
||||
|
||||
/*const userdata_req = await fetch(`http://localhost:3002/users/${uuid}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (userdata_req.status == 404)
|
||||
{
|
||||
console.error("invalid user");
|
||||
return ;
|
||||
}
|
||||
|
||||
let userdata = await userdata_req.json();
|
||||
|
||||
console.log(userdata_req);
|
||||
console.log(userdata_req);*/
|
||||
|
||||
const main = document.getElementById("main-window");
|
||||
/*const main = document.getElementById("profile-profile");
|
||||
const nametag = main.appendChild(document.createElement("span"));
|
||||
|
||||
nametag.innerHTML = `Hiiiiii ${userdata.displayName} ! :D`;
|
||||
@ -44,6 +89,17 @@ export default class extends Aview {
|
||||
const winrate = main.appendChild(document.createElement("div"));
|
||||
|
||||
winrate.innerHTML = `wins: ${userdata.wins} | losses: ${userdata.losses} | winrate: ${userdata.wins / (userdata.wins + userdata.losses)}`;
|
||||
winrate.classList.add("text-neutral-900", "dark:text-white");
|
||||
winrate.classList.add("text-neutral-900", "dark:text-white");*/
|
||||
//console.log(document.getElementById("menu-logout"));
|
||||
document.getElementById("menu-logout").addEventListener("click", async () => {
|
||||
let req = await fetch("http://localhost:3001/logout", {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
});
|
||||
if (req.status === 200)
|
||||
this.run();
|
||||
else
|
||||
console.error("logout failed");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Aview from "./Aview.ts"
|
||||
import { setOnekoState } from "../oneko.ts"
|
||||
import { isLogged, navigationManager } from "../main.ts"
|
||||
import { dragElement } from "./drag.ts";
|
||||
|
||||
export default class extends Aview {
|
||||
|
||||
@ -13,22 +14,30 @@ export default class extends Aview {
|
||||
|
||||
async getHTML() {
|
||||
return `
|
||||
<form method="dialog" class="text-center p-10 bg-white dark:bg-neutral-800 rounded-xl shadow space-y-4 flex flex-col">
|
||||
<h1 class="text-4xl font-bold text-blue-600">register</h1>
|
||||
<div id="window" class="absolute default-border">
|
||||
<div id="window-header" class="bg-linear-to-r from-orange-200 to-orange-300 flex flex-row min-w-75 justify-between px-2">
|
||||
<span class="font-[Kubasta]">register.ts</span>
|
||||
<div>
|
||||
<button> - </button>
|
||||
<button> □ </button>
|
||||
<a href="/" data-link> × </a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 border rounded-md w-full px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required></input>
|
||||
<input type="password" id="password" placeholder="password" class="bg-white text-neutral-900 border w-full px-4 py-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required></input>
|
||||
<form method="dialog" class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 space-y-4 reverse-border">
|
||||
<p class="text-gray-900 dark:text-white text-lg pt-0 pb-4">welcome ! please register.</p>
|
||||
<input type="text" id="username" placeholder="username" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
|
||||
<input type="password" id="password" placeholder="password" class="bg-white text-neutral-900 px-4 py-2 input-border" required></input>
|
||||
<p id="login-error-message" class="hidden text-red-700 dark:text-red-500"></p>
|
||||
<button id="register-button" type="submit" class="bg-blue-600 text-white hover:bg-blue-500 w-full py-2 rounded-md transition-colors">register</button>
|
||||
|
||||
<a class="text-gray-400 dark:text-gray-600 underline" href="/login" data-link>
|
||||
i already have an account
|
||||
</a>
|
||||
</br>
|
||||
<button id="register-button" type="submit" class="default-button w-full">register</button>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async run() {
|
||||
dragElement(document.getElementById("window"));
|
||||
const login = async () => {
|
||||
const username = (document.getElementById("username") as HTMLInputElement).value;
|
||||
const password = (document.getElementById("password") as HTMLInputElement).value;
|
||||
|
@ -1,11 +1,10 @@
|
||||
import Aview from "./Aview.ts"
|
||||
import Aview from "./Aview.ts";
|
||||
import { dragElement } from "./drag.js";
|
||||
|
||||
export default class extends Aview {
|
||||
|
||||
running: boolean;
|
||||
|
||||
constructor()
|
||||
{
|
||||
constructor() {
|
||||
super();
|
||||
this.setTitle("tetris (local match)");
|
||||
this.running = true;
|
||||
@ -40,6 +39,7 @@ export default class extends Aview {
|
||||
}
|
||||
|
||||
async run() {
|
||||
dragElement(document.getElementById("window"));
|
||||
const COLS = 10;
|
||||
const ROWS = 20;
|
||||
const BLOCK = 30; // pixels per block
|
||||
@ -49,127 +49,176 @@ export default class extends Aview {
|
||||
// Tetromino definitions: each piece is an array of rotations, each rotation is a 2D matrix
|
||||
const TETROMINOES: { [key: string]: number[][][] } = {
|
||||
I: [
|
||||
[[0,0,0,0]
|
||||
,[1,1,1,1]
|
||||
,[0,0,0,0]
|
||||
,[0,0,0,0]],
|
||||
[
|
||||
[0, 0, 0, 0],
|
||||
[1, 1, 1, 1],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
],
|
||||
|
||||
[[0,0,1,0]
|
||||
,[0,0,1,0]
|
||||
,[0,0,1,0]
|
||||
,[0,0,1,0]],
|
||||
[
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 1, 0],
|
||||
],
|
||||
|
||||
[[0,0,0,0]
|
||||
,[0,0,0,0]
|
||||
,[1,1,1,1]
|
||||
,[0,0,0,0]],
|
||||
[
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[1, 1, 1, 1],
|
||||
[0, 0, 0, 0],
|
||||
],
|
||||
|
||||
[[0,1,0,0]
|
||||
,[0,1,0,0]
|
||||
,[0,1,0,0]
|
||||
,[0,1,0,0]],
|
||||
[
|
||||
[0, 1, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
],
|
||||
],
|
||||
J: [
|
||||
[[2,0,0]
|
||||
,[2,2,2]
|
||||
,[0,0,0]],
|
||||
[
|
||||
[2, 0, 0],
|
||||
[2, 2, 2],
|
||||
[0, 0, 0],
|
||||
],
|
||||
|
||||
[[0,2,2]
|
||||
,[0,2,0]
|
||||
,[0,2,0]],
|
||||
[
|
||||
[0, 2, 2],
|
||||
[0, 2, 0],
|
||||
[0, 2, 0],
|
||||
],
|
||||
|
||||
[[0,0,0]
|
||||
,[2,2,2]
|
||||
,[0,0,2]],
|
||||
[
|
||||
[0, 0, 0],
|
||||
[2, 2, 2],
|
||||
[0, 0, 2],
|
||||
],
|
||||
|
||||
[[0,2,0]
|
||||
,[0,2,0]
|
||||
,[2,2,0]],
|
||||
[
|
||||
[0, 2, 0],
|
||||
[0, 2, 0],
|
||||
[2, 2, 0],
|
||||
],
|
||||
],
|
||||
L: [
|
||||
[[0,0,3]
|
||||
,[3,3,3]
|
||||
,[0,0,0]],
|
||||
[
|
||||
[0, 0, 3],
|
||||
[3, 3, 3],
|
||||
[0, 0, 0],
|
||||
],
|
||||
|
||||
[[0,3,0]
|
||||
,[0,3,0]
|
||||
,[0,3,3]],
|
||||
[
|
||||
[0, 3, 0],
|
||||
[0, 3, 0],
|
||||
[0, 3, 3],
|
||||
],
|
||||
|
||||
[[0,0,0]
|
||||
,[3,3,3]
|
||||
,[3,0,0]],
|
||||
[
|
||||
[0, 0, 0],
|
||||
[3, 3, 3],
|
||||
[3, 0, 0],
|
||||
],
|
||||
|
||||
[[3,3,0]
|
||||
,[0,3,0]
|
||||
,[0,3,0]],
|
||||
[
|
||||
[3, 3, 0],
|
||||
[0, 3, 0],
|
||||
[0, 3, 0],
|
||||
],
|
||||
],
|
||||
O: [
|
||||
[[4,4]
|
||||
,[4,4]],
|
||||
[
|
||||
[4, 4],
|
||||
[4, 4],
|
||||
],
|
||||
],
|
||||
S: [
|
||||
[[0,5,5]
|
||||
,[5,5,0]
|
||||
,[0,0,0]],
|
||||
[
|
||||
[0, 5, 5],
|
||||
[5, 5, 0],
|
||||
[0, 0, 0],
|
||||
],
|
||||
|
||||
[[0,5,0]
|
||||
,[0,5,5]
|
||||
,[0,0,5]],
|
||||
[
|
||||
[0, 5, 0],
|
||||
[0, 5, 5],
|
||||
[0, 0, 5],
|
||||
],
|
||||
|
||||
[[0,0,0]
|
||||
,[0,5,5]
|
||||
,[5,5,0]],
|
||||
|
||||
[[5,0,0]
|
||||
,[5,5,0]
|
||||
,[0,5,0]],
|
||||
[
|
||||
[0, 0, 0],
|
||||
[0, 5, 5],
|
||||
[5, 5, 0],
|
||||
],
|
||||
|
||||
[
|
||||
[5, 0, 0],
|
||||
[5, 5, 0],
|
||||
[0, 5, 0],
|
||||
],
|
||||
],
|
||||
T: [
|
||||
[[0,6,0]
|
||||
,[6,6,6]
|
||||
,[0,0,0]],
|
||||
[
|
||||
[0, 6, 0],
|
||||
[6, 6, 6],
|
||||
[0, 0, 0],
|
||||
],
|
||||
|
||||
[[0,6,0]
|
||||
,[0,6,6]
|
||||
,[0,6,0]],
|
||||
[
|
||||
[0, 6, 0],
|
||||
[0, 6, 6],
|
||||
[0, 6, 0],
|
||||
],
|
||||
|
||||
[[0,0,0]
|
||||
,[6,6,6]
|
||||
,[0,6,0]],
|
||||
[
|
||||
[0, 0, 0],
|
||||
[6, 6, 6],
|
||||
[0, 6, 0],
|
||||
],
|
||||
|
||||
[[0,6,0]
|
||||
,[6,6,0]
|
||||
,[0,6,0]],
|
||||
[
|
||||
[0, 6, 0],
|
||||
[6, 6, 0],
|
||||
[0, 6, 0],
|
||||
],
|
||||
],
|
||||
Z: [
|
||||
[[7,7,0]
|
||||
,[0,7,7]
|
||||
,[0,0,0]],
|
||||
[
|
||||
[7, 7, 0],
|
||||
[0, 7, 7],
|
||||
[0, 0, 0],
|
||||
],
|
||||
|
||||
[[0,0,7]
|
||||
,[0,7,7]
|
||||
,[0,7,0]],
|
||||
[
|
||||
[0, 0, 7],
|
||||
[0, 7, 7],
|
||||
[0, 7, 0],
|
||||
],
|
||||
|
||||
[[0,0,0]
|
||||
,[7,7,0]
|
||||
,[0,7,7]],
|
||||
[
|
||||
[0, 0, 0],
|
||||
[7, 7, 0],
|
||||
[0, 7, 7],
|
||||
],
|
||||
|
||||
[[0,7,0]
|
||||
,[7,7,0]
|
||||
,[7,0,0]],
|
||||
[
|
||||
[0, 7, 0],
|
||||
[7, 7, 0],
|
||||
[7, 0, 0],
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
const COLORS = [
|
||||
'#000000', // placeholder for 0
|
||||
'#00ffff', // I - cyan
|
||||
'#0000ff', // J - blue
|
||||
'#ff7f00', // L - orange
|
||||
'#ffff00', // O - yellow
|
||||
'#00ff00', // S - green
|
||||
'#800080', // T - purple
|
||||
'#ff0000', // Z - red
|
||||
"#000000", // placeholder for 0
|
||||
"#00ffff", // I - cyan
|
||||
"#0000ff", // J - blue
|
||||
"#ff7f00", // L - orange
|
||||
"#ffff00", // O - yellow
|
||||
"#00ff00", // S - green
|
||||
"#800080", // T - purple
|
||||
"#ff0000", // Z - red
|
||||
];
|
||||
|
||||
class Piece {
|
||||
@ -191,10 +240,7 @@ export default class extends Aview {
|
||||
}
|
||||
|
||||
findColorIndex() {
|
||||
for (const row of this.shape)
|
||||
for (const v of row)
|
||||
if (v)
|
||||
return v;
|
||||
for (const row of this.shape) for (const v of row) if (v) return v;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -204,20 +250,19 @@ export default class extends Aview {
|
||||
}
|
||||
|
||||
rotateCCW() {
|
||||
this.rotationIndex = (this.rotationIndex - 1 + this.rotations.length) % this.rotations.length;
|
||||
this.rotationIndex =
|
||||
(this.rotationIndex - 1 + this.rotations.length) %
|
||||
this.rotations.length;
|
||||
this.shape = this.rotations[this.rotationIndex];
|
||||
}
|
||||
|
||||
getCells(): { x: number; y: number; val: number }[] {
|
||||
const cells: { x: number; y: number; val: number }[] = [];
|
||||
|
||||
for (let r = 0; r < this.shape.length; r++)
|
||||
{
|
||||
for (let c = 0; c < this.shape[r].length; c++)
|
||||
{
|
||||
for (let r = 0; r < this.shape.length; r++) {
|
||||
for (let c = 0; c < this.shape[r].length; c++) {
|
||||
const val = this.shape[r][c];
|
||||
if (val)
|
||||
cells.push({ x: this.x + c, y: this.y + r, val });
|
||||
if (val) cells.push({ x: this.x + c, y: this.y + r, val });
|
||||
}
|
||||
}
|
||||
return cells;
|
||||
@ -226,9 +271,9 @@ export default class extends Aview {
|
||||
|
||||
class Game {
|
||||
board: Cell[][];
|
||||
canvas: HTMLCanvasElement;
|
||||
holdCanvas: HTMLCanvasElement;
|
||||
queueCanvas: HTMLCanvasElement;
|
||||
canvas: HTMLCanvasElement | null;
|
||||
holdCanvas: HTMLCanvasElement | null;
|
||||
queueCanvas: HTMLCanvasElement | null;
|
||||
ctx: CanvasRenderingContext2D;
|
||||
holdCtx: CanvasRenderingContext2D;
|
||||
queueCtx: CanvasRenderingContext2D;
|
||||
@ -245,17 +290,19 @@ export default class extends Aview {
|
||||
isPaused = false;
|
||||
|
||||
constructor(canvasId: string) {
|
||||
const el = document.getElementById(canvasId);
|
||||
const el = document.getElementById(
|
||||
canvasId,
|
||||
) as HTMLCanvasElement | null;
|
||||
this.canvas = el;
|
||||
this.canvas.width = COLS * BLOCK;
|
||||
this.canvas.height = ROWS * BLOCK;
|
||||
const ctx = this.canvas.getContext('2d');
|
||||
const ctx = this.canvas.getContext("2d");
|
||||
this.ctx = ctx;
|
||||
|
||||
this.holdCanvas = document.getElementById('hold');
|
||||
this.queueCanvas = document.getElementById('queue');
|
||||
this.holdCtx = this.holdCanvas.getContext('2d');
|
||||
this.queueCtx = this.queueCanvas.getContext('2d');
|
||||
this.holdCanvas = document.getElementById("hold");
|
||||
this.queueCanvas = document.getElementById("queue");
|
||||
this.holdCtx = this.holdCanvas.getContext("2d");
|
||||
this.queueCtx = this.queueCanvas.getContext("2d");
|
||||
|
||||
this.board = this.createEmptyBoard();
|
||||
this.fillBag();
|
||||
@ -267,8 +314,7 @@ export default class extends Aview {
|
||||
|
||||
createEmptyBoard(): Cell[][] {
|
||||
const b: Cell[][] = [];
|
||||
for (let r = 0; r < ROWS; r++)
|
||||
{
|
||||
for (let r = 0; r < ROWS; r++) {
|
||||
const row: Cell[] = new Array(COLS).fill(0);
|
||||
b.push(row);
|
||||
}
|
||||
@ -279,8 +325,7 @@ export default class extends Aview {
|
||||
// classic 7-bag randomizer
|
||||
const pieces = Object.keys(TETROMINOES);
|
||||
const bag = [...pieces];
|
||||
for (let i = bag.length - 1; i > 0; i--)
|
||||
{
|
||||
for (let i = bag.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[bag[i], bag[j]] = [bag[j], bag[i]];
|
||||
}
|
||||
@ -288,12 +333,10 @@ export default class extends Aview {
|
||||
}
|
||||
|
||||
hold() {
|
||||
if (!this.canHold)
|
||||
return;
|
||||
if (!this.canHold) return;
|
||||
|
||||
[this.piece, this.holdPiece] = [this.holdPiece, this.piece];
|
||||
if (!this.piece)
|
||||
this.spawnPiece();
|
||||
if (!this.piece) this.spawnPiece();
|
||||
|
||||
this.piece.x = Math.floor((COLS - this.piece.shape[0].length) / 2);
|
||||
this.piece.y = -2;
|
||||
@ -306,13 +349,11 @@ export default class extends Aview {
|
||||
|
||||
spawnPiece() {
|
||||
this.canHold = true;
|
||||
if (this.nextQueue.length < 7)
|
||||
this.fillBag();
|
||||
if (this.nextQueue.length < 7) this.fillBag();
|
||||
const type = this.nextQueue.shift()!;
|
||||
this.piece = new Piece(type);
|
||||
// If spawn collides immediately -> game over
|
||||
if (this.collides(this.piece))
|
||||
{
|
||||
if (this.collides(this.piece)) {
|
||||
this.isGameOver = true;
|
||||
}
|
||||
|
||||
@ -321,26 +362,23 @@ export default class extends Aview {
|
||||
}
|
||||
|
||||
collides(piece: Piece): boolean {
|
||||
for (const cell of piece.getCells())
|
||||
{
|
||||
if (cell.y >= ROWS)
|
||||
return true;
|
||||
if (cell.x < 0 || cell.x >= COLS)
|
||||
return true;
|
||||
if (cell.y >= 0 && this.board[cell.y][cell.x])
|
||||
return true;
|
||||
for (const cell of piece.getCells()) {
|
||||
if (cell.y >= ROWS) return true;
|
||||
if (cell.x < 0 || cell.x >= COLS) return true;
|
||||
if (cell.y >= 0 && this.board[cell.y][cell.x]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getGhostOffset(piece: Piece): boolean {
|
||||
getGhostOffset(piece: Piece): number {
|
||||
let y: number = 0;
|
||||
while (true)
|
||||
{
|
||||
for (const cell of piece.getCells())
|
||||
{
|
||||
while (true) {
|
||||
for (const cell of piece.getCells()) {
|
||||
console.log(cell.y + y);
|
||||
if ((cell.y + y >= ROWS) || (cell.y + y >= 0 && this.board[cell.y + y][cell.x]))
|
||||
if (
|
||||
cell.y + y >= ROWS ||
|
||||
(cell.y + y >= 0 && this.board[cell.y + y][cell.x])
|
||||
)
|
||||
return y - 1;
|
||||
}
|
||||
|
||||
@ -349,18 +387,14 @@ export default class extends Aview {
|
||||
}
|
||||
|
||||
lockPiece() {
|
||||
if (!this.piece)
|
||||
return;
|
||||
let isValid:boolean = false;
|
||||
for (const cell of this.piece.getCells())
|
||||
{
|
||||
if (!this.piece) return;
|
||||
let isValid: boolean = false;
|
||||
for (const cell of this.piece.getCells()) {
|
||||
if (cell.y >= 0 && cell.y < ROWS && cell.x >= 0 && cell.x < COLS)
|
||||
this.board[cell.y][cell.x] = cell.val;
|
||||
if (cell.y < 20)
|
||||
isValid = true;
|
||||
if (cell.y < 20) isValid = true;
|
||||
}
|
||||
if (!isValid)
|
||||
this.isGameOver = true;
|
||||
if (!isValid) this.isGameOver = true;
|
||||
|
||||
this.clearLines();
|
||||
this.spawnPiece();
|
||||
@ -368,11 +402,8 @@ export default class extends Aview {
|
||||
|
||||
clearLines() {
|
||||
let linesCleared = 0;
|
||||
outer: for (let r = ROWS - 1; r >= 0; r--)
|
||||
{
|
||||
for (let c = 0; c < COLS; c++)
|
||||
if (!this.board[r][c])
|
||||
continue outer;
|
||||
outer: for (let r = ROWS - 1; r >= 0; r--) {
|
||||
for (let c = 0; c < COLS; c++) if (!this.board[r][c]) continue outer;
|
||||
|
||||
this.board.splice(r, 1);
|
||||
this.board.unshift(new Array(COLS).fill(0));
|
||||
@ -380,36 +411,30 @@ export default class extends Aview {
|
||||
r++;
|
||||
}
|
||||
|
||||
if (linesCleared > 0)
|
||||
{
|
||||
if (linesCleared > 0) {
|
||||
this.lines += linesCleared;
|
||||
// scoring like classic tetris
|
||||
const points = [0, 40, 100, 300, 1200];
|
||||
this.score += (points[linesCleared] || 0) * this.level;
|
||||
// level up every 10 lines (Fixed Goal System)
|
||||
const newLevel = Math.floor(this.lines / 10) + 1;
|
||||
if (newLevel > this.level)
|
||||
{
|
||||
if (newLevel > this.level) {
|
||||
this.level = newLevel;
|
||||
this.dropInterval = Math.max(100, 1000 - (this.level - 1) * 75);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rotatePiece(dir: 'cw' | 'ccw') {
|
||||
if (!this.piece)
|
||||
return;
|
||||
rotatePiece(dir: "cw" | "ccw") {
|
||||
if (!this.piece) return;
|
||||
// Try rotation with wall kicks
|
||||
const originalIndex = this.piece.rotationIndex;
|
||||
if (dir === 'cw')
|
||||
this.piece.rotateCW();
|
||||
else
|
||||
this.piece.rotateCCW();
|
||||
if (dir === "cw") this.piece.rotateCW();
|
||||
else this.piece.rotateCCW();
|
||||
const kicks = [0, -1, 1, -2, 2];
|
||||
for (const k of kicks) {
|
||||
this.piece.x += k;
|
||||
if (!this.collides(this.piece))
|
||||
return;
|
||||
if (!this.collides(this.piece)) return;
|
||||
this.piece.x -= k;
|
||||
}
|
||||
// no valid kick, revert
|
||||
@ -418,12 +443,10 @@ export default class extends Aview {
|
||||
}
|
||||
|
||||
movePiece(dx: number, dy: number) {
|
||||
if (!this.piece)
|
||||
return;
|
||||
if (!this.piece) return;
|
||||
this.piece.x += dx;
|
||||
this.piece.y += dy;
|
||||
if (this.collides(this.piece))
|
||||
{
|
||||
if (this.collides(this.piece)) {
|
||||
this.piece.x -= dx;
|
||||
this.piece.y -= dy;
|
||||
return false;
|
||||
@ -432,69 +455,58 @@ export default class extends Aview {
|
||||
}
|
||||
|
||||
hardDrop() {
|
||||
if (!this.piece)
|
||||
return;
|
||||
if (!this.piece) return;
|
||||
let dropped = 0;
|
||||
while (this.movePiece(0, 1))
|
||||
dropped++;
|
||||
while (this.movePiece(0, 1)) dropped++;
|
||||
this.score += dropped * 2;
|
||||
this.lockPiece();
|
||||
}
|
||||
|
||||
softDrop() {
|
||||
if (!this.piece)
|
||||
return;
|
||||
if (!this.movePiece(0, 1))
|
||||
return;
|
||||
if (!this.piece) return;
|
||||
if (!this.movePiece(0, 1)) return;
|
||||
//this.lockPiece();
|
||||
else
|
||||
this.score += 1;
|
||||
else this.score += 1;
|
||||
}
|
||||
|
||||
keys: Record<string, boolean> = {};
|
||||
|
||||
registerListeners() {
|
||||
window.addEventListener('keydown', (e) => {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
this.keys[e.key] = true;
|
||||
|
||||
if (this.isGameOver) return;
|
||||
if (e.key === 'ArrowLeft')
|
||||
this.movePiece(-1, 0);
|
||||
else if (e.key === 'ArrowRight')
|
||||
this.movePiece(1, 0);
|
||||
else if (e.key === 'ArrowDown')
|
||||
this.softDrop();
|
||||
else if (e.code === 'Space')
|
||||
{
|
||||
|
||||
if (e.key === "p" || e.key === "P" || e.key === "Escape")
|
||||
this.isPaused = !this.isPaused;
|
||||
|
||||
if (this.isPaused) return;
|
||||
|
||||
if (e.key === "ArrowLeft") this.movePiece(-1, 0);
|
||||
else if (e.key === "ArrowRight") this.movePiece(1, 0);
|
||||
else if (e.key === "ArrowDown") this.softDrop();
|
||||
else if (e.code === "Space") {
|
||||
e.preventDefault();
|
||||
this.hardDrop();
|
||||
}
|
||||
else if (e.code === 'ShiftLeft')
|
||||
{
|
||||
} else if (e.code === "ShiftLeft") {
|
||||
e.preventDefault();
|
||||
this.hold();
|
||||
}
|
||||
else if (e.key === 'x' || e.key === 'X' || e.key === 'ArrowUp')
|
||||
this.rotatePiece('cw');
|
||||
else if (e.key === 'z' || e.key === 'Z' || e.code === 'ControlLeft')
|
||||
this.rotatePiece('ccw');
|
||||
else if (e.key === 'p' || e.key === 'P' || e.key === 'Escape')
|
||||
this.isPaused = !this.isPaused;
|
||||
} else if (e.key === "x" || e.key === "X" || e.key === "ArrowUp")
|
||||
this.rotatePiece("cw");
|
||||
else if (e.key === "z" || e.key === "Z" || e.code === "ControlLeft")
|
||||
this.rotatePiece("ccw");
|
||||
});
|
||||
|
||||
document.addEventListener("keyup", e => { this.keys[e.key] = false; });
|
||||
document.addEventListener("keyup", (e) => {
|
||||
this.keys[e.key] = false;
|
||||
});
|
||||
}
|
||||
|
||||
loop(timestamp: number)
|
||||
{
|
||||
if (!this.lastDrop)
|
||||
this.lastDrop = timestamp;
|
||||
if (!this.isPaused && !this.isGameOver)
|
||||
{
|
||||
if (timestamp - this.lastDrop > this.dropInterval)
|
||||
{
|
||||
if (!this.movePiece(0, 1))
|
||||
this.lockPiece();
|
||||
loop(timestamp: number) {
|
||||
if (!this.lastDrop) this.lastDrop = timestamp;
|
||||
if (!this.isPaused && !this.isGameOver) {
|
||||
if (timestamp - this.lastDrop > this.dropInterval) {
|
||||
if (!this.movePiece(0, 1)) this.lockPiece();
|
||||
this.lastDrop = timestamp;
|
||||
}
|
||||
}
|
||||
@ -505,17 +517,15 @@ export default class extends Aview {
|
||||
drawGrid() {
|
||||
const ctx = this.ctx;
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
ctx.strokeStyle = '#222';
|
||||
for (let r = 0; r <= ROWS; r++)
|
||||
{
|
||||
ctx.strokeStyle = "#222";
|
||||
for (let r = 0; r <= ROWS; r++) {
|
||||
// horizontal lines
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, r * BLOCK);
|
||||
ctx.lineTo(COLS * BLOCK, r * BLOCK);
|
||||
ctx.stroke();
|
||||
}
|
||||
for (let c = 0; c <= COLS; c++)
|
||||
{
|
||||
for (let c = 0; c <= COLS; c++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(c * BLOCK, 0);
|
||||
ctx.lineTo(c * BLOCK, ROWS * BLOCK);
|
||||
@ -524,51 +534,48 @@ export default class extends Aview {
|
||||
}
|
||||
|
||||
drawBoard() {
|
||||
for (let r = 0; r < ROWS; r++)
|
||||
{
|
||||
for (let c = 0; c < COLS; c++)
|
||||
{
|
||||
for (let r = 0; r < ROWS; r++) {
|
||||
for (let c = 0; c < COLS; c++) {
|
||||
const val = this.board[r][c];
|
||||
if (val)
|
||||
this.fillBlock(c, r, COLORS[val]);
|
||||
else
|
||||
this.clearBlock(c, r);
|
||||
if (val) this.fillBlock(c, r, COLORS[val]);
|
||||
else this.clearBlock(c, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawPiece() {
|
||||
if (!this.piece)
|
||||
return;
|
||||
if (!this.piece) return;
|
||||
|
||||
for (const cell of this.piece.getCells())
|
||||
if (cell.y >= 0)
|
||||
this.fillBlock(cell.x, cell.y, COLORS[cell.val]);
|
||||
if (cell.y >= 0) this.fillBlock(cell.x, cell.y, COLORS[cell.val]);
|
||||
|
||||
let offset:number = this.getGhostOffset(this.piece);
|
||||
let offset: number = this.getGhostOffset(this.piece);
|
||||
for (const cell of this.piece.getCells())
|
||||
if (cell.y + offset >= 0)
|
||||
this.fillGhostBlock(cell.x, cell.y + offset, COLORS[cell.val]);
|
||||
}
|
||||
|
||||
drawHold() {
|
||||
if (!this.holdPiece)
|
||||
return ;
|
||||
if (!this.holdPiece) return;
|
||||
|
||||
this.holdCtx.clearRect(0, 0, 200, 200)
|
||||
this.holdCtx.clearRect(0, 0, 200, 200);
|
||||
let y: number = 0;
|
||||
for (const row of this.holdPiece.rotations[0])
|
||||
{
|
||||
let x:number = 0;
|
||||
for (const val of row)
|
||||
{
|
||||
if (val)
|
||||
{
|
||||
this.holdCtx.fillStyle = this.canHold ? COLORS[this.holdPiece.findColorIndex()] : "gray";
|
||||
for (const row of this.holdPiece.rotations[0]) {
|
||||
let x: number = 0;
|
||||
for (const val of row) {
|
||||
if (val) {
|
||||
this.holdCtx.fillStyle = this.canHold
|
||||
? COLORS[this.holdPiece.findColorIndex()]
|
||||
: "gray";
|
||||
this.holdCtx.fillRect(
|
||||
x * BLOCK + 1 + ((4 - this.holdPiece.rotations[0].length) * 15) + 10,
|
||||
x * BLOCK +
|
||||
1 +
|
||||
(4 - this.holdPiece.rotations[0].length) * 15 +
|
||||
10,
|
||||
y * BLOCK + 1 + 20,
|
||||
BLOCK - 2, BLOCK - 2);
|
||||
BLOCK - 2,
|
||||
BLOCK - 2,
|
||||
);
|
||||
}
|
||||
x++;
|
||||
}
|
||||
@ -576,26 +583,33 @@ export default class extends Aview {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
drawQueue() {
|
||||
this.queueCtx.clearRect(0, 0, 500, 500)
|
||||
let placement:number = 0;
|
||||
console.log(this.nextQueue.slice(0, 5))
|
||||
for (const nextPiece of this.nextQueue.slice(0, 5))
|
||||
{
|
||||
this.queueCtx.clearRect(0, 0, 500, 500);
|
||||
let placement: number = 0;
|
||||
console.log(this.nextQueue.slice(0, 5));
|
||||
for (const nextPiece of this.nextQueue.slice(0, 5)) {
|
||||
let y: number = 0;
|
||||
for (const row of TETROMINOES[nextPiece][0])
|
||||
{
|
||||
let x:number = 0;
|
||||
for (const val of row)
|
||||
{
|
||||
if (val)
|
||||
{
|
||||
this.queueCtx.fillStyle = COLORS[['I', 'J', 'L', 'O', 'S', 'T', 'Z'].indexOf(nextPiece) + 1];
|
||||
for (const row of TETROMINOES[nextPiece][0]) {
|
||||
let x: number = 0;
|
||||
for (const val of row) {
|
||||
if (val) {
|
||||
this.queueCtx.fillStyle =
|
||||
COLORS[
|
||||
["I", "J", "L", "O", "S", "T", "Z"].indexOf(nextPiece) + 1
|
||||
];
|
||||
this.queueCtx.fillRect(
|
||||
x * BLOCK + 1 + ((4 - TETROMINOES[nextPiece][0].length) * 15) + 10,
|
||||
y * BLOCK + 1 + (placement * 80) + 20 - (nextPiece === 'I' ? 15 : 0),
|
||||
BLOCK - 2, BLOCK - 2);
|
||||
x * BLOCK +
|
||||
1 +
|
||||
(4 - TETROMINOES[nextPiece][0].length) * 15 +
|
||||
10,
|
||||
y * BLOCK +
|
||||
1 +
|
||||
placement * 80 +
|
||||
20 -
|
||||
(nextPiece === "I" ? 15 : 0),
|
||||
BLOCK - 2,
|
||||
BLOCK - 2,
|
||||
);
|
||||
}
|
||||
x++;
|
||||
}
|
||||
@ -623,34 +637,40 @@ export default class extends Aview {
|
||||
|
||||
drawHUD() {
|
||||
const ctx = this.ctx;
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.6)';
|
||||
ctx.fillStyle = "rgba(0,0,0,0.6)";
|
||||
ctx.fillRect(4, 4, 120, 60);
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.font = '12px Kubasta';
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "12px Kubasta";
|
||||
ctx.fillText(`Score: ${this.score}`, 8, 20);
|
||||
ctx.fillText(`Lines: ${this.lines}`, 8, 36);
|
||||
ctx.fillText(`Level: ${this.level}`, 8, 52);
|
||||
|
||||
if (this.isPaused)
|
||||
{
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.7)';
|
||||
ctx.fillRect(0, (this.canvas.height / 2) - 24, this.canvas.width, 48);
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.font = '24px Kubasta';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('PAUSED', this.canvas.width / 2, this.canvas.height / 2 + 8);
|
||||
ctx.textAlign = 'start';
|
||||
if (this.isPaused) {
|
||||
ctx.fillStyle = "rgba(0,0,0,0.7)";
|
||||
ctx.fillRect(0, this.canvas.height / 2 - 24, this.canvas.width, 48);
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "24px Kubasta";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(
|
||||
"PAUSED",
|
||||
this.canvas.width / 2,
|
||||
this.canvas.height / 2 + 8,
|
||||
);
|
||||
ctx.textAlign = "start";
|
||||
}
|
||||
|
||||
if (this.isGameOver)
|
||||
{
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.7)';
|
||||
ctx.fillRect(0, (this.canvas.height / 2) - 36, this.canvas.width, 72);
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.font = '28px Kubasta';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('GAME OVER', this.canvas.width / 2, this.canvas.height / 2 + 8);
|
||||
ctx.textAlign = 'start';
|
||||
if (this.isGameOver) {
|
||||
ctx.fillStyle = "rgba(0,0,0,0.7)";
|
||||
ctx.fillRect(0, this.canvas.height / 2 - 36, this.canvas.width, 72);
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "28px Kubasta";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(
|
||||
"GAME OVER",
|
||||
this.canvas.width / 2,
|
||||
this.canvas.height / 2 + 8,
|
||||
);
|
||||
ctx.textAlign = "start";
|
||||
}
|
||||
}
|
||||
|
||||
@ -658,19 +678,17 @@ export default class extends Aview {
|
||||
// clear everything
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
this.ctx.fillStyle = '#000';
|
||||
this.ctx.fillStyle = "#000";
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
this.ctx.strokeStyle = '#111';
|
||||
for (let r = 0; r <= ROWS; r++)
|
||||
{
|
||||
this.ctx.strokeStyle = "#111";
|
||||
for (let r = 0; r <= ROWS; r++) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(0, r * BLOCK);
|
||||
this.ctx.lineTo(COLS * BLOCK, r * BLOCK);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
for (let c = 0; c <= COLS; c++)
|
||||
{
|
||||
for (let c = 0; c <= COLS; c++) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(c * BLOCK, 0);
|
||||
this.ctx.lineTo(c * BLOCK, ROWS * BLOCK);
|
||||
@ -683,17 +701,6 @@ export default class extends Aview {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
try {
|
||||
const canvas = document.getElementById('board');
|
||||
if (!canvas) {
|
||||
console.error('D:');
|
||||
return;
|
||||
}
|
||||
const game = new Game('board');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
const game = new Game("board");
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user