fix bugs in this game


index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Connect Four Online</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Google Fonts for better typography -->
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet"> 
    <style>
        /* CSS Variables for easy theming */
        :root {
            --background-color: #1e1e1e;
            --primary-color: #00bcd4;
            --secondary-color: #2c2c2c;
            --accent-color: #ff5252;
            --neutral-light: #d2dae2;
            --neutral-dark: #3c3c3c;
            --text-color: #f5f5f5;
            --success-color: #4caf50;
            --error-color: #f44336;
            --button-hover: rgba(0, 188, 212, 0.8);
            --transition-speed: 0.3s;
            --modal-background: rgba(0, 0, 0, 0.8);
            --card-background: #2c2c2c;
            --toast-success: #4caf50;
            --toast-error: #f44336;
            --highlight-color: #ffd700; /* Gold color for highlighting last move */
        }

        /* Global Styles */
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            transition: var(--transition-speed);
        }

        body {
            font-family: 'Roboto', sans-serif;
            background-color: var(--background-color);
            color: var(--text-color);
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
            min-height: 100vh;
        }

        h1, h2 {
            color: var(--primary-color);
            margin-bottom: 20px;
            text-align: center;
        }

        /* Container Styles */
        .container {
            background-color: var(--card-background);
            padding: 30px;
            border-radius: 12px;
            width: 100%;
            max-width: 500px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.7);
            transition: transform 0.3s, opacity var(--transition-speed);
            margin-bottom: 20px;
        }

        .container:hover {
            transform: translateY(-5px);
            opacity: 0.98;
        }

        /* Input Styles */
        input[type="text"], input[type="password"], input[type="submit"] {
            width: 100%;
            padding: 12px 15px;
            margin: 10px 0;
            border: none;
            border-radius: 8px;
            font-size: 16px;
        }

        input[type="text"], input[type="password"] {
            background-color: var(--neutral-dark);
            color: var(--text-color);
        }

        input[type="text"]::placeholder, input[type="password"]::placeholder {
            color: var(--neutral-light);
        }

        input[type="text"]:focus, input[type="password"]:focus {
            outline: none;
            border: 2px solid var(--primary-color);
            background-color: var(--neutral-dark);
        }

        /* Button Styles */
        .button {
            padding: 12px 25px;
            margin: 10px 5px 0 0;
            border: none;
            border-radius: 8px;
            background-color: var(--primary-color);
            color: white;
            font-size: 16px;
            cursor: pointer;
            transition: background-color var(--transition-speed), transform 0.2s, box-shadow var(--transition-speed);
            display: inline-flex;
            align-items: center;
            justify-content: center;
        }

        .button:hover {
            background-color: var(--button-hover);
            box-shadow: 0 4px 10px rgba(0, 188, 212, 0.5);
        }

        .button:active {
            transform: scale(0.98);
        }

        .button:focus {
            outline: 2px solid var(--primary-color);
            outline-offset: 2px;
        }

        /* Flex Container for Buttons */
        .button-group {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
        }

        /* Game Board Styles */
        #game {
            display: grid;
            grid-template-columns: repeat(7, 60px);
            grid-template-rows: repeat(6, 60px);
            gap: 5px;
            background-color: var(--neutral-dark);
            padding: 15px;
            border-radius: 12px;
            box-shadow: inset 0 0 10px rgba(0,0,0,0.7);
            margin-top: 20px;
            position: relative;
            animation: fadeIn 0.5s ease-in-out;
        }

        .cell {
            width: 60px;
            height: 60px;
            background-color: #34495e;
            border-radius: 50%;
            position: relative;
            cursor: pointer;
            transition: transform 0.2s, background-color 0.3s;
            display: flex;
            align-items: center;
            justify-content: center;
            overflow: hidden;
        }

        .cell:hover {
            transform: scale(1.1);
            background-color: #3a539b;
        }

        .cell::after {
            content: '';
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background-color: transparent;
            transition: background-color 0.3s, transform 0.5s;
        }

        .cell.red::after {
            background-color: var(--accent-color);
            transform: translateY(-60px);
            animation: drop 0.5s forwards;
        }

        .cell.blue::after {
            background-color: var(--primary-color);
            transform: translateY(-60px);
            animation: drop 0.5s forwards;
        }

        /* Highlighted Last Move */
        .cell.highlight::after {
            box-shadow: 0 0 10px var(--highlight-color);
            border: 3px solid var(--highlight-color);
        }

        /* Status and Points */
        #status, #points {
            margin-top: 20px;
            text-align: center;
            font-size: 18px;
            min-height: 24px;
            animation: fadeIn 0.5s ease-in-out;
        }

        /* Rooms List */
        #rooms-list {
            max-height: 250px;
            overflow-y: auto;
            margin-top: 15px;
            animation: fadeIn 0.5s ease-in-out;
        }

        .room-item {
            background-color: var(--neutral-dark);
            padding: 12px;
            border-radius: 8px;
            margin-bottom: 10px;
            cursor: pointer;
            transition: background-color var(--transition-speed), transform 0.2s;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .room-item:hover {
            background-color: var(--primary-color);
            transform: translateX(5px);
        }

        .room-item:focus {
            outline: 2px solid var(--primary-color);
            outline-offset: 2px;
        }

        /* Hidden Class */
        .hidden {
            display: none;
        }

        /* Loading Spinner */
        .spinner {
            border: 6px solid rgba(255, 255, 255, 0.2);
            border-top: 6px solid var(--primary-color);
            border-radius: 50%;
            width: 40px;
            height: 40px;
            animation: spin 1s linear infinite;
            margin: 20px auto;
        }

        /* Animations */
        @keyframes spin {
            to { transform: rotate(360deg); }
        }

        @keyframes drop {
            to { transform: translateY(0); }
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }

        /* Toast Notification Styles */
        #toast {
            position: fixed;
            bottom: 30px;
            right: 30px;
            background-color: var(--card-background);
            color: var(--text-color);
            padding: 16px 24px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            display: flex;
            align-items: center;
            min-width: 250px;
            opacity: 0;
            transform: translateY(100%);
            transition: opacity var(--transition-speed), transform var(--transition-speed);
            z-index: 1000;
        }

        #toast.show {
            opacity: 1;
            transform: translateY(0);
        }

        #toast.success {
            border-left: 6px solid var(--success-color);
        }

        #toast.error {
            border-left: 6px solid var(--error-color);
        }

        /* Modal Styles */
        .modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: var(--modal-background);
            display: flex;
            align-items: center;
            justify-content: center;
            opacity: 0;
            visibility: hidden;
            transition: opacity var(--transition-speed), visibility var(--transition-speed);
            z-index: 1000;
        }

        .modal.show {
            opacity: 1;
            visibility: visible;
        }

        .modal-content {
            background-color: var(--card-background);
            padding: 20px 30px;
            border-radius: 12px;
            width: 90%;
            max-width: 400px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.5);
            animation: slideDown 0.3s ease-out;
            position: relative;
        }

        @keyframes slideDown {
            from { transform: translateY(-50px); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
        }

        .modal-header {
            margin-bottom: 15px;
        }

        .modal-header h2 {
            margin: 0;
        }

        .close-modal {
            position: absolute;
            top: 20px;
            right: 30px;
            background: none;
            border: none;
            font-size: 24px;
            color: var(--text-color);
            cursor: pointer;
        }

        /* Chat Section Styles */
        #chat-section {
            max-width: 500px;
            margin-top: 20px;
            display: flex;
            flex-direction: column;
            height: 400px;
        }

        .chat-messages {
            flex: 1;
            background-color: var(--neutral-dark);
            padding: 15px;
            border-radius: 8px;
            overflow-y: auto;
            margin-bottom: 10px;
            box-shadow: inset 0 0 5px rgba(0,0,0,0.5);
        }

        .chat-message {
            margin-bottom: 10px;
            padding: 8px 12px;
            background-color: var(--secondary-color);
            border-radius: 8px;
            word-wrap: break-word;
        }

        .chat-message.self {
            background-color: var(--primary-color);
            color: white;
            align-self: flex-end;
        }

        .chat-input-container {
            display: flex;
        }

        #chat-input {
            flex: 1;
            padding: 10px;
            border: none;
            border-radius: 8px 0 0 8px;
            font-size: 16px;
            background-color: var(--neutral-dark);
            color: var(--text-color);
        }

        #chat-input::placeholder {
            color: var(--neutral-light);
        }

        #chat-input:focus {
            outline: none;
            border: 2px solid var(--primary-color);
            background-color: var(--neutral-dark);
        }

        #send-chat-btn {
            padding: 10px 20px;
            border: none;
            border-radius: 0 8px 8px 0;
            background-color: var(--primary-color);
            color: white;
            font-size: 16px;
            cursor: pointer;
            transition: background-color var(--transition-speed), transform 0.2s, box-shadow var(--transition-speed);
        }

        #send-chat-btn:hover {
            background-color: var(--button-hover);
            box-shadow: 0 4px 10px rgba(0, 188, 212, 0.5);
        }

        #send-chat-btn:active {
            transform: scale(0.98);
        }

        #send-chat-btn:focus {
            outline: 2px solid var(--primary-color);
            outline-offset: 2px;
        }

        /* Responsive Design */
        @media (max-width: 768px) {
            #game {
                grid-template-columns: repeat(7, 50px);
                grid-template-rows: repeat(6, 50px);
                gap: 4px;
                padding: 10px;
            }

            .cell {
                width: 50px;
                height: 50px;
            }

            .cell::after {
                width: 42px;
                height: 42px;
            }
        }

        @media (max-width: 480px) {
            .container {
                padding: 20px;
            }

            #game {
                grid-template-columns: repeat(7, 40px);
                grid-template-rows: repeat(6, 40px);
                gap: 3px;
                padding: 8px;
            }

            .cell {
                width: 40px;
                height: 40px;
            }

            .cell::after {
                width: 34px;
                height: 34px;
            }

            .button {
                width: 100%;
                margin: 10px 0 0 0;
            }
        }
    </style>
