Sigma Ligma on the wall
This commit is contained in:
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
Generated
+4
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
@@ -1,19 +1 @@
|
|||||||
[
|
[]
|
||||||
{
|
|
||||||
"username": "jerome",
|
|
||||||
"password": "Sigma",
|
|
||||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb3dlcl91c2VybmFtZSI6Implcm9tZSIsImlhdCI6MTc0NDg4OTY3MSwiZXhwIjoxNzQ0ODkxNDcxfQ.zYg3uxh_iNZ2xdEma8p3p-URT3Xa7wnYiQyOx0-QTac",
|
|
||||||
"money": 400
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "Gianimon",
|
|
||||||
"password": "gianimon",
|
|
||||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkdpYW5pbW9uIiwiaWF0IjoxNzQ0ODc0NTczLCJleHAiOjE3NDQ4NzYzNzN9.dGp5WTKIDVhomKZ8J7nevz8O7PqwRhR9wEgeyDtL_XE",
|
|
||||||
"money": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"username": "any",
|
|
||||||
"password": "any",
|
|
||||||
"money": 300
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|||||||
+1199
-86
File diff suppressed because it is too large
Load Diff
+5
-1
@@ -12,11 +12,15 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
"cookie-parser": "^1.4.7",
|
"cookie-parser": "^1.4.7",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"swagger-ui-express": "^5.0.1"
|
"sqlite": "^5.1.1",
|
||||||
|
"sqlite3": "^6.0.1",
|
||||||
|
"swagger-ui-express": "^5.0.1",
|
||||||
|
"ws": "^8.20.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"swagger-autogen": "^2.23.7"
|
"swagger-autogen": "^2.23.7"
|
||||||
|
|||||||
+264
-93
@@ -3,50 +3,204 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const balance = document.getElementById("balance");
|
const balance = document.getElementById("balance");
|
||||||
const hostButton = document.getElementById("host");
|
const hostButton = document.getElementById("host");
|
||||||
const joinButton = document.getElementById("join");
|
const joinButton = document.getElementById("join");
|
||||||
|
const hostForm = document.getElementById("host-form");
|
||||||
|
const createLobbyButton = document.getElementById("create-lobby");
|
||||||
|
const lobbyVisibilityInputs = document.querySelectorAll("input[name='lobby-visibility']");
|
||||||
const joinForm = document.getElementById("join-form");
|
const joinForm = document.getElementById("join-form");
|
||||||
const joinCodeInput = document.getElementById("join-code");
|
const joinCodeInput = document.getElementById("join-code");
|
||||||
const joinSubmit = document.getElementById("submit-join");
|
const joinSubmit = document.getElementById("submit-join");
|
||||||
|
const refreshLobbies = document.getElementById("refresh-lobbies");
|
||||||
|
const publicLobbies = document.getElementById("public-lobbies");
|
||||||
const leaveButton = document.getElementById("leave-button");
|
const leaveButton = document.getElementById("leave-button");
|
||||||
let playerUpdateInterval = null;
|
const joinError = document.getElementById("join-error");
|
||||||
|
const lobbyRoom = document.getElementById("lobby-room");
|
||||||
|
const lobbyIdDisplay = document.getElementById("lobby-id");
|
||||||
|
const playerList = document.getElementById("player-list");
|
||||||
|
const startGameButton = document.getElementById("start-game");
|
||||||
|
let socket = null;
|
||||||
|
let socketReady = null;
|
||||||
|
let currentUsername = null;
|
||||||
|
let currentLobbyId = null;
|
||||||
|
|
||||||
const startPlayerUpdates = (lobbyId) => {
|
const showJoinError = (message) => {
|
||||||
if (playerUpdateInterval) {
|
joinError.textContent = message;
|
||||||
clearInterval(playerUpdateInterval);
|
joinError.classList.remove("hidden");
|
||||||
}
|
|
||||||
|
|
||||||
playerUpdateInterval = setInterval(async () => {
|
|
||||||
const res = await fetch(`/lobby/players?lobbyId=${lobbyId}`);
|
|
||||||
const data = await res.json();
|
|
||||||
const playerList = document.getElementById("player-list");
|
|
||||||
playerList.innerHTML = data.players.map(p => `<p>${p}</p>`).join("") || "<p>No players yet</p>";
|
|
||||||
}, 3000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
leaveButton.addEventListener("click", async () => {
|
const clearJoinError = () => {
|
||||||
const username = await getUsernameFromToken();
|
joinError.textContent = "";
|
||||||
const lobbyId = document.getElementById("lobby-id").textContent;
|
joinError.classList.add("hidden");
|
||||||
try {
|
};
|
||||||
const res = await fetch(`/lobby/leave?lobbyId=${lobbyId}&username=${username}`, {
|
|
||||||
method: "DELETE",
|
const renderPlayers = (players) => {
|
||||||
});
|
playerList.innerHTML = "";
|
||||||
if (res.ok) {
|
|
||||||
// Stoppe das Interval beim Verlassen
|
if (!players.length) {
|
||||||
if (playerUpdateInterval) {
|
playerList.innerHTML = "<p>No players yet</p>";
|
||||||
clearInterval(playerUpdateInterval);
|
return;
|
||||||
playerUpdateInterval = null;
|
|
||||||
}
|
|
||||||
document.getElementById("lobby-room").classList.add("hidden");
|
|
||||||
document.getElementById("player-list").innerHTML = "<p>Waiting for players...</p>";
|
|
||||||
document.getElementById("lobby-id").textContent = "N/A";
|
|
||||||
} else {
|
|
||||||
const err = await res.json();
|
|
||||||
alert("Failed to leave lobby: " + (err.error || "Unknown error"));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error leaving lobby:", err);
|
|
||||||
alert("Could not leave lobby.");
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
for (const player of players) {
|
||||||
|
const entry = document.createElement("p");
|
||||||
|
entry.textContent = player;
|
||||||
|
playerList.appendChild(entry);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateStartButton = (host, canStart) => {
|
||||||
|
const isHost = currentUsername && host === currentUsername;
|
||||||
|
startGameButton.classList.toggle("hidden", !isHost);
|
||||||
|
startGameButton.disabled = !isHost || !canStart;
|
||||||
|
startGameButton.textContent = canStart ? "Start Game" : "Waiting for 2 players";
|
||||||
|
};
|
||||||
|
|
||||||
|
const showLobby = (lobbyId, players = [], host = null, canStart = false) => {
|
||||||
|
currentLobbyId = lobbyId;
|
||||||
|
lobbyIdDisplay.textContent = lobbyId;
|
||||||
|
lobbyRoom.classList.remove("hidden");
|
||||||
|
joinForm.classList.add("hidden");
|
||||||
|
hostForm.classList.add("hidden");
|
||||||
|
renderPlayers(players);
|
||||||
|
updateStartButton(host, canStart);
|
||||||
|
};
|
||||||
|
|
||||||
|
const joinLobby = (lobbyId) => {
|
||||||
|
if (!currentUsername) {
|
||||||
|
showJoinError("User not authenticated.");
|
||||||
|
return Promise.reject(new Error("User not authenticated"));
|
||||||
|
}
|
||||||
|
return sendSocketMessage({type: "lobby:join", lobbyId});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPublicLobbies = (lobbies = []) => {
|
||||||
|
publicLobbies.innerHTML = "";
|
||||||
|
|
||||||
|
if (!lobbies.length) {
|
||||||
|
publicLobbies.innerHTML = "<p>No public lobbies found</p>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lobbies.forEach((lobby) => {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "public-lobby";
|
||||||
|
|
||||||
|
const info = document.createElement("div");
|
||||||
|
info.className = "public-lobby-info";
|
||||||
|
|
||||||
|
const title = document.createElement("strong");
|
||||||
|
title.textContent = lobby.host ? `${lobby.host}'s lobby` : "Public lobby";
|
||||||
|
info.appendChild(title);
|
||||||
|
|
||||||
|
const meta = document.createElement("span");
|
||||||
|
meta.textContent = `${lobby.players} player${lobby.players === 1 ? "" : "s"} - ${lobby.gameStarted ? "Game running" : "Waiting"}`;
|
||||||
|
info.appendChild(meta);
|
||||||
|
|
||||||
|
const join = document.createElement("button");
|
||||||
|
join.type = "button";
|
||||||
|
join.textContent = "Join";
|
||||||
|
join.addEventListener("click", () => {
|
||||||
|
clearJoinError();
|
||||||
|
joinLobby(lobby.lobbyId).catch(() => showJoinError("Could not join lobby."));
|
||||||
|
});
|
||||||
|
|
||||||
|
row.appendChild(info);
|
||||||
|
row.appendChild(join);
|
||||||
|
publicLobbies.appendChild(row);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadPublicLobbies = async () => {
|
||||||
|
try {
|
||||||
|
publicLobbies.innerHTML = "<p>Loading lobbies...</p>";
|
||||||
|
const res = await fetch("/lobby/public");
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Failed to load lobbies");
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
renderPublicLobbies(data.lobbies || []);
|
||||||
|
} catch (error) {
|
||||||
|
publicLobbies.innerHTML = "<p>Could not load lobbies</p>";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideLobby = () => {
|
||||||
|
currentLobbyId = null;
|
||||||
|
lobbyRoom.classList.add("hidden");
|
||||||
|
lobbyIdDisplay.textContent = "N/A";
|
||||||
|
playerList.innerHTML = "<p>Waiting for players...</p>";
|
||||||
|
startGameButton.classList.add("hidden");
|
||||||
|
startGameButton.disabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendSocketMessage = async (message) => {
|
||||||
|
const ws = await connectSocket();
|
||||||
|
ws.send(JSON.stringify(message));
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectSocket = () => {
|
||||||
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||||
|
return Promise.resolve(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socketReady) {
|
||||||
|
return socketReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
|
socket = new WebSocket(`${protocol}//${window.location.host}`);
|
||||||
|
|
||||||
|
socketReady = new Promise((resolve, reject) => {
|
||||||
|
socket.addEventListener("open", () => resolve(socket), {once: true});
|
||||||
|
socket.addEventListener("error", () => reject(new Error("WebSocket connection failed")), {once: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener("message", (event) => {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
|
||||||
|
if (message.type === "auth:error") {
|
||||||
|
showJoinError(message.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "auth:success") {
|
||||||
|
currentUsername = message.username;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "lobby:state") {
|
||||||
|
clearJoinError();
|
||||||
|
if (message.gameStarted) {
|
||||||
|
localStorage.setItem("activeLobbyId", message.lobbyId);
|
||||||
|
window.location.href = message.gameUrl || `/poker.html?lobbyId=${encodeURIComponent(message.lobbyId)}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showLobby(message.lobbyId, message.players || [], message.host, Boolean(message.canStart));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "poker:started") {
|
||||||
|
localStorage.setItem("activeLobbyId", message.lobbyId);
|
||||||
|
window.location.href = message.url || `/poker.html?lobbyId=${encodeURIComponent(message.lobbyId)}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "lobby:left") {
|
||||||
|
hideLobby();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "lobby:error" || message.type === "error" || message.type === "poker:error") {
|
||||||
|
showJoinError(message.message || "WebSocket request failed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener("close", () => {
|
||||||
|
socket = null;
|
||||||
|
socketReady = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return socketReady;
|
||||||
|
};
|
||||||
|
|
||||||
const getUsernameFromToken = async () => {
|
const getUsernameFromToken = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -54,24 +208,35 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
return data.user?.username;
|
return data.user?.username;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to get username from token");
|
console.error("Failed to get username from token", err);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const balanceRes = await fetch('/balance', {method: "GET"});
|
await connectSocket();
|
||||||
|
currentUsername = await getUsernameFromToken();
|
||||||
|
} catch (error) {
|
||||||
|
showJoinError("Real-time poker connection failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const balanceRes = await fetch("/balance", {method: "GET"});
|
||||||
if (balanceRes.ok) {
|
if (balanceRes.ok) {
|
||||||
const balanceData = await balanceRes.json();
|
const balanceData = await balanceRes.json();
|
||||||
balance.innerHTML = balanceData.balance;
|
balance.textContent = balanceData.balance;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Balance error:", error);
|
console.log("Balance error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
logoutButton.addEventListener("click", async (e) => {
|
logoutButton.addEventListener("click", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
const logoutRes = await fetch('/logout', {method: "DELETE"});
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
const logoutRes = await fetch("/logout", {method: "DELETE"});
|
||||||
if (logoutRes.ok) {
|
if (logoutRes.ok) {
|
||||||
document.cookie = "authorization=; Max-Age=0; path=/;";
|
document.cookie = "authorization=; Max-Age=0; path=/;";
|
||||||
window.location.href = "/login.html";
|
window.location.href = "/login.html";
|
||||||
@@ -80,74 +245,80 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
console.log("Logout error:", err);
|
console.log("Logout error:", err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Im hostButton Event Listener
|
|
||||||
hostButton.addEventListener("click", async (e) => {
|
hostButton.addEventListener("click", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const username = await getUsernameFromToken();
|
clearJoinError();
|
||||||
if (!username) return alert("User not authenticated");
|
hostForm.classList.toggle("hidden");
|
||||||
try {
|
joinForm.classList.add("hidden");
|
||||||
const res = await fetch(`/lobby/create?username=${username}`);
|
|
||||||
if (res.redirected) {
|
|
||||||
const joinURL = new URL(res.url);
|
|
||||||
const lobbyId = joinURL.searchParams.get("lobbyId");
|
|
||||||
const joinRes = await fetch(joinURL.href);
|
|
||||||
const {lobbyId: finalLobbyId} = await joinRes.json();
|
|
||||||
document.getElementById("lobby-id").textContent = finalLobbyId;
|
|
||||||
document.getElementById("lobby-room").classList.remove("hidden");
|
|
||||||
// ... bisheriger Code bis zum Interval ...
|
|
||||||
startPlayerUpdates(finalLobbyId);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Lobby create/join error:", err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createLobbyButton.addEventListener("click", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
clearJoinError();
|
||||||
|
if (!currentUsername) {
|
||||||
|
showJoinError("User not authenticated.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedVisibility = [...lobbyVisibilityInputs].find(input => input.checked)?.value || "public";
|
||||||
|
sendSocketMessage({type: "lobby:create", private: selectedVisibility === "private"}).catch(() => {
|
||||||
|
showJoinError("Could not create lobby.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
joinButton.addEventListener("click", (e) => {
|
joinButton.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (joinForm.classList.contains("hidden")) {
|
joinForm.classList.toggle("hidden");
|
||||||
joinForm.classList.remove("hidden");
|
hostForm.classList.add("hidden");
|
||||||
|
if (!joinForm.classList.contains("hidden")) {
|
||||||
joinCodeInput.focus();
|
joinCodeInput.focus();
|
||||||
} else {
|
loadPublicLobbies();
|
||||||
joinForm.classList.add("hidden");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Im joinSubmit Event Listener
|
|
||||||
|
refreshLobbies.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
loadPublicLobbies();
|
||||||
|
});
|
||||||
|
|
||||||
joinSubmit.addEventListener("click", async (e) => {
|
joinSubmit.addEventListener("click", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
clearJoinError();
|
||||||
|
|
||||||
const lobbyId = joinCodeInput.value.trim();
|
const lobbyId = joinCodeInput.value.trim();
|
||||||
const joinError = document.getElementById("join-error");
|
|
||||||
joinError.classList.add("hidden"); // hide any previous error
|
|
||||||
joinError.textContent = "";
|
|
||||||
if (!lobbyId) {
|
if (!lobbyId) {
|
||||||
joinError.textContent = "Please enter a lobby ID.";
|
showJoinError("Please enter a lobby ID.");
|
||||||
joinError.classList.remove("hidden");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const username = await getUsernameFromToken();
|
|
||||||
if (!username) {
|
if (!currentUsername) {
|
||||||
joinError.textContent = "User not authenticated.";
|
showJoinError("User not authenticated.");
|
||||||
joinError.classList.remove("hidden");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const joinRes = await fetch(`/lobby/join?lobbyId=${lobbyId}&username=${username}`);
|
joinLobby(lobbyId).then(() => {
|
||||||
if (joinRes.ok) {
|
joinForm.classList.add("hidden");
|
||||||
const {lobbyId: finalLobbyId} = await joinRes.json();
|
joinCodeInput.value = "";
|
||||||
document.getElementById("lobby-id").textContent = finalLobbyId;
|
}).catch(() => {
|
||||||
document.getElementById("lobby-room").classList.remove("hidden");
|
showJoinError("Could not join lobby. Please try again.");
|
||||||
joinForm.classList.add("hidden");
|
});
|
||||||
joinCodeInput.value = "";
|
});
|
||||||
// ... bisheriger Code bis zum Interval ...
|
|
||||||
startPlayerUpdates(finalLobbyId);
|
leaveButton.addEventListener("click", (e) => {
|
||||||
} else {
|
e.preventDefault();
|
||||||
const error = await joinRes.json();
|
const lobbyId = lobbyIdDisplay.textContent;
|
||||||
joinError.textContent = "Join failed: " + (error.error || "Unknown error");
|
if (!lobbyId || lobbyId === "N/A") return;
|
||||||
joinError.classList.remove("hidden");
|
sendSocketMessage({type: "lobby:leave", lobbyId}).catch(() => {
|
||||||
}
|
showJoinError("Could not leave lobby.");
|
||||||
} catch (err) {
|
});
|
||||||
console.error("Join lobby error:", err);
|
});
|
||||||
joinError.textContent = "Could not join lobby. Please try again.";
|
|
||||||
joinError.classList.remove("hidden");
|
startGameButton.addEventListener("click", (e) => {
|
||||||
}
|
e.preventDefault();
|
||||||
|
if (!currentLobbyId || startGameButton.disabled) return;
|
||||||
|
sendSocketMessage({type: "poker:start", lobbyId: currentLobbyId}).catch(() => {
|
||||||
|
showJoinError("Could not start game.");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadLeaderboard() {
|
async function loadLeaderboard() {
|
||||||
@@ -164,7 +335,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
item.textContent = `${i + 1}. ${player.username} - $${player.money}`;
|
item.textContent = `${i + 1}. ${player.username} - $${player.money}`;
|
||||||
if (player.username === data.self.username) {
|
if (player.username === data.self.username) {
|
||||||
item.classList.add("you");
|
item.classList.add("you");
|
||||||
item.textContent += " 👑";
|
item.textContent += " (you)";
|
||||||
}
|
}
|
||||||
list.appendChild(item);
|
list.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const roundDisplay = document.getElementById("round");
|
||||||
|
const streetDisplay = document.getElementById("street");
|
||||||
|
const potDisplay = document.getElementById("pot");
|
||||||
|
const communityCards = document.getElementById("community-cards");
|
||||||
|
const playerCards = document.getElementById("player-cards");
|
||||||
|
const spectatorNote = document.getElementById("spectator-note");
|
||||||
|
const playerName = document.getElementById("player-name");
|
||||||
|
const currentBet = document.getElementById("current-bet");
|
||||||
|
const yourContribution = document.getElementById("your-contribution");
|
||||||
|
const yourStack = document.getElementById("your-stack");
|
||||||
|
const currentTurn = document.getElementById("current-turn");
|
||||||
|
const turnState = document.getElementById("turn-state");
|
||||||
|
const turnQueue = document.getElementById("turn-queue");
|
||||||
|
const lastAction = document.getElementById("last-action");
|
||||||
|
const showdownPanel = document.getElementById("showdown-panel");
|
||||||
|
const errorMessage = document.getElementById("error-message");
|
||||||
|
const leaveTable = document.getElementById("leave-table");
|
||||||
|
const betModal = document.getElementById("bet-modal");
|
||||||
|
const closeBet = document.getElementById("close-bet");
|
||||||
|
const betRange = document.getElementById("bet-range");
|
||||||
|
const betValue = document.getElementById("bet-value");
|
||||||
|
const decreaseBet = document.getElementById("decrease-bet");
|
||||||
|
const increaseBet = document.getElementById("increase-bet");
|
||||||
|
const confirmBet = document.getElementById("confirm-bet");
|
||||||
|
const actionButtons = document.querySelectorAll(".actions button");
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const lobbyId = params.get("lobbyId") || localStorage.getItem("activeLobbyId");
|
||||||
|
let socket = null;
|
||||||
|
let socketReady = null;
|
||||||
|
let isYourTurn = false;
|
||||||
|
let canLeaveTable = true;
|
||||||
|
let isSpectator = false;
|
||||||
|
let currentStackValue = 400;
|
||||||
|
let minimumStake = 10;
|
||||||
|
let callAmount = 0;
|
||||||
|
let countdownTimer = null;
|
||||||
|
const actionTooltips = {
|
||||||
|
hit: "Check or call if your bet already matches the table.",
|
||||||
|
fold: "Give up this hand and lose your current stake.",
|
||||||
|
bet: "Open the bet dialog to add chips to the pot."
|
||||||
|
};
|
||||||
|
|
||||||
|
actionButtons.forEach((button) => {
|
||||||
|
const tooltip = actionTooltips[button.dataset.action];
|
||||||
|
if (!tooltip) return;
|
||||||
|
button.title = tooltip;
|
||||||
|
button.setAttribute("aria-label", `${button.textContent.trim()}: ${tooltip}`);
|
||||||
|
});
|
||||||
|
closeBet.title = "Close the bet dialog.";
|
||||||
|
decreaseBet.title = "Decrease the bet amount.";
|
||||||
|
increaseBet.title = "Increase the bet amount.";
|
||||||
|
confirmBet.title = "Place the selected bet.";
|
||||||
|
|
||||||
|
const showError = (message) => {
|
||||||
|
errorMessage.textContent = message;
|
||||||
|
errorMessage.classList.remove("hidden");
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideError = () => {
|
||||||
|
errorMessage.textContent = "";
|
||||||
|
errorMessage.classList.add("hidden");
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCard = (card, fallback) => {
|
||||||
|
const cardElement = document.createElement("div");
|
||||||
|
cardElement.className = "card";
|
||||||
|
if (!card) {
|
||||||
|
cardElement.classList.add("placeholder");
|
||||||
|
cardElement.textContent = fallback;
|
||||||
|
return cardElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
cardElement.classList.toggle("red", card.suit === "H" || card.suit === "D");
|
||||||
|
cardElement.innerHTML = `<span>${card.rank}</span><span>${card.suit}</span>`;
|
||||||
|
return cardElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCards = (container, cards, count, fallbackPrefix) => {
|
||||||
|
container.innerHTML = "";
|
||||||
|
for (let index = 0; index < count; index++) {
|
||||||
|
container.appendChild(renderCard(cards[index], `${fallbackPrefix} ${index + 1}`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderQueue = (queue = [], currentPlayer = null) => {
|
||||||
|
turnQueue.innerHTML = "";
|
||||||
|
queue.forEach((player, index) => {
|
||||||
|
const item = document.createElement("li");
|
||||||
|
item.textContent = index === 0 ? `${player} (now)` : player;
|
||||||
|
item.classList.toggle("active", player === currentPlayer);
|
||||||
|
turnQueue.appendChild(item);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderShowdown = (showdown = [], winner = null) => {
|
||||||
|
showdownPanel.innerHTML = "";
|
||||||
|
showdownPanel.classList.toggle("hidden", showdown.length === 0 && !winner);
|
||||||
|
|
||||||
|
if (winner) {
|
||||||
|
const heading = document.createElement("h2");
|
||||||
|
heading.textContent = `Winner: ${winner}`;
|
||||||
|
showdownPanel.appendChild(heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
showdown.forEach((entry) => {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "showdown-row";
|
||||||
|
|
||||||
|
const name = document.createElement("span");
|
||||||
|
name.textContent = `${entry.player}: ${entry.result.name}`;
|
||||||
|
row.appendChild(name);
|
||||||
|
|
||||||
|
const cards = document.createElement("div");
|
||||||
|
cards.className = "mini-cards";
|
||||||
|
(entry.result.comboCards || entry.hand).forEach((card) => cards.appendChild(renderCard(card, "?")));
|
||||||
|
row.appendChild(cards);
|
||||||
|
|
||||||
|
showdownPanel.appendChild(row);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendSocketMessage = async (message) => {
|
||||||
|
const ws = await connectSocket();
|
||||||
|
ws.send(JSON.stringify(message));
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateActionButtons = () => {
|
||||||
|
actionButtons.forEach((button) => {
|
||||||
|
button.disabled = !isYourTurn || isSpectator;
|
||||||
|
if (button.dataset.action === "hit") {
|
||||||
|
button.title = callAmount > 0
|
||||||
|
? `Call with $${Math.min(callAmount, currentStackValue)}.`
|
||||||
|
: actionTooltips.hit;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
confirmBet.disabled = !isYourTurn || isSpectator;
|
||||||
|
leaveTable.disabled = false;
|
||||||
|
leaveTable.title = "Leave table. Chips already in the pot are lost.";
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopCountdown = () => {
|
||||||
|
if (countdownTimer) {
|
||||||
|
clearInterval(countdownTimer);
|
||||||
|
countdownTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderIntermissionCountdown = (startsAt, baseMessage) => {
|
||||||
|
stopCountdown();
|
||||||
|
const updateCountdown = () => {
|
||||||
|
const seconds = Math.max(0, Math.ceil((Number(startsAt) - Date.now()) / 1000));
|
||||||
|
currentTurn.textContent = `${seconds}s`;
|
||||||
|
turnState.textContent = "Waiting for next round";
|
||||||
|
lastAction.textContent = baseMessage || `Next round starts in ${seconds}s. Minimum stake: $${minimumStake}.`;
|
||||||
|
if (seconds <= 0) {
|
||||||
|
stopCountdown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
updateCountdown();
|
||||||
|
countdownTimer = setInterval(updateCountdown, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTurnCountdown = (startsAt, currentPlayer) => {
|
||||||
|
stopCountdown();
|
||||||
|
const updateCountdown = () => {
|
||||||
|
const seconds = Math.max(0, Math.ceil((Number(startsAt) - Date.now()) / 1000));
|
||||||
|
currentTurn.textContent = `${currentPlayer || "Player"} (${seconds}s)`;
|
||||||
|
turnState.textContent = isYourTurn
|
||||||
|
? `Your decision (${seconds}s)`
|
||||||
|
: `Waiting for other player (${seconds}s)`;
|
||||||
|
if (seconds <= 0) {
|
||||||
|
stopCountdown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
updateCountdown();
|
||||||
|
countdownTimer = setInterval(updateCountdown, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectSocket = () => {
|
||||||
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||||
|
return Promise.resolve(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socketReady) {
|
||||||
|
return socketReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
|
socket = new WebSocket(`${protocol}//${window.location.host}`);
|
||||||
|
|
||||||
|
socketReady = new Promise((resolve, reject) => {
|
||||||
|
socket.addEventListener("open", () => resolve(socket), {once: true});
|
||||||
|
socket.addEventListener("error", () => reject(new Error("WebSocket connection failed")), {once: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener("message", (event) => {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
|
||||||
|
if (message.type === "poker:state") {
|
||||||
|
hideError();
|
||||||
|
isYourTurn = Boolean(message.isYourTurn);
|
||||||
|
canLeaveTable = Boolean(message.canLeaveTable);
|
||||||
|
isSpectator = Boolean(message.isSpectator);
|
||||||
|
currentStackValue = Number(message.yourStack ?? 0);
|
||||||
|
minimumStake = Number(message.minimumStake || 10);
|
||||||
|
callAmount = Number(message.callAmount || 0);
|
||||||
|
roundDisplay.textContent = message.round;
|
||||||
|
potDisplay.textContent = message.pot;
|
||||||
|
currentBet.textContent = message.currentBet ?? message.bet;
|
||||||
|
yourContribution.textContent = message.yourContribution || 0;
|
||||||
|
yourStack.textContent = currentStackValue;
|
||||||
|
streetDisplay.textContent = message.street || "Preflop";
|
||||||
|
playerName.textContent = message.username;
|
||||||
|
if (message.phase === "intermission" && message.nextRoundStartsAt) {
|
||||||
|
renderIntermissionCountdown(message.nextRoundStartsAt, message.lastAction);
|
||||||
|
} else if (message.phase === "betting" && message.turnEndsAt) {
|
||||||
|
renderTurnCountdown(message.turnEndsAt, message.currentPlayer);
|
||||||
|
lastAction.textContent = message.lastAction || "";
|
||||||
|
} else {
|
||||||
|
stopCountdown();
|
||||||
|
currentTurn.textContent = message.currentPlayer || "Round ended";
|
||||||
|
turnState.textContent = isSpectator
|
||||||
|
? "Spectating"
|
||||||
|
: isYourTurn
|
||||||
|
? "Your decision"
|
||||||
|
: message.phase === "betting" ? "Waiting for other player" : message.phase;
|
||||||
|
lastAction.textContent = message.lastAction || "";
|
||||||
|
}
|
||||||
|
renderCards(communityCards, message.communityCards || [], Math.max(3, message.communityCards?.length || 0), "Dealer Card");
|
||||||
|
renderCards(playerCards, message.hand || [], isSpectator ? 0 : 2, "Card");
|
||||||
|
spectatorNote.classList.toggle("hidden", !isSpectator);
|
||||||
|
renderQueue(message.queue || [], message.currentPlayer);
|
||||||
|
renderShowdown(message.showdown || [], message.winner);
|
||||||
|
updateActionButtons();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "poker:action") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "lobby:left") {
|
||||||
|
localStorage.removeItem("activeLobbyId");
|
||||||
|
window.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === "auth:error" || message.type === "poker:error" || message.type === "error") {
|
||||||
|
showError(message.message || "Poker connection failed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener("close", () => {
|
||||||
|
socket = null;
|
||||||
|
socketReady = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return socketReady;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setBetValue = (value) => {
|
||||||
|
const nextValue = Math.max(Number(betRange.min), Math.min(Number(betRange.max), Number(value)));
|
||||||
|
betRange.value = nextValue;
|
||||||
|
betValue.textContent = nextValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBetRange = () => {
|
||||||
|
const maxBet = Math.max(0, currentStackValue);
|
||||||
|
const requiredBet = Math.max(minimumStake, callAmount);
|
||||||
|
const minBet = maxBet > 0 ? Math.min(requiredBet, maxBet) : 0;
|
||||||
|
betRange.min = String(minBet);
|
||||||
|
betRange.max = String(maxBet);
|
||||||
|
betRange.step = String(minimumStake);
|
||||||
|
setBetValue(Math.max(minBet, Number(betRange.value) || minBet));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!lobbyId) {
|
||||||
|
showError("No active lobby found.");
|
||||||
|
isYourTurn = false;
|
||||||
|
updateActionButtons();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateActionButtons();
|
||||||
|
|
||||||
|
connectSocket()
|
||||||
|
.then(() => sendSocketMessage({type: "poker:join", lobbyId}))
|
||||||
|
.catch(() => showError("Could not connect to poker table."));
|
||||||
|
|
||||||
|
actionButtons.forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const action = button.dataset.action;
|
||||||
|
if (isSpectator) {
|
||||||
|
showError("You are out of chips and can only spectate.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isYourTurn) {
|
||||||
|
showError("Wait until it is your turn.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action === "bet") {
|
||||||
|
updateBetRange();
|
||||||
|
betModal.classList.remove("hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSocketMessage({type: "poker:action", action, payload: {lobbyId}}).catch(() => {
|
||||||
|
showError("Could not send poker action.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
leaveTable.addEventListener("click", () => {
|
||||||
|
sendSocketMessage({type: "lobby:leave", lobbyId}).catch(() => {
|
||||||
|
showError("Could not leave table.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
closeBet.addEventListener("click", () => betModal.classList.add("hidden"));
|
||||||
|
betRange.addEventListener("input", () => setBetValue(betRange.value));
|
||||||
|
decreaseBet.addEventListener("click", () => setBetValue(Number(betRange.value) - 10));
|
||||||
|
increaseBet.addEventListener("click", () => setBetValue(Number(betRange.value) + 10));
|
||||||
|
confirmBet.addEventListener("click", () => {
|
||||||
|
if (isSpectator) {
|
||||||
|
showError("You are out of chips and can only spectate.");
|
||||||
|
betModal.classList.add("hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isYourTurn) {
|
||||||
|
showError("Wait until it is your turn.");
|
||||||
|
betModal.classList.add("hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendSocketMessage({
|
||||||
|
type: "poker:action",
|
||||||
|
action: "bet",
|
||||||
|
payload: {amount: Number(betRange.value), lobbyId}
|
||||||
|
}).catch(() => showError("Could not place bet."));
|
||||||
|
betModal.classList.add("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
+26
-3
@@ -25,9 +25,31 @@
|
|||||||
<button id="host">Host Lobby</button>
|
<button id="host">Host Lobby</button>
|
||||||
<button id="join">Join Lobby</button>
|
<button id="join">Join Lobby</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="join-form" class="hidden">
|
<div id="host-form" class="host-form hidden">
|
||||||
<input type="text" id="join-code" placeholder="Enter Lobby ID"/>
|
<div class="lobby-visibility" aria-label="Lobby visibility">
|
||||||
<button id="submit-join">Join</button>
|
<label>
|
||||||
|
<input type="radio" name="lobby-visibility" value="public" checked/>
|
||||||
|
Public Lobby
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="lobby-visibility" value="private"/>
|
||||||
|
Private Lobby
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button id="create-lobby" type="button">Create Lobby</button>
|
||||||
|
</div>
|
||||||
|
<div id="join-form" class="join-browser hidden">
|
||||||
|
<div class="join-code-row">
|
||||||
|
<input type="text" id="join-code" placeholder="Enter Lobby ID"/>
|
||||||
|
<button id="submit-join">Join</button>
|
||||||
|
</div>
|
||||||
|
<div class="browser-header">
|
||||||
|
<h2>Public Lobbies</h2>
|
||||||
|
<button id="refresh-lobbies" type="button">Refresh</button>
|
||||||
|
</div>
|
||||||
|
<div id="public-lobbies" class="public-lobbies">
|
||||||
|
<p>No public lobbies found</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="join-error" class="error-message hidden"></div>
|
<div id="join-error" class="error-message hidden"></div>
|
||||||
<div id="lobby-room" class="lobby-room hidden">
|
<div id="lobby-room" class="lobby-room hidden">
|
||||||
@@ -38,6 +60,7 @@
|
|||||||
<div id="player-list">
|
<div id="player-list">
|
||||||
<p>Waiting for players...</p>
|
<p>Waiting for players...</p>
|
||||||
</div>
|
</div>
|
||||||
|
<button id="start-game" class="start-game hidden" disabled>Start Game</button>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script src="frontend_home.js" defer></script>
|
<script src="frontend_home.js" defer></script>
|
||||||
|
|||||||
+34
-12
@@ -1,27 +1,49 @@
|
|||||||
const canvas = document.getElementById("matrix-canvas");
|
const canvas = document.getElementById("matrix-canvas");
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
canvas.width = window.innerWidth;
|
|
||||||
canvas.height = window.innerHeight;
|
|
||||||
const letters = "アァイィウヴエェオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
|
const letters = "アァイィウヴエェオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
|
||||||
const fontSize = 16;
|
const fontSize = 16;
|
||||||
const columns = Math.floor(canvas.width / fontSize);
|
const frameInterval = 65;
|
||||||
const drops = new Array(columns).fill(1);
|
let drops = [];
|
||||||
|
let lastFrameTime = 0;
|
||||||
|
|
||||||
|
function resizeCanvas() {
|
||||||
|
const pixelRatio = Math.min(window.devicePixelRatio || 1, 2);
|
||||||
|
const width = window.innerWidth;
|
||||||
|
const height = window.innerHeight;
|
||||||
|
canvas.width = Math.floor(width * pixelRatio);
|
||||||
|
canvas.height = Math.floor(height * pixelRatio);
|
||||||
|
canvas.style.width = `${width}px`;
|
||||||
|
canvas.style.height = `${height}px`;
|
||||||
|
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||||
|
|
||||||
|
const columns = Math.ceil(width / fontSize);
|
||||||
|
drops = new Array(columns).fill(1);
|
||||||
|
}
|
||||||
|
|
||||||
function drawMatrix() {
|
function drawMatrix() {
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.2)";
|
ctx.fillStyle = "rgba(0, 0, 0, 0.2)";
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
|
||||||
ctx.fillStyle = "#39FF14";
|
ctx.fillStyle = "#39FF14";
|
||||||
ctx.font = fontSize + "px monospace";
|
ctx.font = fontSize + "px monospace";
|
||||||
|
|
||||||
for (let i = 0; i < drops.length; i++) {
|
for (let i = 0; i < drops.length; i++) {
|
||||||
const text = letters[Math.floor(Math.random() * letters.length)];
|
const text = letters[Math.floor(Math.random() * letters.length)];
|
||||||
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
|
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
|
||||||
if (drops[i] * fontSize > canvas.height || Math.random() > 0.975) {
|
if (drops[i] * fontSize > window.innerHeight || Math.random() > 0.975) {
|
||||||
drops[i] = 0;
|
drops[i] = 0;
|
||||||
}
|
}
|
||||||
drops[i]++;
|
drops[i]++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setInterval(drawMatrix, 50);
|
|
||||||
window.addEventListener("resize", () => {
|
function animate(timestamp) {
|
||||||
canvas.width = window.innerWidth;
|
if (timestamp - lastFrameTime >= frameInterval) {
|
||||||
canvas.height = window.innerHeight;
|
drawMatrix();
|
||||||
});
|
lastFrameTime = timestamp;
|
||||||
|
}
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeCanvas();
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
window.addEventListener("resize", resizeCanvas);
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Poker Table</title>
|
||||||
|
<link rel="stylesheet" href="styles_poker.css">
|
||||||
|
<link rel="icon" href="gambling.png" type="image/x-icon">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="matrix-canvas"></canvas>
|
||||||
|
|
||||||
|
<main class="poker-table">
|
||||||
|
<header class="table-header">
|
||||||
|
<div class="round-panel">
|
||||||
|
<span>Round <strong id="round">1</strong></span>
|
||||||
|
<span class="divider">|</span>
|
||||||
|
<span id="street">Preflop</span>
|
||||||
|
<span class="divider">|</span>
|
||||||
|
<span>Pot: $<strong id="pot">0</strong></span>
|
||||||
|
</div>
|
||||||
|
<button id="leave-table" class="leave-table">Leave Table</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="community-area" aria-label="Community cards">
|
||||||
|
<div class="turn-panel">
|
||||||
|
<span>Turn: <strong id="current-turn">Waiting...</strong></span>
|
||||||
|
<span id="turn-state">Waiting for table state</span>
|
||||||
|
</div>
|
||||||
|
<ol id="turn-queue" class="turn-queue"></ol>
|
||||||
|
<div id="community-cards" class="card-row">
|
||||||
|
<div class="card placeholder">?</div>
|
||||||
|
<div class="card placeholder">?</div>
|
||||||
|
<div class="card placeholder">?</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="actions" aria-label="Poker actions">
|
||||||
|
<button data-action="hit">Hit</button>
|
||||||
|
<button data-action="fold">Fold</button>
|
||||||
|
<button data-action="bet">Bet</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="player-area" aria-label="Your cards">
|
||||||
|
<div id="player-cards" class="hand">
|
||||||
|
<div class="card player-card placeholder">Card 1</div>
|
||||||
|
<div class="card player-card placeholder">Card 2</div>
|
||||||
|
</div>
|
||||||
|
<div id="spectator-note" class="spectator-note hidden">Spectating</div>
|
||||||
|
<div class="player-status">
|
||||||
|
<span id="player-name">Player</span>
|
||||||
|
<span>Your Bet: $<strong id="current-bet">0</strong></span>
|
||||||
|
<span>Your Total: $<strong id="your-contribution">0</strong></span>
|
||||||
|
<span>Your Stack: $<strong id="your-stack">400</strong></span>
|
||||||
|
<span id="last-action">Game starting</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="showdown-panel" class="showdown-panel hidden" aria-label="Showdown"></section>
|
||||||
|
<div id="error-message" class="error-message hidden"></div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div id="bet-modal" class="bet-modal hidden" role="dialog" aria-modal="true" aria-labelledby="bet-title">
|
||||||
|
<div class="bet-panel">
|
||||||
|
<button id="close-bet" class="close-bet" aria-label="Close bet dialog">x</button>
|
||||||
|
<h2 id="bet-title">Choose amount to bet</h2>
|
||||||
|
<input id="bet-range" type="range" min="0" max="400" step="10" value="50">
|
||||||
|
<div class="range-labels">
|
||||||
|
<span>$0</span>
|
||||||
|
<span>$400</span>
|
||||||
|
</div>
|
||||||
|
<div class="bet-amount">
|
||||||
|
<button id="decrease-bet">-</button>
|
||||||
|
<span>$<strong id="bet-value">50</strong></span>
|
||||||
|
<button id="increase-bet">+</button>
|
||||||
|
</div>
|
||||||
|
<button id="confirm-bet" class="confirm-bet">Confirm</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="frontend_poker.js" defer></script>
|
||||||
|
<script src="matrix.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+124
-3
@@ -58,6 +58,37 @@ button.logout {
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.host-form {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
justify-items: center;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-visibility {
|
||||||
|
color: #fff;
|
||||||
|
font-family: monospace;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-shadow: 0 0 3px #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-visibility label {
|
||||||
|
border: 1px solid rgba(0, 255, 0, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.65);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-visibility input {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: #0f0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
.lobby-room {
|
.lobby-room {
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
border: 2px solid #0f0;
|
border: 2px solid #0f0;
|
||||||
@@ -84,14 +115,24 @@ button.logout {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#join-form {
|
.join-browser {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
width: min(520px, calc(100vw - 32px));
|
||||||
|
background: rgba(0, 0, 0, 0.78);
|
||||||
|
border: 2px solid #0f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 0 10px #0f0;
|
||||||
|
padding: 18px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-code-row {
|
||||||
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
display: none; /* default hidden */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#join-form:not(.hidden) {
|
#join-form:not(.hidden) {
|
||||||
display: flex;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#join-form input {
|
#join-form input {
|
||||||
@@ -105,6 +146,73 @@ button.logout {
|
|||||||
width: 200px;
|
width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.browser-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
border-top: 1px solid rgba(0, 255, 0, 0.35);
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #0f0;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-header button,
|
||||||
|
.public-lobby button {
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-lobbies {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 12px;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-lobbies p {
|
||||||
|
margin: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-lobby {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
border: 1px solid rgba(0, 255, 0, 0.45);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
background: rgba(0, 255, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-lobby-info {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
color: #fff;
|
||||||
|
font-family: monospace;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-lobby-info strong {
|
||||||
|
color: #0f0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-lobby-info span {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #d8ffd8;
|
||||||
|
}
|
||||||
|
|
||||||
#join-form button {
|
#join-form button {
|
||||||
background-color: #0f0;
|
background-color: #0f0;
|
||||||
color: #000;
|
color: #000;
|
||||||
@@ -220,3 +328,16 @@ button.logout {
|
|||||||
background-color: #c00;
|
background-color: #c00;
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.start-game {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.start-game:disabled {
|
||||||
|
background-color: #163016;
|
||||||
|
color: #67a667;
|
||||||
|
cursor: not-allowed;
|
||||||
|
box-shadow: 0 0 5px #0f0 inset;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,388 @@
|
|||||||
|
body {
|
||||||
|
background: radial-gradient(circle at top, #111, #000);
|
||||||
|
color: #0f0;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #0f0;
|
||||||
|
color: #000;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 18px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 0 10px #0f0, 0 0 5px #0f0 inset;
|
||||||
|
transition: background 0.2s ease-in-out, transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #1f1;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background-color: #163016;
|
||||||
|
color: #67a667;
|
||||||
|
cursor: not-allowed;
|
||||||
|
box-shadow: 0 0 5px #0f0 inset;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled:hover {
|
||||||
|
background-color: #163016;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#matrix-canvas {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poker-table {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr auto 1.5fr;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
padding: 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.round-panel {
|
||||||
|
grid-column: 2;
|
||||||
|
border: 2px solid #0f0;
|
||||||
|
background: rgba(0, 0, 0, 0.78);
|
||||||
|
color: #0f0;
|
||||||
|
box-shadow: 0 0 12px #0f0;
|
||||||
|
padding: 16px 28px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 20px;
|
||||||
|
text-shadow: 0 0 5px #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
margin: 0 14px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leave-table {
|
||||||
|
grid-column: 3;
|
||||||
|
justify-self: end;
|
||||||
|
background-color: #111;
|
||||||
|
color: #0f0;
|
||||||
|
border: 1px solid #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-row,
|
||||||
|
.hand,
|
||||||
|
.actions,
|
||||||
|
.player-status,
|
||||||
|
.bet-amount,
|
||||||
|
.range-labels {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-row {
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.community-area {
|
||||||
|
display: grid;
|
||||||
|
justify-items: center;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.turn-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 18px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: min(460px, calc(100vw - 32px));
|
||||||
|
border: 1px solid #0f0;
|
||||||
|
background: rgba(0, 0, 0, 0.78);
|
||||||
|
box-shadow: 0 0 8px #0f0;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.turn-queue {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.turn-queue li {
|
||||||
|
border: 1px solid #0f0;
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 0 6px #0f0;
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.turn-queue li.active {
|
||||||
|
background: #0f0;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 78px;
|
||||||
|
height: 116px;
|
||||||
|
background: #f4fff4;
|
||||||
|
color: #050505;
|
||||||
|
border: 2px solid #0f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 0 10px #0f0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.red {
|
||||||
|
color: #b00020;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.placeholder {
|
||||||
|
background: #0b0b0b;
|
||||||
|
color: #0f0;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 0 5px #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-area {
|
||||||
|
align-self: end;
|
||||||
|
display: grid;
|
||||||
|
justify-items: center;
|
||||||
|
gap: 18px;
|
||||||
|
padding-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hand {
|
||||||
|
gap: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-card:first-child {
|
||||||
|
transform: rotate(-8deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-card:last-child {
|
||||||
|
transform: rotate(9deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-status {
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.78);
|
||||||
|
border: 1px solid #0f0;
|
||||||
|
box-shadow: 0 0 8px #0f0;
|
||||||
|
padding: 10px 18px;
|
||||||
|
font-family: monospace;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spectator-note {
|
||||||
|
border: 1px solid #0f0;
|
||||||
|
background: rgba(0, 0, 0, 0.78);
|
||||||
|
box-shadow: 0 0 8px #0f0;
|
||||||
|
color: #fff;
|
||||||
|
font-family: monospace;
|
||||||
|
padding: 8px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 18px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 0 0 3px red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showdown-panel {
|
||||||
|
position: fixed;
|
||||||
|
right: 24px;
|
||||||
|
bottom: 24px;
|
||||||
|
width: min(340px, calc(100vw - 48px));
|
||||||
|
max-height: 42vh;
|
||||||
|
overflow: auto;
|
||||||
|
background: rgba(0, 0, 0, 0.88);
|
||||||
|
border: 2px solid #0f0;
|
||||||
|
box-shadow: 0 0 12px #0f0;
|
||||||
|
padding: 14px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showdown-panel h2 {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showdown-row {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-top: 1px solid rgba(0, 255, 0, 0.35);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-cards {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-cards .card {
|
||||||
|
width: 38px;
|
||||||
|
height: 54px;
|
||||||
|
font-size: 13px;
|
||||||
|
box-shadow: 0 0 5px #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bet-modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background: rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bet-panel {
|
||||||
|
position: relative;
|
||||||
|
width: min(360px, calc(100vw - 32px));
|
||||||
|
background: rgba(0, 0, 0, 0.92);
|
||||||
|
border: 2px solid #0f0;
|
||||||
|
box-shadow: 0 0 16px #0f0;
|
||||||
|
color: #0f0;
|
||||||
|
padding: 28px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bet-panel h2 {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
font-size: 16px;
|
||||||
|
text-shadow: 0 0 5px #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-bet {
|
||||||
|
position: absolute;
|
||||||
|
top: -14px;
|
||||||
|
right: -14px;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0;
|
||||||
|
background: #f00;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 8px #f00;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bet-range {
|
||||||
|
width: 100%;
|
||||||
|
accent-color: #0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-labels {
|
||||||
|
justify-content: space-between;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bet-amount {
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bet-amount span {
|
||||||
|
min-width: 90px;
|
||||||
|
border: 1px solid #0f0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bet-amount button {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-bet {
|
||||||
|
padding: 8px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.poker-table {
|
||||||
|
padding: 14px;
|
||||||
|
grid-template-rows: auto 1fr auto 1.2fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.round-panel,
|
||||||
|
.leave-table {
|
||||||
|
grid-column: 1;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.round-panel {
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 58px;
|
||||||
|
height: 88px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
+443
@@ -0,0 +1,443 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"title": "GAMBLE!",
|
||||||
|
"description": "Gambling API",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "http://localhost:25565/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/register": {
|
||||||
|
"post": {
|
||||||
|
"description": "",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"example": "any"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"example": "any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/leaderboard": {
|
||||||
|
"get": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "authorization",
|
||||||
|
"in": "header",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/login": {
|
||||||
|
"get": {
|
||||||
|
"description": "",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"description": "",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"example": "any"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"example": "any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "authorization",
|
||||||
|
"in": "header",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/balance": {
|
||||||
|
"get": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "authorization",
|
||||||
|
"in": "header",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/verify": {
|
||||||
|
"post": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "authorization",
|
||||||
|
"in": "header",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/logout": {
|
||||||
|
"delete": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "authorization",
|
||||||
|
"in": "header",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/leave-lobby": {
|
||||||
|
"post": {
|
||||||
|
"description": "",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"lobbyId": {
|
||||||
|
"example": "any"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"example": "any"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/lobby/create": {
|
||||||
|
"get": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "authorization",
|
||||||
|
"in": "header",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/lobby/join": {
|
||||||
|
"get": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "lobbyId",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "authorization",
|
||||||
|
"in": "header",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/lobby/leave": {
|
||||||
|
"delete": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "lobbyId",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "authorization",
|
||||||
|
"in": "header",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/lobby/players": {
|
||||||
|
"get": {
|
||||||
|
"description": "",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "lobbyId",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "authorization",
|
||||||
|
"in": "header",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Forbidden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {
|
||||||
|
"bearerAuth": {
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "bearer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user