<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Game &#8211; Tech AI Connect</title>
	<atom:link href="https://techaiconnect.com/game/feed/" rel="self" type="application/rss+xml" />
	<link>https://techaiconnect.com</link>
	<description>All Tek Information for You</description>
	<lastBuildDate>Sun, 06 Jul 2025 08:34:12 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.2</generator>
	<item>
		<title>Othello online 2 player</title>
		<link>https://techaiconnect.com/othello-online-2-player/</link>
					<comments>https://techaiconnect.com/othello-online-2-player/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sun, 06 Jul 2025 08:21:38 +0000</pubDate>
				<category><![CDATA[Game]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4291</guid>

					<description><![CDATA[Othello Online (Reversi) Othello (Reversi) Flip your opponent's discs to win! Your Name Create New [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Othello Online (Reversi)</title>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Google Fonts: Inter -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #111827; /* bg-gray-900 */
            color: #f0f0f0;
        }
        .board {
            display: grid;
            grid-template-columns: repeat(8, 1fr);
            grid-template-rows: repeat(8, 1fr);
            width: 100%;
            max-width: 640px;
            aspect-ratio: 1 / 1;
            background-color: #047857; /* emerald-700 */
            border: 8px solid #4a3a2a;
            border-radius: 8px;
            box-shadow: 0 10px 20px rgba(0,0,0,0.4);
            padding: 5px;
            gap: 5px;
        }
        .square {
            display: flex;
            align-items: center;
            justify-content: center;
            background-color: #059669; /* emerald-600 */
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.2s;
        }
        .square:hover {
            background-color: #047857;
        }

        .piece {
            width: 85%;
            height: 85%;
            border-radius: 50%;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.4), 0 2px 3px rgba(0,0,0,0.3);
            transform: scale(0);
            animation: place-piece 0.3s ease-out forwards;
        }
        .piece.black { background-color: #1f2937; } /* gray-800 */
        .piece.white { background-color: #f9fafb; } /* gray-50 */

        @keyframes place-piece {
            from { transform: scale(0); }
            to { transform: scale(1); }
        }

        .valid-move-indicator {
            width: 40%;
            height: 40%;
            border-radius: 50%;
            background-color: rgba(255, 255, 255, 0.3);
            pointer-events: none;
        }
        
        .board-disabled {
            pointer-events: none;
            opacity: 0.8;
        }
        .modal { transition: opacity 0.25s ease; }
    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-6xl mx-auto">
        <header class="text-center mb-6">
            <h1 class="text-4xl md:text-5xl font-bold text-white">Othello (Reversi)</h1>
            <p class="text-gray-400 mt-2">Flip your opponent's discs to win!</p>
        </header>

        <!-- Lobby Section -->
        <div id="lobby" class="bg-gray-800 p-8 rounded-2xl shadow-lg border border-gray-700 max-w-md mx-auto">
            <div class="mb-4">
                <label for="playerName" class="block mb-2 text-sm font-medium text-gray-300">Your Name</label>
                <input type="text" id="playerName" class="w-full text-sm rounded-lg p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="Enter your name" required>
            </div>
            <button id="createGameBtn" class="w-full text-white bg-emerald-600 hover:bg-emerald-700 focus:ring-4 focus:outline-none focus:ring-emerald-800 font-medium rounded-lg text-sm px-5 py-3 text-center mb-3">Create New Room</button>
            <div class="relative flex py-3 items-center">
                <div class="flex-grow border-t border-gray-600"></div>
                <span class="flex-shrink mx-4 text-gray-400">or</span>
                <div class="flex-grow border-t border-gray-600"></div>
            </div>
            <div class="flex gap-2">
                <input type="text" id="joinGameId" class="w-full text-sm rounded-lg p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="Enter Room ID (6 digits)">
                <button id="joinGameBtn" class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Join Room</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                Your User ID: <span id="userIdDisplay" class="font-mono">Loading...</span>
            </div>
        </div>

        <!-- Game Section -->
        <div id="game-container" class="hidden w-full">
            <div class="flex flex-col lg:flex-row gap-6 items-start justify-center">
                <!-- Game Info Panel -->
                <div class="w-full lg:w-80 bg-gray-800 p-5 rounded-xl shadow-lg border border-gray-700 flex-shrink-0 order-last lg:order-first">
                    <h3 class="text-xl font-semibold mb-3 text-white">Game Information</h3>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">Room ID:</label>
                        <div class="flex items-center gap-2 mt-1">
                            <input readonly id="gameIdDisplay" class="w-full bg-gray-700 font-mono text-lg p-2 rounded-md border border-gray-600 text-center tracking-widest"/>
                            <button id="copyGameIdBtn" class="p-2 bg-gray-600 hover:bg-gray-500 rounded-md text-gray-300" title="Copy ID">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
                            </button>
                        </div>
                    </div>

                    <div id="playerInfo" class="space-y-3 text-sm"></div>
                    <p id="game-status" class="mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-gray-700 text-gray-200"></p>
                    
                    <button id="resetGameBtn" class="w-full mt-4 text-white bg-yellow-600 hover:bg-yellow-700 focus:ring-4 focus:outline-none focus:ring-yellow-800 font-medium rounded-lg text-sm px-5 py-3 text-center hidden">Play Again</button>
                    <button id="leaveGameBtn" class="w-full mt-2 text-white bg-gray-600 hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-gray-800 font-medium rounded-lg text-sm px-5 py-3 text-center">Leave Room</button>
                </div>
                
                <!-- Game Board -->
                <div id="board" class="board">
                    <!-- Squares will be generated by JS -->
                </div>
            </div>
        </div>
    </div>

    <!-- Message Modal -->
    <div id="message-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
        <div class="bg-gray-800 rounded-xl shadow-2xl p-8 max-w-sm text-center border border-gray-700">
            <h2 id="modal-title" class="text-2xl font-bold mb-4"></h2>
            <p id="modal-body" class="text-gray-300 mb-6"></p>
            <button id="modal-close-btn" class="w-full text-white bg-blue-600 hover:bg-blue-700 font-medium rounded-lg text-sm px-5 py-3">Close</button>
        </div>
    </div>

    <!-- Firebase SDK -->
    <script type="module">
        // Import functions from Firebase SDKs
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, getDoc, setDoc, updateDoc, onSnapshot } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        // --- CONFIG ---
        const firebaseConfig = typeof __firebase_config !== 'undefined' 
            ? JSON.parse(__firebase_config)
            : {
                // Fallback configuration
                apiKey: "AIzaSyBL3USs4_BgCBM1_eFrOA0Htmjj2FWa5XM",
                authDomain: "app-and-game.firebaseapp.com",
                projectId: "app-and-game",
                storageBucket: "app-and-game.firebasestorage.app",
                messagingSenderId: "670250047171",
                appId: "1:670250047171:web:ca90d4df93674be6fef476",
                measurementId: "G-4KR5Q5RCQV"
            };
        const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);
        setLogLevel('debug');

        // --- DOM ELEMENTS ---
        const lobbyEl = document.getElementById('lobby');
        const gameContainerEl = document.getElementById('game-container');
        const boardEl = document.getElementById('board');
        const gameStatusEl = document.getElementById('game-status');
        const playerInfoEl = document.getElementById('playerInfo');
        const createGameBtn = document.getElementById('createGameBtn');
        const joinGameBtn = document.getElementById('joinGameBtn');
        const resetGameBtn = document.getElementById('resetGameBtn');
        const leaveGameBtn = document.getElementById('leaveGameBtn');
        const copyGameIdBtn = document.getElementById('copyGameIdBtn');
        const userIdDisplay = document.getElementById('userIdDisplay');
        const gameIdDisplay = document.getElementById('gameIdDisplay');
        const modal = document.getElementById('message-modal');
        const modalTitle = document.getElementById('modal-title');
        const modalBody = document.getElementById('modal-body');
        const modalCloseBtn = document.getElementById('modal-close-btn');

        // --- GAME STATE & CONSTANTS ---
        const BOARD_SIZE = 8;
        let userId = null;
        let playerName = '';
        let currentGameId = null;
        let playerSymbol = null; // 'B' for Black, 'W' for White
        let unsubscribeGame = null;
        let isHost = false;

        // --- AUTHENTICATION ---
        onAuthStateChanged(auth, async (user) => {
            if (user) {
                userId = user.uid;
                userIdDisplay.textContent = userId;
            } else {
                try {
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                } catch (error) {
                    console.error("Login error:", error);
                    showMessage("Error", "Could not authenticate user. Please reload the page.");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function showMessage(title, body) {
            modalTitle.textContent = title;
            modalBody.textContent = body;
            modal.classList.remove('hidden');
        }
        function showGameView(gameId) {
            currentGameId = gameId;
            gameIdDisplay.value = gameId;
            lobbyEl.classList.add('hidden');
            gameContainerEl.classList.remove('hidden');
        }
        function showLobbyView() {
            gameContainerEl.classList.add('hidden');
            lobbyEl.classList.remove('hidden');
            if (unsubscribeGame) {
                unsubscribeGame();
                unsubscribeGame = null;
            }
            currentGameId = null;
            playerSymbol = null;
            isHost = false;
        }

        // --- OTHELLO GAME LOGIC ---

        /**
         * Checks for valid moves for a given player.
         * @param {Array<Array<string|null>>} board - The 8x8 game board.
         * @param {string} player - The player to check for ('B' or 'W').
         * @returns {Array<{row: number, col: number}>} An array of valid move coordinates.
         */
        function getValidMoves(board, player) {
            const validMoves = [];
            const opponent = player === 'B' ? 'W' : 'B';
            const directions = [
                [-1, -1], [-1, 0], [-1, 1],
                [0, -1],           [0, 1],
                [1, -1], [1, 0], [1, 1]
            ];

            for (let r = 0; r < BOARD_SIZE; r++) {
                for (let c = 0; c < BOARD_SIZE; c++) {
                    if (board[r][c] !== null) continue; // Must be an empty square

                    let isValid = false;
                    for (const [dr, dc] of directions) {
                        let row = r + dr;
                        let col = c + dc;
                        let hasOpponentPieces = false;

                        while (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] === opponent) {
                            hasOpponentPieces = true;
                            row += dr;
                            col += dc;
                        }

                        if (hasOpponentPieces && row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] === player) {
                            isValid = true;
                            break; // Found a valid direction, no need to check others for this square
                        }
                    }
                    if (isValid) {
                        validMoves.push({ row: r, col: c });
                    }
                }
            }
            return validMoves;
        }

        /**
         * Gets all pieces that would be flipped by a move.
         * @param {Array<Array<string|null>>} board - The game board.
         * @param {number} startRow - The row of the new piece.
         * @param {number} startCol - The column of the new piece.
         * @param {string} player - The current player ('B' or 'W').
         * @returns {Array<{row: number, col: number}>} An array of coordinates of pieces to flip.
         */
        function getFlipsForMove(board, startRow, startCol, player) {
            const opponent = player === 'B' ? 'W' : 'B';
            const directions = [
                [-1, -1], [-1, 0], [-1, 1],
                [0, -1],           [0, 1],
                [1, -1], [1, 0], [1, 1]
            ];
            const piecesToFlip = [];

            for (const [dr, dc] of directions) {
                let row = startRow + dr;
                let col = startCol + dc;
                const potentialFlips = [];

                while (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] === opponent) {
                    potentialFlips.push({ row, col });
                    row += dr;
                    col += dc;
                }

                if (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] === player) {
                    piecesToFlip.push(...potentialFlips);
                }
            }
            return piecesToFlip;
        }

        // --- FIREBASE & GAME FLOW ---

        function getGameDocRef(gameId) {
            return doc(db, `artifacts/${appId}/public/data/othello-games/${gameId}`);
        }

        function createInitialBoard() {
            const board = Array(BOARD_SIZE).fill(null).map(() => Array(BOARD_SIZE).fill(null));
            board[3][3] = 'W';
            board[3][4] = 'B';
            board[4][3] = 'B';
            board[4][4] = 'W';
            return board;
        }

        function calculateScores(board) {
            let blackScore = 0;
            let whiteScore = 0;
            for (let r = 0; r < BOARD_SIZE; r++) {
                for (let c = 0; c < BOARD_SIZE; c++) {
                    if (board[r][c] === 'B') blackScore++;
                    if (board[r][c] === 'W') whiteScore++;
                }
            }
            return { B: blackScore, W: whiteScore };
        }

        async function generateUniqueGameId() {
            let gameId;
            let attempts = 0;
            const maxAttempts = 20;
            while (attempts < maxAttempts) {
                gameId = Math.floor(100000 + Math.random() * 900000).toString();
                const docSnap = await getDoc(getGameDocRef(gameId));
                if (!docSnap.exists()) return gameId;
                attempts++;
            }
            throw new Error("Failed to generate a unique room ID.");
        }

        async function createNewGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            if (!userId) {
                showMessage("Error", "User ID not available yet. Please wait a moment.");
                return;
            }

            createGameBtn.disabled = true;
            createGameBtn.textContent = 'Creating...';

            try {
                const newGameId = await generateUniqueGameId();
                const gameDocRef = getGameDocRef(newGameId);
                const initialBoard = createInitialBoard();
                const newGame = {
                    board: JSON.stringify(initialBoard),
                    players: { p1: { id: userId, name: playerName } }, // p1 is Black ('B')
                    currentPlayer: 'B',
                    status: 'waiting',
                    hostId: userId,
                    scores: { B: 2, W: 2 },
                    winner: null,
                    createdAt: new Date().toISOString(),
                };
                await setDoc(gameDocRef, newGame);
                
                isHost = true;
                playerSymbol = 'B';
                listenToGameUpdates(newGameId);
                showGameView(newGameId);

            } catch (error) {
                console.error("Error creating room: ", error);
                showMessage("Error", "Could not create room. Please try again.");
            } finally {
                createGameBtn.disabled = false;
                createGameBtn.textContent = 'Create New Room';
            }
        }

        async function joinExistingGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            const gameIdToJoin = document.getElementById('joinGameId').value.trim();
            if (!gameIdToJoin) {
                showMessage("Error", "Please enter a Room ID.");
                return;
            }

            joinGameBtn.disabled = true;
            const gameDocRef = getGameDocRef(gameIdToJoin);
            
            try {
                const gameSnap = await getDoc(gameDocRef);
                if (!gameSnap.exists()) {
                    showMessage("Error", "Room with this ID not found.");
                    return;
                }

                const gameData = gameSnap.data();
                if (gameData.players.p2 && gameData.players.p1.id !== userId) {
                    showMessage("Error", "The room is full.");
                    return;
                }
                
                if (!gameData.players.p2 && gameData.players.p1.id !== userId) {
                    await updateDoc(gameDocRef, {
                        'players.p2': { id: userId, name: playerName },
                        status: 'active'
                    });
                    playerSymbol = 'W';
                } else {
                    playerSymbol = gameData.players.p1.id === userId ? 'B' : 'W';
                }
                
                isHost = gameData.hostId === userId;
                listenToGameUpdates(gameIdToJoin);
                showGameView(gameIdToJoin);

            } catch (error) {
                console.error("Error joining room: ", error);
                showMessage("Error", "Could not join room. Please check the ID.");
            } finally {
                joinGameBtn.disabled = false;
            }
        }
        
        async function onSquareClick(row, col, isValidMove) {
            if (!isValidMove) return;

            const gameDocRef = getGameDocRef(currentGameId);
            const gameSnap = await getDoc(gameDocRef);
            if (!gameSnap.exists()) return;

            const gameData = gameSnap.data();
            const board = JSON.parse(gameData.board);
            const currentPlayer = gameData.currentPlayer;

            if (currentPlayer !== playerSymbol) return;

            // Apply the move
            const flips = getFlipsForMove(board, row, col, currentPlayer);
            board[row][col] = currentPlayer;
            flips.forEach(p => { board[p.row][p.col] = currentPlayer; });

            // Determine next state
            let nextPlayer = currentPlayer === 'B' ? 'W' : 'B';
            let nextPlayerValidMoves = getValidMoves(board, nextPlayer);
            
            // If next player has no moves, it's current player's turn again
            if (nextPlayerValidMoves.length === 0) {
                nextPlayer = currentPlayer;
                nextPlayerValidMoves = getValidMoves(board, nextPlayer);
            }
            
            const newScores = calculateScores(board);
            let newStatus = 'active';
            let winner = null;

            // Check for game over
            if (nextPlayerValidMoves.length === 0) {
                newStatus = 'finished';
                if (newScores.B > newScores.W) winner = 'B';
                else if (newScores.W > newScores.B) winner = 'W';
                else winner = 'draw';
            }

            await updateDoc(gameDocRef, {
                board: JSON.stringify(board),
                currentPlayer: nextPlayer,
                scores: newScores,
                status: newStatus,
                winner: winner
            });
        }

        async function resetCurrentGame() {
            if (!isHost || !currentGameId) return;
            const gameDocRef = getGameDocRef(currentGameId);
            const initialBoard = createInitialBoard();
            await updateDoc(gameDocRef, {
                board: JSON.stringify(initialBoard),
                currentPlayer: 'B',
                status: 'active',
                scores: { B: 2, W: 2 },
                winner: null
            });
        }

        // --- RENDERING ---

        function renderBoard(gameData) {
            const { board: boardStr, currentPlayer, status } = gameData;
            const board = JSON.parse(boardStr);
            const isMyTurn = currentPlayer === playerSymbol && status === 'active';
            
            boardEl.innerHTML = '';
            boardEl.classList.toggle('board-disabled', !isMyTurn);
            
            const validMoves = isMyTurn ? getValidMoves(board, playerSymbol) : [];

            for (let r = 0; r < BOARD_SIZE; r++) {
                for (let c = 0; c < BOARD_SIZE; c++) {
                    const squareEl = document.createElement('div');
                    squareEl.className = 'square';
                    
                    const piece = board[r][c];
                    if (piece) {
                        const pieceEl = document.createElement('div');
                        pieceEl.className = `piece ${piece === 'B' ? 'black' : 'white'}`;
                        squareEl.appendChild(pieceEl);
                    }

                    const isValidMove = validMoves.some(move => move.row === r && move.col === c);
                    if (isValidMove) {
                        const indicator = document.createElement('div');
                        indicator.className = 'valid-move-indicator';
                        squareEl.appendChild(indicator);
                    }

                    squareEl.addEventListener('click', () => onSquareClick(r, c, isValidMove));
                    boardEl.appendChild(squareEl);
                }
            }
        }

        function updateGameInfo(gameData) {
            const { players, status, currentPlayer, scores, winner } = gameData;
            const p1 = players.p1; // Black
            const p2 = players.p2; // White

            playerInfoEl.innerHTML = `
                <div class="flex items-center justify-between p-3 rounded-lg transition-colors ${currentPlayer === 'B' && status === 'active' ? 'bg-gray-600' : 'bg-gray-700'}">
                    <div class="flex items-center gap-3">
                        <div class="w-6 h-6 rounded-full bg-gray-800 border-2 border-gray-500"></div>
                        <span class="font-semibold text-white">${p1.name} ${p1.id === userId ? "(You)" : ""}</span>
                    </div>
                    <span class="font-bold text-xl">${scores.B}</span>
                </div>
            `;

            if (p2) {
                 playerInfoEl.innerHTML += `
                    <div class="flex items-center justify-between p-3 rounded-lg transition-colors ${currentPlayer === 'W' && status === 'active' ? 'bg-gray-600' : 'bg-gray-700'}">
                        <div class="flex items-center gap-3">
                            <div class="w-6 h-6 rounded-full bg-gray-50 border-2 border-gray-400"></div>
                            <span class="font-semibold text-white">${p2.name} ${p2.id === userId ? "(You)" : ""}</span>
                        </div>
                        <span class="font-bold text-xl">${scores.W}</span>
                    </div>
                 `;
            } else {
                 playerInfoEl.innerHTML += `<div class="p-3 text-center text-gray-400 border border-dashed rounded-lg border-gray-600">Waiting for opponent...</div>`;
            }
            
            let statusText = '';
            let statusClass = 'bg-gray-700 text-gray-200';
            if (status === 'waiting') {
                statusText = 'Waiting for player...';
            } else if (status === 'active') {
                const currentName = currentPlayer === 'B' ? p1.name : (p2 ? p2.name : '');
                statusText = `${currentName}'s Turn`;
                if (currentPlayer === playerSymbol) {
                    statusText = "Your Turn!";
                    statusClass = 'bg-blue-600 text-white';
                }
            } else if (status === 'finished') {
                if (winner === 'draw') {
                    statusText = "It's a Draw!";
                } else {
                    const winnerName = winner === 'B' ? p1.name : p2.name;
                    statusText = `${winnerName} Wins!`;
                }
                statusClass = 'bg-yellow-500 text-black font-bold';
            }
            gameStatusEl.textContent = statusText;
            gameStatusEl.className = `mt-4 text-center font-semibold text-lg p-3 rounded-lg ${statusClass}`;
            
            resetGameBtn.classList.toggle('hidden', status !== 'finished' || !isHost);
        }

        function listenToGameUpdates(gameId) {
            if (unsubscribeGame) unsubscribeGame();
            const gameDocRef = getGameDocRef(gameId);
            unsubscribeGame = onSnapshot(gameDocRef, (docSnap) => {
                if (docSnap.exists()) {
                    const gameData = docSnap.data();
                    renderBoard(gameData);
                    updateGameInfo(gameData);
                } else {
                    showMessage("Notice", "The game room has been closed or does not exist.");
                    showLobbyView();
                }
            });
        }

        // --- EVENT LISTENERS ---
        createGameBtn.addEventListener('click', createNewGame);
        joinGameBtn.addEventListener('click', joinExistingGame);
        leaveGameBtn.addEventListener('click', showLobbyView);
        resetGameBtn.addEventListener('click', resetCurrentGame);
        modalCloseBtn.addEventListener('click', () => modal.classList.add('hidden'));
        
        copyGameIdBtn.addEventListener('click', () => {
            const gameId = gameIdDisplay.value;
            const textArea = document.createElement("textarea");
            textArea.value = gameId;
            document.body.appendChild(textArea);
            textArea.select();
            try {
                document.execCommand('copy');
                showMessage("Success", `Copied Room ID: ${gameId}`);
            } catch (err) {
                showMessage("Error", "Could not copy ID.");
            }
            document.body.removeChild(textArea);
        });

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/othello-online-2-player/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Chess Online Free (2 player)</title>
		<link>https://techaiconnect.com/chess-online-2-player/</link>
					<comments>https://techaiconnect.com/chess-online-2-player/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sun, 06 Jul 2025 07:50:57 +0000</pubDate>
				<category><![CDATA[Game]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4285</guid>

					<description><![CDATA[Online Chess (Firestore) Online Chess Challenge your friends to a game of wits Your Name [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Online Chess (Firestore)</title>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Chess.js for game logic -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js"></script>
    
    <!-- Google Fonts: Inter -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #1a1a1a;
            color: #f0f0f0;
        }
        .board {
            display: grid;
            grid-template-columns: repeat(8, 1fr);
            grid-template-rows: repeat(8, 1fr);
            width: 100%;
            max-width: 640px;
            aspect-ratio: 1 / 1;
            border: 4px solid #4a3a2a;
            border-radius: 8px;
            box-shadow: 0 10px 20px rgba(0,0,0,0.4);
        }
        .square {
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: clamp(20px, 6vmin, 48px);
            user-select: none;
        }
        .square.light { background-color: #f0d9b5; }
        .square.dark { background-color: #b58863; }
        
        .piece { cursor: grab; }
        .piece:active { cursor: grabbing; }

        /* Styling for highlights */
        .selected {
            background-color: rgba(34, 197, 94, 0.7) !important; /* green-500 with opacity */
        }
        .last-move {
            background-color: rgba(245, 158, 11, 0.5) !important; /* amber-500 with opacity */
        }
        .possible-move-dot {
            width: 30%;
            height: 30%;
            background-color: rgba(40, 40, 40, 0.4);
            border-radius: 50%;
            pointer-events: none; /* Make sure it doesn't block clicks on the square */
        }
        .in-check {
             background-color: rgba(239, 68, 68, 0.6) !important; /* red-500 with opacity */
        }
        
        .board-disabled {
            pointer-events: none;
            opacity: 0.8;
        }
        .modal { transition: opacity 0.25s ease; }
    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-6xl mx-auto">
        <header class="text-center mb-6">
            <h1 class="text-4xl md:text-5xl font-bold text-white">Online Chess</h1>
            <p class="text-gray-400 mt-2">Challenge your friends to a game of wits</p>
        </header>

        <!-- Lobby Section -->
        <div id="lobby" class="bg-gray-800 p-8 rounded-2xl shadow-lg border border-gray-700 max-w-md mx-auto">
            <div class="mb-4">
                <label for="playerName" class="block mb-2 text-sm font-medium text-gray-300">Your Name</label>
                <input type="text" id="playerName" class="w-full text-sm rounded-lg p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="Enter your name" required>
            </div>
            <button id="createGameBtn" class="w-full text-white bg-emerald-600 hover:bg-emerald-700 focus:ring-4 focus:outline-none focus:ring-emerald-800 font-medium rounded-lg text-sm px-5 py-3 text-center mb-3">Create New Room</button>
            <div class="relative flex py-3 items-center">
                <div class="flex-grow border-t border-gray-600"></div>
                <span class="flex-shrink mx-4 text-gray-400">or</span>
                <div class="flex-grow border-t border-gray-600"></div>
            </div>
            <div class="flex gap-2">
                <input type="text" id="joinGameId" class="w-full text-sm rounded-lg p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="Enter Room ID (6 digits)">
                <button id="joinGameBtn" class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Join Room</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                Your User ID: <span id="userIdDisplay" class="font-mono">Loading...</span>
            </div>
        </div>

        <!-- Game Section -->
        <div id="game-container" class="hidden w-full">
            <div class="flex flex-col lg:flex-row gap-6 items-start justify-center">
                <!-- Game Info Panel -->
                <div class="w-full lg:w-80 bg-gray-800 p-5 rounded-xl shadow-lg border border-gray-700 flex-shrink-0 order-last lg:order-first">
                    <h3 class="text-xl font-semibold mb-3 text-white">Game Information</h3>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">Room ID:</label>
                        <div class="flex items-center gap-2 mt-1">
                            <input readonly id="gameIdDisplay" class="w-full bg-gray-700 font-mono text-lg p-2 rounded-md border border-gray-600 text-center tracking-widest"/>
                            <button id="copyGameIdBtn" class="p-2 bg-gray-600 hover:bg-gray-500 rounded-md text-gray-300" title="Copy ID">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
                            </button>
                        </div>
                    </div>

                    <div id="playerInfo" class="space-y-3 text-sm"></div>
                    <p id="game-status" class="mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-gray-700 text-gray-200"></p>
                    
                    <div id="captured-pieces-white" class="mt-4 text-2xl"></div>
                    <div id="captured-pieces-black" class="mt-4 text-2xl"></div>

                    <button id="resetGameBtn" class="w-full mt-4 text-white bg-yellow-600 hover:bg-yellow-700 focus:ring-4 focus:outline-none focus:ring-yellow-800 font-medium rounded-lg text-sm px-5 py-3 text-center hidden">Play Again</button>
                    <button id="leaveGameBtn" class="w-full mt-2 text-white bg-gray-600 hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-gray-800 font-medium rounded-lg text-sm px-5 py-3 text-center">Leave Room</button>
                </div>
                
                <!-- Game Board -->
                <div id="board" class="board">
                    <!-- Squares will be generated by JS -->
                </div>
            </div>
        </div>
    </div>

    <!-- Message Modal -->
    <div id="message-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
        <div class="bg-gray-800 rounded-xl shadow-2xl p-8 max-w-sm text-center border border-gray-700">
            <h2 id="modal-title" class="text-2xl font-bold mb-4"></h2>
            <p id="modal-body" class="text-gray-300 mb-6"></p>
            <button id="modal-close-btn" class="w-full text-white bg-blue-600 hover:bg-blue-700 font-medium rounded-lg text-sm px-5 py-3">Close</button>
        </div>
    </div>

    <!-- Firebase SDK -->
    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, getDoc, setDoc, updateDoc, onSnapshot } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        // --- CONFIG ---
        const firebaseConfig = typeof __firebase_config !== 'undefined' 
            ? JSON.parse(__firebase_config)
            : {
                apiKey: "AIzaSyBL3USs4_BgCBM1_eFrOA0Htmjj2FWa5XM",
                authDomain: "app-and-game.firebaseapp.com",
                projectId: "app-and-game",
                storageBucket: "app-and-game.firebasestorage.app",
                messagingSenderId: "670250047171",
                appId: "1:670250047171:web:ca90d4df93674be6fef476",
                measurementId: "G-4KR5Q5RCQV"
            };
        const appId = typeof __app_id !== 'undefined' ? __app_id : firebaseConfig.projectId;
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);
        setLogLevel('debug');

        // --- DOM ELEMENTS ---
        const lobbyEl = document.getElementById('lobby');
        const gameContainerEl = document.getElementById('game-container');
        const boardEl = document.getElementById('board');
        const gameStatusEl = document.getElementById('game-status');
        const playerInfoEl = document.getElementById('playerInfo');
        const createGameBtn = document.getElementById('createGameBtn');
        const joinGameBtn = document.getElementById('joinGameBtn');
        const resetGameBtn = document.getElementById('resetGameBtn');
        const leaveGameBtn = document.getElementById('leaveGameBtn');
        const copyGameIdBtn = document.getElementById('copyGameIdBtn');
        const userIdDisplay = document.getElementById('userIdDisplay');
        const gameIdDisplay = document.getElementById('gameIdDisplay');
        const modal = document.getElementById('message-modal');
        const modalTitle = document.getElementById('modal-title');
        const modalBody = document.getElementById('modal-body');
        const modalCloseBtn = document.getElementById('modal-close-btn');

        // --- GAME STATE ---
        let userId = null;
        let playerName = '';
        let currentGameId = null;
        let playerColor = null; // 'w' or 'b'
        let unsubscribeGame = null;
        let chess = new Chess();
        let selectedSquare = null;
        let isHost = false;

        // Unicode pieces based on FEN standard (Uppercase: White, Lowercase: Black)
        const pieceUnicode = {
            'P': '♙', 'R': '♖', 'N': '♘', 'B': '♗', 'Q': '♕', 'K': '♔',
            'p': '♟', 'r': '♜', 'n': '♞', 'b': '♝', 'q': '♛', 'k': '♚'
        };

        // --- AUTHENTICATION ---
        onAuthStateChanged(auth, async (user) => {
            if (user) {
                userId = user.uid;
                userIdDisplay.textContent = userId;
            } else {
                try {
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                } catch (error) {
                    console.error("Login error:", error);
                    showMessage("Error", "Could not authenticate user. Please reload the page.");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function showMessage(title, body) {
            modalTitle.textContent = title;
            modalBody.textContent = body;
            modal.classList.remove('hidden');
        }
        function showGameView(gameId) {
            currentGameId = gameId;
            gameIdDisplay.value = gameId;
            lobbyEl.classList.add('hidden');
            gameContainerEl.classList.remove('hidden');
        }
        function showLobbyView() {
            gameContainerEl.classList.add('hidden');
            lobbyEl.classList.remove('hidden');
            if (unsubscribeGame) {
                unsubscribeGame();
                unsubscribeGame = null;
            }
            currentGameId = null;
            playerColor = null;
            isHost = false;
        }

        // --- GAME LOGIC ---
        function getGameDocRef(gameId) {
            return doc(db, `artifacts/${appId}/public/data/chess-games/${gameId}`);
        }

        function renderBoard(fen, lastMove, playerColorToMove) {
            chess.load(fen);
            boardEl.innerHTML = '';
            boardEl.classList.toggle('board-disabled', playerColor !== playerColorToMove || chess.game_over());

            const squares = chess.SQUARES;
            const isFlipped = playerColor === 'b';

            for (let i = 0; i < squares.length; i++) {
                // If board is flipped for black player, iterate from the end of the squares array
                const squareIndex = isFlipped ? (squares.length - 1 - i) : i;
                const squareName = squares[squareIndex];

                const squareEl = document.createElement('div');
                
                // Determine square color from its actual rank and file
                const rank = parseInt(squareName.charAt(1), 10);
                const file = squareName.charCodeAt(0) - 'a'.charCodeAt(0);
                // A1 (rank 1, file 0) is dark. (1+0)%2 != 0. Correct.
                // H1 (rank 1, file 7) is light. (1+7)%2 == 0. Correct.
                squareEl.className = `square ${(rank + file) % 2 === 0 ? 'light' : 'dark'}`;
                squareEl.dataset.square = squareName;

                const piece = chess.get(squareName);
                if (piece) {
                    const pieceEl = document.createElement('span');
                    pieceEl.className = 'piece';
                    
                    // Get the FEN character for the piece to use with the unicode map
                    let fenChar = piece.type;
                    if (piece.color === 'w') {
                        fenChar = fenChar.toUpperCase();
                    }
                    pieceEl.textContent = pieceUnicode[fenChar];
                    pieceEl.style.color = piece.color === 'w' ? '#FFFFFF' : '#000000';
                    squareEl.appendChild(pieceEl);
                }
                
                if (lastMove && (squareName === lastMove.from || squareName === lastMove.to)) {
                    squareEl.classList.add('last-move');
                }
                
                if (chess.in_check() && piece && piece.type === 'k' && piece.color === chess.turn()) {
                    squareEl.classList.add('in-check');
                }

                squareEl.addEventListener('click', () => onSquareClick(squareName));
                boardEl.appendChild(squareEl);
            }
        }
        
        function updateGameInfo(gameData) {
            const { players, status, turn } = gameData;
            const p1 = players.p1; // White
            const p2 = players.p2; // Black

            playerInfoEl.innerHTML = '';
            const p1_html = `<div class="flex items-center justify-between p-3 rounded-lg transition-colors ${turn === 'w' ? 'bg-green-800' : 'bg-gray-700'}">
                <span class="font-semibold text-white">White: ${p1.name}</span>
                <span class="font-mono text-xs text-gray-400">${p1.id === userId ? "(You)" : ""}</span>
            </div>`;
            playerInfoEl.insertAdjacentHTML('beforeend', p1_html);

            if (p2) {
                 const p2_html = `<div class="flex items-center justify-between p-3 rounded-lg transition-colors ${turn === 'b' ? 'bg-green-800' : 'bg-gray-700'}">
                    <span class="font-semibold text-white">Black: ${p2.name}</span>
                    <span class="font-mono text-xs text-gray-400">${p2.id === userId ? "(You)" : ""}</span>
                 </div>`;
                 playerInfoEl.insertAdjacentHTML('beforeend', p2_html);
            } else {
                 playerInfoEl.insertAdjacentHTML('beforeend', `<div class="p-3 text-center text-gray-400 border border-dashed rounded-lg border-gray-600">Waiting for opponent...</div>`);
            }
            
            // Update status message
            let statusText = '';
            let statusClass = 'bg-gray-700 text-gray-200';
            switch(status) {
                case 'waiting':
                    statusText = 'Waiting for player...';
                    break;
                case 'active':
                    statusText = turn === 'w' ? "White's Turn" : "Black's Turn";
                    if ((turn === 'w' && playerColor === 'w') || (turn === 'b' && playerColor === 'b')) {
                        statusText = "Your Turn!";
                        statusClass = 'bg-blue-600 text-white';
                    }
                    break;
                case 'checkmate':
                    const winner = turn === 'b' ? 'White' : 'Black';
                    statusText = `Checkmate! ${winner} wins!`;
                    statusClass = 'bg-yellow-500 text-black font-bold';
                    break;
                case 'stalemate':
                    statusText = 'Stalemate!';
                    statusClass = 'bg-yellow-500 text-black font-bold';
                    break;
                case 'draw':
                    statusText = 'Draw by repetition';
                    statusClass = 'bg-yellow-500 text-black font-bold';
                    break;
            }
            gameStatusEl.textContent = statusText;
            gameStatusEl.className = `mt-4 text-center font-semibold text-lg p-3 rounded-lg ${statusClass}`;
            
            resetGameBtn.classList.toggle('hidden', status === 'active' || status === 'waiting' || !isHost);
        }

        async function generateUniqueGameId() {
            let gameId;
            let attempts = 0;
            const maxAttempts = 20;
            while (attempts < maxAttempts) {
                gameId = Math.floor(100000 + Math.random() * 900000).toString();
                const docSnap = await getDoc(getGameDocRef(gameId));
                if (!docSnap.exists()) return gameId;
                attempts++;
            }
            throw new Error("Failed to generate a unique room ID.");
        }

        async function createNewGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            if (!userId) {
                showMessage("Error", "User ID not available yet. Please wait a moment.");
                return;
            }

            createGameBtn.disabled = true;
            createGameBtn.textContent = 'Creating...';

            try {
                const newGameId = await generateUniqueGameId();
                const gameDocRef = getGameDocRef(newGameId);
                const newGame = {
                    fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
                    players: { p1: { id: userId, name: playerName } }, // p1 is White
                    turn: 'w',
                    status: 'waiting',
                    hostId: userId,
                    lastMove: null,
                    createdAt: new Date().toISOString(),
                };
                await setDoc(gameDocRef, newGame);
                
                isHost = true;
                playerColor = 'w';
                listenToGameUpdates(newGameId);
                showGameView(newGameId);

            } catch (error) {
                console.error("Error creating room: ", error);
                showMessage("Error", "Could not create room. Please try again.");
            } finally {
                createGameBtn.disabled = false;
                createGameBtn.textContent = 'Create New Room';
            }
        }

        async function joinExistingGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            const gameIdToJoin = document.getElementById('joinGameId').value.trim();
            if (!gameIdToJoin) {
                showMessage("Error", "Please enter a Room ID.");
                return;
            }

            joinGameBtn.disabled = true;
            const gameDocRef = getGameDocRef(gameIdToJoin);
            
            try {
                const gameSnap = await getDoc(gameDocRef);
                if (!gameSnap.exists()) {
                    showMessage("Error", "Room with this ID not found.");
                    return;
                }

                const gameData = gameSnap.data();
                if (gameData.players.p2 && gameData.players.p1.id !== userId) {
                    showMessage("Error", "The room is full.");
                    return;
                }
                
                if (!gameData.players.p2 && gameData.players.p1.id !== userId) {
                    await updateDoc(gameDocRef, {
                        'players.p2': { id: userId, name: playerName },
                        status: 'active'
                    });
                    playerColor = 'b';
                } else {
                    playerColor = gameData.players.p1.id === userId ? 'w' : 'b';
                }
                
                isHost = gameData.hostId === userId;
                listenToGameUpdates(gameIdToJoin);
                showGameView(gameIdToJoin);

            } catch (error) {
                console.error("Error joining room: ", error);
                showMessage("Error", "Could not join room. Please check the ID.");
            } finally {
                joinGameBtn.disabled = false;
            }
        }

        function listenToGameUpdates(gameId) {
            if (unsubscribeGame) unsubscribeGame();
            const gameDocRef = getGameDocRef(gameId);
            // FIX: Pass the document reference as the first argument to onSnapshot
            unsubscribeGame = onSnapshot(gameDocRef, (docSnap) => {
                if (docSnap.exists()) {
                    const gameData = docSnap.data();
                    renderBoard(gameData.fen, gameData.lastMove, gameData.turn);
                    updateGameInfo(gameData);
                } else {
                    showMessage("Notice", "The game room has been closed or does not exist.");
                    showLobbyView();
                }
            });
        }
        
        function clearHighlights() {
            document.querySelectorAll('.square').forEach(s => {
                s.classList.remove('selected');
                const dot = s.querySelector('.possible-move-dot');
                if (dot) dot.remove();
            });
        }

        async function onSquareClick(squareName) {
            const piece = chess.get(squareName);

            // If a piece is already selected, try to move it
            if (selectedSquare) {
                const move = {
                    from: selectedSquare,
                    to: squareName,
                    promotion: 'q' // Always promote to queen for simplicity
                };
                
                const result = chess.move(move);
                
                if (result) {
                    // Move is valid, update Firestore
                    let newStatus = 'active';
                    if (chess.in_checkmate()) newStatus = 'checkmate';
                    else if (chess.in_stalemate()) newStatus = 'stalemate';
                    else if (chess.in_draw()) newStatus = 'draw';

                    const gameDocRef = getGameDocRef(currentGameId);
                    await updateDoc(gameDocRef, {
                        fen: chess.fen(),
                        turn: chess.turn(),
                        status: newStatus,
                        lastMove: { from: result.from, to: result.to }
                    });
                }
                
                // Deselect square regardless of move validity
                clearHighlights();
                selectedSquare = null;

            } else {
                // No piece selected, so select one if it's the player's piece
                if (piece && piece.color === playerColor) {
                    selectedSquare = squareName;
                    clearHighlights();
                    document.querySelector(`[data-square="${squareName}"]`).classList.add('selected');
                    
                    // Show possible moves
                    const moves = chess.moves({ square: squareName, verbose: true });
                    moves.forEach(move => {
                        const moveSquare = document.querySelector(`[data-square="${move.to}"]`);
                        const dot = document.createElement('div');
                        dot.className = 'possible-move-dot';
                        moveSquare.appendChild(dot);
                    });
                }
            }
        }
        
        async function resetCurrentGame() {
            if (!isHost || !currentGameId) return;
            const gameDocRef = getGameDocRef(currentGameId);
            await updateDoc(gameDocRef, {
                fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
                turn: 'w',
                status: 'active',
                lastMove: null
            });
        }

        // --- EVENT LISTENERS ---
        createGameBtn.addEventListener('click', createNewGame);
        joinGameBtn.addEventListener('click', joinExistingGame);
        leaveGameBtn.addEventListener('click', showLobbyView);
        resetGameBtn.addEventListener('click', resetCurrentGame);
        modalCloseBtn.addEventListener('click', () => modal.classList.add('hidden'));
        
        copyGameIdBtn.addEventListener('click', () => {
            const gameId = gameIdDisplay.value;
            const textArea = document.createElement("textarea");
            textArea.value = gameId;
            document.body.appendChild(textArea);
            textArea.select();
            try {
                document.execCommand('copy');
                showMessage("Success", `Copied Room ID: ${gameId}`);
            } catch (err) {
                showMessage("Error", "Could not copy ID.");
            }
            document.body.removeChild(textArea);
        });

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/chess-online-2-player/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Find the Number Online</title>
		<link>https://techaiconnect.com/find-the-number-online/</link>
					<comments>https://techaiconnect.com/find-the-number-online/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sat, 05 Jul 2025 16:03:18 +0000</pubDate>
				<category><![CDATA[Game]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4278</guid>

					<description><![CDATA[Tìm Số 1-100 Online Tìm Số Siêu Tốc (1-100) Ai nhanh tay nhanh mắt hơn? [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tìm Số 1-100 Online</title>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;700;900&family=Patrick+Hand&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Be Vietnam Pro', sans-serif;
            background-color: #111827; /* bg-gray-900 */
            color: #D1D5DB; /* text-gray-300 */
        }
        .dark-input {
            background-color: #374151; border-color: #4B5563; color: #F9FAFB;
        }
        .dark-input::placeholder { color: #6B7280; }
        .dark-input:focus { --tw-ring-color: #14b8a6; border-color: #14b8a6; }
        .modal { transition: opacity 0.25s ease; }

        /* Game-specific styles */
        #game-board {
            position: relative;
            width: 100%;
            aspect-ratio: 4 / 5;
            max-width: 800px;
            margin: auto;
            background-color: #f7f3e9;
            border-radius: 1rem;
            box-shadow: 0 0 20px rgba(0,0,0,0.2);
            overflow: hidden;
            border: 10px solid #d4c5a2;
            background-image: linear-gradient(#e7e1d0 1px, transparent 1px), linear-gradient(to right, #e7e1d0 1px, #f7f3e9 1px);
            background-size: 25px 25px;
        }

        .number-container {
            position: absolute;
            width: 50px;
            height: 50px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: transform 0.2s ease;
        }
        .number-container:hover {
             transform: scale(1.1);
        }
        .number-container.found {
            pointer-events: none;
        }

        .number-cell {
            font-family: 'Patrick Hand', cursive;
            font-size: clamp(18px, 4vmin, 30px);
            font-weight: bold;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 40px;
            height: 40px;
            user-select: none;
            border-radius: 50%;
            transition: border-width 0.2s ease;
        }
        
        .number-cell.found-by-player {
            border-width: 3px;
            animation: found-pop 0.3s ease-out;
        }
        
        @keyframes found-pop {
            0% { transform: scale(1); }
            50% { transform: scale(1.3); }
            100% { transform: scale(1); }
        }

    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-7xl mx-auto">
        <!-- Header -->
        <header class="text-center mb-6">
            <h1 class="text-4xl md:text-5xl font-black text-transparent bg-clip-text bg-gradient-to-r from-teal-400 to-blue-500">Tìm Số Siêu Tốc (1-100)</h1>
            <p class="text-gray-400 mt-2 text-lg">Ai nhanh tay nhanh mắt hơn?</p>
        </header>

        <!-- Lobby Section -->
        <div id="lobby" class="bg-gray-800 p-8 rounded-2xl shadow-lg border border-gray-700 max-w-md mx-auto">
            <div class="mb-4">
                <label for="playerName" class="block mb-2 text-sm font-medium text-gray-300">Tên của bạn</label>
                <input type="text" id="playerName" class="dark-input w-full text-sm rounded-lg p-2.5" placeholder="Nhập tên của bạn" required>
            </div>
            <!-- Host controls -->
            <div class="mb-4">
                <label for="maxPlayers" class="block mb-2 text-sm font-medium text-gray-300">Số người chơi</label>
                <select id="maxPlayers" class="dark-input w-full text-sm rounded-lg p-2.5">
                    <option value="2">2 người</option>
                    <option value="3">3 người</option>
                    <option value="4">4 người</option>
                </select>
            </div>
            <button id="createRoomBtn" class="w-full text-white bg-emerald-600 hover:bg-emerald-700 focus:ring-4 focus:outline-none focus:ring-emerald-800 font-medium rounded-lg text-sm px-5 py-3 text-center mb-3">Tạo phòng mới</button>
            <div class="relative flex py-3 items-center">
                <div class="flex-grow border-t border-gray-600"></div>
                <span class="flex-shrink mx-4 text-gray-400">hoặc</span>
                <div class="flex-grow border-t border-gray-600"></div>
            </div>
            <div class="flex gap-2">
                <input type="text" id="joinRoomId" class="dark-input w-full text-sm rounded-lg p-2.5" placeholder="Nhập ID phòng (6 chữ số)">
                <button id="joinRoomBtn" class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Vào phòng</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                User ID của bạn: <span id="userIdDisplay" class="font-mono">Đang tải...</span>
            </div>
        </div>

        <!-- Game Section -->
        <main id="game-container" class="hidden">
            <div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
                <!-- Left Panel: Info & Players -->
                <div class="lg:col-span-1 bg-gray-800 p-6 rounded-2xl shadow-lg border border-gray-700 self-start">
                    <h2 class="text-2xl font-bold mb-4 text-white">Thông tin phòng</h2>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">ID Phòng:</label>
                        <div class="flex items-center gap-2 mt-1">
                            <input readonly id="roomIdDisplay" class="w-full bg-gray-700 font-mono text-lg p-2 rounded-md border border-gray-600 text-center tracking-widest text-gray-50"/>
                            <button id="copyRoomIdBtn" class="p-2 bg-gray-600 hover:bg-gray-500 rounded-md text-gray-300" title="Sao chép ID">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
                            </button>
                        </div>
                    </div>
                    <div class="mb-4">
                        <h3 class="text-lg font-bold mb-2 text-white">Bảng xếp hạng</h3>
                        <ul id="player-list" class="space-y-2 text-gray-300 max-h-60 overflow-y-auto pr-2">
                            <!-- Player list will be populated here -->
                        </ul>
                    </div>
                    <div id="game-status-display" class="mt-4 p-3 bg-gray-700 rounded-lg text-center font-semibold">
                        <!-- Game status will be shown here -->
                    </div>
                     <button id="startGameBtn" class="hidden w-full mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed">
                        Bắt đầu chơi!
                    </button>
                     <button id="leaveRoomBtn" class="w-full mt-4 bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-lg transition">
                        Rời phòng
                    </button>
                </div>

                <!-- Middle Panel: Game Board -->
                <div class="lg:col-span-3 bg-gray-800 p-4 sm:p-6 rounded-2xl shadow-lg border border-gray-700 flex flex-col items-center justify-center">
                    <div id="game-board-container" class="w-full">
                         <div id="game-board">
                            <!-- Numbers will be populated here -->
                         </div>
                    </div>
                </div>
            </div>
        </main>
    </div>

    <!-- Message Modal -->
    <div id="message-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
        <div class="bg-gray-800 rounded-xl shadow-2xl p-8 max-w-sm text-center border border-gray-700">
            <h2 id="modal-title" class="text-2xl font-bold mb-4"></h2>
            <p id="modal-body" class="text-gray-300 mb-6"></p>
            <button id="modal-close-btn" class="w-full text-white bg-blue-600 hover:bg-blue-700 font-medium rounded-lg text-sm px-5 py-3">Đóng</button>
        </div>
    </div>

    <!-- Firebase SDK -->
    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, getDoc, setDoc, updateDoc, onSnapshot, arrayUnion, arrayRemove, runTransaction } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        // --- CONFIG ---
        const firebaseConfig = typeof __firebase_config !== 'undefined' 
            ? JSON.parse(__firebase_config)
            : {
                apiKey: "AIzaSyBL3USs4_BgCBM1_eFrOA0Htmjj2FWa5XM",
                authDomain: "app-and-game.firebaseapp.com",
                projectId: "app-and-game",
                storageBucket: "app-and-game.appspot.com",
                messagingSenderId: "670250047171",
                appId: "1:670250047171:web:ca90d4df93674be6fef476",
                measurementId: "G-4KR5Q5RCQV"
            };
        const appId = typeof __app_id !== 'undefined' ? __app_id : firebaseConfig.projectId || 'default-app-id';
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);
        setLogLevel('debug');

        // --- DOM ELEMENTS ---
        const lobbyEl = document.getElementById('lobby');
        const gameContainerEl = document.getElementById('game-container');
        const createRoomBtn = document.getElementById('createRoomBtn');
        const joinRoomBtn = document.getElementById('joinRoomBtn');
        const leaveRoomBtn = document.getElementById('leaveRoomBtn');
        const copyRoomIdBtn = document.getElementById('copyRoomIdBtn');
        const startGameBtn = document.getElementById('startGameBtn');
        const userIdDisplay = document.getElementById('userIdDisplay');
        const roomIdDisplay = document.getElementById('roomIdDisplay');
        const playerListEl = document.getElementById('player-list');
        const gameBoardEl = document.getElementById('game-board');
        const gameStatusDisplay = document.getElementById('game-status-display');
        const modal = document.getElementById('message-modal');
        const modalTitle = document.getElementById('modal-title');
        const modalBody = document.getElementById('modal-body');
        const modalCloseBtn = document.getElementById('modal-close-btn');

        // --- GAME STATE ---
        let userId = null;
        let currentRoomId = null;
        let unsubscribeRoom = null;
        let localPlayerName = '';
        let isHost = false;
        const playerColors = ["#ef4444", "#3b82f6", "#22c55e", "#f97316"]; // red, blue, green, orange
        let localGameState = null; // Store latest game state for optimistic updates

        // --- AUTHENTICATION ---
        onAuthStateChanged(auth, async (user) => {
            if (user) {
                userId = user.uid;
                userIdDisplay.textContent = userId;
            } else {
                try {
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                } catch (error) {
                    console.error("Lỗi đăng nhập:", error);
                    showMessage("Lỗi", "Không thể xác thực người dùng. Vui lòng tải lại trang.");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function showMessage(title, body) {
            modalTitle.textContent = title;
            modalBody.textContent = body;
            modal.classList.remove('hidden');
        }
        function showLobbyView() {
            gameContainerEl.classList.add('hidden');
            lobbyEl.classList.remove('hidden');
            if (unsubscribeRoom) {
                unsubscribeRoom();
                unsubscribeRoom = null;
            }
            currentRoomId = null;
            isHost = false;
            localGameState = null;
        }
        function showGameView(roomId) {
            currentRoomId = roomId;
            roomIdDisplay.value = roomId;
            lobbyEl.classList.add('hidden');
            gameContainerEl.classList.remove('hidden');
        }

        // --- GAME BOARD DRAWING ---
        function drawGameBoard(numbers, players) {
            gameBoardEl.innerHTML = '';
            const playerColorMap = new Map(players.map(p => [p.id, p.color]));

            numbers.forEach(num => {
                const container = document.createElement('div');
                container.className = 'number-container';
                container.style.left = num.position.left;
                container.style.top = num.position.top;
                container.style.transform = `rotate(${num.rotation}deg)`;
                // Add a data-attribute for easy selection
                container.dataset.number = num.value;

                const cell = document.createElement('div');
                cell.className = 'number-cell';
                cell.textContent = num.value;
                cell.style.color = num.color;

                if (num.foundBy) {
                    container.classList.add('found');
                    cell.classList.add('found-by-player');
                    cell.style.borderColor = playerColorMap.get(num.foundBy) || '#888';
                } else {
                    // Add both click and touch listeners for better responsiveness
                    container.onclick = () => handleNumberClick(num.value);
                    container.addEventListener('touchstart', (e) => {
                        e.preventDefault(); // Prevent firing both touch and click
                        handleNumberClick(num.value);
                    }, { passive: false });
                }
                
                container.appendChild(cell);
                gameBoardEl.appendChild(container);
            });
        }
        
        // --- FIREBASE & GAME LOGIC ---
        function getRoomDocRef(roomId) {
            return doc(db, `artifacts/${appId}/public/data/find-number-sessions/${roomId}`);
        }

        async function createNewRoom() {
            localPlayerName = document.getElementById('playerName').value.trim();
            if (!localPlayerName) {
                showMessage("Lỗi", "Vui lòng nhập tên của bạn.");
                return;
            }
            if (!userId) {
                showMessage("Lỗi", "Chưa có User ID. Vui lòng chờ giây lát.");
                return;
            }

            createRoomBtn.disabled = true;
            createRoomBtn.textContent = 'Đang tạo...';

            try {
                let newRoomId;
                let roomExists = true;
                while (roomExists) {
                    newRoomId = Math.floor(100000 + Math.random() * 900000).toString();
                    const roomDocRef = getRoomDocRef(newRoomId);
                    const docSnap = await getDoc(roomDocRef);
                    roomExists = docSnap.exists();
                }

                const maxPlayers = parseInt(document.getElementById('maxPlayers').value, 10);
                const numbers = generateNumbers();

                const newRoomData = {
                    hostId: userId,
                    players: [{ id: userId, name: localPlayerName, score: 0, color: playerColors[0] }],
                    maxPlayers: maxPlayers,
                    status: 'waiting', // 'waiting', 'playing', 'finished'
                    numbers: numbers,
                    nextNumberToFind: 1,
                    createdAt: new Date().toISOString()
                };

                await setDoc(getRoomDocRef(newRoomId), newRoomData);
                isHost = true;
                listenToRoomUpdates(newRoomId);
            } catch (error) {
                console.error("Lỗi tạo phòng: ", error);
                showMessage("Lỗi", "Không thể tạo phòng. Vui lòng thử lại.");
            } finally {
                createRoomBtn.disabled = false;
                createRoomBtn.textContent = 'Tạo phòng mới';
            }
        }
        
        function generateNumbers() {
            const numbers = [];
            const textColors = ["#334155", "#1e40af", "#b91c1c", "#581c87", "#b45309", "#065f46"];
            const gridCells = [];
            const cols = 10;
            const rows = 10;
            const padding = 4; // % padding from edges to prevent cutoff

            // Create a grid of positions within a safe area
            for(let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                    const cellWidth = (100 - padding * 2) / cols;
                    const cellHeight = (100 - padding * 2) / rows;
                    gridCells.push({
                        left: `${padding + c * cellWidth + (Math.random() * (cellWidth / 4) - cellWidth / 8)}%`,
                        top: `${padding + r * cellHeight + (Math.random() * (cellHeight / 4) - cellHeight / 8)}%`
                    });
                }
            }

            // Shuffle the grid positions
            for (let i = gridCells.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [gridCells[i], gridCells[j]] = [gridCells[j], gridCells[i]];
            }

            for (let i = 1; i <= 100; i++) {
                numbers.push({
                    value: i,
                    position: gridCells[i-1],
                    rotation: Math.random() * 50 - 25, // -25 to +25 degrees
                    color: textColors[Math.floor(Math.random() * textColors.length)],
                    foundBy: null
                });
            }
            return numbers;
        }

        async function joinExistingRoom() {
            localPlayerName = document.getElementById('playerName').value.trim();
            if (!localPlayerName) {
                showMessage("Lỗi", "Vui lòng nhập tên của bạn.");
                return;
            }
            const roomIdToJoin = document.getElementById('joinRoomId').value.trim();
            if (!roomIdToJoin) {
                showMessage("Lỗi", "Vui lòng nhập ID phòng.");
                return;
            }

            joinRoomBtn.disabled = true;
            const roomDocRef = getRoomDocRef(roomIdToJoin);
            
            try {
                const docSnap = await getDoc(roomDocRef);
                if (!docSnap.exists()) {
                    showMessage("Lỗi", "Không tìm thấy phòng với ID này.");
                    return;
                }

                const roomData = docSnap.data();
                if (roomData.status !== 'waiting') {
                    showMessage("Lỗi", "Trò chơi đã bắt đầu hoặc kết thúc. Không thể vào phòng.");
                    return;
                }
                if (roomData.players.length >= roomData.maxPlayers) {
                    showMessage("Lỗi", "Phòng đã đầy.");
                    return;
                }
                
                if (!roomData.players.some(p => p.id === userId)) {
                    const newPlayerColor = playerColors[roomData.players.length];
                    await updateDoc(roomDocRef, {
                        players: arrayUnion({ id: userId, name: localPlayerName, score: 0, color: newPlayerColor })
                    });
                }
                isHost = false;
                listenToRoomUpdates(roomIdToJoin);
            } catch (error) {
                console.error("Lỗi vào phòng: ", error);
                showMessage("Lỗi", "Không thể vào phòng. Vui lòng kiểm tra lại ID.");
            } finally {
                joinRoomBtn.disabled = false;
            }
        }

        async function leaveCurrentRoom() {
            if (!currentRoomId || !userId) return;
            const roomDocRef = getRoomDocRef(currentRoomId);
            try {
                await runTransaction(db, async (transaction) => {
                    const sfDoc = await transaction.get(roomDocRef);
                    if (!sfDoc.exists()) return;

                    const currentPlayers = sfDoc.data().players;
                    const updatedPlayers = currentPlayers.filter(p => p.id !== userId);
                    transaction.update(roomDocRef, { players: updatedPlayers });
                });
            } catch (error) {
                console.error("Lỗi rời phòng: ", error);
            } finally {
                showLobbyView();
            }
        }

        function listenToRoomUpdates(roomId) {
            showGameView(roomId);
            const roomDocRef = getRoomDocRef(roomId);
            unsubscribeRoom = onSnapshot(roomDocRef, (docSnap) => {
                if (!docSnap.exists()) {
                    showMessage("Thông báo", "Phòng chơi đã bị đóng hoặc không tồn tại.");
                    showLobbyView();
                    return;
                }
                const roomData = docSnap.data();
                localGameState = roomData; // Keep local state in sync
                
                // Update player list & scores
                const sortedPlayers = [...roomData.players].sort((a, b) => b.score - a.score);
                playerListEl.innerHTML = '';
                sortedPlayers.forEach(p => {
                    const li = document.createElement('li');
                    li.className = `p-2 rounded-md flex justify-between items-center`;
                    li.style.backgroundColor = p.color + '33'; // Add alpha for background
                    li.innerHTML = `
                        <span class="flex items-center gap-2">
                            <span class="w-3 h-3 rounded-full" style="background-color: ${p.color};"></span>
                            <span>${p.name} ${p.id === roomData.hostId ? '👑' : ''}</span>
                        </span> 
                        <span class="font-bold text-lg">${p.score}</span>`;
                    playerListEl.appendChild(li);
                });

                // Update game board
                if (roomData.status === 'playing' || roomData.status === 'finished') {
                    drawGameBoard(roomData.numbers, roomData.players);
                } else {
                    gameBoardEl.innerHTML = `<div class="absolute inset-0 flex items-center justify-center text-gray-500 text-2xl font-bold">Đang chờ chủ phòng bắt đầu...</div>`;
                }

                // Update game status
                updateGameStatus(roomData);
                
                // Update host controls
                startGameBtn.classList.toggle('hidden', !isHost || roomData.status !== 'waiting');
                startGameBtn.disabled = roomData.players.length < 2; // Can start with 2 players
            });
        }
        
        function updateGameStatus(roomData) {
            switch(roomData.status) {
                case 'waiting':
                    gameStatusDisplay.textContent = `Đang chờ người chơi... (${roomData.players.length}/${roomData.maxPlayers})`;
                    gameStatusDisplay.className = 'mt-4 p-3 bg-blue-900/70 rounded-lg text-center font-semibold text-blue-200';
                    break;
                case 'playing':
                    gameStatusDisplay.innerHTML = `Tìm số tiếp theo: <span class="text-2xl font-black text-yellow-300">${roomData.nextNumberToFind}</span>`;
                    gameStatusDisplay.className = 'mt-4 p-3 bg-gray-700 rounded-lg text-center font-semibold';
                    break;
                case 'finished':
                    const winner = roomData.players.reduce((prev, current) => (prev.score > current.score) ? prev : current);
                    gameStatusDisplay.innerHTML = `<span class="font-bold text-yellow-400 text-lg">TRÒ CHƠI KẾT THÚC!</span><br/>Người chiến thắng: ${winner.name}`;
                    gameStatusDisplay.className = 'mt-4 p-3 bg-green-900/70 rounded-lg text-center font-semibold text-green-200';
                    break;
            }
        }
        
        async function handleStartGame() {
            if (!isHost || !currentRoomId) return;
            const roomDocRef = getRoomDocRef(currentRoomId);
            await updateDoc(roomDocRef, { status: 'playing' });
        }
        
        async function handleNumberClick(numberValue) {
            // Check for valid local state before doing anything
            if (!currentRoomId || !userId || !localGameState || localGameState.status !== 'playing') {
                return;
            }

            // Check if the clicked number is the correct one based on the local state
            if (numberValue !== localGameState.nextNumberToFind) {
                return; // Not the right number, do nothing
            }

            // --- OPTIMISTIC UI UPDATE ---
            const player = localGameState.players.find(p => p.id === userId);
            if (!player) return;

            const targetContainer = document.querySelector(`.number-container[data-number="${numberValue}"]`);

            if (targetContainer && !targetContainer.classList.contains('found')) {
                // Instantly update the UI to feel responsive
                targetContainer.classList.add('found');
                const cell = targetContainer.querySelector('.number-cell');
                cell.classList.add('found-by-player');
                cell.style.borderColor = player.color || '#888';
                
                // Optimistically update the status display
                const nextNumber = numberValue + 1;
                if (nextNumber <= 100) {
                     gameStatusDisplay.innerHTML = `Tìm số tiếp theo: <span class="text-2xl font-black text-yellow-300">${nextNumber}</span>`;
                } else {
                     gameStatusDisplay.innerHTML = `<span class="font-bold text-yellow-400 text-lg">TRÒ CHƠI KẾT THÚC!</span>`;
                }
            }
            
            // --- FIREBASE TRANSACTION (in the background) ---
            const roomDocRef = getRoomDocRef(currentRoomId);
            try {
                await runTransaction(db, async (transaction) => {
                    const roomSnap = await transaction.get(roomDocRef);
                    if (!roomSnap.exists()) { throw "Phòng không tồn tại!"; }
                    
                    const serverRoomData = roomSnap.data();

                    // Re-validate on the server to handle race conditions
                    if (serverRoomData.status !== 'playing' || numberValue !== serverRoomData.nextNumberToFind) {
                        // Another player was faster. The optimistic update was wrong.
                        // onSnapshot will automatically correct the UI, so we just stop here.
                        return;
                    }

                    const numbers = serverRoomData.numbers;
                    const players = serverRoomData.players;
                    const numberIndex = numbers.findIndex(n => n.value === numberValue);
                    const playerIndex = players.findIndex(p => p.id === userId);

                    if (numberIndex === -1 || playerIndex === -1) return;

                    // Update the true state on the server
                    numbers[numberIndex].foundBy = userId;
                    players[playerIndex].score += 1;
                    const nextNumberOnServer = serverRoomData.nextNumberToFind + 1;
                    
                    const updatePayload = {
                        numbers: numbers,
                        players: players,
                        nextNumberToFind: nextNumberOnServer
                    };

                    if (nextNumberOnServer > 100) {
                        updatePayload.status = 'finished';
                    }

                    transaction.update(roomDocRef, updatePayload);
                });
            } catch (error) {
                console.error("Lỗi khi chọn số: ", error);
                // The UI will be corrected by the onSnapshot listener eventually.
            }
        }


        // --- EVENT LISTENERS ---
        createRoomBtn.addEventListener('click', createNewRoom);
        joinRoomBtn.addEventListener('click', joinExistingRoom);
        leaveRoomBtn.addEventListener('click', leaveCurrentRoom);
        startGameBtn.addEventListener('click', handleStartGame);
        
        copyRoomIdBtn.addEventListener('click', () => {
            const gameId = roomIdDisplay.value;
            const textArea = document.createElement("textarea");
            textArea.value = gameId;
            document.body.appendChild(textArea);
            textArea.select();
            try {
                document.execCommand('copy');
                showMessage("Thành công", `Đã sao chép ID phòng: ${gameId}`);
            } catch (err) {
                showMessage("Lỗi", "Không thể sao chép ID.");
            }
            document.body.removeChild(textArea);
        });
        
        modalCloseBtn.addEventListener('click', () => modal.classList.add('hidden'));

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/find-the-number-online/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Spin the Wheel Online (2-20 player)</title>
		<link>https://techaiconnect.com/spin-the-wheel-online/</link>
					<comments>https://techaiconnect.com/spin-the-wheel-online/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sat, 05 Jul 2025 15:33:38 +0000</pubDate>
				<category><![CDATA[Game]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4274</guid>

					<description><![CDATA[Lucky Wheel Online Lucky Wheel Online Share exciting moments together! Your Name Create New Room [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Lucky Wheel Online</title>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;700;900&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Be Vietnam Pro', sans-serif;
            background-color: #111827; /* bg-gray-900 */
            color: #D1D5DB; /* text-gray-300 */
        }
        #wheel-container {
            position: relative;
            width: 100%;
            max-width: 450px;
            aspect-ratio: 1 / 1;
            margin: 0 auto;
        }
        #wheel-canvas {
            width: 100%;
            height: 100%;
            transition: transform 6s cubic-bezier(0.2, 0.8, 0.2, 1);
        }
        #pointer {
            position: absolute;
            top: -15px;
            left: 50%;
            transform: translateX(-50%);
            width: 0;
            height: 0;
            border-left: 25px solid transparent;
            border-right: 25px solid transparent;
            border-top: 40px solid #f59e0b; /* amber-500 */
            z-index: 10;
        }
        .dark-input {
            background-color: #374151; border-color: #4B5563; color: #F9FAFB;
        }
        .dark-input::placeholder { color: #6B7280; }
        .dark-input:focus { --tw-ring-color: #14b8a6; border-color: #14b8a6; }
        .modal { transition: opacity 0.25s ease; }
    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-6xl mx-auto">
        <!-- Header -->
        <header class="text-center mb-8">
            <h1 class="text-4xl md:text-5xl font-black text-transparent bg-clip-text bg-gradient-to-r from-teal-400 to-blue-500">Lucky Wheel Online</h1>
            <p class="text-gray-400 mt-2 text-lg">Share exciting moments together!</p>
        </header>

        <!-- Lobby Section -->
        <div id="lobby" class="bg-gray-800 p-8 rounded-2xl shadow-lg border border-gray-700 max-w-md mx-auto">
            <div class="mb-4">
                <label for="playerName" class="block mb-2 text-sm font-medium text-gray-300">Your Name</label>
                <input type="text" id="playerName" class="dark-input w-full text-sm rounded-lg p-2.5" placeholder="Enter your name" required>
            </div>
            <button id="createRoomBtn" class="w-full text-white bg-emerald-600 hover:bg-emerald-700 focus:ring-4 focus:outline-none focus:ring-emerald-800 font-medium rounded-lg text-sm px-5 py-3 text-center mb-3">Create New Room</button>
            <div class="relative flex py-3 items-center">
                <div class="flex-grow border-t border-gray-600"></div>
                <span class="flex-shrink mx-4 text-gray-400">or</span>
                <div class="flex-grow border-t border-gray-600"></div>
            </div>
            <div class="flex gap-2">
                <input type="text" id="joinRoomId" class="dark-input w-full text-sm rounded-lg p-2.5" placeholder="Enter Room ID (6 digits)">
                <button id="joinRoomBtn" class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Join Room</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                Your User ID: <span id="userIdDisplay" class="font-mono">Loading...</span>
            </div>
        </div>

        <!-- Game Section -->
        <main id="game-container" class="hidden">
            <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
                <!-- Left Panel: Info & Players -->
                <div class="lg:col-span-1 bg-gray-800 p-6 rounded-2xl shadow-lg border border-gray-700">
                    <h2 class="text-2xl font-bold mb-4 text-white">Room Information</h2>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">Room ID (share with friends):</label>
                        <div class="flex items-center gap-2 mt-1">
                            <input readonly id="roomIdDisplay" class="w-full bg-gray-700 font-mono text-lg p-2 rounded-md border border-gray-600 text-center tracking-widest text-gray-50"/>
                            <button id="copyRoomIdBtn" class="p-2 bg-gray-600 hover:bg-gray-500 rounded-md text-gray-300" title="Copy ID">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
                            </button>
                        </div>
                    </div>
                    <div class="mb-4">
                        <h3 class="text-lg font-bold mb-2 text-white">Participants (<span id="player-count">0</span>/20)</h3>
                        <ul id="player-list" class="space-y-2 text-gray-300 max-h-60 overflow-y-auto pr-2">
                            <!-- Player list will be populated here -->
                        </ul>
                    </div>
                     <button id="leaveRoomBtn" class="w-full mt-4 bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-lg transition">
                        Leave Room
                    </button>
                </div>

                <!-- Middle Panel: Wheel -->
                <div class="lg:col-span-1 bg-gray-800 p-6 rounded-2xl shadow-lg border border-gray-700 flex flex-col items-center justify-center">
                    <div id="wheel-container">
                        <div id="pointer"></div>
                        <canvas id="wheel-canvas" width="450" height="450"></canvas>
                    </div>
                    <button id="spin-btn" class="mt-6 bg-gradient-to-br from-teal-500 to-blue-600 text-white font-bold py-4 px-12 rounded-xl text-xl transition-all transform hover:scale-105 shadow-lg hover:shadow-blue-500/50 disabled:opacity-50 disabled:cursor-not-allowed">
                        SPIN!
                    </button>
                    <div id="result-display" class="mt-4 text-center h-12"></div>
                </div>

                <!-- Right Panel: Customization (Host only) -->
                <div id="host-controls" class="lg:col-span-1 bg-gray-800 p-6 rounded-2xl shadow-lg border border-gray-700 hidden">
                    <h2 class="text-2xl font-bold mb-4 text-center text-white">Customize Wheel</h2>
                    <p class="text-sm text-center text-gray-400 mb-4">Only the host can change the wheel.</p>
                    
                    <div class="space-y-4">
                         <button id="use-players-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded-lg transition">
                            Use Player List
                        </button>
                        <div>
                            <label for="custom-items-input" class="block mb-2 text-sm font-medium text-gray-300">Or enter options (one per line):</label>
                            <textarea id="custom-items-input" rows="8" class="block p-2.5 w-full text-sm text-gray-200 bg-gray-700 rounded-lg border border-gray-600 focus:ring-teal-500 focus:border-teal-500"></textarea>
                        </div>
                        <button id="update-wheel-btn" class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-4 rounded-lg transition">
                            Update Wheel
                        </button>
                    </div>
                </div>
            </div>
        </main>
    </div>

    <!-- Message Modal -->
    <div id="message-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
        <div class="bg-gray-800 rounded-xl shadow-2xl p-8 max-w-sm text-center border border-gray-700">
            <h2 id="modal-title" class="text-2xl font-bold mb-4"></h2>
            <p id="modal-body" class="text-gray-300 mb-6"></p>
            <button id="modal-close-btn" class="w-full text-white bg-blue-600 hover:bg-blue-700 font-medium rounded-lg text-sm px-5 py-3">Close</button>
        </div>
    </div>

    <!-- Firebase SDK -->
    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, getDoc, setDoc, updateDoc, onSnapshot, arrayUnion, arrayRemove } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        const firebaseConfig = typeof __firebase_config !== 'undefined' 
            ? JSON.parse(__firebase_config)
            : {
                apiKey: "AIzaSyBL3USs4_BgCBM1_eFrOA0Htmjj2FWa5XM",
                authDomain: "app-and-game.firebaseapp.com",
                projectId: "app-and-game",
                storageBucket: "app-and-game.firebasestorage.app",
                messagingSenderId: "670250047171",
                appId: "1:670250047171:web:ca90d4df93674be6fef476",
                measurementId: "G-4KR5Q5RCQV"
            };
        const appId = typeof __app_id !== 'undefined' ? __app_id : firebaseConfig.projectId;
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);
        setLogLevel('debug');

        // --- DOM ELEMENTS ---
        const lobbyEl = document.getElementById('lobby');
        const gameContainerEl = document.getElementById('game-container');
        const canvas = document.getElementById('wheel-canvas');
        const ctx = canvas.getContext('2d');
        const spinBtn = document.getElementById('spin-btn');
        const resultDisplay = document.getElementById('result-display');
        const customItemsInput = document.getElementById('custom-items-input');
        const updateWheelBtn = document.getElementById('update-wheel-btn');
        const usePlayersBtn = document.getElementById('use-players-btn');
        const createRoomBtn = document.getElementById('createRoomBtn');
        const joinRoomBtn = document.getElementById('joinRoomBtn');
        const leaveRoomBtn = document.getElementById('leaveRoomBtn');
        const copyRoomIdBtn = document.getElementById('copyRoomIdBtn');
        const userIdDisplay = document.getElementById('userIdDisplay');
        const roomIdDisplay = document.getElementById('roomIdDisplay');
        const playerListEl = document.getElementById('player-list');
        const playerCountEl = document.getElementById('player-count');
        const hostControlsEl = document.getElementById('host-controls');
        const modal = document.getElementById('message-modal');
        const modalTitle = document.getElementById('modal-title');
        const modalBody = document.getElementById('modal-body');
        const modalCloseBtn = document.getElementById('modal-close-btn');

        // --- GAME STATE ---
        let currentRoomId = null;
        let unsubscribeRoom = null;
        let isHost = false;
        let isSpinning = false;
        let lastKnownSpinTime = null;
        const colors = ["#ef4444", "#f97316", "#eab308", "#84cc16", "#22c55e", "#14b8a6", "#06b6d4", "#3b82f6", "#8b5cf6", "#d946ef", "#ec4899", "#78716c"];

        // --- AUTHENTICATION ---
        onAuthStateChanged(auth, async (user) => {
            if (user) {
                userIdDisplay.textContent = user.uid;
            } else {
                try {
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                } catch (error) {
                    console.error("Login error:", error);
                    showMessage("Error", "Could not authenticate user. Please reload the page.");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function showMessage(title, body) {
            modalTitle.textContent = title;
            modalBody.textContent = body;
            modal.classList.remove('hidden');
        }
        function showLobbyView() {
            gameContainerEl.classList.add('hidden');
            lobbyEl.classList.remove('hidden');
            if (unsubscribeRoom) {
                unsubscribeRoom();
                unsubscribeRoom = null;
            }
            currentRoomId = null;
            isHost = false;
        }
        function showGameView(roomId) {
            currentRoomId = roomId;
            roomIdDisplay.value = roomId;
            lobbyEl.classList.add('hidden');
            gameContainerEl.classList.remove('hidden');
        }

        // --- WHEEL DRAWING ---
        function drawWheel(segments) {
            const numSegments = segments ? segments.length : 0;
            if (numSegments === 0) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                return;
            };
            const anglePerSegment = (2 * Math.PI) / numSegments;
            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;
            const radius = canvas.width / 2 - 10;

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.font = '16px "Be Vietnam Pro", sans-serif';
            
            segments.forEach((segment, i) => {
                const startAngle = i * anglePerSegment;
                ctx.beginPath();
                ctx.moveTo(centerX, centerY);
                ctx.arc(centerX, centerY, radius, startAngle, startAngle + anglePerSegment);
                ctx.closePath();
                ctx.fillStyle = colors[i % colors.length];
                ctx.fill();
                ctx.strokeStyle = '#4b5563';
                ctx.lineWidth = 3;
                ctx.stroke();

                ctx.save();
                ctx.translate(centerX, centerY);
                ctx.rotate(startAngle + anglePerSegment / 2);
                ctx.textAlign = "right";
                ctx.fillStyle = "#ffffff";
                ctx.font = 'bold 14px "Be Vietnam Pro"';
                let text = segment.length > 22 ? segment.substring(0, 20) + '...' : segment;
                ctx.fillText(text, radius - 15, 0);
                ctx.restore();
            });
        }

        // --- FIREBASE & GAME LOGIC ---
        async function createNewRoom() {
            const localPlayerName = document.getElementById('playerName').value.trim();
            if (!localPlayerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            if (!auth.currentUser) {
                showMessage("Error", "User not authenticated. Please wait a moment and try again.");
                return;
            }
            const currentUserId = auth.currentUser.uid;

            createRoomBtn.disabled = true;
            createRoomBtn.textContent = 'Creating...';

            try {
                let newRoomId;
                let roomExists = true;
                while (roomExists) {
                    newRoomId = Math.floor(100000 + Math.random() * 900000).toString();
                    const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${newRoomId}`);
                    const docSnap = await getDoc(roomDocRef);
                    roomExists = docSnap.exists();
                }

                const newRoomData = {
                    hostId: currentUserId,
                    players: [{ id: currentUserId, name: localPlayerName }],
                    wheelItems: [localPlayerName],
                    status: 'waiting',
                    currentRotation: 0,
                    targetRotation: 0,
                    result: '',
                    lastSpinTimestamp: null,
                    createdAt: new Date().toISOString()
                };

                await setDoc(doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${newRoomId}`), newRoomData);
                isHost = true;
                listenToRoomUpdates(newRoomId);
            } catch (error) {
                console.error("Error creating room: ", error);
                showMessage("Error", "Could not create room. Please try again.");
            } finally {
                createRoomBtn.disabled = false;
                createRoomBtn.textContent = 'Create New Room';
            }
        }

        async function joinExistingRoom() {
            const localPlayerName = document.getElementById('playerName').value.trim();
            if (!localPlayerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            const roomIdToJoin = document.getElementById('joinRoomId').value.trim();
            if (!roomIdToJoin) {
                showMessage("Error", "Please enter a Room ID.");
                return;
            }
            if (!auth.currentUser) {
                showMessage("Error", "User not authenticated. Please wait a moment and try again.");
                return;
            }
            const currentUserId = auth.currentUser.uid;

            joinRoomBtn.disabled = true;
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${roomIdToJoin}`);
            
            try {
                const docSnap = await getDoc(roomDocRef);
                if (!docSnap.exists()) {
                    showMessage("Error", "Room with this ID not found.");
                    return;
                }

                const roomData = docSnap.data();
                if (roomData.players.length >= 20 && !roomData.players.some(p => p.id === currentUserId)) {
                    showMessage("Error", "The room is full.");
                    return;
                }
                if (!roomData.players.some(p => p.id === currentUserId)) {
                    await updateDoc(roomDocRef, {
                        players: arrayUnion({ id: currentUserId, name: localPlayerName })
                    });
                }
                isHost = (roomData.hostId === currentUserId);
                listenToRoomUpdates(roomIdToJoin);
            } catch (error) {
                console.error("Error joining room: ", error);
                showMessage("Error", "Could not join the room. Please check the ID.");
            } finally {
                joinRoomBtn.disabled = false;
            }
        }

        async function leaveCurrentRoom() {
            if (!currentRoomId || !auth.currentUser) return;
            const currentUserId = auth.currentUser.uid;
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${currentRoomId}`);
            try {
                const docSnap = await getDoc(roomDocRef);
                if (docSnap.exists()) {
                    const roomData = docSnap.data();
                    const playerToRemove = roomData.players.find(p => p.id === currentUserId);
                    if (playerToRemove) {
                         await updateDoc(roomDocRef, {
                            players: arrayRemove(playerToRemove)
                        });
                    }
                }
            } catch (error) {
                console.error("Error leaving room: ", error);
            } finally {
                showLobbyView();
            }
        }

        function listenToRoomUpdates(roomId) {
            showGameView(roomId);
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${roomId}`);
            unsubscribeRoom = onSnapshot(roomDocRef, (docSnap) => {
                if (!docSnap.exists()) {
                    showMessage("Notice", "The room has been closed or does not exist.");
                    showLobbyView();
                    return;
                }
                const roomData = docSnap.data();
                const currentUserId = auth.currentUser ? auth.currentUser.uid : null;
                isHost = (roomData.hostId === currentUserId);
                
                // Update player list
                playerListEl.innerHTML = '';
                roomData.players.forEach(p => {
                    const li = document.createElement('li');
                    li.className = `p-2 rounded-md flex justify-between items-center ${p.id === roomData.hostId ? 'bg-yellow-900/50' : 'bg-gray-700'}`;
                    li.innerHTML = `<span>${p.name}</span> ${p.id === roomData.hostId ? '<span class="text-xs font-bold text-yellow-400">HOST</span>' : ''}`;
                    playerListEl.appendChild(li);
                });
                playerCountEl.textContent = roomData.players.length;

                // Update wheel
                drawWheel(roomData.wheelItems);

                // Update host controls
                hostControlsEl.classList.toggle('hidden', !isHost);
                spinBtn.disabled = !isHost || isSpinning || !roomData.wheelItems || roomData.wheelItems.length < 2;
                
                // Handle spin animation
                if (roomData.lastSpinTimestamp && roomData.lastSpinTimestamp !== lastKnownSpinTime) {
                    isSpinning = true;
                    lastKnownSpinTime = roomData.lastSpinTimestamp;
                    spinBtn.disabled = true;
                    resultDisplay.innerHTML = '';
                    canvas.style.transform = `rotate(${roomData.targetRotation}deg)`;

                    setTimeout(() => {
                        isSpinning = false;
                        spinBtn.disabled = !isHost;
                        if (roomData.result) {
                            resultDisplay.innerHTML = `<p class="text-xl font-bold text-amber-400 animate-pulse">Result: <span class="text-2xl">${roomData.result}</span></p>`;
                        }
                    }, 6000);
                }
            });
        }
        
        async function handleSpin() {
            if (!isHost || !currentRoomId || isSpinning) return;
            
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${currentRoomId}`);
            const docSnap = await getDoc(roomDocRef);
            if (!docSnap.exists()) return;
            const roomData = docSnap.data();

            if (!roomData.wheelItems || roomData.wheelItems.length < 2) {
                showMessage("Error", "At least 2 options are needed to spin.");
                return;
            }

            const randomSpins = Math.floor(Math.random() * 6) + 8;
            const randomAngle = Math.random() * 360;
            const currentRotation = roomData.currentRotation || 0;
            const targetRotation = currentRotation + (randomSpins * 360) + randomAngle;

            const finalAngle = targetRotation % 360;
            const winningAngle = (270 - finalAngle + 360) % 360;
            const anglePerSegment = 360 / roomData.wheelItems.length;
            const winningIndex = Math.floor(winningAngle / anglePerSegment);
            const winner = roomData.wheelItems[winningIndex];

            await updateDoc(roomDocRef, {
                targetRotation: targetRotation,
                currentRotation: finalAngle,
                lastSpinTimestamp: new Date().getTime(),
                result: winner
            });
        }

        async function updateWheelItems(newItems) {
            if (!isHost || !currentRoomId) return;
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${currentRoomId}`);
            await updateDoc(roomDocRef, {
                wheelItems: newItems
            });
        }

        // --- EVENT LISTENERS ---
        createRoomBtn.addEventListener('click', createNewRoom);
        joinRoomBtn.addEventListener('click', joinExistingRoom);
        leaveRoomBtn.addEventListener('click', leaveCurrentRoom);
        
        updateWheelBtn.addEventListener('click', () => {
             const items = customItemsInput.value.split('\n').map(item => item.trim()).filter(item => item !== '');
             if (items.length < 2) {
                 showMessage("Note", "Please enter at least 2 options.");
                 return;
             }
             updateWheelItems(items);
        });

        usePlayersBtn.addEventListener('click', async () => {
            if (!isHost || !currentRoomId) return;
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${currentRoomId}`);
            const docSnap = await getDoc(roomDocRef);
            if(docSnap.exists()){
                const players = docSnap.data().players;
                if(players.length < 2){
                    showMessage("Note", "There must be at least 2 players in the room.");
                    return;
                }
                const playerNames = players.map(p => p.name);
                updateWheelItems(playerNames);
            }
        });

        spinBtn.addEventListener('click', handleSpin);
        
        copyRoomIdBtn.addEventListener('click', () => {
            const gameId = roomIdDisplay.value;
            const textArea = document.createElement("textarea");
            textArea.value = gameId;
            document.body.appendChild(textArea);
            textArea.select();
            try {
                document.execCommand('copy');
                showMessage("Success", `Copied Room ID: ${gameId}`);
            } catch (err) {
                showMessage("Error", "Could not copy ID.");
            }
            document.body.removeChild(textArea);
        });
        
        modalCloseBtn.addEventListener('click', () => modal.classList.add('hidden'));

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/spin-the-wheel-online/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Gomoku 2 player</title>
		<link>https://techaiconnect.com/gomoku-2-player/</link>
					<comments>https://techaiconnect.com/gomoku-2-player/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sat, 05 Jul 2025 15:16:00 +0000</pubDate>
				<category><![CDATA[Game]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4271</guid>

					<description><![CDATA[Gomoku Online Gomoku Online The first player to get five in a row wins Your [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gomoku Online</title>
    
    <!-- Tailwind CSS for styling -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Google Fonts: Inter -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

    <style>
        /* Custom styles for Dark Mode */
        body {
            font-family: 'Inter', sans-serif;
            background-color: #111827; /* bg-gray-900 */
            color: #D1D5DB; /* text-gray-300 */
        }
        h1, h2, h3 {
            color: #F9FAFB; /* text-gray-50 */
        }
        /* Piece styles (colors kept for visibility) */
        .cell .piece-x {
            color: #10B981; /* emerald-500 */
            font-weight: bold;
            font-size: clamp(1rem, 2.5vw, 2rem);
            line-height: 1;
        }
        .cell .piece-o {
            color: #EF4444; /* red-500 */
            font-weight: bold;
            font-size: clamp(1rem, 2.5vw, 2rem);
            line-height: 1;
        }
        /* Hover effect for cells */
        .cell:not(.occupied):hover {
            background-color: #374151; /* bg-gray-700 */
        }
        /* Disabled board state */
        .board-disabled {
            pointer-events: none;
            opacity: 0.6;
        }
        /* Modal styles */
        .modal {
            transition: opacity 0.25s ease;
        }
        /* Custom input styles for dark mode */
        .dark-input {
            background-color: #374151; /* bg-gray-700 */
            border-color: #4B5563; /* border-gray-600 */
            color: #F9FAFB; /* text-gray-50 */
        }
        .dark-input::placeholder {
            color: #6B7280; /* placeholder-gray-500 */
        }
        .dark-input:focus {
            --tw-ring-color: #3B82F6; /* ring-blue-500 */
            border-color: #3B82F6; /* border-blue-500 */
        }
    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-4xl mx-auto">
        <h1 class="text-4xl font-bold text-center mb-2">Gomoku Online</h1>
        <p class="text-center text-gray-400 mb-6">The first player to get five in a row wins</p>

        <!-- Lobby Section -->
        <div id="lobby" class="bg-gray-800 p-8 rounded-xl shadow-lg border border-gray-700 max-w-md mx-auto">
            <div class="mb-4">
                <label for="playerName" class="block mb-2 text-sm font-medium text-gray-300">Your Name</label>
                <input type="text" id="playerName" class="dark-input text-sm rounded-lg block w-full p-2.5" placeholder="Enter your name" required>
            </div>
            <button id="createGameBtn" class="w-full text-white bg-emerald-600 hover:bg-emerald-700 focus:ring-4 focus:outline-none focus:ring-emerald-800 font-medium rounded-lg text-sm px-5 py-3 text-center mb-3">Create New Room</button>
            <div class="relative flex py-3 items-center">
                <div class="flex-grow border-t border-gray-600"></div>
                <span class="flex-shrink mx-4 text-gray-400">or</span>
                <div class="flex-grow border-t border-gray-600"></div>
            </div>
            <div class="flex gap-2">
                <input type="text" id="joinGameId" class="dark-input text-sm rounded-lg block w-full p-2.5" placeholder="Enter Room ID (6 digits)">
                <button id="joinGameBtn" class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Join Room</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                Your User ID: <span id="userIdDisplay" class="font-mono">Loading...</span>
            </div>
        </div>

        <!-- Game Section -->
        <div id="game-container" class="hidden w-full">
            <div class="flex flex-col lg:flex-row gap-6 items-start justify-center">
                <!-- Game Info Panel -->
                <div class="w-full lg:w-72 bg-gray-800 p-4 rounded-xl shadow-lg border border-gray-700 flex-shrink-0">
                    <h3 class="text-lg font-semibold mb-3">Game Information</h3>
                    <p class="text-sm text-gray-400">Room ID:</p>
                    <div class="flex items-center gap-2 mb-3">
                        <input readonly id="gameIdDisplay" class="w-full bg-gray-700 font-mono text-lg p-2 rounded-md border border-gray-600 text-center tracking-widest text-gray-50"/>
                        <button id="copyGameIdBtn" class="p-2 bg-gray-700 hover:bg-gray-600 rounded-md text-gray-300">
                            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
                        </button>
                    </div>

                    <div id="playerInfo" class="space-y-2 text-sm"></div>
                    <p id="turn-indicator" class="mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-gray-700 text-gray-300"></p>
                    <button id="resetGameBtn" class="w-full mt-4 text-white bg-red-600 hover:bg-red-700 focus:ring-4 focus:outline-none focus:ring-red-800 font-medium rounded-lg text-sm px-5 py-3 text-center hidden">Play Again</button>
                     <button id="leaveGameBtn" class="w-full mt-2 text-white bg-gray-600 hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-gray-700 font-medium rounded-lg text-sm px-5 py-3 text-center">Leave Room</button>
                </div>
                <!-- Game Board -->
                <div id="board" class="grid w-full aspect-square bg-gray-800 rounded-xl shadow-lg border border-gray-700" style="grid-template-columns: repeat(15, minmax(0, 1fr)); grid-template-rows: repeat(15, minmax(0, 1fr));">
                    <!-- Cells will be generated by JS -->
                </div>
            </div>
        </div>
    </div>

    <!-- Message Modal -->
    <div id="message-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
        <div class="bg-gray-800 rounded-xl shadow-2xl p-8 max-w-sm text-center border border-gray-700">
            <h2 id="modal-title" class="text-2xl font-bold mb-4"></h2>
            <p id="modal-body" class="text-gray-300 mb-6"></p>
            <button id="modal-close-btn" class="w-full text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Close</button>
        </div>
    </div>

    <!-- Firebase SDK -->
    <script type="module">
        // Import functions from Firebase SDKs
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, getDoc, setDoc, updateDoc, onSnapshot, collection } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        // PASTE YOUR FIREBASE CONFIGURATION THAT YOU COPIED IN STEP 3 HERE
        const firebaseConfig = {
            apiKey: "AIzaSyBL3USs4_BgCBM1_eFrOA0Htmjj2FWa5XM",
            authDomain: "app-and-game.firebaseapp.com",
            projectId: "app-and-game",
            storageBucket: "app-and-game.firebasestorage.app",
            messagingSenderId: "670250047171",
            appId: "1:670250047171:web:ca90d4df93674be6fef476",
            measurementId: "G-4KR5Q5RCQV"
        };
        // The lines below are kept the same or slightly adjusted
        const appId = firebaseConfig.projectId; // Use projectId as the identifier
        const initialAuthToken = null; // Not needed for local setup

        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);
        setLogLevel('debug');

        // --- DOM ELEMENTS ---
        const lobbyEl = document.getElementById('lobby');
        const gameContainerEl = document.getElementById('game-container');
        const boardEl = document.getElementById('board');
        const turnIndicatorEl = document.getElementById('turn-indicator');
        const playerInfoEl = document.getElementById('playerInfo');
        const createGameBtn = document.getElementById('createGameBtn');
        const joinGameBtn = document.getElementById('joinGameBtn');
        const resetGameBtn = document.getElementById('resetGameBtn');
        const leaveGameBtn = document.getElementById('leaveGameBtn');
        const copyGameIdBtn = document.getElementById('copyGameIdBtn');
        const userIdDisplay = document.getElementById('userIdDisplay');
        const gameIdDisplay = document.getElementById('gameIdDisplay');
        const modal = document.getElementById('message-modal');
        const modalTitle = document.getElementById('modal-title');
        const modalBody = document.getElementById('modal-body');
        const modalCloseBtn = document.getElementById('modal-close-btn');

        // --- GAME STATE ---
        const BOARD_SIZE = 15;
        let userId = null;
        let playerName = '';
        let currentGameId = null;
        let playerSymbol = null;
        let unsubscribeGame = null;
        let gameActive = false;

        // --- AUTHENTICATION ---
        onAuthStateChanged(auth, async (user) => {
            if (user) {
                userId = user.uid;
                userIdDisplay.textContent = userId;
                enableLobby();
            } else {
                try {
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                } catch (error) {
                    console.error("Error signing in:", error);
                    showMessage("Error", "Could not authenticate user. Please reload the page.");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function enableLobby() {
            createGameBtn.disabled = false;
            joinGameBtn.disabled = false;
            createGameBtn.textContent = 'Create New Room';
        }
        
        function showMessage(title, body) {
            modalTitle.textContent = title;
            modalBody.textContent = body;
            modal.classList.remove('hidden');
        }

        function hideMessage() {
            modal.classList.add('hidden');
        }

        function showGameView() {
            lobbyEl.classList.add('hidden');
            gameContainerEl.classList.remove('hidden');
        }

        function showLobbyView() {
            gameContainerEl.classList.add('hidden');
            lobbyEl.classList.remove('hidden');
            if (unsubscribeGame) {
                unsubscribeGame();
                unsubscribeGame = null;
            }
            currentGameId = null;
            playerSymbol = null;
            gameActive = false;
        }

        // --- GAME LOGIC ---

        function renderBoard(boardState, currentPlayer) {
            boardEl.innerHTML = '';
            boardEl.classList.toggle('board-disabled', !gameActive || currentPlayer !== playerSymbol);

            for (let r = 0; r < BOARD_SIZE; r++) {
                for (let c = 0; c < BOARD_SIZE; c++) {
                    const cell = document.createElement('div');
                    cell.className = 'cell flex items-center justify-center w-full h-full border border-gray-700 cursor-pointer transition-colors duration-150';
                    cell.dataset.row = r;
                    cell.dataset.col = c;

                    const piece = boardState[r][c];
                    if (piece) {
                        cell.classList.add('occupied');
                        const pieceEl = document.createElement('span');
                        pieceEl.className = piece === 'X' ? 'piece-x' : 'piece-o';
                        pieceEl.textContent = piece;
                        cell.appendChild(pieceEl);
                    } else {
                        cell.addEventListener('click', () => handleCellClick(r, c));
                    }
                    boardEl.appendChild(cell);
                }
            }
        }
        
        function updateGameInfo(gameData) {
            playerInfoEl.innerHTML = '';
            const p1 = gameData.players.p1;
            const p2 = gameData.players.p2;

            const p1_html = `<div class="flex items-center justify-between p-2 rounded-lg transition-colors ${gameData.currentPlayer === 'X' ? 'bg-emerald-900/50 text-emerald-300' : 'bg-gray-800'}"><span>(X) ${p1.name}</span><span class="font-mono text-xs text-gray-500">${p1.id.substring(0,6)}..</span></div>`;
            playerInfoEl.insertAdjacentHTML('beforeend', p1_html);

            if (p2) {
                 const p2_html = `<div class="flex items-center justify-between p-2 rounded-lg transition-colors ${gameData.currentPlayer === 'O' ? 'bg-red-900/50 text-red-300' : 'bg-gray-800'}"><span>(O) ${p2.name}</span><span class="font-mono text-xs text-gray-500">${p2.id.substring(0,6)}..</span></div>`;
                 playerInfoEl.insertAdjacentHTML('beforeend', p2_html);
            } else {
                 playerInfoEl.insertAdjacentHTML('beforeend', `<div class="p-2 text-center text-gray-500 border border-dashed border-gray-600 rounded-lg">Waiting for Player 2...</div>`);
            }

            if (gameData.status === 'active') {
                if (gameData.currentPlayer === playerSymbol) {
                    turnIndicatorEl.textContent = "Your Turn!";
                    turnIndicatorEl.className = 'mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-emerald-600 text-white';
                } else {
                    turnIndicatorEl.textContent = "Opponent's Turn...";
                    turnIndicatorEl.className = 'mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-red-600 text-white';
                }
            } else if (gameData.status === 'waiting') {
                turnIndicatorEl.textContent = "Waiting for another player...";
                turnIndicatorEl.className = 'mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-gray-700 text-gray-300';
            } else if (gameData.status.startsWith('win')) {
                 const winnerSymbol = gameData.status.split('_')[1];
                 const winner = winnerSymbol === 'X' ? gameData.players.p1 : gameData.players.p2;
                 turnIndicatorEl.textContent = `${winner.name} (${winnerSymbol}) wins!`;
                 turnIndicatorEl.className = 'mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-yellow-500 text-yellow-900';
            } else if (gameData.status === 'draw') {
                turnIndicatorEl.textContent = "It's a draw!";
                turnIndicatorEl.className = 'mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-gray-600 text-gray-200';
            }
        }
        
        async function generateUniqueGameId() {
            let gameId;
            let attempts = 0;
            const maxAttempts = 20; // Prevent infinite loops

            while (attempts < maxAttempts) {
                gameId = Math.floor(100000 + Math.random() * 900000).toString();
                const gameDocRef = doc(db, `artifacts/${appId}/public/data/caro-games`, gameId);
                const docSnap = await getDoc(gameDocRef);

                if (!docSnap.exists()) {
                    return gameId; // Found an unused ID
                }
                attempts++;
            }
            throw new Error("Failed to generate a unique game ID.");
        }

        async function createNewGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            if (!userId) {
                showMessage("Error", "Could not get User ID. Please try again.");
                return;
            }

            createGameBtn.disabled = true;
            createGameBtn.textContent = 'Creating...';

            const initialBoard = Array(BOARD_SIZE).fill(null).map(() => Array(BOARD_SIZE).fill(null));
            
            try {
                const newGameId = await generateUniqueGameId();
                const gameDocRef = doc(db, `artifacts/${appId}/public/data/caro-games`, newGameId);

                await setDoc(gameDocRef, {
                    board: JSON.stringify(initialBoard),
                    players: { p1: { id: userId, name: playerName } },
                    currentPlayer: 'X',
                    status: 'waiting',
                    createdAt: new Date().toISOString(),
                });

                currentGameId = newGameId;
                playerSymbol = 'X';
                listenForGameUpdates(currentGameId);
                showGameView();
                gameIdDisplay.value = currentGameId;

            } catch (error) {
                console.error("Error creating game: ", error);
                showMessage("Error", "Could not create game room. Please try again.");
                createGameBtn.disabled = false;
                createGameBtn.textContent = 'Create New Room';
            }
        }

        async function joinExistingGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            
            const gameIdToJoin = document.getElementById('joinGameId').value.trim();
            if (!gameIdToJoin) {
                showMessage("Error", "Please enter a Room ID.");
                return;
            }

            joinGameBtn.disabled = true;
            const gameDocRef = doc(db, `artifacts/${appId}/public/data/caro-games`, gameIdToJoin);
            
            try {
                const gameSnap = await getDoc(gameDocRef);
                if (!gameSnap.exists()) {
                    showMessage("Error", "Room with this ID not found.");
                    joinGameBtn.disabled = false;
                    return;
                }

                const gameData = gameSnap.data();
                if (gameData.players.p2 && gameData.players.p1.id !== userId && gameData.players.p2.id !== userId) {
                    showMessage("Error", "The room is full.");
                    joinGameBtn.disabled = false;
                    return;
                }
                
                if (!gameData.players.p2 && gameData.players.p1.id !== userId) {
                    await updateDoc(gameDocRef, {
                        'players.p2': { id: userId, name: playerName },
                        status: 'active'
                    });
                }
                
                currentGameId = gameIdToJoin;
                playerSymbol = gameData.players.p1.id === userId ? 'X' : 'O';
                listenForGameUpdates(currentGameId);
                showGameView();
                gameIdDisplay.value = currentGameId;

            } catch (error) {
                console.error("Error joining game: ", error);
                showMessage("Error", "Could not join the room. Please check the ID.");
            } finally {
                joinGameBtn.disabled = false;
            }
        }

        function listenForGameUpdates(gameId) {
            if (unsubscribeGame) unsubscribeGame();
            const gameDocRef = doc(db, `artifacts/${appId}/public/data/caro-games`, gameId);
            unsubscribeGame = onSnapshot(gameDocRef, (docSnap) => {
                if (docSnap.exists()) {
                    const gameData = docSnap.data();
                    const boardState = JSON.parse(gameData.board);
                    
                    gameActive = gameData.status === 'active';
                    resetGameBtn.classList.toggle('hidden', gameData.status === 'active' || gameData.status === 'waiting');

                    renderBoard(boardState, gameData.currentPlayer);
                    updateGameInfo(gameData);
                } else {
                    showMessage("Notice", "The game room has been closed or does not exist.");
                    showLobbyView();
                }
            });
        }

        async function handleCellClick(row, col) {
            if (!gameActive || !currentGameId) return;

            const gameDocRef = doc(db, `artifacts/${appId}/public/data/caro-games`, currentGameId);
            const gameSnap = await getDoc(gameDocRef);
            if (!gameSnap.exists()) return;

            const gameData = gameSnap.data();
            const boardState = JSON.parse(gameData.board);

            if (gameData.currentPlayer === playerSymbol && boardState[row][col] === null) {
                boardState[row][col] = playerSymbol;
                
                let newStatus = 'active';
                let nextPlayer = playerSymbol === 'X' ? 'O' : 'X';

                if (checkWin(boardState, playerSymbol)) {
                    newStatus = `win_${playerSymbol}`;
                    gameActive = false;
                } else if (checkDraw(boardState)) {
                    newStatus = 'draw';
                    gameActive = false;
                }

                await updateDoc(gameDocRef, {
                    board: JSON.stringify(boardState),
                    currentPlayer: nextPlayer,
                    status: newStatus
                });
            }
        }
        
        async function resetGame() {
            if (!currentGameId) return;
            const initialBoard = Array(BOARD_SIZE).fill(null).map(() => Array(BOARD_SIZE).fill(null));
            const gameDocRef = doc(db, `artifacts/${appId}/public/data/caro-games`, currentGameId);
            
            await updateDoc(gameDocRef, {
                board: JSON.stringify(initialBoard),
                currentPlayer: 'X',
                status: 'active'
            });
        }

        function checkWin(board, player) {
            for (let r = 0; r < BOARD_SIZE; r++) {
                for (let c = 0; c < BOARD_SIZE; c++) {
                    // Check horizontal
                    if (c <= BOARD_SIZE - 5) {
                        if (board[r][c] === player && board[r][c+1] === player && board[r][c+2] === player && board[r][c+3] === player && board[r][c+4] === player) return true;
                    }
                    // Check vertical
                    if (r <= BOARD_SIZE - 5) {
                        if (board[r][c] === player && board[r+1][c] === player && board[r+2][c] === player && board[r+3][c] === player && board[r+4][c] === player) return true;
                    }
                    // Check diagonal (down-right)
                    if (r <= BOARD_SIZE - 5 && c <= BOARD_SIZE - 5) {
                        if (board[r][c] === player && board[r+1][c+1] === player && board[r+2][c+2] === player && board[r+3][c+3] === player && board[r+4][c+4] === player) return true;
                    }
                    // Check diagonal (up-right)
                    if (r >= 4 && c <= BOARD_SIZE - 5) {
                        if (board[r][c] === player && board[r-1][c+1] === player && board[r-2][c+2] === player && board[r-3][c+3] === player && board[r-4][c+4] === player) return true;
                    }
                }
            }
            return false;
        }

        function checkDraw(board) {
            return board.every(row => row.every(cell => cell !== null));
        }

        // --- EVENT LISTENERS ---
        createGameBtn.addEventListener('click', createNewGame);
        joinGameBtn.addEventListener('click', joinExistingGame);
        resetGameBtn.addEventListener('click', resetGame);
        leaveGameBtn.addEventListener('click', showLobbyView);
        modalCloseBtn.addEventListener('click', hideMessage);
        
        copyGameIdBtn.addEventListener('click', () => {
            const gameId = gameIdDisplay.value;
            const textArea = document.createElement("textarea");
            textArea.value = gameId;
            document.body.appendChild(textArea);
            textArea.select();
            try {
                document.execCommand('copy');
                showMessage("Success", `Copied Room ID: ${gameId}`);
            } catch (err) {
                showMessage("Error", "Could not copy ID.");
            }
            document.body.removeChild(textArea);
        });

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/gomoku-2-player/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