</head>
<body>
    <h1>Connect Four Online</h1>

    <!-- Room Selection Section -->
    <div id="room-selection" class="container" aria-labelledby="room-selection-header">
        <h2 id="room-selection-header">Join or Create a Room</h2>
        <input type="text" id="room-name" placeholder="Room Name" aria-label="Room Name">
        <input type="password" id="room-password" placeholder="Room Password (Optional)" aria-label="Room Password">
        <div class="button-group">
            <button class="button" id="create-room-btn">Create Room</button>
            <button class="button" id="search-rooms-btn">Search Rooms</button>
        </div>
        <div id="rooms-list" class="hidden" aria-live="polite"></div>
    </div>

    <!-- Join Game Section -->
    <div id="join-game" class="container hidden" aria-labelledby="join-game-header">
        <h2 id="join-game-header">Enter Your Details</h2>
        <input type="text" id="player-name" placeholder="Enter your name" aria-label="Player Name">
        <div class="button-group">
            <button class="button" id="join-game-btn">Join Game</button>
        </div>
    </div>

    <!-- Game Board -->
    <div id="game" class="hidden" aria-label="Game Board"></div>

    <!-- Status and Points Display -->
    <div id="status" class="hidden" aria-live="polite"></div>
    <div id="points" class="hidden" aria-live="polite"></div>

    <!-- Chat Section -->
    <div id="chat-section" class="container hidden" aria-labelledby="chat-section-header">
        <h2 id="chat-section-header">Chat</h2>
        <div id="chat-messages" class="chat-messages" aria-live="polite" aria-atomic="false"></div>
        <div class="chat-input-container">
            <input type="text" id="chat-input" placeholder="Type your message..." aria-label="Chat Message">
            <button class="button" id="send-chat-btn">Send</button>
        </div>
    </div>

    <!-- Loading Spinner -->
    <div id="loading" class="spinner hidden" aria-hidden="true"></div>

    <!-- Toast Notification -->
    <div id="toast" aria-live="assertive" aria-atomic="true"></div>

    <!-- Modal for Joining Selected Room -->
    <div id="join-room-modal" class="modal" role="dialog" aria-modal="true" aria-labelledby="join-room-modal-header">
        <div class="modal-content">
            <button class="close-modal" aria-label="Close">&times;</button>
            <div class="modal-header">
                <h2 id="join-room-modal-header">Join Room: <span id="selected-room-name"></span></h2>
            </div>
            <input type="password" id="join-room-password" placeholder="Enter Room Password (if any)" aria-label="Room Password">
            <input type="text" id="join-player-name" placeholder="Enter your name" aria-label="Player Name">
            <div class="button-group">
                <button class="button" id="confirm-join-room-btn">Join Room</button>
            </div>
        </div>
    </div>

    <footer>
        <p>&copy; 2024 Connect Four Online by wesamdev v0.1.1-test</p>
    </footer>

    <script>
        // WebSocket configuration
        const socketUrl = 'ws://localhost:6789'; // Update to 'wss://' if using SSL
        let socket;
        let playerColor = null;
        let currentRoom = null;
        let lastMove = { row: null, col: null };

        // DOM Elements
        const roomSelectionDiv = document.getElementById('room-selection');
        const joinGameDiv = document.getElementById('join-game');
        const roomsListDiv = document.getElementById('rooms-list');
        const gameDiv = document.getElementById('game');
        const statusDiv = document.getElementById('status');
        const pointsDiv = document.getElementById('points');
        const loadingSpinner = document.getElementById('loading');
        const toast = document.getElementById('toast');

        const joinRoomModal = document.getElementById('join-room-modal');
        const closeModalBtn = document.querySelector('.close-modal');
        const selectedRoomNameSpan = document.getElementById('selected-room-name');
        const joinRoomPasswordInput = document.getElementById('join-room-password');
        const joinPlayerNameInput = document.getElementById('join-player-name');
        const confirmJoinRoomBtn = document.getElementById('confirm-join-room-btn');

        // Chat Elements
        const chatSection = document.getElementById('chat-section');
        const chatMessages = document.getElementById('chat-messages');
        const chatInput = document.getElementById('chat-input');
        const sendChatBtn = document.getElementById('send-chat-btn');

        // Buttons
        const createRoomBtn = document.getElementById('create-room-btn');
        const searchRoomsBtn = document.getElementById('search-rooms-btn');
        const joinGameBtn = document.getElementById('join-game-btn');

        // Utility Functions
        const showElement = (element) => element.classList.remove('hidden');
        const hideElement = (element) => element.classList.add('hidden');
        const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);

        // Toast Notification
        const showToast = (message, type = 'success') => {
            toast.textContent = message;
            toast.className = '';
            toast.classList.add(type === 'error' ? 'error' : 'success');
            toast.classList.add('show');
            setTimeout(() => {
                toast.classList.remove('show');
            }, 3000);
        };

        // Modal Functions
        const showModal = () => {
            joinRoomModal.classList.add('show');
        };

        const hideModal = () => {
            joinRoomModal.classList.remove('show');
            // Clear modal inputs
            joinRoomPasswordInput.value = '';
            joinPlayerNameInput.value = '';
        };

        closeModalBtn.addEventListener('click', hideModal);

        // Initialize WebSocket Connection
        const initializeSocket = () => {
            hideElement(roomsListDiv);
            showElement(loadingSpinner);
            socket = new WebSocket(socketUrl);

            socket.onopen = () => {
                hideElement(loadingSpinner);
                console.log('Connected to the server.');
            };

            socket.onmessage = (event) => {
                const data = JSON.parse(event.data);
                handleServerMessage(data);
            };

            socket.onerror = (error) => {
                console.error('WebSocket error:', error);
                showToast('Connection error. Please try again.', 'error');
            };

            socket.onclose = () => {
                console.log('Connection closed.');
                showToast('Disconnected from the server.', 'error');
                hideElement(loadingSpinner);
            };
        };

        // Handle Incoming Server Messages
        const handleServerMessage = (data) => {
            switch (data.type) {
                case 'error':
                    showToast(data.message, 'error');
                    break;
                case 'room_created':
                    currentRoom = data.room;
                    hideElement(roomSelectionDiv);
                    showElement(joinGameDiv);
                    showToast(`Room "${currentRoom}" created. Join with your name.`, 'success');
                    break;
                case 'room_list':
                    displayRooms(data.rooms);
                    break;
                case 'joined_room':
                    currentRoom = data.room;
                    hideElement(roomSelectionDiv);
                    hideElement(joinGameDiv);
                    showElement(gameDiv);
                    showElement(statusDiv);
                    showElement(pointsDiv);
                    showChat(); // Show chat when joined a room
                    showToast(`Joined room "${currentRoom}". Waiting for players...`, 'success');
                    break;
                case 'update':
                    updateGame(data);
                    break;
                case 'chat':
                    displayChatMessage(data.message, data.sender);
                    break;
                case 'game_over':
                    handleGameOver(data);
                    break;
                default:
                    console.warn('Unknown message type:', data.type);
            }
        };

        // Display Available Rooms
        const displayRooms = (rooms) => {
            roomsListDiv.innerHTML = '';
            if (rooms.length === 0) {
                roomsListDiv.innerHTML = '<p>No available rooms.</p>';
            } else {
                rooms.forEach(room => {
                    const roomItem = document.createElement('div');
                    roomItem.classList.add('room-item');
                    roomItem.setAttribute('tabindex', '0');
                    roomItem.setAttribute('role', 'button');
                    roomItem.setAttribute('aria-pressed', 'false');
                    roomItem.innerHTML = `<span>${room.name}</span><span>${room.players}/2</span>`;
                    roomItem.addEventListener('click', () => initiateJoinRoom(room.name));
                    roomItem.addEventListener('keypress', (e) => {
                        if (e.key === 'Enter') initiateJoinRoom(room.name);
                    });
                    roomsListDiv.appendChild(roomItem);
                });
            }
            showElement(roomsListDiv);
            hideElement(loadingSpinner);
        };

        // Show Status Messages
        const showStatus = (message, type) => {
            statusDiv.innerText = message;
            statusDiv.style.color = type === 'error' ? 'var(--error-color)' : 'var(--success-color)';
            showElement(statusDiv);
            setTimeout(() => {
                statusDiv.innerText = '';
                hideElement(statusDiv);
            }, 5000);
        };

        // Create a New Room
        const createRoom = () => {
            const roomName = document.getElementById('room-name').value.trim();
            const roomPassword = document.getElementById('room-password').value;

            if (!roomName) {
                showToast("Please enter a room name.", 'error');
                return;
            }

            initializeSocket();

            socket.onopen = () => {
                socket.send(JSON.stringify({ type: 'create_room', name: roomName, password: roomPassword }));
            };
        };

        // Search for Available Rooms
        const searchRooms = () => {
            initializeSocket();

            socket.onopen = () => {
                socket.send(JSON.stringify({ type: 'list_rooms' }));
            };
        };

        // Initiate Joining a Selected Room
        const initiateJoinRoom = (roomName) => {
            selectedRoomNameSpan.textContent = roomName;
            showModal();
        };

        // Confirm Joining the Room from Modal
        const confirmJoinRoom = () => {
            const roomName = selectedRoomNameSpan.textContent;
            const roomPassword = joinRoomPasswordInput.value;
            const playerName = joinPlayerNameInput.value.trim();

            if (!playerName) {
                showToast("Name is required.", 'error');
                return;
            }

            initializeSocket();

            socket.onopen = () => {
                socket.send(JSON.stringify({ type: 'join_room', room: roomName, password: roomPassword, name: playerName }));
            };

            hideModal();
        };

        confirmJoinRoomBtn.addEventListener('click', confirmJoinRoom);

        // Join the Game
        const joinGame = () => {
            const playerName = document.getElementById('player-name').value.trim();
            if (!playerName) {
                showToast("Please enter your name.", 'error');
                return;
            }

            if (socket && socket.readyState === WebSocket.OPEN) {
                socket.send(JSON.stringify({ type: 'join', name: playerName }));
                hideElement(joinGameDiv);
                showElement(gameDiv);
                showElement(statusDiv);
                showElement(pointsDiv);
                showChat(); // Show chat when joining the game
                showElement(loadingSpinner); // Show spinner while waiting for game to start
            } else {
                showToast("Not connected to the server.", 'error');
            }
        };

        // Update Game Board
        const updateGame = (data) => {
            // Update board
            gameDiv.innerHTML = '';
            data.board.forEach((row, rowIndex) => {
                row.forEach((cell, colIndex) => {
                    const cellDiv = document.createElement('div');
                    cellDiv.classList.add('cell');
                    if (cell) {
                        cellDiv.classList.add(cell);
                    }
                    cellDiv.dataset.column = colIndex;
                    cellDiv.dataset.row = rowIndex;
                    cellDiv.addEventListener('click', () => makeMove(colIndex));

                    // Highlight if this is the last move
                    if (data.last_move && data.last_move.row === rowIndex && data.last_move.col === colIndex) {
                        cellDiv.classList.add('highlight');
                        lastMove = { row: rowIndex, col: colIndex };
                    }

                    gameDiv.appendChild(cellDiv);
                });
            });

            // Update current turn
            statusDiv.innerText = `Current Turn: ${capitalize(data.current_turn)}`;

            // Update player info
            pointsDiv.innerHTML = '';
            for (const [color, info] of Object.entries(data.players)) {
                if (!playerColor && socket.readyState === WebSocket.OPEN) {
                    // Assign player color based on first connected player
                    playerColor = color;
                }
                pointsDiv.innerHTML += `<p>${capitalize(color)} Player: ${info.name} | Points: ${info.points}</p>`;
            }

            // Hide loading spinner once game updates
            hideElement(loadingSpinner);
        };

        // Handle Game Over (Win or Draw)
        const handleGameOver = (data) => {
            if (data.result === 'win') {
                showToast(`${capitalize(data.winner)} player wins!`, 'success');
                showStatus(`${capitalize(data.winner)} player wins!`, 'success');
            } else if (data.result === 'draw') {
                showToast(`Game ended in a draw!`, 'success');
                showStatus(`Game ended in a draw!`, 'success');
            }
            // Optionally, you can prompt to start a new game or reset the UI
        };

        // Make a Move
        const makeMove = (column) => {
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                showToast("Not connected to the server.", 'error');
                return;
            }
            socket.send(JSON.stringify({ type: 'move', column }));
            showElement(loadingSpinner); // Show spinner while waiting for server response
        };

        // Display Chat Message
        const displayChatMessage = (message, sender) => {
            const messageDiv = document.createElement('div');
            messageDiv.classList.add('chat-message');
            if (sender === playerColor) {
                messageDiv.classList.add('self');
            }
            messageDiv.textContent = `${sender}: ${message}`;
            chatMessages.appendChild(messageDiv);
            chatMessages.scrollTop = chatMessages.scrollHeight;
        };

        // Send Chat Message
        const sendChatMessage = () => {
            const message = chatInput.value.trim();
            if (message && socket && socket.readyState === WebSocket.OPEN) {
                socket.send(JSON.stringify({ type: 'chat', message }));
                chatInput.value = '';
            }
        };

        // Show Chat Section when Game starts
        const showChat = () => {
            showElement(chatSection);
        };

        // Event Listeners for Chat
        sendChatBtn.addEventListener('click', sendChatMessage);
        chatInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                sendChatMessage();
            }
        });

        // Event Listeners for Buttons
        createRoomBtn.addEventListener('click', createRoom);
        searchRoomsBtn.addEventListener('click', searchRooms);
        joinGameBtn.addEventListener('click', joinGame);

        // Initialize Socket on Page Load if Auto-Connect is Desired
        window.onload = () => {
            // Optionally, you can auto-connect or leave it to user actions
        };
    </script>
