🎮ArcadeLab

🎮 Shahab Game

by LuckyOwl26
1259 lines46.3 KB
▶ Play
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>🎮 Shahab Game</title>
    
    <!-- Firebase SDK -->
    <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
    <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-database-compat.js"></script>

    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: #0a0e1a;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
            position: relative;
            overflow-x: hidden;
        }

        body::before {
            content: '';
            position: fixed;
            top: -50%;
            left: -50%;
            width: 200%;
            height: 200%;
            background: 
                radial-gradient(ellipse at 20% 50%, rgba(88, 50, 200, 0.15) 0%, transparent 50%),
                radial-gradient(ellipse at 80% 50%, rgba(50, 100, 255, 0.15) 0%, transparent 50%),
                radial-gradient(ellipse at 50% 100%, rgba(150, 50, 255, 0.1) 0%, transparent 40%);
            animation: gradientShift 8s ease-in-out infinite alternate;
            z-index: 0;
        }

        @keyframes gradientShift {
            0% { transform: translate(0, 0) rotate(0deg); }
            100% { transform: translate(5%, 5%) rotate(3deg); }
        }

        .particles {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 0;
            overflow: hidden;
        }

        .particle {
            position: absolute;
            width: 3px;
            height: 3px;
            background: rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            animation: floatParticle 15s infinite linear;
        }

        @keyframes floatParticle {
            0% { transform: translateY(100vh) rotate(0deg); opacity: 0; }
            10% { opacity: 1; }
            90% { opacity: 1; }
            100% { transform: translateY(-100vh) rotate(720deg); opacity: 0; }
        }

        .container {
            position: relative;
            z-index: 1;
            background: linear-gradient(145deg, #1a1f35, #0f1428);
            border-radius: 30px;
            padding: 35px;
            max-width: 750px;
            width: 100%;
            box-shadow: 
                0 30px 80px rgba(0, 0, 0, 0.8),
                0 0 40px rgba(88, 50, 200, 0.15),
                0 0 80px rgba(50, 100, 255, 0.05),
                inset 0 1px 0 rgba(255, 255, 255, 0.05);
            border: 1px solid rgba(255, 255, 255, 0.05);
            backdrop-filter: blur(10px);
        }

        .header {
            text-align: center;
            margin-bottom: 25px;
        }

        .header h1 {
            font-size: 2.2em;
            background: linear-gradient(135deg, #a78bfa, #818cf8, #60a5fa);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            text-shadow: 0 0 40px rgba(167, 139, 250, 0.2);
            letter-spacing: 2px;
        }

        .header .subtitle {
            color: rgba(255, 255, 255, 0.4);
            font-size: 0.9em;
            margin-top: 5px;
            letter-spacing: 4px;
        }

        /* لابی */
        #lobby {
            display: block;
        }

        .lobby-box {
            background: rgba(255, 255, 255, 0.03);
            border-radius: 16px;
            padding: 25px;
            border: 1px solid rgba(255, 255, 255, 0.05);
        }

        .lobby-box h2 {
            color: rgba(255, 255, 255, 0.8);
            text-align: center;
            margin-bottom: 20px;
            font-size: 1.3em;
        }

        .form-group {
            margin-bottom: 15px;
        }

        .form-group label {
            display: block;
            color: rgba(255, 255, 255, 0.6);
            margin-bottom: 5px;
            font-size: 14px;
        }

        .form-group input,
        .form-group select {
            width: 100%;
            padding: 10px 15px;
            background: rgba(255, 255, 255, 0.05);
            border: 1px solid rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            color: white;
            font-size: 16px;
            transition: all 0.3s;
        }

        .form-group input:focus,
        .form-group select:focus {
            outline: none;
            border-color: rgba(167, 139, 250, 0.5);
            box-shadow: 0 0 20px rgba(167, 139, 250, 0.1);
        }

        .form-group input::placeholder {
            color: rgba(255, 255, 255, 0.3);
        }

        .form-group select option {
            background: #1a1f35;
            color: white;
        }

        .btn-primary {
            width: 100%;
            padding: 12px;
            background: linear-gradient(135deg, #7c3aed, #6d28d9);
            color: white;
            border: none;
            border-radius: 12px;
            font-size: 18px;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.3s;
            box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3);
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 30px rgba(124, 58, 237, 0.4);
        }

        .btn-primary:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none;
        }

        .waiting-message {
            text-align: center;
            padding: 20px;
            margin-top: 15px;
            background: rgba(251, 191, 36, 0.1);
            border-radius: 12px;
            border: 1px solid rgba(251, 191, 36, 0.2);
            display: none;
        }

        .waiting-message .spinner {
            width: 40px;
            height: 40px;
            margin: 0 auto 15px;
            border: 4px solid rgba(167, 139, 250, 0.1);
            border-top: 4px solid #7c3aed;
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .waiting-message p {
            color: rgba(255, 255, 255, 0.7);
            margin: 5px 0;
        }

        .waiting-message .hint {
            font-size: 0.9em;
            color: rgba(251, 191, 36, 0.6);
        }

        .room-code {
            background: rgba(167, 139, 250, 0.1);
            border: 1px solid rgba(167, 139, 250, 0.2);
            border-radius: 10px;
            padding: 12px;
            text-align: center;
            margin: 15px 0;
        }

        .room-code .code {
            font-size: 24px;
            font-weight: bold;
            color: #a78bfa;
            letter-spacing: 4px;
        }

        .room-code .label {
            color: rgba(255, 255, 255, 0.4);
            font-size: 12px;
            display: block;
            margin-bottom: 5px;
        }

        /* بازی */
        #gameArea {
            display: none;
        }

        .game-info {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 12px 20px;
            background: rgba(255, 255, 255, 0.03);
            border-radius: 16px;
            margin-bottom: 20px;
            border: 1px solid rgba(255, 255, 255, 0.05);
            flex-wrap: wrap;
            gap: 10px;
        }

        .players {
            display: flex;
            gap: 15px;
            align-items: center;
        }

        .player-badge {
            padding: 6px 16px;
            border-radius: 12px;
            font-weight: bold;
            font-size: 14px;
            transition: all 0.3s ease;
            border: 1px solid transparent;
        }

        .player-x {
            background: rgba(239, 68, 68, 0.15);
            color: #f87171;
            border-color: rgba(239, 68, 68, 0.2);
        }

        .player-o {
            background: rgba(59, 130, 246, 0.15);
            color: #60a5fa;
            border-color: rgba(59, 130, 246, 0.2);
        }

        .player-active {
            transform: scale(1.05);
            box-shadow: 0 0 20px rgba(167, 139, 250, 0.2);
            border-color: rgba(167, 139, 250, 0.4) !important;
        }

        .player-x.player-active {
            box-shadow: 0 0 25px rgba(239, 68, 68, 0.3);
            border-color: rgba(239, 68, 68, 0.4) !important;
        }

        .player-o.player-active {
            box-shadow: 0 0 25px rgba(59, 130, 246, 0.3);
            border-color: rgba(59, 130, 246, 0.4) !important;
        }

        .turn-indicator {
            padding: 6px 20px;
            border-radius: 12px;
            font-weight: bold;
            font-size: 16px;
            transition: all 0.3s ease;
            background: rgba(167, 139, 250, 0.1);
            color: #a78bfa;
            border: 1px solid rgba(167, 139, 250, 0.2);
        }

        .turn-x {
            background: rgba(239, 68, 68, 0.15);
            color: #f87171;
            border-color: rgba(239, 68, 68, 0.2);
            box-shadow: 0 0 20px rgba(239, 68, 68, 0.1);
        }

        .turn-o {
            background: rgba(59, 130, 246, 0.15);
            color: #60a5fa;
            border-color: rgba(59, 130, 246, 0.2);
            box-shadow: 0 0 20px rgba(59, 130, 246, 0.1);
        }

        .btn {
            padding: 8px 22px;
            border: none;
            border-radius: 12px;
            font-size: 14px;
            font-weight: bold;
            cursor: pointer;
            transition: all 0.3s ease;
            color: white;
            border: 1px solid rgba(255, 255, 255, 0.1);
        }

        .btn-reset {
            background: linear-gradient(135deg, #059669, #047857);
            box-shadow: 0 4px 15px rgba(5, 150, 105, 0.3);
        }

        .btn-reset:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 30px rgba(5, 150, 105, 0.4);
        }

        .btn-leave {
            background: linear-gradient(135deg, #dc2626, #b91c1c);
            box-shadow: 0 4px 15px rgba(220, 38, 38, 0.3);
        }

        .btn-leave:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 30px rgba(220, 38, 38, 0.4);
        }

        .board-wrapper {
            background: linear-gradient(145deg, #2c1810, #3d2317);
            border-radius: 20px;
            padding: 12px;
            box-shadow: 
                inset 0 2px 20px rgba(0, 0, 0, 0.6),
                0 10px 40px rgba(0, 0, 0, 0.5);
            border: 1px solid rgba(255, 255, 255, 0.05);
            position: relative;
        }

        .board-wrapper::before {
            content: '';
            position: absolute;
            inset: -2px;
            border-radius: 22px;
            padding: 2px;
            background: linear-gradient(135deg, rgba(167, 139, 250, 0.1), rgba(96, 165, 250, 0.1));
            -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
            -webkit-mask-composite: xor;
            mask-composite: exclude;
            pointer-events: none;
        }

        #boardCanvas {
            cursor: pointer;
            border-radius: 14px;
            max-width: 100%;
            height: auto;
            display: block;
            margin: 0 auto;
            background: #d4b48c;
            box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.2);
        }

        .status {
            margin-top: 15px;
            padding: 10px;
            text-align: center;
            font-size: 16px;
            color: rgba(255, 255, 255, 0.6);
            background: rgba(255, 255, 255, 0.02);
            border-radius: 12px;
            border: 1px solid rgba(255, 255, 255, 0.03);
            min-height: 45px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
        }

        .winner-text {
            font-size: 22px;
            font-weight: bold;
            background: linear-gradient(135deg, #fbbf24, #f59e0b);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            animation: glowPulse 1s ease-in-out infinite alternate;
        }

        @keyframes glowPulse {
            0% { filter: drop-shadow(0 0 10px rgba(251, 191, 36, 0.3)); }
            100% { filter: drop-shadow(0 0 30px rgba(251, 191, 36, 0.6)); }
        }

        .footer {
            margin-top: 15px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            color: rgba(255, 255, 255, 0.2);
            font-size: 12px;
            border-top: 1px solid rgba(255, 255, 255, 0.03);
            padding-top: 12px;
            flex-wrap: wrap;
            gap: 5px;
        }

        .online-status {
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .online-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            display: inline-block;
        }

        .online-dot.online {
            background: #51cf66;
            box-shadow: 0 0 10px rgba(81, 207, 102, 0.5);
        }

        .online-dot.offline {
            background: #ff6b6b;
            box-shadow: 0 0 10px rgba(255, 107, 107, 0.5);
        }

        @media (max-width: 768px) {
            .container { padding: 20px; border-radius: 20px; }
            .header h1 { font-size: 1.6em; }
            .game-info { flex-direction: column; align-items: stretch; gap: 8px; }
            .players { justify-content: center; }
            .turn-indicator { text-align: center; }
            .btn { width: 100%; padding: 10px; }
            .footer { flex-direction: column; text-align: center; }
        }

        @media (max-width: 480px) {
            .container { padding: 12px; border-radius: 16px; }
            .header h1 { font-size: 1.3em; }
            .player-badge { font-size: 12px; padding: 4px 12px; }
            .turn-indicator { font-size: 14px; padding: 4px 16px; }
            .status { font-size: 14px; min-height: 38px; }
        }
    </style>
</head>
<body>

    <div class="particles" id="particles"></div>

    <div class="container">
        <div class="header">
            <h1>🌐 Shahab Game</h1>
            <div class="subtitle">✦ بازی با دوستان از هر کجا ✦</div>
        </div>

        <!-- ========== لابی ========== -->
        <div id="lobby">
            <div class="lobby-box">
                <h2>🚀 ورود به بازی</h2>
                
                <div class="form-group">
                    <label>👤 نام شما:</label>
                    <input type="text" id="playerName" placeholder="نام خود را وارد کنید" value="بازیکن">
                </div>
                
                <div class="form-group">
                    <label>🔑 کد اتاق:</label>
                    <input type="text" id="roomCode" placeholder="کد اتاق را وارد کنید" value="game123">
                </div>
                
                <div class="form-group">
                    <label>🎯 نقش:</label>
                    <select id="playerRole">
                        <option value="auto">⚡ خودکار</option>
                        <option value="X">❌ بازیکن ۱ (X)</option>
                        <option value="O">⭕ بازیکن ۲ (O)</option>
                    </select>
                </div>
                
                <button class="btn-primary" onclick="joinGame()">🎮 ورود به بازی</button>
                
                <div class="waiting-message" id="waitingMessage">
                    <div class="spinner"></div>
                    <p>⏳ در انتظار بازیکن دوم...</p>
                    <p class="hint">💡 کد اتاق را به دوست خود بدهید</p>
                    <div class="room-code">
                        <span class="label">📋 کد اتاق شما</span>
                        <span class="code" id="displayRoomCode">game123</span>
                    </div>
                </div>
            </div>
        </div>

        <!-- ========== صفحه بازی ========== -->
        <div id="gameArea">
            <div class="game-info">
                <div class="players">
                    <span class="player-badge player-x" id="playerXBadge">❌ در انتظار...</span>
                    <span class="player-badge player-o" id="playerOBadge">⭕ در انتظار...</span>
                </div>
                <div class="turn-indicator turn-x" id="turnDisplay">⏳ در حال اتصال...</div>
                <div style="display: flex; gap: 8px; flex-wrap: wrap;">
                    <button class="btn btn-reset" onclick="resetGame()">🔄 جدید</button>
                    <button class="btn btn-leave" onclick="leaveGame()">🚪 خروج</button>
                </div>
            </div>

            <div class="board-wrapper">
                <canvas id="boardCanvas"></canvas>
            </div>

            <div class="status" id="statusDisplay">⏳ در حال اتصال به سرور...</div>

            <div class="footer">
                <span>🎯 هر بازیکن به نوبت مهره می‌گذارد</span>
                <span class="online-status">
                    <span class="online-dot offline" id="onlineDot"></span>
                    <span id="onlineText">آفلاین</span>
                </span>
            </div>
        </div>
    </div>

    <script>
        // ============================================
        // پیکربندی Firebase
        // ============================================
        const firebaseConfig = {
            apiKey: "AIzaSyC-vlEw3aP5Lw5r0pQxqLvJqY9XzXnZxXk",
            authDomain: "gomoku-online-4a3b2.firebaseapp.com",
            databaseURL: "https://gomoku-online-4a3b2-default-rtdb.firebaseio.com",
            projectId: "gomoku-online-4a3b2",
            storageBucket: "gomoku-online-4a3b2.appspot.com",
            messagingSenderId: "123456789012",
            appId: "1:123456789012:web:abcdef123456"
        };

        // ============================================
        // مقداردهی Firebase
        // ============================================
        firebase.initializeApp(firebaseConfig);
        const database = firebase.database();

        // ============================================
        // ذرات پس‌زمینه
        // ============================================
        (function createParticles() {
            const container = document.getElementById('particles');
            for (let i = 0; i < 25; i++) {
                const p = document.createElement('div');
                p.className = 'particle';
                p.style.left = Math.random() * 100 + '%';
                p.style.width = (Math.random() * 3 + 1) + 'px';
                p.style.height = p.style.width;
                p.style.animationDuration = (Math.random() * 20 + 10) + 's';
                p.style.animationDelay = (Math.random() * 20) + 's';
                p.style.opacity = Math.random() * 0.4 + 0.1;
                container.appendChild(p);
            }
        })();

        // ============================================
        // تنظیمات بازی
        // ============================================
        const BOARD_SIZE = 15;
        const MARGIN = 25;
        const PIECE_RADIUS = 15;

        let board = [];
        let currentPlayer = 'X';
        let gameOver = false;
        let winner = null;
        let winningCells = [];
        let moveCount = 0;
        let myLetter = null;
        let roomCode = '';
        let playerName = '';
        let gameRef = null;
        let isWaiting = false;

        const canvas = document.getElementById('boardCanvas');
        const ctx = canvas.getContext('2d');
        const turnDisplay = document.getElementById('turnDisplay');
        const statusDisplay = document.getElementById('statusDisplay');
        const playerXBadge = document.getElementById('playerXBadge');
        const playerOBadge = document.getElementById('playerOBadge');
        const onlineDot = document.getElementById('onlineDot');
        const onlineText = document.getElementById('onlineText');

        function resizeCanvas() {
            const containerWidth = canvas.parentElement.clientWidth - 24;
            const size = Math.min(containerWidth, 600);
            const cellSize = Math.floor((size - MARGIN * 2) / BOARD_SIZE);
            const totalSize = cellSize * BOARD_SIZE + MARGIN * 2;
            canvas.width = totalSize;
            canvas.height = totalSize;
            window.currentCellSize = cellSize;
            drawBoard();
        }

        // ============================================
        // توابع بازی
        // ============================================
        function initBoard() {
            board = [];
            for (let i = 0; i < BOARD_SIZE; i++) {
                board.push(new Array(BOARD_SIZE).fill(' '));
            }
        }

        function checkWinner(row, col, letter) {
            const directions = [[0,1],[1,0],[1,1],[1,-1]];
            for (const [dr, dc] of directions) {
                const cells = [[row, col]];
                for (let i = 1; i < 5; i++) {
                    const r = row + dr * i, c = col + dc * i;
                    if (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] === letter) {
                        cells.push([r, c]);
                    } else break;
                }
                for (let i = 1; i < 5; i++) {
                    const r = row - dr * i, c = col - dc * i;
                    if (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && board[r][c] === letter) {
                        cells.push([r, c]);
                    } else break;
                }
                if (cells.length >= 5) {
                    winningCells = cells.slice(0, 5);
                    return true;
                }
            }
            return false;
        }

        // ============================================
        // رسم تخته
        // ============================================
        function drawBoard() {
            const cellSize = window.currentCellSize || 38;
            const width = canvas.width;
            const height = canvas.height;

            ctx.clearRect(0, 0, width, height);

            const woodGradient = ctx.createLinearGradient(0, 0, width, height);
            woodGradient.addColorStop(0, '#d4b48c');
            woodGradient.addColorStop(0.3, '#c9a87c');
            woodGradient.addColorStop(0.5, '#d4b48c');
            woodGradient.addColorStop(0.7, '#bfa07a');
            woodGradient.addColorStop(1, '#d4b48c');
            ctx.fillStyle = woodGradient;
            ctx.fillRect(0, 0, width, height);

            ctx.strokeStyle = 'rgba(0, 0, 0, 0.7)';
            ctx.lineWidth = 1.2;

            for (let i = 0; i < BOARD_SIZE; i++) {
                const x = MARGIN + i * cellSize + cellSize / 2;
                const y = MARGIN + i * cellSize + cellSize / 2;
                ctx.beginPath();
                ctx.moveTo(x, MARGIN);
                ctx.lineTo(x, MARGIN + BOARD_SIZE * cellSize);
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(MARGIN, y);
                ctx.lineTo(MARGIN + BOARD_SIZE * cellSize, y);
                ctx.stroke();
            }

            const stars = [3, 7, 11];
            ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
            for (const r of stars) {
                for (const c of stars) {
                    const x = MARGIN + c * cellSize + cellSize / 2;
                    const y = MARGIN + r * cellSize + cellSize / 2;
                    ctx.shadowColor = 'rgba(0,0,0,0.2)';
                    ctx.shadowBlur = 5;
                    ctx.beginPath();
                    ctx.arc(x, y, 4, 0, 2 * Math.PI);
                    ctx.fill();
                    ctx.shadowBlur = 0;
                }
            }

            for (let i = 0; i < BOARD_SIZE; i++) {
                for (let j = 0; j < BOARD_SIZE; j++) {
                    if (board[i][j] === ' ') continue;

                    const x = MARGIN + j * cellSize + cellSize / 2;
                    const y = MARGIN + i * cellSize + cellSize / 2;

                    ctx.shadowColor = 'rgba(0,0,0,0.3)';
                    ctx.shadowBlur = 10;
                    ctx.shadowOffsetX = 2;
                    ctx.shadowOffsetY = 3;

                    if (board[i][j] === 'X') {
                        const gradientX = ctx.createLinearGradient(x - 15, y - 15, x + 15, y + 15);
                        gradientX.addColorStop(0, '#f87171');
                        gradientX.addColorStop(0.5, '#ef4444');
                        gradientX.addColorStop(1, '#dc2626');
                        ctx.shadowColor = 'rgba(239, 68, 68, 0.4)';
                        ctx.shadowBlur = 15;
                        ctx.strokeStyle = gradientX;
                        ctx.lineWidth = 5;
                        ctx.lineCap = 'round';
                        const offset = PIECE_RADIUS;
                        ctx.beginPath();
                        ctx.moveTo(x - offset, y - offset);
                        ctx.lineTo(x + offset, y + offset);
                        ctx.stroke();
                        ctx.beginPath();
                        ctx.moveTo(x + offset, y - offset);
                        ctx.lineTo(x - offset, y + offset);
                        ctx.stroke();
                        ctx.shadowBlur = 0;
                        ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
                        ctx.lineWidth = 2;
                        const hOff = PIECE_RADIUS - 2;
                        ctx.beginPath();
                        ctx.moveTo(x - hOff, y - hOff);
                        ctx.lineTo(x + hOff, y + hOff);
                        ctx.stroke();
                        ctx.beginPath();
                        ctx.moveTo(x + hOff, y - hOff);
                        ctx.lineTo(x - hOff, y + hOff);
                        ctx.stroke();
                    } else {
                        const gradientO = ctx.createRadialGradient(x - 5, y - 5, 0, x, y, PIECE_RADIUS);
                        gradientO.addColorStop(0, '#93c5fd');
                        gradientO.addColorStop(0.4, '#60a5fa');
                        gradientO.addColorStop(1, '#2563eb');
                        ctx.shadowColor = 'rgba(59, 130, 246, 0.4)';
                        ctx.shadowBlur = 15;
                        ctx.strokeStyle = gradientO;
                        ctx.lineWidth = 5;
                        ctx.beginPath();
                        ctx.arc(x, y, PIECE_RADIUS, 0, 2 * Math.PI);
                        ctx.stroke();
                        ctx.shadowBlur = 0;
                        ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
                        ctx.lineWidth = 2;
                        ctx.beginPath();
                        ctx.arc(x, y, PIECE_RADIUS - 2, 0, 2 * Math.PI);
                        ctx.stroke();
                        ctx.fillStyle = 'rgba(255, 255, 255, 0.08)';
                        ctx.beginPath();
                        ctx.arc(x - 5, y - 6, 4, 0, 2 * Math.PI);
                        ctx.fill();
                    }

                    ctx.shadowBlur = 0;
                    ctx.shadowOffsetX = 0;
                    ctx.shadowOffsetY = 0;
                }
            }

            if (winningCells.length > 0) {
                ctx.shadowColor = 'rgba(251, 191, 36, 0.6)';
                ctx.shadowBlur = 25;
                ctx.strokeStyle = '#fbbf24';
                ctx.lineWidth = 8;
                ctx.lineCap = 'round';
                for (let i = 0; i < winningCells.length - 1; i++) {
                    const [r1, c1] = winningCells[i];
                    const [r2, c2] = winningCells[i + 1];
                    const x1 = MARGIN + c1 * cellSize + cellSize / 2;
                    const y1 = MARGIN + r1 * cellSize + cellSize / 2;
                    const x2 = MARGIN + c2 * cellSize + cellSize / 2;
                    const y2 = MARGIN + r2 * cellSize + cellSize / 2;
                    ctx.beginPath();
                    ctx.moveTo(x1, y1);
                    ctx.lineTo(x2, y2);
                    ctx.stroke();
                }
                ctx.shadowBlur = 0;
                ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
                ctx.lineWidth = 3;
                for (let i = 0; i < winningCells.length - 1; i++) {
                    const [r1, c1] = winningCells[i];
                    const [r2, c2] = winningCells[i + 1];
                    const x1 = MARGIN + c1 * cellSize + cellSize / 2;
                    const y1 = MARGIN + r1 * cellSize + cellSize / 2;
                    const x2 = MARGIN + c2 * cellSize + cellSize / 2;
                    const y2 = MARGIN + r2 * cellSize + cellSize / 2;
                    ctx.beginPath();
                    ctx.moveTo(x1, y1);
                    ctx.lineTo(x2, y2);
                    ctx.stroke();
                }
                ctx.shadowBlur = 0;
            }
        }

        // ============================================
        // بروزرسانی UI
        // ============================================
        function updateUI() {
            if (!myLetter) return;

            if (currentPlayer === 'X') {
                turnDisplay.textContent = 'نوبت: ❌';
                turnDisplay.className = 'turn-indicator turn-x';
                playerXBadge.classList.add('player-active');
                playerOBadge.classList.remove('player-active');
            } else {
                turnDisplay.textContent = 'نوبت: ⭕';
                turnDisplay.className = 'turn-indicator turn-o';
                playerOBadge.classList.add('player-active');
                playerXBadge.classList.remove('player-active');
            }

            const isMyTurn = (myLetter === currentPlayer && !gameOver);
            canvas.style.cursor = isMyTurn ? 'pointer' : 'default';

            if (gameOver) {
                if (winner === myLetter) {
                    statusDisplay.innerHTML = '🎉 <span class="winner-text">شما برنده شدید!</span> 🎉';
                } else if (winner) {
                    statusDisplay.innerHTML = '😢 <span style="color: #f87171;">حریف برنده شد!</span>';
                } else {
                    statusDisplay.innerHTML = '🤝 بازی مساوی شد!';
                }
                return;
            }

            if (isMyTurn) {
                statusDisplay.innerHTML = '🎯 نوبت شماست';
                statusDisplay.style.color = 'rgba(167, 139, 250, 0.8)';
            } else {
                statusDisplay.innerHTML = '⏳ منتظر حرکت حریف...';
                statusDisplay.style.color = 'rgba(255,255,255,0.4)';
            }
        }

        // ============================================
        // اتصال به Firebase
        // ============================================
        function joinGame() {
            playerName = document.getElementById('playerName').value.trim() || 'بازیکن';
            roomCode = document.getElementById('roomCode').value.trim() || 'game123';
            const role = document.getElementById('playerRole').value;

            if (!roomCode) {
                alert('لطفاً کد اتاق را وارد کنید!');
                return;
            }

            document.getElementById('waitingMessage').style.display = 'block';
            document.querySelector('.btn-primary').disabled = true;
            document.querySelector('.btn-primary').textContent = '⏳ در حال اتصال...';

            gameRef = database.ref('games/' + roomCode);

            gameRef.once('value', (snapshot) => {
                const data = snapshot.val();
                let assignedLetter = null;

                if (!data) {
                    // بازی جدید
                    if (role === 'O') {
                        assignedLetter = 'O';
                    } else {
                        assignedLetter = 'X';
                    }
                } else {
                    const players = data.players || {};
                    const playerCount = Object.keys(players).length;

                    if (playerCount >= 2) {
                        alert('❌ این اتاق پر است!');
                        resetLobby();
                        return;
                    }

                    if (role === 'X' && !players.X) {
                        assignedLetter = 'X';
                    } else if (role === 'O' && !players.O) {
                        assignedLetter = 'O';
                    } else if (role === 'auto') {
                        assignedLetter = !players.X ? 'X' : 'O';
                    } else {
                        alert('❌ نقش انتخاب شده پر است!');
                        resetLobby();
                        return;
                    }
                }

                myLetter = assignedLetter;
                document.getElementById('displayRoomCode').textContent = roomCode;

                // ذخیره در Firebase
                const updateData = {};
                updateData['players/' + assignedLetter] = {
                    name: playerName,
                    joined: Date.now()
                };
                if (!data) {
                    updateData.board = Array(15).fill().map(() => Array(15).fill(' '));
                    updateData.currentPlayer = 'X';
                    updateData.gameOver = false;
                    updateData.winner = null;
                    updateData.winningCells = [];
                    updateData.moveCount = 0;
                }

                gameRef.update(updateData);

                document.getElementById('lobby').style.display = 'none';
                document.getElementById('gameArea').style.display = 'block';
                document.querySelector('.btn-primary').disabled = false;
                document.querySelector('.btn-primary').textContent = '🎮 ورود به بازی';

                initBoard();
                listenToGame();
                setOnlineStatus(true);
                resizeCanvas();

                statusDisplay.innerHTML = '⏳ در حال دریافت اطلاعات بازی...';
            });
        }

        function resetLobby() {
            document.getElementById('waitingMessage').style.display = 'none';
            document.querySelector('.btn-primary').disabled = false;
            document.querySelector('.btn-primary').textContent = '🎮 ورود به بازی';
        }

        // ============================================
        // گوش دادن به تغییرات Firebase
        // ============================================
        function listenToGame() {
            gameRef.on('value', (snapshot) => {
                const data = snapshot.val();
                if (!data) {
                    statusDisplay.innerHTML = '⚠️ بازی حذف شد!';
                    return;
                }

                // بروزرسانی تخته
                if (data.board) {
                    board = data.board;
                }

                if (data.currentPlayer !== undefined) {
                    currentPlayer = data.currentPlayer;
                }

                if (data.gameOver !== undefined) {
                    gameOver = data.gameOver;
                }

                if (data.winner !== undefined) {
                    winner = data.winner;
                }

                if (data.winningCells) {
                    winningCells = data.winningCells || [];
                }

                if (data.moveCount !== undefined) {
                    moveCount = data.moveCount || 0;
                }

                // بروزرسانی بازیکنان
                if (data.players) {
                    const players = data.players;
                    const playerCount = Object.keys(players).length;

                    playerXBadge.textContent = players.X ? `❌ ${players.X.name || 'بازیکن ۱'}` : '❌ در انتظار...';
                    playerOBadge.textContent = players.O ? `⭕ ${players.O.name || 'بازیکن ۲'}` : '⭕ در انتظار...';

                    if (playerCount < 2 && !gameOver) {
                        statusDisplay.innerHTML = '⏳ در انتظار بازیکن دوم...';
                        statusDisplay.style.color = 'rgba(251, 191, 36, 0.6)';
                    }
                }

                drawBoard();
                updateUI();

                // چک کردن برنده
                if (gameOver && winner) {
                    setTimeout(() => {
                        const winnerName = winner === myLetter ? 'شما' : 'حریف';
                        if (confirm(`🎉 ${winnerName} برنده شد! بازی جدید؟`)) {
                            resetGame();
                        }
                    }, 500);
                }
            });
        }

        // ============================================
        // حرکت دادن
        // ============================================
        function makeMove(row, col) {
            if (gameOver) {
                statusDisplay.innerHTML = '⛔ بازی تمام شده!';
                return;
            }

            if (myLetter !== currentPlayer) {
                statusDisplay.innerHTML = '⏳ نوبت شما نیست!';
                return;
            }

            if (board[row][col] !== ' ') {
                statusDisplay.innerHTML = '❌ این خانه پر است!';
                return;
            }

            // ارسال حرکت به Firebase
            const updates = {};
            updates['board/' + row + '/' + col] = myLetter;

            // بررسی برنده
            const tempBoard = board.map(row => [...row]);
            tempBoard[row][col] = myLetter;

            // بررسی موقت برنده
            const tempWin = checkWinnerTemp(row, col, myLetter, tempBoard);
            if (tempWin) {
                updates.gameOver = true;
                updates.winner = myLetter;
                updates.winningCells = winningCells;
            } else {
                const totalMoves = moveCount + 1;
                if (totalMoves === BOARD_SIZE * BOARD_SIZE) {
                    updates.gameOver = true;
                    updates.winner = null;
                } else {
                    updates.currentPlayer = (myLetter === 'X') ? 'O' : 'X';
                }
                updates.moveCount = totalMoves;
            }

            gameRef.update(updates);
        }

        function checkWinnerTemp(row, col, letter, tempBoard) {
            const directions = [[0,1],[1,0],[1,1],[1,-1]];
            for (const [dr, dc] of directions) {
                const cells = [[row, col]];
                for (let i = 1; i < 5; i++) {
                    const r = row + dr * i, c = col + dc * i;
                    if (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && tempBoard[r][c] === letter) {
                        cells.push([r, c]);
                    } else break;
                }
                for (let i = 1; i < 5; i++) {
                    const r = row - dr * i, c = col - dc * i;
                    if (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE && tempBoard[r][c] === letter) {
                        cells.push([r, c]);
                    } else break;
                }
                if (cells.length >= 5) {
                    winningCells = cells.slice(0, 5);
                    return true;
                }
            }
            return false;
        }

        // ============================================
        // ریست بازی
        // ============================================
        function resetGame() {
            if (!gameRef) return;

            const updates = {};
            updates.board = Array(15).fill().map(() => Array(15).fill(' '));
            updates.currentPlayer = 'X';
            updates.gameOver = false;
            updates.winner = null;
            updates.winningCells = [];
            updates.moveCount = 0;

            gameRef.update(updates);
            statusDisplay.innerHTML = '🔄 بازی ریست شد!';
            statusDisplay.style.color = 'rgba(255,255,255,0.6)';
        }

        // ============================================
        // خروج از بازی
        // ============================================
        function leaveGame() {
            if (gameRef && myLetter) {
                const removeData = {};
                removeData['players/' + myLetter] = null;
                gameRef.update(removeData);
            }

            if (gameRef) {
                gameRef.off();
                gameRef = null;
            }

            document.getElementById('gameArea').style.display = 'none';
            document.getElementById('lobby').style.display = 'block';
            setOnlineStatus(false);
            resetLobby();
            myLetter = null;
        }

        // ============================================
        // وضعیت آنلاین
        // ============================================
        function setOnlineStatus(isOnline) {
            if (isOnline) {
                onlineDot.className = 'online-dot online';
                onlineText.textContent = 'آنلاین';
            } else {
                onlineDot.className = 'online-dot offline';
                onlineText.textContent = 'آفلاین';
            }
        }

        // ============================================
        // رویدادهای کلیک
        // ============================================
        canvas.addEventListener('click', (e) => {
            if (!myLetter || gameOver || myLetter !== currentPlayer) return;

            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;

            const x = (e.clientX - rect.left) * scaleX;
            const y = (e.clientY - rect.top) * scaleY;

            const cellSize = window.currentCellSize || 38;
            const col = Math.floor((x - MARGIN) / cellSize);
            const row = Math.floor((y - MARGIN) / cellSize);

            if (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE) {
                makeMove(row, col);
            }
        });

        // ============================================
        // Hover Effect
        // ============================================
        canvas.addEventListener('mousemove', (e) => {
            if (!myLetter || gameOver || myLetter !== currentPlayer) {
                canvas.style.cursor = 'default';
                return;
            }

            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;

            const x = (e.clientX - rect.left) * scaleX;
            const y = (e.clientY - rect.top) * scaleY;

            const cellSize = window.currentCellSize || 38;
            const col = Math.floor((x - MARGIN) / cellSize);
            const row = Math.floor((y - MARGIN) / cellSize);

            if (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE) {
                if (board[row][col] === ' ') {
                    canvas.style.cursor = 'pointer';
                    drawBoard();

                    const cx = MARGIN + col * cellSize + cellSize / 2;
                    const cy = MARGIN + row * cellSize + cellSize / 2;

                    const glow = ctx.createRadialGradient(cx, cy, 0, cx, cy, PIECE_RADIUS + 8);
                    const color = currentPlayer === 'X' ? 'rgba(239, 68, 68, 0.15)' : 'rgba(59, 130, 246, 0.15)';
                    glow.addColorStop(0, color);
                    glow.addColorStop(1, 'rgba(0,0,0,0)');
                    ctx.fillStyle = glow;
                    ctx.shadowBlur = 0;
                    ctx.beginPath();
                    ctx.arc(cx, cy, PIECE_RADIUS + 8, 0, 2 * Math.PI);
                    ctx.fill();

                    ctx.strokeStyle = currentPlayer === 'X' ? 'rgba(239, 68, 68, 0.3)' : 'rgba(59, 130, 246, 0.3)';
                    ctx.lineWidth = 1.5;
                    ctx.setLineDash([4, 4]);
                    ctx.beginPath();
                    ctx.arc(cx, cy, PIECE_RADIUS + 2, 0, 2 * Math.PI);
                    ctx.stroke();
                    ctx.setLineDash([]);
                } else {
                    canvas.style.cursor = 'not-allowed';
                }
            } else {
                canvas.style.cursor = 'default';
            }
        });

        canvas.addEventListener('mouseleave', () => {
            drawBoard();
        });

        // ============================================
        // شروع
        // ============================================
        window.addEventListener('resize', resizeCanvas);
        initBoard();
        resizeCanvas();

        console.log('🌐 Gomoku Online loaded!');
    </script>
</body>
</html>

Game Source: 🎮 Shahab Game

Creator: LuckyOwl26

Libraries: none

Complexity: complex (1259 lines, 46.3 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: shahab-game-luckyowl26" to link back to the original. Then publish at arcadelab.ai/publish.