mirror of
https://github.com/DeNNiiInc/Connect-5.git
synced 2026-04-17 20:36:00 +00:00
Add surrender and rematch UI - Part 1
- Add surrender button to game controls - Add game-over modal with stats and rematch option - Add surrender confirmation modal - Add all CSS styling for new modals and buttons - Add surrender-rematch.js with global helper functions - Update multiplayer.js constructor to track opponent for rematch
This commit is contained in:
285
index.html
285
index.html
@@ -45,10 +45,19 @@
|
|||||||
<main class="game-container">
|
<main class="game-container">
|
||||||
<!-- Game Mode Toggle -->
|
<!-- Game Mode Toggle -->
|
||||||
<div class="mode-selector">
|
<div class="mode-selector">
|
||||||
<button class="mode-btn active" id="localModeBtn" onclick="toggleGameMode('local')">
|
<button
|
||||||
|
class="mode-btn active"
|
||||||
|
id="localModeBtn"
|
||||||
|
onclick="toggleGameMode('local')"
|
||||||
|
>
|
||||||
🎮 Local Play
|
🎮 Local Play
|
||||||
</button>
|
</button>
|
||||||
<button class="mode-btn" id="multiplayerModeBtn" onclick="toggleGameMode('multiplayer')" disabled>
|
<button
|
||||||
|
class="mode-btn"
|
||||||
|
id="multiplayerModeBtn"
|
||||||
|
onclick="toggleGameMode('multiplayer')"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
🌐 Multiplayer
|
🌐 Multiplayer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,11 +83,17 @@
|
|||||||
<span class="player-marker" id="currentPlayer">X</span>
|
<span class="player-marker" id="currentPlayer">X</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Status text integrated here -->
|
<!-- Status text integrated here -->
|
||||||
<span class="status-text-small" id="statusMessage">Player X starts</span>
|
<span class="status-text-small" id="statusMessage"
|
||||||
|
>Player X starts</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Side: Player Identity (Multiplayer only) -->
|
<!-- Right Side: Player Identity (Multiplayer only) -->
|
||||||
<div class="status-right" id="playerIdentitySection" style="display: none;">
|
<div
|
||||||
|
class="status-right"
|
||||||
|
id="playerIdentitySection"
|
||||||
|
style="display: none"
|
||||||
|
>
|
||||||
<span class="label">You are:</span>
|
<span class="label">You are:</span>
|
||||||
<div class="player-display">
|
<div class="player-display">
|
||||||
<span class="player-marker" id="playerIdentity">?</span>
|
<span class="player-marker" id="playerIdentity">?</span>
|
||||||
@@ -113,6 +128,27 @@
|
|||||||
</svg>
|
</svg>
|
||||||
New Game
|
New Game
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="surrender-btn"
|
||||||
|
id="surrenderBtn"
|
||||||
|
style="display: none"
|
||||||
|
onclick="confirmSurrender()"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Surrender
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="board-wrapper">
|
<div class="board-wrapper">
|
||||||
@@ -120,7 +156,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Multiplayer Panel -->
|
<!-- Multiplayer Panel -->
|
||||||
<div id="multiplayerPanel" class="multiplayer-panel" style="display: none;">
|
<div
|
||||||
|
id="multiplayerPanel"
|
||||||
|
class="multiplayer-panel"
|
||||||
|
style="display: none"
|
||||||
|
>
|
||||||
<div class="player-stats-card">
|
<div class="player-stats-card">
|
||||||
<h3>Your Stats</h3>
|
<h3>Your Stats</h3>
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
@@ -191,20 +231,39 @@
|
|||||||
<span class="db-status-value" id="dbTimestamp">--</span>
|
<span class="db-status-value" id="dbTimestamp">--</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="db-status-item">
|
<div class="db-status-item">
|
||||||
<button class="db-retry-btn" id="dbRetryBtn" onclick="window.dbStatusMonitor && window.dbStatusMonitor.retryConnection()" title="Retry SQL Connection">
|
<button
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
class="db-retry-btn"
|
||||||
<path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
|
id="dbRetryBtn"
|
||||||
|
onclick="window.dbStatusMonitor && window.dbStatusMonitor.retryConnection()"
|
||||||
|
title="Retry SQL Connection"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Database Status Details (for testing phase) -->
|
<!-- Database Status Details (for testing phase) -->
|
||||||
<div class="db-status-details" id="dbStatusDetails" style="display: none;">
|
<div class="db-status-details" id="dbStatusDetails" style="display: none">
|
||||||
<div class="db-details-header">
|
<div class="db-details-header">
|
||||||
<span class="db-details-title">🔍 SQL Connection Details</span>
|
<span class="db-details-title">🔍 SQL Connection Details</span>
|
||||||
<button class="db-details-close" onclick="document.getElementById('dbStatusDetails').style.display='none'">×</button>
|
<button
|
||||||
|
class="db-details-close"
|
||||||
|
onclick="document.getElementById('dbStatusDetails').style.display='none'"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="db-details-content" id="dbDetailsContent">
|
<div class="db-details-content" id="dbDetailsContent">
|
||||||
<div class="db-detail-item">
|
<div class="db-detail-item">
|
||||||
@@ -221,14 +280,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="db-detail-item">
|
<div class="db-detail-item">
|
||||||
<span class="db-detail-label">Error:</span>
|
<span class="db-detail-label">Error:</span>
|
||||||
<span class="db-detail-value db-detail-error" id="detailError">None</span>
|
<span class="db-detail-value db-detail-error" id="detailError"
|
||||||
|
>None</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="db-detail-logs" id="detailLogs"></div>
|
<div class="db-detail-logs" id="detailLogs"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SQL Error Display (Testing Phase) -->
|
<!-- SQL Error Display (Testing Phase) -->
|
||||||
<div class="sql-error-banner" id="sqlErrorBanner" style="display: none;">
|
<div class="sql-error-banner" id="sqlErrorBanner" style="display: none">
|
||||||
<div class="sql-error-header">
|
<div class="sql-error-header">
|
||||||
<span class="sql-error-icon">⚠️</span>
|
<span class="sql-error-icon">⚠️</span>
|
||||||
<span class="sql-error-title">SQL Connection Error</span>
|
<span class="sql-error-title">SQL Connection Error</span>
|
||||||
@@ -256,15 +317,97 @@
|
|||||||
<div class="modal-overlay" id="usernameModal">
|
<div class="modal-overlay" id="usernameModal">
|
||||||
<div class="modal-content username-modal">
|
<div class="modal-content username-modal">
|
||||||
<h2>Enter Your Username</h2>
|
<h2>Enter Your Username</h2>
|
||||||
<p class="modal-subtitle">Choose a family-friendly name (3-20 characters)</p>
|
<p class="modal-subtitle">
|
||||||
<input type="text" id="usernameInput" placeholder="Your username" maxlength="20" />
|
Choose a family-friendly name (3-20 characters)
|
||||||
<div id="usernameError" class="error-message" style="display: none;"></div>
|
</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="usernameInput"
|
||||||
|
placeholder="Your username"
|
||||||
|
maxlength="20"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
id="usernameError"
|
||||||
|
class="error-message"
|
||||||
|
style="display: none"
|
||||||
|
></div>
|
||||||
<button class="submit-btn" onclick="submitUsername()">
|
<button class="submit-btn" onclick="submitUsername()">
|
||||||
Join Multiplayer
|
Join Multiplayer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Game Over Modal -->
|
||||||
|
<div class="modal-overlay" id="gameOverModal">
|
||||||
|
<div class="modal-content game-over-modal">
|
||||||
|
<div class="game-over-icon" id="gameOverIcon">🎮</div>
|
||||||
|
<h2 id="gameOverTitle">Game Over</h2>
|
||||||
|
<p class="game-over-subtitle" id="gameOverMessage">
|
||||||
|
Thanks for playing!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="game-over-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Wins:</span>
|
||||||
|
<span class="stat-value" id="gameOverWins">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Losses:</span>
|
||||||
|
<span class="stat-value" id="gameOverLosses">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Draws:</span>
|
||||||
|
<span class="stat-value" id="gameOverDraws">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="game-over-actions">
|
||||||
|
<button
|
||||||
|
class="rematch-btn"
|
||||||
|
id="rematchBtn"
|
||||||
|
onclick="requestRematch()"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Challenge Again
|
||||||
|
</button>
|
||||||
|
<button class="lobby-btn" onclick="returnToLobby()">
|
||||||
|
← Return to Lobby
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Surrender Confirmation Modal -->
|
||||||
|
<div class="modal-overlay" id="surrenderModal">
|
||||||
|
<div class="modal-content surrender-modal">
|
||||||
|
<div class="surrender-icon">⚠️</div>
|
||||||
|
<h2>Surrender Game?</h2>
|
||||||
|
<p class="modal-subtitle">
|
||||||
|
Are you sure you want to surrender? This will count as a loss.
|
||||||
|
</p>
|
||||||
|
<div class="surrender-actions">
|
||||||
|
<button class="confirm-surrender-btn" onclick="executeSurrender()">
|
||||||
|
Yes, Surrender
|
||||||
|
</button>
|
||||||
|
<button class="cancel-surrender-btn" onclick="cancelSurrender()">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="surrender-rematch.js"></script>
|
||||||
<script src="storage.js"></script>
|
<script src="storage.js"></script>
|
||||||
<script src="db-status.js"></script>
|
<script src="db-status.js"></script>
|
||||||
<script src="multiplayer.js"></script>
|
<script src="multiplayer.js"></script>
|
||||||
@@ -272,51 +415,51 @@
|
|||||||
<script>
|
<script>
|
||||||
// Game mode toggling
|
// Game mode toggling
|
||||||
function toggleGameMode(mode) {
|
function toggleGameMode(mode) {
|
||||||
const localBtn = document.getElementById('localModeBtn');
|
const localBtn = document.getElementById("localModeBtn");
|
||||||
const multiplayerBtn = document.getElementById('multiplayerModeBtn');
|
const multiplayerBtn = document.getElementById("multiplayerModeBtn");
|
||||||
const gameControls = document.getElementById('gameControls');
|
const gameControls = document.getElementById("gameControls");
|
||||||
const multiplayerPanel = document.getElementById('multiplayerPanel');
|
const multiplayerPanel = document.getElementById("multiplayerPanel");
|
||||||
const boardWrapper = document.querySelector('.board-wrapper');
|
const boardWrapper = document.querySelector(".board-wrapper");
|
||||||
// statusMessage is now inside gameControls, so we don't need to toggle it separately
|
// statusMessage is now inside gameControls, so we don't need to toggle it separately
|
||||||
|
|
||||||
if (mode === 'local') {
|
if (mode === "local") {
|
||||||
localBtn.classList.add('active');
|
localBtn.classList.add("active");
|
||||||
multiplayerBtn.classList.remove('active');
|
multiplayerBtn.classList.remove("active");
|
||||||
gameControls.style.display = 'grid';
|
gameControls.style.display = "grid";
|
||||||
multiplayerPanel.style.display = 'none';
|
multiplayerPanel.style.display = "none";
|
||||||
boardWrapper.style.display = 'flex';
|
boardWrapper.style.display = "flex";
|
||||||
|
|
||||||
// Reset to local mode
|
// Reset to local mode
|
||||||
if (window.multiplayerClient) {
|
if (window.multiplayerClient) {
|
||||||
window.multiplayerClient.isMultiplayer = false;
|
window.multiplayerClient.isMultiplayer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset turn label to default
|
// Reset turn label to default
|
||||||
const turnLabel = document.getElementById('turnLabel');
|
const turnLabel = document.getElementById("turnLabel");
|
||||||
if (turnLabel) {
|
if (turnLabel) {
|
||||||
turnLabel.textContent = 'Current Turn:';
|
turnLabel.textContent = "Current Turn:";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
localBtn.classList.remove('active');
|
localBtn.classList.remove("active");
|
||||||
multiplayerBtn.classList.add('active');
|
multiplayerBtn.classList.add("active");
|
||||||
gameControls.style.display = 'none';
|
gameControls.style.display = "none";
|
||||||
multiplayerPanel.style.display = 'block';
|
multiplayerPanel.style.display = "block";
|
||||||
boardWrapper.style.display = 'none';
|
boardWrapper.style.display = "none";
|
||||||
|
|
||||||
// Initialize multiplayer if not already
|
// Initialize multiplayer if not already
|
||||||
if (!window.multiplayerClient) {
|
if (!window.multiplayerClient) {
|
||||||
if (!window.game) {
|
if (!window.game) {
|
||||||
console.log('⏳ Waiting for game to initialize...');
|
console.log("⏳ Waiting for game to initialize...");
|
||||||
// Poll for game to be ready
|
// Poll for game to be ready
|
||||||
const checkGame = setInterval(() => {
|
const checkGame = setInterval(() => {
|
||||||
if (window.game) {
|
if (window.game) {
|
||||||
clearInterval(checkGame);
|
clearInterval(checkGame);
|
||||||
console.log('✅ Game ready, initializing multiplayer...');
|
console.log("✅ Game ready, initializing multiplayer...");
|
||||||
window.multiplayerClient = new MultiplayerClient(window.game);
|
window.multiplayerClient = new MultiplayerClient(window.game);
|
||||||
window.multiplayerClient.connect();
|
window.multiplayerClient.connect();
|
||||||
}
|
}
|
||||||
}, 100); // Check every 100ms
|
}, 100); // Check every 100ms
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.multiplayerClient = new MultiplayerClient(window.game);
|
window.multiplayerClient = new MultiplayerClient(window.game);
|
||||||
window.multiplayerClient.connect();
|
window.multiplayerClient.connect();
|
||||||
@@ -326,59 +469,61 @@
|
|||||||
|
|
||||||
// Submit username
|
// Submit username
|
||||||
function submitUsername() {
|
function submitUsername() {
|
||||||
const input = document.getElementById('usernameInput');
|
const input = document.getElementById("usernameInput");
|
||||||
const error = document.getElementById('usernameError');
|
const error = document.getElementById("usernameError");
|
||||||
const username = input.value.trim();
|
const username = input.value.trim();
|
||||||
|
|
||||||
if (!username) {
|
if (!username) {
|
||||||
error.textContent = 'Please enter a username';
|
error.textContent = "Please enter a username";
|
||||||
error.style.display = 'block';
|
error.style.display = "block";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (username.length < 3 || username.length > 20) {
|
if (username.length < 3 || username.length > 20) {
|
||||||
error.textContent = 'Username must be 3-20 characters';
|
error.textContent = "Username must be 3-20 characters";
|
||||||
error.style.display = 'block';
|
error.style.display = "block";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
|
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
|
||||||
error.textContent = 'Only letters, numbers, underscores, and hyphens allowed';
|
error.textContent =
|
||||||
error.style.display = 'block';
|
"Only letters, numbers, underscores, and hyphens allowed";
|
||||||
|
error.style.display = "block";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
error.style.display = 'none';
|
error.style.display = "none";
|
||||||
|
|
||||||
if (window.multiplayerClient) {
|
if (window.multiplayerClient) {
|
||||||
window.multiplayerClient.registerPlayer(username);
|
window.multiplayerClient.registerPlayer(username);
|
||||||
} else {
|
} else {
|
||||||
console.error("Multiplayer client not initialized");
|
console.error("Multiplayer client not initialized");
|
||||||
error.textContent = "Error: Multiplayer not initialized. Refresh page.";
|
error.textContent =
|
||||||
error.style.display = 'block';
|
"Error: Multiplayer not initialized. Refresh page.";
|
||||||
|
error.style.display = "block";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change username
|
// Change username
|
||||||
function changeUsername() {
|
function changeUsername() {
|
||||||
// Clear saved username from localStorage
|
// Clear saved username from localStorage
|
||||||
localStorage.removeItem('connect5_username');
|
localStorage.removeItem("connect5_username");
|
||||||
|
|
||||||
// Show username modal
|
// Show username modal
|
||||||
const modal = document.getElementById('usernameModal');
|
const modal = document.getElementById("usernameModal");
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.classList.add('active');
|
modal.classList.add("active");
|
||||||
// Clear the input field
|
// Clear the input field
|
||||||
document.getElementById('usernameInput').value = '';
|
document.getElementById("usernameInput").value = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow Enter key to submit username
|
// Allow Enter key to submit username
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const input = document.getElementById('usernameInput');
|
const input = document.getElementById("usernameInput");
|
||||||
if (input) {
|
if (input) {
|
||||||
input.addEventListener('keypress', (e) => {
|
input.addEventListener("keypress", (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === "Enter") {
|
||||||
submitUsername();
|
submitUsername();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -453,3 +453,200 @@
|
|||||||
padding: 2rem 1.5rem;
|
padding: 2rem 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Surrender Button */
|
||||||
|
.surrender-btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.surrender-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(220, 38, 38, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Game Over Modal */
|
||||||
|
.game-over-modal {
|
||||||
|
max-width: 450px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
animation: bounce 1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-20px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-modal h2 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
background: var(--accent-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-subtitle {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-stats {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-stats .stat-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-stats .stat-label {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-stats .stat-value {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-over-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rematch-btn,
|
||||||
|
.lobby-btn {
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rematch-btn {
|
||||||
|
background: var(--accent-gradient);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: 0 4px 16px rgba(147, 51, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rematch-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 24px rgba(147, 51, 234, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-btn {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 2px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby-btn:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(147, 51, 234, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Surrender Confirmation Modal */
|
||||||
|
.surrender-modal {
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.surrender-icon {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
animation: shake 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
25% { transform: translateX(-10px); }
|
||||||
|
75% { transform: translateX(10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.surrender-modal h2 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.surrender-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-surrender-btn,
|
||||||
|
.cancel-surrender-btn {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.875rem 1.5rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-family: "Inter", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-surrender-btn {
|
||||||
|
background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-surrender-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(220, 38, 38, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-surrender-btn {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 2px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-surrender-btn:hover {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(147, 51, 234, 0.2);
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ class MultiplayerClient {
|
|||||||
this.activePlayers = [];
|
this.activePlayers = [];
|
||||||
this.pendingChallenges = new Map();
|
this.pendingChallenges = new Map();
|
||||||
this.selectedBoardSize = 15; // Default board size for multiplayer
|
this.selectedBoardSize = 15; // Default board size for multiplayer
|
||||||
|
this.opponent = null; // Track opponent for rematch
|
||||||
|
this.opponentId = null;
|
||||||
|
this.lastGameResult = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
68
surrender-rematch.js
Normal file
68
surrender-rematch.js
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Global helper functions for surrender and rematch features
|
||||||
|
|
||||||
|
// Show surrender confirmation
|
||||||
|
function confirmSurrender() {
|
||||||
|
if (!window.multiplayerClient || !window.multiplayerClient.isMultiplayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = document.getElementById('surrenderModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel surrender
|
||||||
|
function cancelSurrender() {
|
||||||
|
const modal = document.getElementById('surrenderModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute surrender
|
||||||
|
function executeSurrender() {
|
||||||
|
const modal = document.getElementById('surrenderModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.multiplayerClient) {
|
||||||
|
window.multiplayerClient.socket.emit('surrender', {
|
||||||
|
gameId: window.multiplayerClient.currentGameId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request rematch
|
||||||
|
function requestRematch() {
|
||||||
|
if (!window.multiplayerClient || !window.multiplayerClient.opponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.multiplayerClient.socket.emit('send_rematch', {
|
||||||
|
opponentId: window.multiplayerClient.opponentId,
|
||||||
|
boardSize: window.multiplayerClient.selectedBoardSize || 15
|
||||||
|
});
|
||||||
|
|
||||||
|
if (window.multiplayerClient.showMessage) {
|
||||||
|
window.multiplayerClient.showMessage(`Rematch request sent to ${window.multiplayerClient.opponent}`, 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = document.getElementById('gameOverModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return to lobby from game over modal
|
||||||
|
function returnToLobby() {
|
||||||
|
if (window.multiplayerClient) {
|
||||||
|
window.multiplayerClient.returnToLobby();
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = document.getElementById('gameOverModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
71
temp-multiplayer-methods.txt
Normal file
71
temp-multiplayer-methods.txt
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
|
||||||
|
// Surrender game
|
||||||
|
surrenderGame() {
|
||||||
|
if (!this.isMultiplayer || !this.currentGameId) {
|
||||||
|
this.showMessage('No active game to surrender', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.emit('surrender', { gameId: this.currentGameId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle rematch request from opponent
|
||||||
|
handleRematchRequest(data) {
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.className = 'challenge-notification';
|
||||||
|
notification.innerHTML = `
|
||||||
|
<div class="challenge-content">
|
||||||
|
<h3>Rematch Request!</h3>
|
||||||
|
<p><strong>${data.from}</strong> wants a rematch</p>
|
||||||
|
<p>Board size: ${data.boardSize}×${data.boardSize}</p>
|
||||||
|
<div class="challenge-actions">
|
||||||
|
<button class="accept-btn" onclick="multiplayerClient.acceptRematch('${data.rematchId}')">
|
||||||
|
Accept
|
||||||
|
</button>
|
||||||
|
<button class="decline-btn" onclick="multiplayerClient.declineRematch('${data.rematchId}')">
|
||||||
|
Decline
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
setTimeout(() => notification.classList.add('active'), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send rematch request
|
||||||
|
sendRematchRequest() {
|
||||||
|
if (!this.opponentId) {
|
||||||
|
this.showMessage('No opponent to challenge', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket.emit('send_rematch', {
|
||||||
|
opponentId: this.opponentId,
|
||||||
|
boardSize: this.selectedBoardSize
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showMessage(`Rematch request sent to ${this.opponent}`, 'info');
|
||||||
|
document.getElementById('gameOverModal').classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept rematch
|
||||||
|
acceptRematch(rematchId) {
|
||||||
|
this.socket.emit('accept_rematch', { rematchId });
|
||||||
|
|
||||||
|
// Hide game over modal
|
||||||
|
document.getElementById('gameOverModal').classList.remove('active');
|
||||||
|
|
||||||
|
// Remove notification
|
||||||
|
const notifications = document.querySelectorAll('.challenge-notification');
|
||||||
|
notifications.forEach(n => n.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decline rematch
|
||||||
|
declineRematch(rematchId) {
|
||||||
|
this.socket.emit('decline_rematch', { rematchId });
|
||||||
|
|
||||||
|
// Remove notification
|
||||||
|
const notifications = document.querySelectorAll('.challenge-notification');
|
||||||
|
notifications.forEach(n => n.remove());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user