</body>
</html>



server.py
import asyncio
import websockets
import json
from collections import defaultdict

# Game constants
GAME_ROWS = 6
GAME_COLS = 7

class Game:
    def __init__(self):
        self.reset_game()
        self.players = {}  # websocket: { 'name': str, 'color': 'red'/'blue', 'points': int }

    def reset_game(self):
        self.board = [[None for _ in range(GAME_COLS)] for _ in range(GAME_ROWS)]
        self.current_turn = 'red'  # red starts
        self.game_over = False
        self.last_move = {'row': None, 'col': None}

    def add_player(self, websocket, name):
        if len(self.players) >= 2:
            return False  # Game full
        color = 'red' if not any(p['color'] == 'red' for p in self.players.values()) else 'blue'
        self.players[websocket] = {'name': name, 'color': color, 'points': 0}
        return True

    def remove_player(self, websocket):
        if websocket in self.players:
            del self.players[websocket]

    def make_move(self, column, color):
        if self.game_over or color != self.current_turn:
            return False, "Not your turn or game over."

        # Place the piece in the column
        for row in reversed(range(GAME_ROWS)):
            if self.board[row][column] is None:
                self.board[row][column] = color
                self.last_move = {'row': row, 'col': column}
                if self.check_winner(row, column, color):
                    self.game_over = True
                    self.players_by_color(color)['points'] += 1
                    return True, "win"
                elif self.is_draw():
                    self.game_over = True
                    return True, "draw"
                self.current_turn = 'blue' if self.current_turn == 'red' else 'red'
                return True, "success"
        return False, "Column full."

    def check_winner(self, row, col, color):
        # Check horizontal, vertical, and both diagonals for a win
        directions = [
            [(0, 1), (0, -1)],    # Horizontal
            [(1, 0), (-1, 0)],    # Vertical
            [(1, 1), (-1, -1)],   # Diagonal \
            [(1, -1), (-1, 1)]    # Diagonal /
        ]
        for direction in directions:
            count = 1  # Including the last placed piece
            for dx, dy in direction:
                r, c = row, col
                while True:
                    r += dx
                    c += dy
                    if 0 <= r < GAME_ROWS and 0 <= c < GAME_COLS and self.board[r][c] == color:
                        count += 1
                    else:
                        break
            if count >= 4:
                return True
        return False

    def is_draw(self):
        for row in self.board:
            if None in row:
                return False
        return True

    def players_by_color(self, color):
        for player in self.players.values():
            if player['color'] == color:
                return player
        return None

    def to_json(self, message_type='update'):
        return json.dumps({
            'type': message_type,
            'board': self.board,
            'current_turn': self.current_turn,
            'last_move': self.last_move,
            'players': {p['color']: {'name': p['name'], 'points': p['points']} for p in self.players.values()}
        })

