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-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="https://kanel.ovh/oneko.js"></script-->
|
||||||
<!--script src="./static/ts/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 id="app" class="flex-1 flex items-center justify-center">
|
||||||
</div>
|
</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">
|
<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]">
|
<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]">
|
||||||
<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>
|
<div class="flex px-4 items-center content-center space-x-2">
|
||||||
<a class="text-neutral-900 hover:text-neutral-700 dark:text-white dark:hover:text-neutral-400" href="/" data-link>home</a>
|
<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>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script type="module" src="/static/ts/main.ts"></script>
|
<script type="module" src="/static/ts/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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
|
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 { 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", {
|
let uuid_req = await fetch("http://localhost:3001/me", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
@ -9,63 +11,11 @@ export async function isLogged(): boolean {
|
|||||||
{
|
{
|
||||||
let uuid = await uuid_req.json();
|
let uuid = await uuid_req.json();
|
||||||
document.cookie = `uuid=${uuid.user};max-age=${60*60*24*7}`;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
else // 401
|
else // 401
|
||||||
{
|
{
|
||||||
document.cookie = `uuid=;max-age=0`;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,8 +40,6 @@ const routes = [
|
|||||||
|
|
||||||
{ path: "/login", view: () => import("./views/LoginPage.ts") },
|
{ path: "/login", view: () => import("./views/LoginPage.ts") },
|
||||||
{ path: "/register", view: () => import("./views/RegisterPage.ts") },
|
{ path: "/register", view: () => import("./views/RegisterPage.ts") },
|
||||||
|
|
||||||
{ path: "/profile", view: () => import("./views/Profile.ts") },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = async () => {
|
const router = async () => {
|
||||||
@ -117,12 +65,18 @@ const router = async () => {
|
|||||||
view.run();
|
view.run();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
document.getElementById("profile-button")?.addEventListener("click", () => {profile_view.run();});
|
||||||
window.addEventListener("popstate", router);
|
window.addEventListener("popstate", router);
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
isLogged();
|
isLogged();
|
||||||
|
|
||||||
document.body.addEventListener("click", e=> {
|
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]"))
|
if (e.target.matches("[data-link]"))
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -144,3 +98,21 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
oneko();
|
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 Aview from "./Aview.ts"
|
||||||
|
import { dragElement } from "./drag.js"
|
||||||
import { setOnekoState } from "../oneko.ts"
|
import { setOnekoState } from "../oneko.ts"
|
||||||
import { isLogged, navigationManager } from "../main.ts"
|
import { isLogged, navigationManager } from "../main.ts"
|
||||||
|
|
||||||
@ -13,22 +14,30 @@ export default class extends Aview {
|
|||||||
|
|
||||||
async getHTML() {
|
async getHTML() {
|
||||||
return `
|
return `
|
||||||
<form method="dialog" class="text-center p-10 bg-white dark:bg-neutral-800 rounded-xl shadow space-y-4 flex flex-col">
|
<div id="window" class="absolute default-border">
|
||||||
<h1 class="text-4xl font-bold text-blue-600">login</h1>
|
<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>
|
<form method="dialog" class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 space-y-4 reverse-border">
|
||||||
<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>
|
<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>
|
<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>
|
</br>
|
||||||
|
<button id="login-button" type="submit" class="default-button w-full">login</button>
|
||||||
<a class="text-gray-400 dark:text-gray-600 underline" href="/register" data-link>
|
|
||||||
register
|
|
||||||
</a>
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
|
dragElement(document.getElementById("window"));
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
const username = (document.getElementById("username") as HTMLInputElement).value;
|
const username = (document.getElementById("username") as HTMLInputElement).value;
|
||||||
const password = (document.getElementById("password") as HTMLInputElement).value;
|
const password = (document.getElementById("password") as HTMLInputElement).value;
|
||||||
|
@ -6,36 +6,81 @@ export default class extends Aview {
|
|||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
if (!isLogged())
|
|
||||||
navigationManager("/login");
|
|
||||||
this.setTitle("profile");
|
this.setTitle("profile");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getHTML() {
|
async getHTML() {
|
||||||
return `
|
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>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open: boolean = false;
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
const uuid = document.cookie.match(new RegExp('(^| )' + "uuid" + '=([^;]+)'))[2];
|
let uuid: String;
|
||||||
const userdata_req = await fetch(`http://localhost:3002/users/${uuid}`, {
|
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",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userdata_req.status == 404)
|
if (userdata_req.status == 404)
|
||||||
{
|
{
|
||||||
console.error("invalid user");
|
console.error("invalid user");
|
||||||
return ;
|
return ;
|
||||||
}
|
}
|
||||||
|
|
||||||
let userdata = await userdata_req.json();
|
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"));
|
const nametag = main.appendChild(document.createElement("span"));
|
||||||
|
|
||||||
nametag.innerHTML = `Hiiiiii ${userdata.displayName} ! :D`;
|
nametag.innerHTML = `Hiiiiii ${userdata.displayName} ! :D`;
|
||||||
@ -44,6 +89,17 @@ export default class extends Aview {
|
|||||||
const winrate = main.appendChild(document.createElement("div"));
|
const winrate = main.appendChild(document.createElement("div"));
|
||||||
|
|
||||||
winrate.innerHTML = `wins: ${userdata.wins} | losses: ${userdata.losses} | winrate: ${userdata.wins / (userdata.wins + userdata.losses)}`;
|
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 Aview from "./Aview.ts"
|
||||||
import { setOnekoState } from "../oneko.ts"
|
import { setOnekoState } from "../oneko.ts"
|
||||||
import { isLogged, navigationManager } from "../main.ts"
|
import { isLogged, navigationManager } from "../main.ts"
|
||||||
|
import { dragElement } from "./drag.ts";
|
||||||
|
|
||||||
export default class extends Aview {
|
export default class extends Aview {
|
||||||
|
|
||||||
@ -13,22 +14,30 @@ export default class extends Aview {
|
|||||||
|
|
||||||
async getHTML() {
|
async getHTML() {
|
||||||
return `
|
return `
|
||||||
<form method="dialog" class="text-center p-10 bg-white dark:bg-neutral-800 rounded-xl shadow space-y-4 flex flex-col">
|
<div id="window" class="absolute default-border">
|
||||||
<h1 class="text-4xl font-bold text-blue-600">register</h1>
|
<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>
|
<form method="dialog" class="bg-neutral-200 dark:bg-neutral-800 text-center pb-10 pt-5 px-10 space-y-4 reverse-border">
|
||||||
<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>
|
<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>
|
<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>
|
</br>
|
||||||
|
<button id="register-button" type="submit" class="default-button w-full">register</button>
|
||||||
<a class="text-gray-400 dark:text-gray-600 underline" href="/login" data-link>
|
|
||||||
i already have an account
|
|
||||||
</a>
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
|
dragElement(document.getElementById("window"));
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
const username = (document.getElementById("username") as HTMLInputElement).value;
|
const username = (document.getElementById("username") as HTMLInputElement).value;
|
||||||
const password = (document.getElementById("password") 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 {
|
export default class extends Aview {
|
||||||
|
|
||||||
running: boolean;
|
running: boolean;
|
||||||
|
|
||||||
constructor()
|
constructor() {
|
||||||
{
|
|
||||||
super();
|
super();
|
||||||
this.setTitle("tetris (local match)");
|
this.setTitle("tetris (local match)");
|
||||||
this.running = true;
|
this.running = true;
|
||||||
@ -40,6 +39,7 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
|
dragElement(document.getElementById("window"));
|
||||||
const COLS = 10;
|
const COLS = 10;
|
||||||
const ROWS = 20;
|
const ROWS = 20;
|
||||||
const BLOCK = 30; // pixels per block
|
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
|
// Tetromino definitions: each piece is an array of rotations, each rotation is a 2D matrix
|
||||||
const TETROMINOES: { [key: string]: number[][][] } = {
|
const TETROMINOES: { [key: string]: number[][][] } = {
|
||||||
I: [
|
I: [
|
||||||
[[0,0,0,0]
|
[
|
||||||
,[1,1,1,1]
|
[0, 0, 0, 0],
|
||||||
,[0,0,0,0]
|
[1, 1, 1, 1],
|
||||||
,[0,0,0,0]],
|
[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]
|
[0, 0, 0, 0],
|
||||||
,[1,1,1,1]
|
[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: [
|
J: [
|
||||||
[[2,0,0]
|
[
|
||||||
,[2,2,2]
|
[2, 0, 0],
|
||||||
,[0,0,0]],
|
[2, 2, 2],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,2,2]
|
[
|
||||||
,[0,2,0]
|
[0, 2, 2],
|
||||||
,[0,2,0]],
|
[0, 2, 0],
|
||||||
|
[0, 2, 0],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,0,0]
|
[
|
||||||
,[2,2,2]
|
[0, 0, 0],
|
||||||
,[0,0,2]],
|
[2, 2, 2],
|
||||||
|
[0, 0, 2],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,2,0]
|
[
|
||||||
,[0,2,0]
|
[0, 2, 0],
|
||||||
,[2,2,0]],
|
[0, 2, 0],
|
||||||
|
[2, 2, 0],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
L: [
|
L: [
|
||||||
[[0,0,3]
|
[
|
||||||
,[3,3,3]
|
[0, 0, 3],
|
||||||
,[0,0,0]],
|
[3, 3, 3],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,3,0]
|
[
|
||||||
,[0,3,0]
|
[0, 3, 0],
|
||||||
,[0,3,3]],
|
[0, 3, 0],
|
||||||
|
[0, 3, 3],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,0,0]
|
[
|
||||||
,[3,3,3]
|
[0, 0, 0],
|
||||||
,[3,0,0]],
|
[3, 3, 3],
|
||||||
|
[3, 0, 0],
|
||||||
|
],
|
||||||
|
|
||||||
[[3,3,0]
|
[
|
||||||
,[0,3,0]
|
[3, 3, 0],
|
||||||
,[0,3,0]],
|
[0, 3, 0],
|
||||||
|
[0, 3, 0],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
O: [
|
O: [
|
||||||
[[4,4]
|
[
|
||||||
,[4,4]],
|
[4, 4],
|
||||||
|
[4, 4],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
S: [
|
S: [
|
||||||
[[0,5,5]
|
[
|
||||||
,[5,5,0]
|
[0, 5, 5],
|
||||||
,[0,0,0]],
|
[5, 5, 0],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,5,0]
|
[
|
||||||
,[0,5,5]
|
[0, 5, 0],
|
||||||
,[0,0,5]],
|
[0, 5, 5],
|
||||||
|
[0, 0, 5],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,0,0]
|
[
|
||||||
,[0,5,5]
|
[0, 0, 0],
|
||||||
,[5,5,0]],
|
[0, 5, 5],
|
||||||
|
[5, 5, 0],
|
||||||
[[5,0,0]
|
],
|
||||||
,[5,5,0]
|
|
||||||
,[0,5,0]],
|
|
||||||
|
|
||||||
|
[
|
||||||
|
[5, 0, 0],
|
||||||
|
[5, 5, 0],
|
||||||
|
[0, 5, 0],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
T: [
|
T: [
|
||||||
[[0,6,0]
|
[
|
||||||
,[6,6,6]
|
[0, 6, 0],
|
||||||
,[0,0,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, 0, 0],
|
||||||
,[0,6,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: [
|
Z: [
|
||||||
[[7,7,0]
|
[
|
||||||
,[0,7,7]
|
[7, 7, 0],
|
||||||
,[0,0,0]],
|
[0, 7, 7],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,0,7]
|
[
|
||||||
,[0,7,7]
|
[0, 0, 7],
|
||||||
,[0,7,0]],
|
[0, 7, 7],
|
||||||
|
[0, 7, 0],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,0,0]
|
[
|
||||||
,[7,7,0]
|
[0, 0, 0],
|
||||||
,[0,7,7]],
|
[7, 7, 0],
|
||||||
|
[0, 7, 7],
|
||||||
|
],
|
||||||
|
|
||||||
[[0,7,0]
|
[
|
||||||
,[7,7,0]
|
[0, 7, 0],
|
||||||
,[7,0,0]],
|
[7, 7, 0],
|
||||||
|
[7, 0, 0],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
'#000000', // placeholder for 0
|
"#000000", // placeholder for 0
|
||||||
'#00ffff', // I - cyan
|
"#00ffff", // I - cyan
|
||||||
'#0000ff', // J - blue
|
"#0000ff", // J - blue
|
||||||
'#ff7f00', // L - orange
|
"#ff7f00", // L - orange
|
||||||
'#ffff00', // O - yellow
|
"#ffff00", // O - yellow
|
||||||
'#00ff00', // S - green
|
"#00ff00", // S - green
|
||||||
'#800080', // T - purple
|
"#800080", // T - purple
|
||||||
'#ff0000', // Z - red
|
"#ff0000", // Z - red
|
||||||
];
|
];
|
||||||
|
|
||||||
class Piece {
|
class Piece {
|
||||||
@ -191,10 +240,7 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findColorIndex() {
|
findColorIndex() {
|
||||||
for (const row of this.shape)
|
for (const row of this.shape) for (const v of row) if (v) return v;
|
||||||
for (const v of row)
|
|
||||||
if (v)
|
|
||||||
return v;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,20 +250,19 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rotateCCW() {
|
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];
|
this.shape = this.rotations[this.rotationIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
getCells(): { x: number; y: number; val: number }[] {
|
getCells(): { x: number; y: number; val: number }[] {
|
||||||
const cells: { 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 r = 0; r < this.shape.length; r++) {
|
||||||
{
|
for (let c = 0; c < this.shape[r].length; c++) {
|
||||||
for (let c = 0; c < this.shape[r].length; c++)
|
|
||||||
{
|
|
||||||
const val = this.shape[r][c];
|
const val = this.shape[r][c];
|
||||||
if (val)
|
if (val) cells.push({ x: this.x + c, y: this.y + r, val });
|
||||||
cells.push({ x: this.x + c, y: this.y + r, val });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cells;
|
return cells;
|
||||||
@ -226,9 +271,9 @@ export default class extends Aview {
|
|||||||
|
|
||||||
class Game {
|
class Game {
|
||||||
board: Cell[][];
|
board: Cell[][];
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement | null;
|
||||||
holdCanvas: HTMLCanvasElement;
|
holdCanvas: HTMLCanvasElement | null;
|
||||||
queueCanvas: HTMLCanvasElement;
|
queueCanvas: HTMLCanvasElement | null;
|
||||||
ctx: CanvasRenderingContext2D;
|
ctx: CanvasRenderingContext2D;
|
||||||
holdCtx: CanvasRenderingContext2D;
|
holdCtx: CanvasRenderingContext2D;
|
||||||
queueCtx: CanvasRenderingContext2D;
|
queueCtx: CanvasRenderingContext2D;
|
||||||
@ -245,17 +290,19 @@ export default class extends Aview {
|
|||||||
isPaused = false;
|
isPaused = false;
|
||||||
|
|
||||||
constructor(canvasId: string) {
|
constructor(canvasId: string) {
|
||||||
const el = document.getElementById(canvasId);
|
const el = document.getElementById(
|
||||||
|
canvasId,
|
||||||
|
) as HTMLCanvasElement | null;
|
||||||
this.canvas = el;
|
this.canvas = el;
|
||||||
this.canvas.width = COLS * BLOCK;
|
this.canvas.width = COLS * BLOCK;
|
||||||
this.canvas.height = ROWS * BLOCK;
|
this.canvas.height = ROWS * BLOCK;
|
||||||
const ctx = this.canvas.getContext('2d');
|
const ctx = this.canvas.getContext("2d");
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
|
|
||||||
this.holdCanvas = document.getElementById('hold');
|
this.holdCanvas = document.getElementById("hold");
|
||||||
this.queueCanvas = document.getElementById('queue');
|
this.queueCanvas = document.getElementById("queue");
|
||||||
this.holdCtx = this.holdCanvas.getContext('2d');
|
this.holdCtx = this.holdCanvas.getContext("2d");
|
||||||
this.queueCtx = this.queueCanvas.getContext('2d');
|
this.queueCtx = this.queueCanvas.getContext("2d");
|
||||||
|
|
||||||
this.board = this.createEmptyBoard();
|
this.board = this.createEmptyBoard();
|
||||||
this.fillBag();
|
this.fillBag();
|
||||||
@ -267,8 +314,7 @@ export default class extends Aview {
|
|||||||
|
|
||||||
createEmptyBoard(): Cell[][] {
|
createEmptyBoard(): Cell[][] {
|
||||||
const b: 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);
|
const row: Cell[] = new Array(COLS).fill(0);
|
||||||
b.push(row);
|
b.push(row);
|
||||||
}
|
}
|
||||||
@ -279,8 +325,7 @@ export default class extends Aview {
|
|||||||
// classic 7-bag randomizer
|
// classic 7-bag randomizer
|
||||||
const pieces = Object.keys(TETROMINOES);
|
const pieces = Object.keys(TETROMINOES);
|
||||||
const bag = [...pieces];
|
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));
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
[bag[i], bag[j]] = [bag[j], bag[i]];
|
[bag[i], bag[j]] = [bag[j], bag[i]];
|
||||||
}
|
}
|
||||||
@ -288,12 +333,10 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hold() {
|
hold() {
|
||||||
if (!this.canHold)
|
if (!this.canHold) return;
|
||||||
return;
|
|
||||||
|
|
||||||
[this.piece, this.holdPiece] = [this.holdPiece, this.piece];
|
[this.piece, this.holdPiece] = [this.holdPiece, this.piece];
|
||||||
if (!this.piece)
|
if (!this.piece) this.spawnPiece();
|
||||||
this.spawnPiece();
|
|
||||||
|
|
||||||
this.piece.x = Math.floor((COLS - this.piece.shape[0].length) / 2);
|
this.piece.x = Math.floor((COLS - this.piece.shape[0].length) / 2);
|
||||||
this.piece.y = -2;
|
this.piece.y = -2;
|
||||||
@ -306,13 +349,11 @@ export default class extends Aview {
|
|||||||
|
|
||||||
spawnPiece() {
|
spawnPiece() {
|
||||||
this.canHold = true;
|
this.canHold = true;
|
||||||
if (this.nextQueue.length < 7)
|
if (this.nextQueue.length < 7) this.fillBag();
|
||||||
this.fillBag();
|
|
||||||
const type = this.nextQueue.shift()!;
|
const type = this.nextQueue.shift()!;
|
||||||
this.piece = new Piece(type);
|
this.piece = new Piece(type);
|
||||||
// If spawn collides immediately -> game over
|
// If spawn collides immediately -> game over
|
||||||
if (this.collides(this.piece))
|
if (this.collides(this.piece)) {
|
||||||
{
|
|
||||||
this.isGameOver = true;
|
this.isGameOver = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,26 +362,23 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
collides(piece: Piece): boolean {
|
collides(piece: Piece): boolean {
|
||||||
for (const cell of piece.getCells())
|
for (const cell of piece.getCells()) {
|
||||||
{
|
if (cell.y >= ROWS) return true;
|
||||||
if (cell.y >= ROWS)
|
if (cell.x < 0 || cell.x >= COLS) return true;
|
||||||
return true;
|
if (cell.y >= 0 && this.board[cell.y][cell.x]) 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getGhostOffset(piece: Piece): boolean {
|
getGhostOffset(piece: Piece): number {
|
||||||
let y: number = 0;
|
let y: number = 0;
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
for (const cell of piece.getCells()) {
|
||||||
for (const cell of piece.getCells())
|
|
||||||
{
|
|
||||||
console.log(cell.y + y);
|
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;
|
return y - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,18 +387,14 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lockPiece() {
|
lockPiece() {
|
||||||
if (!this.piece)
|
if (!this.piece) return;
|
||||||
return;
|
|
||||||
let isValid: boolean = false;
|
let isValid: boolean = false;
|
||||||
for (const cell of this.piece.getCells())
|
for (const cell of this.piece.getCells()) {
|
||||||
{
|
|
||||||
if (cell.y >= 0 && cell.y < ROWS && cell.x >= 0 && cell.x < COLS)
|
if (cell.y >= 0 && cell.y < ROWS && cell.x >= 0 && cell.x < COLS)
|
||||||
this.board[cell.y][cell.x] = cell.val;
|
this.board[cell.y][cell.x] = cell.val;
|
||||||
if (cell.y < 20)
|
if (cell.y < 20) isValid = true;
|
||||||
isValid = true;
|
|
||||||
}
|
}
|
||||||
if (!isValid)
|
if (!isValid) this.isGameOver = true;
|
||||||
this.isGameOver = true;
|
|
||||||
|
|
||||||
this.clearLines();
|
this.clearLines();
|
||||||
this.spawnPiece();
|
this.spawnPiece();
|
||||||
@ -368,11 +402,8 @@ export default class extends Aview {
|
|||||||
|
|
||||||
clearLines() {
|
clearLines() {
|
||||||
let linesCleared = 0;
|
let linesCleared = 0;
|
||||||
outer: for (let r = ROWS - 1; r >= 0; r--)
|
outer: for (let r = ROWS - 1; r >= 0; r--) {
|
||||||
{
|
for (let c = 0; c < COLS; c++) if (!this.board[r][c]) continue outer;
|
||||||
for (let c = 0; c < COLS; c++)
|
|
||||||
if (!this.board[r][c])
|
|
||||||
continue outer;
|
|
||||||
|
|
||||||
this.board.splice(r, 1);
|
this.board.splice(r, 1);
|
||||||
this.board.unshift(new Array(COLS).fill(0));
|
this.board.unshift(new Array(COLS).fill(0));
|
||||||
@ -380,36 +411,30 @@ export default class extends Aview {
|
|||||||
r++;
|
r++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (linesCleared > 0)
|
if (linesCleared > 0) {
|
||||||
{
|
|
||||||
this.lines += linesCleared;
|
this.lines += linesCleared;
|
||||||
// scoring like classic tetris
|
// scoring like classic tetris
|
||||||
const points = [0, 40, 100, 300, 1200];
|
const points = [0, 40, 100, 300, 1200];
|
||||||
this.score += (points[linesCleared] || 0) * this.level;
|
this.score += (points[linesCleared] || 0) * this.level;
|
||||||
// level up every 10 lines (Fixed Goal System)
|
// level up every 10 lines (Fixed Goal System)
|
||||||
const newLevel = Math.floor(this.lines / 10) + 1;
|
const newLevel = Math.floor(this.lines / 10) + 1;
|
||||||
if (newLevel > this.level)
|
if (newLevel > this.level) {
|
||||||
{
|
|
||||||
this.level = newLevel;
|
this.level = newLevel;
|
||||||
this.dropInterval = Math.max(100, 1000 - (this.level - 1) * 75);
|
this.dropInterval = Math.max(100, 1000 - (this.level - 1) * 75);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rotatePiece(dir: 'cw' | 'ccw') {
|
rotatePiece(dir: "cw" | "ccw") {
|
||||||
if (!this.piece)
|
if (!this.piece) return;
|
||||||
return;
|
|
||||||
// Try rotation with wall kicks
|
// Try rotation with wall kicks
|
||||||
const originalIndex = this.piece.rotationIndex;
|
const originalIndex = this.piece.rotationIndex;
|
||||||
if (dir === 'cw')
|
if (dir === "cw") this.piece.rotateCW();
|
||||||
this.piece.rotateCW();
|
else this.piece.rotateCCW();
|
||||||
else
|
|
||||||
this.piece.rotateCCW();
|
|
||||||
const kicks = [0, -1, 1, -2, 2];
|
const kicks = [0, -1, 1, -2, 2];
|
||||||
for (const k of kicks) {
|
for (const k of kicks) {
|
||||||
this.piece.x += k;
|
this.piece.x += k;
|
||||||
if (!this.collides(this.piece))
|
if (!this.collides(this.piece)) return;
|
||||||
return;
|
|
||||||
this.piece.x -= k;
|
this.piece.x -= k;
|
||||||
}
|
}
|
||||||
// no valid kick, revert
|
// no valid kick, revert
|
||||||
@ -418,12 +443,10 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
movePiece(dx: number, dy: number) {
|
movePiece(dx: number, dy: number) {
|
||||||
if (!this.piece)
|
if (!this.piece) return;
|
||||||
return;
|
|
||||||
this.piece.x += dx;
|
this.piece.x += dx;
|
||||||
this.piece.y += dy;
|
this.piece.y += dy;
|
||||||
if (this.collides(this.piece))
|
if (this.collides(this.piece)) {
|
||||||
{
|
|
||||||
this.piece.x -= dx;
|
this.piece.x -= dx;
|
||||||
this.piece.y -= dy;
|
this.piece.y -= dy;
|
||||||
return false;
|
return false;
|
||||||
@ -432,69 +455,58 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hardDrop() {
|
hardDrop() {
|
||||||
if (!this.piece)
|
if (!this.piece) return;
|
||||||
return;
|
|
||||||
let dropped = 0;
|
let dropped = 0;
|
||||||
while (this.movePiece(0, 1))
|
while (this.movePiece(0, 1)) dropped++;
|
||||||
dropped++;
|
|
||||||
this.score += dropped * 2;
|
this.score += dropped * 2;
|
||||||
this.lockPiece();
|
this.lockPiece();
|
||||||
}
|
}
|
||||||
|
|
||||||
softDrop() {
|
softDrop() {
|
||||||
if (!this.piece)
|
if (!this.piece) return;
|
||||||
return;
|
if (!this.movePiece(0, 1)) return;
|
||||||
if (!this.movePiece(0, 1))
|
|
||||||
return;
|
|
||||||
//this.lockPiece();
|
//this.lockPiece();
|
||||||
else
|
else this.score += 1;
|
||||||
this.score += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keys: Record<string, boolean> = {};
|
keys: Record<string, boolean> = {};
|
||||||
|
|
||||||
registerListeners() {
|
registerListeners() {
|
||||||
window.addEventListener('keydown', (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
this.keys[e.key] = true;
|
this.keys[e.key] = true;
|
||||||
|
|
||||||
if (this.isGameOver) return;
|
if (this.isGameOver) return;
|
||||||
if (e.key === 'ArrowLeft')
|
|
||||||
this.movePiece(-1, 0);
|
if (e.key === "p" || e.key === "P" || e.key === "Escape")
|
||||||
else if (e.key === 'ArrowRight')
|
this.isPaused = !this.isPaused;
|
||||||
this.movePiece(1, 0);
|
|
||||||
else if (e.key === 'ArrowDown')
|
if (this.isPaused) return;
|
||||||
this.softDrop();
|
|
||||||
else if (e.code === 'Space')
|
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();
|
e.preventDefault();
|
||||||
this.hardDrop();
|
this.hardDrop();
|
||||||
}
|
} else if (e.code === "ShiftLeft") {
|
||||||
else if (e.code === 'ShiftLeft')
|
|
||||||
{
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.hold();
|
this.hold();
|
||||||
}
|
} else if (e.key === "x" || e.key === "X" || e.key === "ArrowUp")
|
||||||
else if (e.key === 'x' || e.key === 'X' || e.key === 'ArrowUp')
|
this.rotatePiece("cw");
|
||||||
this.rotatePiece('cw');
|
else if (e.key === "z" || e.key === "Z" || e.code === "ControlLeft")
|
||||||
else if (e.key === 'z' || e.key === 'Z' || e.code === 'ControlLeft')
|
this.rotatePiece("ccw");
|
||||||
this.rotatePiece('ccw');
|
|
||||||
else if (e.key === 'p' || e.key === 'P' || e.key === 'Escape')
|
|
||||||
this.isPaused = !this.isPaused;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("keyup", e => { this.keys[e.key] = false; });
|
document.addEventListener("keyup", (e) => {
|
||||||
|
this.keys[e.key] = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loop(timestamp: number)
|
loop(timestamp: number) {
|
||||||
{
|
if (!this.lastDrop) this.lastDrop = timestamp;
|
||||||
if (!this.lastDrop)
|
if (!this.isPaused && !this.isGameOver) {
|
||||||
this.lastDrop = timestamp;
|
if (timestamp - this.lastDrop > this.dropInterval) {
|
||||||
if (!this.isPaused && !this.isGameOver)
|
if (!this.movePiece(0, 1)) this.lockPiece();
|
||||||
{
|
|
||||||
if (timestamp - this.lastDrop > this.dropInterval)
|
|
||||||
{
|
|
||||||
if (!this.movePiece(0, 1))
|
|
||||||
this.lockPiece();
|
|
||||||
this.lastDrop = timestamp;
|
this.lastDrop = timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -505,17 +517,15 @@ export default class extends Aview {
|
|||||||
drawGrid() {
|
drawGrid() {
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
ctx.strokeStyle = '#222';
|
ctx.strokeStyle = "#222";
|
||||||
for (let r = 0; r <= ROWS; r++)
|
for (let r = 0; r <= ROWS; r++) {
|
||||||
{
|
|
||||||
// horizontal lines
|
// horizontal lines
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(0, r * BLOCK);
|
ctx.moveTo(0, r * BLOCK);
|
||||||
ctx.lineTo(COLS * BLOCK, r * BLOCK);
|
ctx.lineTo(COLS * BLOCK, r * BLOCK);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
for (let c = 0; c <= COLS; c++)
|
for (let c = 0; c <= COLS; c++) {
|
||||||
{
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(c * BLOCK, 0);
|
ctx.moveTo(c * BLOCK, 0);
|
||||||
ctx.lineTo(c * BLOCK, ROWS * BLOCK);
|
ctx.lineTo(c * BLOCK, ROWS * BLOCK);
|
||||||
@ -524,26 +534,20 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
drawBoard() {
|
drawBoard() {
|
||||||
for (let r = 0; r < ROWS; r++)
|
for (let r = 0; r < ROWS; r++) {
|
||||||
{
|
for (let c = 0; c < COLS; c++) {
|
||||||
for (let c = 0; c < COLS; c++)
|
|
||||||
{
|
|
||||||
const val = this.board[r][c];
|
const val = this.board[r][c];
|
||||||
if (val)
|
if (val) this.fillBlock(c, r, COLORS[val]);
|
||||||
this.fillBlock(c, r, COLORS[val]);
|
else this.clearBlock(c, r);
|
||||||
else
|
|
||||||
this.clearBlock(c, r);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawPiece() {
|
drawPiece() {
|
||||||
if (!this.piece)
|
if (!this.piece) return;
|
||||||
return;
|
|
||||||
|
|
||||||
for (const cell of this.piece.getCells())
|
for (const cell of this.piece.getCells())
|
||||||
if (cell.y >= 0)
|
if (cell.y >= 0) this.fillBlock(cell.x, cell.y, COLORS[cell.val]);
|
||||||
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())
|
for (const cell of this.piece.getCells())
|
||||||
@ -552,23 +556,26 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
drawHold() {
|
drawHold() {
|
||||||
if (!this.holdPiece)
|
if (!this.holdPiece) return;
|
||||||
return ;
|
|
||||||
|
|
||||||
this.holdCtx.clearRect(0, 0, 200, 200)
|
this.holdCtx.clearRect(0, 0, 200, 200);
|
||||||
let y: number = 0;
|
let y: number = 0;
|
||||||
for (const row of this.holdPiece.rotations[0])
|
for (const row of this.holdPiece.rotations[0]) {
|
||||||
{
|
|
||||||
let x: number = 0;
|
let x: number = 0;
|
||||||
for (const val of row)
|
for (const val of row) {
|
||||||
{
|
if (val) {
|
||||||
if (val)
|
this.holdCtx.fillStyle = this.canHold
|
||||||
{
|
? COLORS[this.holdPiece.findColorIndex()]
|
||||||
this.holdCtx.fillStyle = this.canHold ? COLORS[this.holdPiece.findColorIndex()] : "gray";
|
: "gray";
|
||||||
this.holdCtx.fillRect(
|
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,
|
y * BLOCK + 1 + 20,
|
||||||
BLOCK - 2, BLOCK - 2);
|
BLOCK - 2,
|
||||||
|
BLOCK - 2,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
x++;
|
x++;
|
||||||
}
|
}
|
||||||
@ -576,26 +583,33 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
drawQueue() {
|
drawQueue() {
|
||||||
this.queueCtx.clearRect(0, 0, 500, 500)
|
this.queueCtx.clearRect(0, 0, 500, 500);
|
||||||
let placement: number = 0;
|
let placement: number = 0;
|
||||||
console.log(this.nextQueue.slice(0, 5))
|
console.log(this.nextQueue.slice(0, 5));
|
||||||
for (const nextPiece of this.nextQueue.slice(0, 5))
|
for (const nextPiece of this.nextQueue.slice(0, 5)) {
|
||||||
{
|
|
||||||
let y: number = 0;
|
let y: number = 0;
|
||||||
for (const row of TETROMINOES[nextPiece][0])
|
for (const row of TETROMINOES[nextPiece][0]) {
|
||||||
{
|
|
||||||
let x: number = 0;
|
let x: number = 0;
|
||||||
for (const val of row)
|
for (const val of row) {
|
||||||
{
|
if (val) {
|
||||||
if (val)
|
this.queueCtx.fillStyle =
|
||||||
{
|
COLORS[
|
||||||
this.queueCtx.fillStyle = COLORS[['I', 'J', 'L', 'O', 'S', 'T', 'Z'].indexOf(nextPiece) + 1];
|
["I", "J", "L", "O", "S", "T", "Z"].indexOf(nextPiece) + 1
|
||||||
|
];
|
||||||
this.queueCtx.fillRect(
|
this.queueCtx.fillRect(
|
||||||
x * BLOCK + 1 + ((4 - TETROMINOES[nextPiece][0].length) * 15) + 10,
|
x * BLOCK +
|
||||||
y * BLOCK + 1 + (placement * 80) + 20 - (nextPiece === 'I' ? 15 : 0),
|
1 +
|
||||||
BLOCK - 2, BLOCK - 2);
|
(4 - TETROMINOES[nextPiece][0].length) * 15 +
|
||||||
|
10,
|
||||||
|
y * BLOCK +
|
||||||
|
1 +
|
||||||
|
placement * 80 +
|
||||||
|
20 -
|
||||||
|
(nextPiece === "I" ? 15 : 0),
|
||||||
|
BLOCK - 2,
|
||||||
|
BLOCK - 2,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
x++;
|
x++;
|
||||||
}
|
}
|
||||||
@ -623,34 +637,40 @@ export default class extends Aview {
|
|||||||
|
|
||||||
drawHUD() {
|
drawHUD() {
|
||||||
const ctx = this.ctx;
|
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.fillRect(4, 4, 120, 60);
|
||||||
ctx.fillStyle = '#fff';
|
ctx.fillStyle = "#fff";
|
||||||
ctx.font = '12px Kubasta';
|
ctx.font = "12px Kubasta";
|
||||||
ctx.fillText(`Score: ${this.score}`, 8, 20);
|
ctx.fillText(`Score: ${this.score}`, 8, 20);
|
||||||
ctx.fillText(`Lines: ${this.lines}`, 8, 36);
|
ctx.fillText(`Lines: ${this.lines}`, 8, 36);
|
||||||
ctx.fillText(`Level: ${this.level}`, 8, 52);
|
ctx.fillText(`Level: ${this.level}`, 8, 52);
|
||||||
|
|
||||||
if (this.isPaused)
|
if (this.isPaused) {
|
||||||
{
|
ctx.fillStyle = "rgba(0,0,0,0.7)";
|
||||||
ctx.fillStyle = 'rgba(0,0,0,0.7)';
|
ctx.fillRect(0, this.canvas.height / 2 - 24, this.canvas.width, 48);
|
||||||
ctx.fillRect(0, (this.canvas.height / 2) - 24, this.canvas.width, 48);
|
ctx.fillStyle = "#fff";
|
||||||
ctx.fillStyle = '#fff';
|
ctx.font = "24px Kubasta";
|
||||||
ctx.font = '24px Kubasta';
|
ctx.textAlign = "center";
|
||||||
ctx.textAlign = 'center';
|
ctx.fillText(
|
||||||
ctx.fillText('PAUSED', this.canvas.width / 2, this.canvas.height / 2 + 8);
|
"PAUSED",
|
||||||
ctx.textAlign = 'start';
|
this.canvas.width / 2,
|
||||||
|
this.canvas.height / 2 + 8,
|
||||||
|
);
|
||||||
|
ctx.textAlign = "start";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isGameOver)
|
if (this.isGameOver) {
|
||||||
{
|
ctx.fillStyle = "rgba(0,0,0,0.7)";
|
||||||
ctx.fillStyle = 'rgba(0,0,0,0.7)';
|
ctx.fillRect(0, this.canvas.height / 2 - 36, this.canvas.width, 72);
|
||||||
ctx.fillRect(0, (this.canvas.height / 2) - 36, this.canvas.width, 72);
|
ctx.fillStyle = "#fff";
|
||||||
ctx.fillStyle = '#fff';
|
ctx.font = "28px Kubasta";
|
||||||
ctx.font = '28px Kubasta';
|
ctx.textAlign = "center";
|
||||||
ctx.textAlign = 'center';
|
ctx.fillText(
|
||||||
ctx.fillText('GAME OVER', this.canvas.width / 2, this.canvas.height / 2 + 8);
|
"GAME OVER",
|
||||||
ctx.textAlign = 'start';
|
this.canvas.width / 2,
|
||||||
|
this.canvas.height / 2 + 8,
|
||||||
|
);
|
||||||
|
ctx.textAlign = "start";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -658,19 +678,17 @@ export default class extends Aview {
|
|||||||
// clear everything
|
// clear everything
|
||||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
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.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
|
||||||
this.ctx.strokeStyle = '#111';
|
this.ctx.strokeStyle = "#111";
|
||||||
for (let r = 0; r <= ROWS; r++)
|
for (let r = 0; r <= ROWS; r++) {
|
||||||
{
|
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
this.ctx.moveTo(0, r * BLOCK);
|
this.ctx.moveTo(0, r * BLOCK);
|
||||||
this.ctx.lineTo(COLS * BLOCK, r * BLOCK);
|
this.ctx.lineTo(COLS * BLOCK, r * BLOCK);
|
||||||
this.ctx.stroke();
|
this.ctx.stroke();
|
||||||
}
|
}
|
||||||
for (let c = 0; c <= COLS; c++)
|
for (let c = 0; c <= COLS; c++) {
|
||||||
{
|
|
||||||
this.ctx.beginPath();
|
this.ctx.beginPath();
|
||||||
this.ctx.moveTo(c * BLOCK, 0);
|
this.ctx.moveTo(c * BLOCK, 0);
|
||||||
this.ctx.lineTo(c * BLOCK, ROWS * BLOCK);
|
this.ctx.lineTo(c * BLOCK, ROWS * BLOCK);
|
||||||
@ -683,17 +701,6 @@ export default class extends Aview {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
const game = new Game("board");
|
||||||
try {
|
|
||||||
const canvas = document.getElementById('board');
|
|
||||||
if (!canvas) {
|
|
||||||
console.error('D:');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const game = new Game('board');
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user