<?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>ゲーム &#8211; デジタル未来 (Dejitaru Mirai)</title>
	<atom:link href="https://dejitarumirai.com/%e3%82%b2%e3%83%bc%e3%83%a0/feed" rel="self" type="application/rss+xml" />
	<link>https://dejitarumirai.com</link>
	<description></description>
	<lastBuildDate>Sun, 06 Jul 2025 08:36:13 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.1</generator>
	<item>
		<title>オセロ オンライン</title>
		<link>https://dejitarumirai.com/archives/4242</link>
					<comments>https://dejitarumirai.com/archives/4242#respond</comments>
		
		<dc:creator><![CDATA[deji]]></dc:creator>
		<pubDate>Sun, 06 Jul 2025 08:36:13 +0000</pubDate>
				<category><![CDATA[ゲーム]]></category>
		<guid isPermaLink="false">https://dejitarumirai.com/?p=4242</guid>

					<description><![CDATA[オンラインオセロ（リバーシ）  [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>オンラインオセロ（リバーシ）</title>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Google Fonts: Noto Sans JP -->
    <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=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Noto Sans JP', 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">オセロ（リバーシ）</h1>
            <p class="text-gray-400 mt-2">相手の石を裏返して勝利しよう！</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">あなたの名前</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="名前を入力してください" 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">新しいルームを作成</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">または</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="ルームID（6桁）を入力">
                <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">ルームに参加</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                あなたのユーザーID: <span id="userIdDisplay" class="font-mono">読み込み中...</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">ゲーム情報</h3>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">ルーム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="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">もう一度プレイ</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">ルームを退出</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">閉じる</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("ログインエラー:", error);
                    showMessage("エラー", "ユーザーを認証できませんでした。ページを再読み込みしてください。");
                }
            }
        });

        // --- 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 ---

        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;

                    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;
                        }
                    }
                    if (isValid) {
                        validMoves.push({ row: r, col: c });
                    }
                }
            }
            return validMoves;
        }

        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("ユニークなルームIDの生成に失敗しました。");
        }

        async function createNewGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("エラー", "名前を入力してください。");
                return;
            }
            if (!userId) {
                showMessage("エラー", "ユーザーIDがまだ利用できません。しばらくお待ちください。");
                return;
            }

            createGameBtn.disabled = true;
            createGameBtn.textContent = '作成中...';

            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);
                showMessage("エラー", "ルームを作成できませんでした。もう一度お試しください。");
            } finally {
                createGameBtn.disabled = false;
                createGameBtn.textContent = '新しいルームを作成';
            }
        }

        async function joinExistingGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("エラー", "名前を入力してください。");
                return;
            }
            const gameIdToJoin = document.getElementById('joinGameId').value.trim();
            if (!gameIdToJoin) {
                showMessage("エラー", "ルームIDを入力してください。");
                return;
            }

            joinGameBtn.disabled = true;
            const gameDocRef = getGameDocRef(gameIdToJoin);
            
            try {
                const gameSnap = await getDoc(gameDocRef);
                if (!gameSnap.exists()) {
                    showMessage("エラー", "このIDのルームは見つかりませんでした。");
                    return;
                }

                const gameData = gameSnap.data();
                if (gameData.players.p2 && gameData.players.p1.id !== userId) {
                    showMessage("エラー", "このルームは満員です。");
                    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);
                showMessage("エラー", "ルームに参加できませんでした。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;

            const flips = getFlipsForMove(board, row, col, currentPlayer);
            board[row][col] = currentPlayer;
            flips.forEach(p => { board[p.row][p.col] = currentPlayer; });

            let nextPlayer = currentPlayer === 'B' ? 'W' : 'B';
            let nextPlayerValidMoves = getValidMoves(board, nextPlayer);
            
            if (nextPlayerValidMoves.length === 0) {
                nextPlayer = currentPlayer;
                nextPlayerValidMoves = getValidMoves(board, nextPlayer);
            }
            
            const newScores = calculateScores(board);
            let newStatus = 'active';
            let winner = null;

            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 ? "(あなた)" : ""}</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 ? "(あなた)" : ""}</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">対戦相手を待っています...</div>`;
            }
            
            let statusText = '';
            let statusClass = 'bg-gray-700 text-gray-200';
            if (status === 'waiting') {
                statusText = 'プレイヤーを待っています...';
            } else if (status === 'active') {
                const currentName = currentPlayer === 'B' ? p1.name : (p2 ? p2.name : '');
                statusText = `${currentName}の番です`;
                if (currentPlayer === playerSymbol) {
                    statusText = "あなたの番です！";
                    statusClass = 'bg-blue-600 text-white';
                }
            } else if (status === 'finished') {
                if (winner === 'draw') {
                    statusText = "引き分けです！";
                } else {
                    const winnerName = winner === 'B' ? p1.name : p2.name;
                    statusText = `${winnerName}の勝利です！`;
                }
                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("お知らせ", "このゲームルームは閉鎖されたか、存在しません。");
                    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("成功", `ルームIDをコピーしました: ${gameId}`);
            } catch (err) {
                showMessage("エラー", "IDをコピーできませんでした。");
            }
            document.body.removeChild(textArea);
        });

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://dejitarumirai.com/archives/4242/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>チェス オンライン</title>
		<link>https://dejitarumirai.com/archives/4239</link>
					<comments>https://dejitarumirai.com/archives/4239#respond</comments>
		
		<dc:creator><![CDATA[deji]]></dc:creator>
		<pubDate>Sun, 06 Jul 2025 08:11:56 +0000</pubDate>
				<category><![CDATA[ゲーム]]></category>
		<guid isPermaLink="false">https://dejitarumirai.com/?p=4239</guid>

					<description><![CDATA[オンラインチェス (Fires [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>オンラインチェス (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: Noto Sans JP -->
    <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=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Noto Sans JP', 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">オンラインチェス</h1>
            <p class="text-gray-400 mt-2">友達と知恵を競い合おう</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">あなたの名前</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="名前を入力してください" 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">新しいルームを作成</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">または</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="ルームID (6桁) を入力">
                <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">ルームに参加</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                あなたのユーザーID: <span id="userIdDisplay" class="font-mono">読み込み中...</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">対局情報</h3>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">ルーム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="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">もう一度プレイ</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">ルームを退出</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">閉じる</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;

        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("ログインエラー:", error);
                    showMessage("エラー", "ユーザー認証に失敗しました。ページを再読み込みしてください。");
                }
            }
        });

        // --- 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++) {
                const squareIndex = isFlipped ? (squares.length - 1 - i) : i;
                const squareName = squares[squareIndex];

                const squareEl = document.createElement('div');
                
                const rank = parseInt(squareName.charAt(1), 10);
                const file = squareName.charCodeAt(0) - 'a'.charCodeAt(0);
                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';
                    
                    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">先手 (白): ${p1.name}</span>
                <span class="font-mono text-xs text-gray-400">${p1.id === userId ? "(あなた)" : ""}</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">後手 (黒): ${p2.name}</span>
                    <span class="font-mono text-xs text-gray-400">${p2.id === userId ? "(あなた)" : ""}</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">対戦相手を待っています...</div>`);
            }
            
            let statusText = '';
            let statusClass = 'bg-gray-700 text-gray-200';
            switch(status) {
                case 'waiting':
                    statusText = 'プレイヤーを待っています...';
                    break;
                case 'active':
                    statusText = turn === 'w' ? "先手 (白) の番です" : "後手 (黒) の番です";
                    if ((turn === 'w' && playerColor === 'w') || (turn === 'b' && playerColor === 'b')) {
                        statusText = "あなたの番です！";
                        statusClass = 'bg-blue-600 text-white';
                    }
                    break;
                case 'checkmate':
                    const winner = turn === 'b' ? '先手 (白)' : '後手 (黒)';
                    statusText = `チェックメイト！ ${winner} の勝ちです！`;
                    statusClass = 'bg-yellow-500 text-black font-bold';
                    break;
                case 'stalemate':
                    statusText = 'ステールメイト！';
                    statusClass = 'bg-yellow-500 text-black font-bold';
                    break;
                case 'draw':
                    statusText = '千日手で引き分け';
                    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("ユニークなルームIDの生成に失敗しました。");
        }

        async function createNewGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("エラー", "名前を入力してください。");
                return;
            }
            if (!userId) {
                showMessage("エラー", "ユーザーIDがまだ利用できません。少々お待ちください。");
                return;
            }

            createGameBtn.disabled = true;
            createGameBtn.textContent = '作成中...';

            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);
                showMessage("エラー", "ルームを作成できませんでした。もう一度お試しください。");
            } finally {
                createGameBtn.disabled = false;
                createGameBtn.textContent = '新しいルームを作成';
            }
        }

        async function joinExistingGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("エラー", "名前を入力してください。");
                return;
            }
            const gameIdToJoin = document.getElementById('joinGameId').value.trim();
            if (!gameIdToJoin) {
                showMessage("エラー", "ルームIDを入力してください。");
                return;
            }

            joinGameBtn.disabled = true;
            const gameDocRef = getGameDocRef(gameIdToJoin);
            
            try {
                const gameSnap = await getDoc(gameDocRef);
                if (!gameSnap.exists()) {
                    showMessage("エラー", "このIDのルームは見つかりませんでした。");
                    return;
                }

                const gameData = gameSnap.data();
                if (gameData.players.p2 && gameData.players.p1.id !== userId) {
                    showMessage("エラー", "このルームは満員です。");
                    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);
                showMessage("エラー", "ルームに参加できませんでした。IDを確認してください。");
            } finally {
                joinGameBtn.disabled = false;
            }
        }

        function listenToGameUpdates(gameId) {
            if (unsubscribeGame) unsubscribeGame();
            const gameDocRef = getGameDocRef(gameId);
            unsubscribeGame = onSnapshot(gameDocRef, (docSnap) => {
                if (docSnap.exists()) {
                    const gameData = docSnap.data();
                    renderBoard(gameData.fen, gameData.lastMove, gameData.turn);
                    updateGameInfo(gameData);
                } else {
                    showMessage("お知らせ", "対局ルームが閉鎖されたか、存在しません。");
                    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 (selectedSquare) {
                const move = {
                    from: selectedSquare,
                    to: squareName,
                    promotion: 'q'
                };
                
                const result = chess.move(move);
                
                if (result) {
                    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 }
                    });
                }
                
                clearHighlights();
                selectedSquare = null;

            } else {
                if (piece && piece.color === playerColor) {
                    selectedSquare = squareName;
                    clearHighlights();
                    document.querySelector(`[data-square="${squareName}"]`).classList.add('selected');
                    
                    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("成功", `ルームIDをコピーしました: ${gameId}`);
            } catch (err) {
                showMessage("エラー", "IDをコピーできませんでした。");
            }
            document.body.removeChild(textArea);
        });

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://dejitarumirai.com/archives/4239/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ラッキーホイールオンライン</title>
		<link>https://dejitarumirai.com/archives/4236</link>
					<comments>https://dejitarumirai.com/archives/4236#respond</comments>
		
		<dc:creator><![CDATA[deji]]></dc:creator>
		<pubDate>Sat, 05 Jul 2025 15:50:59 +0000</pubDate>
				<category><![CDATA[ゲーム]]></category>
		<guid isPermaLink="false">https://dejitarumirai.com/?p=4236</guid>

					<description><![CDATA[ラッキーホイールオンライン ラ [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ラッキーホイールオンライン</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=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Noto Sans JP', '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">ラッキーホイールオンライン</h1>
            <p class="text-gray-400 mt-2 text-lg">エキサイティングな瞬間を一緒に分かち合おう！</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">あなたの名前</label>
                <input type="text" id="playerName" class="dark-input w-full text-sm rounded-lg p-2.5" placeholder="名前を入力してください" 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">新しいルームを作成</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">または</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="ルームID (6桁) を入力">
                <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">ルームに参加</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                あなたのユーザーID: <span id="userIdDisplay" class="font-mono">読み込み中...</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">ルーム情報</h2>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">ルームID (友達と共有):</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="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">参加者 (<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">
                        ルームを退出
                    </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">
                        スピン!
                    </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">ホイールをカスタマイズ</h2>
                    <p class="text-sm text-center text-gray-400 mb-4">ホストのみがホイールを変更できます。</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">
                            プレイヤーリストを使用
                        </button>
                        <div>
                            <label for="custom-items-input" class="block mb-2 text-sm font-medium text-gray-300">または、オプションを入力 (1行に1つ):</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">
                            ホイールを更新
                        </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">閉じる</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("ログインエラー:", error);
                    showMessage("エラー", "ユーザー認証に失敗しました。ページを再読み込みしてください。");
                }
            }
        });

        // --- 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 "Noto Sans JP", 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 "Noto Sans JP"';
                let text = segment.length > 15 ? segment.substring(0, 14) + '...' : 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("エラー", "名前を入力してください。");
                return;
            }
            if (!auth.currentUser) {
                showMessage("エラー", "ユーザーが認証されていません。しばらく待ってからもう一度お試しください。");
                return;
            }
            const currentUserId = auth.currentUser.uid;

            createRoomBtn.disabled = true;
            createRoomBtn.textContent = '作成中...';

            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);
                showMessage("エラー", "ルームを作成できませんでした。もう一度お試しください。");
            } finally {
                createRoomBtn.disabled = false;
                createRoomBtn.textContent = '新しいルームを作成';
            }
        }

        async function joinExistingRoom() {
            const localPlayerName = document.getElementById('playerName').value.trim();
            if (!localPlayerName) {
                showMessage("エラー", "名前を入力してください。");
                return;
            }
            const roomIdToJoin = document.getElementById('joinRoomId').value.trim();
            if (!roomIdToJoin) {
                showMessage("エラー", "ルームIDを入力してください。");
                return;
            }
            if (!auth.currentUser) {
                showMessage("エラー", "ユーザーが認証されていません。しばらく待ってからもう一度お試しください。");
                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("エラー", "このIDのルームは見つかりませんでした。");
                    return;
                }

                const roomData = docSnap.data();
                if (roomData.players.length >= 20 && !roomData.players.some(p => p.id === currentUserId)) {
                    showMessage("エラー", "このルームは満員です。");
                    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);
                showMessage("エラー", "ルームに参加できませんでした。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);
            } 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("お知らせ", "ルームが閉鎖されたか、存在しません。");
                    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">ホスト</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">結果: <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("エラー", "スピンするには少なくとも2つのオプションが必要です。");
                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("注意", "少なくとも2つのオプションを入力してください。");
                 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("注意", "ルームには少なくとも2人のプレイヤーが必要です。");
                    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("成功", `ルームIDをコピーしました: ${gameId}`);
            } catch (err) {
                showMessage("エラー", "IDをコピーできませんでした。");
            }
            document.body.removeChild(textArea);
        });
        
        modalCloseBtn.addEventListener('click', () => modal.classList.add('hidden'));

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://dejitarumirai.com/archives/4236/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>五目並べ 2人</title>
		<link>https://dejitarumirai.com/archives/4233</link>
					<comments>https://dejitarumirai.com/archives/4233#respond</comments>
		
		<dc:creator><![CDATA[deji]]></dc:creator>
		<pubDate>Sat, 05 Jul 2025 15:09:41 +0000</pubDate>
				<category><![CDATA[ゲーム]]></category>
		<guid isPermaLink="false">https://dejitarumirai.com/?p=4233</guid>

					<description><![CDATA[五目並べオンライン 五目並べオ [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>五目並べオンライン</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&family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">

    <style>
        /* Custom styles for Dark Mode */
        body {
            font-family: 'Noto Sans JP', '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">五目並べオンライン</h1>
        <p class="text-center text-gray-400 mb-6">先に五つ並べた方の勝ちです</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">あなたの名前</label>
                <input type="text" id="playerName" class="dark-input text-sm rounded-lg block w-full p-2.5" placeholder="名前を入力してください" 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">新しいルームを作成</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">または</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="ルームID (6桁) を入力">
                <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">ルームに参加</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                あなたのユーザーID: <span id="userIdDisplay" class="font-mono">読み込み中...</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">ゲーム情報</h3>
                    <p class="text-sm text-gray-400">ルーム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">もう一度プレイ</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">ルームを退出</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">閉じる</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("エラー", "ユーザー認証に失敗しました。ページを再読み込みしてください。");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function enableLobby() {
            createGameBtn.disabled = false;
            joinGameBtn.disabled = false;
            createGameBtn.textContent = '新しいルームを作成';
        }
        
        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">プレイヤー2を待っています...</div>`);
            }

            if (gameData.status === 'active') {
                if (gameData.currentPlayer === playerSymbol) {
                    turnIndicatorEl.textContent = "あなたの番です！";
                    turnIndicatorEl.className = 'mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-emerald-600 text-white';
                } else {
                    turnIndicatorEl.textContent = "相手の番です...";
                    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 = "他のプレイヤーを待っています...";
                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}) の勝ちです！`;
                 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 = "引き分けです！";
                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("エラー", "名前を入力してください。");
                return;
            }
            if (!userId) {
                showMessage("エラー", "ユーザーIDを取得できませんでした。もう一度お試しください。");
                return;
            }

            createGameBtn.disabled = true;
            createGameBtn.textContent = '作成中...';

            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("エラー", "ゲームルームを作成できませんでした。もう一度お試しください。");
                createGameBtn.disabled = false;
                createGameBtn.textContent = '新しいルームを作成';
            }
        }

        async function joinExistingGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("エラー", "名前を入力してください。");
                return;
            }
            
            const gameIdToJoin = document.getElementById('joinGameId').value.trim();
            if (!gameIdToJoin) {
                showMessage("エラー", "ルーム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("エラー", "このIDのルームは見つかりませんでした。");
                    joinGameBtn.disabled = false;
                    return;
                }

                const gameData = gameSnap.data();
                if (gameData.players.p2 && gameData.players.p1.id !== userId && gameData.players.p2.id !== userId) {
                    showMessage("エラー", "このルームは満員です。");
                    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("エラー", "ルームに参加できませんでした。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("お知らせ", "ゲームルームが閉鎖されたか、存在しません。");
                    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("成功", `ルームIDをコピーしました: ${gameId}`);
            } catch (err) {
                showMessage("エラー", "IDをコピーできませんでした。");
            }
            document.body.removeChild(textArea);
        });

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://dejitarumirai.com/archives/4233/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