class Room:
    def __init__(self, name, password=''):
        self.name = name
        self.password = password
        self.game = Game()
        self.players = {}  # websocket: player_info

    def add_player(self, websocket, name, password):
        if self.password and self.password != password:
            return False, "Incorrect password."
        if len(self.players) >= 2:
            return False, "Room is full."
        color = 'red' if not any(p['color'] == 'red' for p in self.players.values()) else 'blue'
        self.players[websocket] = {'name': name, 'color': color, 'points': 0}
        self.game.players = self.players
        return True, color

    def remove_player(self, websocket):
        if websocket in self.players:
            del self.players[websocket]
            self.game.remove_player(websocket)

    def to_json(self, message_type='update'):
        return self.game.to_json(message_type=message_type)

rooms = defaultdict(lambda: None)  # room_name: Room

async def notify_all_players_room(room, message):
    if room.players:
        await asyncio.gather(*[ws.send(message) for ws in room.players])

async def notify_all_players(room, message_type='update'):
    if room.players:
        message = room.to_json(message_type=message_type)
        await asyncio.gather(*[ws.send(message) for ws in room.players])

async def handler(websocket, path):
    current_room = None

    try:
        async for message in websocket:
            data = json.loads(message)

            if data['type'] == 'create_room':
                room_name = data['name']
                room_password = data.get('password', '')
                if room_name in rooms and rooms[room_name]:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'Room already exists.'}))
                else:
                    room = Room(room_name, room_password)
                    rooms[room_name] = room
                    success = room.add_player(websocket, 'Host', room_password)
                    if success[0]:
                        current_room = room
                        await websocket.send(json.dumps({'type': 'room_created', 'room': room_name}))
                    else:
                        await websocket.send(json.dumps({'type': 'error', 'message': 'Failed to create room.'}))

            elif data['type'] == 'list_rooms':
                available_rooms = []
                for room in rooms.values():
                    if room and len(room.players) < 2:
                        available_rooms.append({'name': room.name, 'players': len(room.players)})
                await websocket.send(json.dumps({'type': 'room_list', 'rooms': available_rooms}))

            elif data['type'] == 'join_room':
                room_name = data['room']
                room_password = data.get('password', '')
                player_name = data.get('name', 'Player')

                if room_name not in rooms or not rooms[room_name]:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'Room does not exist.'}))
                else:
                    room = rooms[room_name]
                    success, info = room.add_player(websocket, player_name, room_password)
                    if not success:
                        await websocket.send(json.dumps({'type': 'error', 'message': info}))
                    else:
                        current_room = room
                        await websocket.send(json.dumps({'type': 'joined_room', 'room': room_name}))
                        # Notify all players in the room about the new player
                        await notify_all_players(room)

            elif data['type'] == 'move':
                if not current_room:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'No room joined.'}))
                    continue
                column = data['column']
                player = current_room.players.get(websocket)
                if not player:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'Player not found in room.'}))
                    continue
                color = player['color']
                success, status = current_room.game.make_move(column, color)
                if not success:
                    await websocket.send(json.dumps({'type': 'error', 'message': status}))
                    continue
                if status == "win":
                    # Notify all players about the win
                    await notify_all_players(current_room)
                    # Send a win message
                    win_message = json.dumps({'type': 'game_over', 'result': 'win', 'winner': color})
                    await notify_all_players(current_room, message=win_message)
                    # Reset the game after a short delay to allow clients to update
                    await asyncio.sleep(3)  # Optional: adjust as needed
                    current_room.game.reset_game()
                    await notify_all_players(current_room)
                elif status == "draw":
                    # Notify all players about the draw
                    await notify_all_players(current_room)
                    draw_message = json.dumps({'type': 'game_over', 'result': 'draw'})
                    await notify_all_players(current_room, message=draw_message)
                    # Reset the game after a short delay
                    await asyncio.sleep(3)
                    current_room.game.reset_game()
                    await notify_all_players(current_room)
                else:
                    # Notify all players about the successful move
                    await notify_all_players(current_room)

            elif data['type'] == 'chat':
                if not current_room:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'No room joined.'}))
                    continue
                sender = current_room.players.get(websocket)
                if not sender:
                    await websocket.send(json.dumps({'type': 'error', 'message': 'Player not found in room.'}))
                    continue
                chat_message = data.get('message', '').strip()
                if chat_message:
                    # Prepare the chat message to broadcast
                    chat_data = {
                        'type': 'chat',
                        'message': chat_message,
                        'sender': sender['name']  # Using player name as sender identifier
                    }
                    chat_json = json.dumps(chat_data)
                    await notify_all_players_room(current_room, chat_json)

    except websockets.exceptions.ConnectionClosed:
        pass
    finally:
        if current_room:
            current_room.remove_player(websocket)
            # Notify remaining players
            if current_room.players:
                await notify_all_players(current_room)
            else:
                # Remove the room if empty
                rooms.pop(current_room.name, None)

start_server = websockets.serve(handler, "0.0.0.0", 6789)

print("Server started on ws://0.0.0.0:6789")
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()