togethere.cloud/private_html/disciplines/ping-pong/index.php

956 lines
28 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/session_bootstrap.php';
// Sprawdzenie czy użytkownik jest zalogowany
if (!isset($_SESSION['logged_in']) || !$_SESSION['logged_in']) {
// Przekierowanie do strony logowania
header('Location: /login/index.php');
exit();
}
// Admini też mogą grać w ping-ponga
?>
<!--
Author: Wspólnie
Author URL: https://togethere.cloud
-->
<!DOCTYPE html>
<html>
<head>
<title>Ping-pong | kontakt: wspolpraca@togethere.cloud</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<meta name="keywords" content="projekty przyszłości"/>
<link rel="stylesheet" href="/css/header.css" type="text/css" media="all"/>
<link rel="stylesheet" href="/css/footer.css" type="text/css" media="all"/>
<link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css" media="all">
<link href="/css/style.css" rel="stylesheet" type="text/css" media="all"/>
<link href="//fonts.googleapis.com/css?family=Lato:400,500,600,700,800,900" rel="stylesheet">
<!-- Game Scripts -->
<script src="/disciplines/ping-pong/js/anti-tamper.js"></script>
<script src="/disciplines/ping-pong/js/audio-manager.js"></script>
<script src="/disciplines/ping-pong/js/bot-ai.js"></script>
<script src="/disciplines/ping-pong/js/game.js"></script>
<script src="/disciplines/ping-pong/js/ui-manager.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #0a0a0a 0%, #1a0a2e 100%);
font-family: 'Lato', sans-serif;
overflow: hidden;
height: 100vh;
margin: 0;
padding: 0;
cursor: url('/disciplines/ping-pong/img/cursor-small.png') 10 10,
url('/disciplines/ping-pong/img/cursor.png') 16 16,
default;
}
.menu-container,
.menu-button,
.back-button {
cursor: url('/disciplines/ping-pong/img/cursor-small.png') 10 10,
url('/disciplines/ping-pong/img/cursor.png') 16 16,
default;
}
body.game-active,
body.game-active #gameCanvas {
cursor: url('/disciplines/ping-pong/img/cursor.png') 16 16, crosshair;
}
body.game-active .back-button {
cursor: url('/disciplines/ping-pong/img/cursor.png') 16 16, default;
}
#gameContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
padding: 0;
box-sizing: border-box;
gap: 5px;
}
h1 {
color: #00fff7;
text-shadow: 0 0 20px #00fff7, 0 0 40px #00fff7;
font-size: 1.5em;
margin: 0;
text-align: center;
animation: neonPulse 2s ease-in-out infinite;
}
@keyframes neonPulse {
0%, 100% { text-shadow: 0 0 20px #00fff7, 0 0 40px #00fff7; }
50% { text-shadow: 0 0 30px #00fff7, 0 0 60px #00fff7, 0 0 80px #00fff7; }
}
.menu-container {
background: linear-gradient(135deg, rgba(26, 10, 46, 0.95) 0%, rgba(10, 10, 10, 0.95) 100%);
border: 3px solid #00fff7;
border-radius: 25px;
padding: 40px 50px;
box-shadow: 0 0 50px rgba(0, 255, 247, 0.4),
0 0 100px rgba(0, 128, 255, 0.2),
inset 0 0 60px rgba(0, 255, 247, 0.05);
animation: slideInGlow 0.6s ease-out;
max-height: 80vh;
overflow-y: auto;
backdrop-filter: blur(10px);
}
/* Panel z regułami/snapshotem ustawień */
.rules-panel {
margin: 0 0 18px 0;
padding: 12px 14px;
border-radius: 12px;
border: 2px solid rgba(0, 255, 247, 0.25);
background: rgba(0, 255, 247, 0.05);
color: #dffcff;
font-size: 0.95em;
line-height: 1.6;
}
.rules-grid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 10px; }
.rule-chip {
background: rgba(0, 255, 247, 0.08);
border: 1px solid rgba(0, 255, 247, 0.25);
border-radius: 10px;
padding: 8px 10px;
text-align: center;
font-weight: 700;
color: #9af6ff;
}
.rules-panel .caption { font-weight: 700; margin-bottom: 8px; color: #7ae9ff; }
.rules-panel .special { margin-top: 10px; opacity: 0.9; }
.player-lobby-card {
display: grid;
grid-template-columns: minmax(260px, 1.1fr) minmax(0, 1.4fr);
gap: 14px;
margin-bottom: 22px;
padding: 16px;
border-radius: 18px;
border: 2px solid rgba(0, 255, 247, 0.18);
background: linear-gradient(180deg, rgba(0, 255, 247, 0.08) 0%, rgba(0, 0, 0, 0.16) 100%);
}
.player-lobby-head {
display: flex;
align-items: center;
gap: 14px;
}
.player-lobby-avatar {
width: 58px;
height: 58px;
border-radius: 16px;
display: grid;
place-items: center;
font-size: 1.45em;
font-weight: 900;
color: #0a0a0a;
background: linear-gradient(135deg, #00fff7 0%, #0080ff 100%);
box-shadow: 0 10px 24px rgba(0, 255, 247, 0.24);
}
.player-lobby-kicker {
font-size: 0.72em;
letter-spacing: .18em;
text-transform: uppercase;
color: #8ceef9;
margin-bottom: 6px;
}
.player-lobby-name {
font-size: 1.8em;
font-weight: 900;
color: #ffffff;
line-height: 1.05;
}
.player-lobby-meta {
margin-top: 8px;
color: rgba(223, 252, 255, 0.8);
font-size: 0.94em;
line-height: 1.5;
}
.player-lobby-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
}
.player-lobby-stat {
padding: 12px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.04);
}
.player-lobby-stat-label {
font-size: 0.7em;
letter-spacing: .12em;
text-transform: uppercase;
color: rgba(223, 252, 255, 0.62);
margin-bottom: 8px;
}
.player-lobby-stat-value {
font-size: 1.15em;
font-weight: 800;
color: #ffffff;
}
.mode-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
width: 100%;
}
.mode-card {
width: 100%;
min-height: 220px;
padding: 22px 24px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
text-align: left;
}
.mode-card-eyebrow {
font-size: 0.72em;
letter-spacing: .18em;
text-transform: uppercase;
opacity: .72;
}
.mode-card-title {
font-size: 1.5em;
line-height: 1.15;
font-weight: 900;
margin: 10px 0 8px;
}
.mode-card-copy {
font-size: 0.95em;
line-height: 1.65;
text-transform: none;
letter-spacing: 0;
opacity: .92;
}
.mode-card-points {
margin-top: 14px;
font-size: 0.88em;
line-height: 1.7;
text-transform: none;
letter-spacing: 0;
opacity: .92;
}
@keyframes slideInGlow {
0% {
opacity: 0;
transform: scale(0.95);
box-shadow: 0 0 0 rgba(0, 255, 247, 0);
}
100% {
opacity: 1;
transform: scale(1);
box-shadow: 0 0 50px rgba(0, 255, 247, 0.4);
}
}
.menu-title {
background: linear-gradient(90deg, #00fff7 0%, #0080ff 50%, #00fff7 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-size: 1.8em;
text-align: center;
margin-bottom: 35px;
font-weight: bold;
filter: drop-shadow(0 0 15px rgba(0, 255, 247, 0.8));
animation: titleShine 3s ease-in-out infinite;
letter-spacing: 2px;
}
@keyframes titleShine {
0%, 100% { filter: drop-shadow(0 0 15px rgba(0, 255, 247, 0.8)); }
50% { filter: drop-shadow(0 0 25px rgba(0, 255, 247, 1)); }
}
.buttons-grid {
display: flex;
flex-direction: column;
gap: 18px;
align-items: center;
}
.menu-button {
background: linear-gradient(135deg, #00fff7 0%, #0080ff 100%);
color: #0a0a0a;
border: 2px solid rgba(0, 255, 247, 0.3);
padding: 18px 45px;
font-size: 1.15em;
font-weight: bold;
border-radius: 15px;
margin: 0;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 20px rgba(0, 255, 247, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
display: block;
width: 280px;
position: relative;
overflow: hidden;
text-transform: uppercase;
letter-spacing: 1px;
}
.menu-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s ease;
}
.menu-button:hover::before {
left: 100%;
}
.menu-button:hover {
transform: translateY(-3px) scale(1.03);
box-shadow: 0 8px 30px rgba(0, 255, 247, 0.6),
0 0 50px rgba(0, 255, 247, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
border-color: rgba(0, 255, 247, 0.8);
}
.menu-button:active {
transform: translateY(-1px) scale(1.01);
box-shadow: 0 4px 15px rgba(0, 255, 247, 0.5);
}
.menu-button.bot {
background: linear-gradient(135deg, #ff006e 0%, #ff4500 100%);
border-color: rgba(255, 0, 110, 0.3);
box-shadow: 0 4px 20px rgba(255, 0, 110, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
.menu-button.bot:hover {
box-shadow: 0 8px 30px rgba(255, 0, 110, 0.6),
0 0 50px rgba(255, 0, 110, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
border-color: rgba(255, 0, 110, 0.8);
}
.menu-button.bot:active {
box-shadow: 0 4px 15px rgba(255, 0, 110, 0.5);
}
.menu-button.extreme {
background: linear-gradient(135deg, #8b00ff 0%, #ff0000 50%, #000000 100%);
border-color: rgba(139, 0, 255, 0.5);
box-shadow: 0 4px 20px rgba(139, 0, 255, 0.6),
0 0 30px rgba(255, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
animation: extremePulse 1.5s ease-in-out infinite;
font-weight: 900;
text-shadow: 0 0 10px rgba(255, 0, 0, 0.8);
}
@keyframes extremePulse {
0%, 100% {
box-shadow: 0 4px 20px rgba(139, 0, 255, 0.6),
0 0 30px rgba(255, 0, 0, 0.3);
}
50% {
box-shadow: 0 8px 40px rgba(139, 0, 255, 1),
0 0 60px rgba(255, 0, 0, 0.8),
0 0 80px rgba(0, 0, 0, 0.5);
}
}
.menu-button.extreme:hover {
box-shadow: 0 8px 30px rgba(139, 0, 255, 1),
0 0 70px rgba(255, 0, 0, 0.9),
0 0 100px rgba(0, 0, 0, 0.7),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
border-color: rgba(139, 0, 255, 1);
transform: translateY(-3px) scale(1.05);
}
.menu-button.extreme:active {
box-shadow: 0 4px 15px rgba(139, 0, 255, 0.7);
}
.menu-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
@media (max-width: 920px) {
.player-lobby-card {
grid-template-columns: 1fr;
}
.player-lobby-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.mode-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.player-lobby-grid {
grid-template-columns: 1fr;
}
.player-lobby-card,
.menu-container {
padding: 18px;
}
}
#gameCanvas {
border: 3px solid #00fff7;
border-radius: 10px;
box-shadow: 0 0 40px rgba(0, 255, 247, 0.5);
background: #0a0a0a;
display: none;
outline: none;
}
#gameCanvas:focus {
box-shadow: 0 0 50px rgba(0, 255, 247, 0.8);
}
.score-container {
display: none;
justify-content: space-between;
align-items: center;
width: 700px;
margin: 0;
gap: 0;
}
.score-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
}
.score-label {
font-size: 0.85em;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2px;
}
.score-label.player {
color: #0080ff;
text-shadow: 0 0 10px #0080ff;
}
.score-label.bot {
color: #ff006e;
text-shadow: 0 0 10px #ff006e;
}
.score-label.timer {
color: #00fff7;
text-shadow: 0 0 10px #00fff7;
}
nav,
footer {
display: none !important;
}
.score {
font-size: 2em;
font-weight: bold;
text-shadow: 0 0 10px;
}
.score.player {
color: #0080ff;
text-shadow: 0 0 20px #0080ff;
}
.score.bot {
color: #ff006e;
text-shadow: 0 0 20px #ff006e;
}
.score.timer {
color: #00fff7;
text-shadow: 0 0 15px #00fff7;
font-size: 1.8em;
}
.back-button {
background: rgba(0, 255, 247, 0.05);
color: #00fff7;
border: 2px solid rgba(0, 255, 247, 0.5);
padding: 14px 35px;
font-size: 1em;
font-weight: bold;
border-radius: 12px;
margin-top: 25px;
box-shadow: 0 0 20px rgba(0, 255, 247, 0.2);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
display: none;
text-transform: uppercase;
letter-spacing: 1px;
}
.back-button:hover {
background: rgba(0, 255, 247, 0.15);
box-shadow: 0 0 30px rgba(0, 255, 247, 0.4);
border-color: rgba(0, 255, 247, 0.8);
transform: translateY(-2px);
}
.back-button:active {
transform: translateY(0);
box-shadow: 0 0 15px rgba(0, 255, 247, 0.3);
}
.modal {
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
animation: fadeIn 0.3s ease;
display: none;
align-items: center;
justify-content: center;
}
.modal[style*="display: block"] {
display: flex !important;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
background: linear-gradient(135deg, #1a0a2e 0%, #0a0a0a 100%);
border: 3px solid #00fff7;
border-radius: 20px;
padding: 40px;
width: 90%;
max-width: 500px;
text-align: center;
box-shadow: 0 0 50px rgba(0, 255, 247, 0.5);
animation: modalSlide 0.5s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
@keyframes modalSlide {
from { transform: translateY(-100px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.modal-title {
color: #00fff7;
font-size: 2.5em;
margin-bottom: 20px;
text-shadow: 0 0 20px #00fff7;
width: 100%;
}
.modal-text {
color: #ffffff;
font-size: 1.3em;
margin-bottom: 30px;
line-height: 1.5;
width: 100%;
word-wrap: break-word;
}
.win-modal .modal-title {
color: #00ff00;
text-shadow: 0 0 20px #00ff00;
animation: winPulse 1s ease-in-out infinite;
}
.lose-modal .modal-title {
color: #ff006e;
text-shadow: 0 0 20px #ff006e;
}
@keyframes winPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
/* Style dla przycisków w modalach */
.modal-content .menu-button {
width: 350px;
padding: 18px 40px;
font-size: 1.3em;
margin: 10px 0;
font-weight: 700;
letter-spacing: 2px;
}
.modal-content .menu-button:first-of-type {
margin-top: 20px;
}
.win-modal .menu-button:not(.bot) {
background: linear-gradient(135deg, #00ff88 0%, #00ccff 100%);
border-color: rgba(0, 255, 136, 0.5);
box-shadow: 0 6px 25px rgba(0, 255, 136, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.win-modal .menu-button:not(.bot):hover {
box-shadow: 0 10px 35px rgba(0, 255, 136, 0.7),
0 0 60px rgba(0, 255, 136, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
border-color: rgba(0, 255, 136, 0.9);
}
.lose-modal .menu-button:not(.bot) {
background: linear-gradient(135deg, #ff006e 0%, #ff0099 100%);
border-color: rgba(255, 0, 110, 0.5);
box-shadow: 0 6px 25px rgba(255, 0, 110, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.lose-modal .menu-button:not(.bot):hover {
box-shadow: 0 10px 35px rgba(255, 0, 110, 0.7),
0 0 60px rgba(255, 0, 110, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
border-color: rgba(255, 0, 110, 0.9);
}
.modal-content .menu-button.bot {
background: linear-gradient(135deg, #6b46c1 0%, #8b5cf6 100%);
border-color: rgba(139, 92, 246, 0.5);
box-shadow: 0 6px 25px rgba(139, 92, 246, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.modal-content .menu-button.bot:hover {
box-shadow: 0 10px 35px rgba(139, 92, 246, 0.7),
0 0 60px rgba(139, 92, 246, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
border-color: rgba(139, 92, 246, 0.9);
}
.buttons-grid {
display: flex;
flex-direction: column;
align-items: center;
}
.game-controls-hint {
color: #00fff7;
font-size: 0.9em;
margin-top: 10px;
text-align: center;
display: none;
opacity: 0.7;
}
</style>
</head>
<body>
<!-- Tutaj PHP sprawdza sesje czy zalogowany i wczytuje odpowiednią nawigację -->
<?php
if (!empty($_SESSION['logged_in'])) {
if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin') {
include __DIR__ . '/../../global/navLoginedAdmin.php';
} else {
include __DIR__ . '/../../global/navLogined.php';
}
} else {
include __DIR__ . '/../../global/navNoLogined.php';
}
?>
<div id="gameContainer">
<h1>Ping-Pong</h1>
<!-- Menu główne -->
<div id="mainMenu" class="menu-container">
<div class="menu-title">Wybierz Tryb Gry</div>
<div class="player-lobby-card" id="playerLobbyCard">
<div class="player-lobby-head">
<div class="player-lobby-avatar" id="playerLobbyAvatar"><?php echo strtoupper(substr((string) ($_SESSION['username'] ?? 'G'), 0, 1)); ?></div>
<div>
<div class="player-lobby-kicker">Profil aktywnego gracza</div>
<div class="player-lobby-name" id="playerLobbyName"><?php echo htmlspecialchars((string) ($_SESSION['username'] ?? 'Gracz'), ENT_QUOTES, 'UTF-8'); ?></div>
<div class="player-lobby-meta" id="playerLobbyMeta">Ładowanie danych konta przed wyborem trybu…</div>
</div>
</div>
<div class="player-lobby-grid" id="playerLobbyGrid"></div>
</div>
<div class="rules-panel" id="rulesPanel" style="display:none">
<div class="caption">Reguły meczu</div>
<div class="rules-grid">
<div class="rule-chip" id="chipPoints">Set do 11</div>
<div class="rule-chip" id="chipSets">Mecz do 3 setów</div>
<div class="rule-chip" id="chipServe">Serwis co 2</div>
</div>
<div class="special" id="specialRulesText"></div>
</div>
<div class="buttons-grid">
<div class="mode-grid">
<button class="menu-button mode-card" onclick="showOnlineMessage()">
<span class="mode-card-eyebrow">Matchmaking na żywo</span>
<span class="mode-card-title">🌐 Graj Online</span>
<span class="mode-card-copy">Wchodzisz do kolejki 1v1, system dobiera przeciwnika i od razu pokazuje pełny panel Twojego konta: saldo, rozegrane mecze, skuteczność i kolejne statystyki.</span>
<span class="mode-card-points">10 sekund przygotowania przed startem • set do 11 • mecz do 3 wygranych setów</span>
</button>
<button class="menu-button bot mode-card" onclick="showDifficultyMenu()">
<span class="mode-card-eyebrow">Solo training</span>
<span class="mode-card-title">🤖 Graj z Botem</span>
<span class="mode-card-copy">Szybki trening offline z natychmiastowym wejściem do gry. Dobry tryb na rozgrzewkę przed meczem online albo testowanie sterowania i tempa odbić.</span>
<span class="mode-card-points">4 poziomy trudności • start bez kolejki • nauka refleksu i ustawienia</span>
</button>
</div>
<button class="back-button" style="display: block;" onclick="window.location.href='/disciplines/'">← Wróć do Dyscyplin</button>
</div>
</div>
<!-- Menu wyboru poziomu trudności -->
<div id="difficultyMenu" class="menu-container" style="display: none;">
<div class="menu-title">Wybierz Poziom Trudności</div>
<div class="buttons-grid">
<button class="menu-button" onclick="startGame('easy')">🟢 Łatwy</button>
<button class="menu-button" onclick="startGame('medium')">🟡 Średni</button>
<button class="menu-button" onclick="startGame('hard')">🔴 Trudny</button>
<button class="menu-button extreme" onclick="startGame('extreme')">💀 EXTREME</button>
<button class="back-button" style="display: block;" onclick="backToMainMenu()">← Powrót do Menu</button>
</div>
</div>
<!-- Wynik -->
<div class="score-container" id="scoreContainer">
<div class="score-wrapper">
<div class="score-label player" id="playerScoreLabel">👤 GRACZ • SETY 0</div>
<div class="score player" id="playerScore">0</div>
</div>
<div class="score-wrapper timer-wrapper">
<div class="score-label timer">⏱️ CZAS</div>
<div class="score timer" id="gameTimer">0:00</div>
</div>
<div class="score-wrapper">
<div class="score-label bot" id="botScoreLabel">🤖 BOT • SETY 0</div>
<div class="score bot" id="botScore">0</div>
</div>
</div>
<!-- Canvas gry -->
<canvas id="gameCanvas" width="700" height="450" tabindex="0"></canvas>
<div class="game-controls-hint" id="controlsHint">
⌨️ Sterowanie: ⬆️⬇️ Strzałki lub W/S
</div>
<button class="back-button" id="gameBackButton" onclick="endGame()">← Zakończ Grę</button>
</div>
<!-- Modal "W przygotowaniu" -->
<div id="comingSoonModal" class="modal">
<div class="modal-content">
<div class="modal-title">W Przygotowaniu</div>
<div class="modal-text">Ta funkcja jest obecnie w fazie rozwoju. Wkrótce będzie dostępna!</div>
<button class="menu-button" onclick="closeModal('comingSoonModal')">OK</button>
</div>
</div>
<!-- Modal wygranej -->
<div id="winModal" class="modal win-modal">
<div class="modal-content">
<div class="modal-title">🎉 WYGRAŁEŚ! 🎉</div>
<div class="modal-text">Gratulacje! Pokonałeś przeciwnika!</div>
<div class="modal-text" style="font-size: 32px; margin: 15px 0; color: #00fff7;">Czas: <span id="winTime">0:00</span></div>
<button class="menu-button" onclick="resetGame()">Zagraj Ponownie</button>
<button class="menu-button bot" onclick="endGame()">Menu Główne</button>
</div>
</div>
<!-- Modal przegranej -->
<div id="loseModal" class="modal lose-modal">
<div class="modal-content">
<div class="modal-title">💔 PRZEGRAŁEŚ 💔</div>
<div class="modal-text">Nie poddawaj się! Spróbuj jeszcze raz!</div>
<div class="modal-text" style="font-size: 32px; margin: 15px 0; color: #ff006e;">Czas: <span id="loseTime">0:00</span></div>
<button class="menu-button" onclick="resetGame()">Zagraj Ponownie</button>
<button class="menu-button bot" onclick="endGame()">Menu Główne</button>
</div>
</div>
<script>
// Inicjalizacja gry po załadowaniu strony
document.addEventListener('DOMContentLoaded', function() {
// Utwórz instancję gry (globalną)
window.game = new PingPongGame('gameCanvas');
// Utwórz menedżer UI
window.uiManager = new UIManager(window.game);
// Bot: używamy stałych zasad/stylu (snapshoty tylko dla online)
renderBotDefaultsPanel();
loadPingPongLobbyProfile();
console.log('Ping-Pong Game initialized!');
});
async function loadPingPongLobbyProfile() {
const avatar = document.getElementById('playerLobbyAvatar');
const name = document.getElementById('playerLobbyName');
const meta = document.getElementById('playerLobbyMeta');
const grid = document.getElementById('playerLobbyGrid');
if (!grid) return;
const fallback = {
userId: <?php echo (int) ($_SESSION['user_id'] ?? 0); ?>,
username: <?php echo json_encode((string) ($_SESSION['username'] ?? 'Gracz'), JSON_UNESCAPED_UNICODE); ?>,
role: <?php echo json_encode((string) ($_SESSION['role'] ?? 'user'), JSON_UNESCAPED_UNICODE); ?>,
balance: 0,
matchesPlayed: 0,
matchesWon: 0,
matchesLost: 0,
winRate: 0,
tournamentsPlayed: 0,
leaguesParticipated: 0,
totalTransactions: 0,
memberSince: '',
};
const render = function(summary) {
const data = summary || fallback;
const username = data.username || fallback.username;
if (avatar) avatar.textContent = username.charAt(0).toUpperCase();
if (name) name.textContent = username;
if (meta) {
const memberSince = data.memberSince ? new Date(data.memberSince).toLocaleDateString('pl-PL') : 'konto aktywne';
meta.textContent = `ID #${data.userId || fallback.userId} • ${data.role || fallback.role} • ${memberSince}`;
}
const cards = [
['Saldo', `${Number(data.balance || 0).toFixed(2)} PLN`],
['Mecze', String(data.matchesPlayed || 0)],
['Win rate', `${Number(data.winRate || 0).toFixed(1)}%`],
['Turnieje', String(data.tournamentsPlayed || 0)],
['Ligi', String(data.leaguesParticipated || 0)],
['Transakcje', String(data.totalTransactions || 0)],
['Wygrane', String(data.matchesWon || 0)],
['Porażki', String(data.matchesLost || 0)],
];
grid.innerHTML = cards.map(function(card) {
return `<div class="player-lobby-stat"><div class="player-lobby-stat-label">${card[0]}</div><div class="player-lobby-stat-value">${card[1]}</div></div>`;
}).join('');
};
try {
const response = await fetch('/api/matches/ping-pong/1v1/player-summary.php', { credentials: 'include' });
const json = await response.json().catch(() => null);
if (!response.ok || !json || !json.success || !json.data) {
throw new Error('player_summary_error');
}
render(json.data);
} catch (error) {
render(fallback);
}
}
async function loadDisciplineSettingsSnapshot() {
const rulesPanel = document.getElementById('rulesPanel');
if (!rulesPanel) return;
const defaults = {
pointsToWin: 11,
setsToWin: 3,
serveRotation: 2,
specialRules: 'Deuce od 1010, gramy do 2 przewagi.'
};
let rules = { ...defaults };
try {
const res = await fetch('/administration/disciplines/ping-pong/settings/?snapshot=true', { credentials: 'same-origin' });
if (res.ok) {
const data = await res.json();
const snap = data.snapshot || data.data || null;
if (snap && snap.rules) {
rules = {
pointsToWin: parseInt(snap.rules.pointsToWin) || defaults.pointsToWin,
setsToWin: parseInt(snap.rules.setsToWin) || defaults.setsToWin,
serveRotation: parseInt(snap.rules.serveRotation) || defaults.serveRotation,
specialRules: snap.rules.specialRules || ''
};
}
}
} catch (e) {
// Fallback na defaults gdy endpoint jest dostępny tylko dla admina
}
// Render chipów
document.getElementById('chipPoints').textContent = `Set do ${rules.pointsToWin}`;
document.getElementById('chipSets').textContent = `Mecz do ${rules.setsToWin} setów`;
document.getElementById('chipServe').textContent = `Serwis co ${rules.serveRotation}`;
const special = document.getElementById('specialRulesText');
special.textContent = rules.specialRules ? `Specjalne: ${rules.specialRules}` : '';
rulesPanel.style.display = 'block';
}
// Panel dla trybu z botem: stałe zasady/styl
function renderBotDefaultsPanel() {
const rulesPanel = document.getElementById('rulesPanel');
if (!rulesPanel) return;
const defaults = { pointsToWin: 11, setsToWin: 3, serveRotation: 2 };
const chipPoints = document.getElementById('chipPoints');
const chipSets = document.getElementById('chipSets');
const chipServe = document.getElementById('chipServe');
const special = document.getElementById('specialRulesText');
if (chipPoints) chipPoints.textContent = `Set do ${defaults.pointsToWin}`;
if (chipSets) chipSets.textContent = `Mecz do ${defaults.setsToWin} setów`;
if (chipServe) chipServe.textContent = `Serwis co ${defaults.serveRotation}`;
if (special) special.textContent = 'Tryb Bot: stałe zasady i styl. Snapshoty działają tylko w trybie online.';
rulesPanel.style.display = 'block';
}
</script>
<?php
if (!empty($_SESSION['logged_in'])) {
include __DIR__ . '/../../global/footerLogined.php';
} else {
include __DIR__ . '/../../global/footerNoLogined.php';
}
?>
</body>
</html>