---
# System prepended metadata

title: Test

---




## game.html

```html=
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tic Tac Toe Game</title>
    <link rel="stylesheet" href="game.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js"
        integrity="sha512-zoJXRvW2gC8Z0Xo3lBbao5+AS3g6YWr5ztKqaicua11xHo+AvE1b0lT9ODgrHTmNUxeCw0Ry4BGRYZfXu70weg=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js"></script>
</head>

<body>
    <div class="game-container">
        <div class="players-info">
            <span id="player1-info"></span> vs. <span id="player2-info"></span>
        </div>
        <table id="game-board">
            <tr>
                <td data-row="0" data-col="0"></td>
                <td data-row="0" data-col="1"></td>
                <td data-row="0" data-col="2"></td>
            </tr>
            <tr>
                <td data-row="1" data-col="0"></td>
                <td data-row="1" data-col="1"></td>
                <td data-row="1" data-col="2"></td>
            </tr>
            <tr>
                <td data-row="2" data-col="0"></td>
                <td data-row="2" data-col="1"></td>
                <td data-row="2" data-col="2"></td>
            </tr>
        </table>
        <div id="game-info"></div>
    </div>
    <script src="game.js"></script>
</body>

</html>
```

## game.css
```css=
@import url('https://fonts.googleapis.com/css2?family=Rubik+Iso&family=Sedgwick+Ave+Display&display=swap');

body, html {
    margin: 0;
    padding: 0;
    border: none;
    outline: none;
    box-sizing: border-box;
    font-family: Arial, sans-serif;
}


.game-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100vh;
    background-color: #36393f;
    color: #ffffff;
}

.players-info {
    margin-bottom: 20px;
    font-family: 'Sedgwick Ave Display', cursive;
    font-size: 28px;
    background-color: #2f3136; 
    padding: 10px 20px;
    border-radius: 8px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); 
    color: #dcddde; 
}

#player1-info, #player2-info {
    font-weight: normal;
    padding: 0 10px;
}

#game-info {
    font-family: 'Sedgwick Ave Display', cursive;
    font-size: 18px;
    margin-top: 16px;
    background-color: #2F313B;
    padding: 15px 25px;
    border-radius: 6px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); 
    color: #dcddde;
}


#game-board td {
    width: 115px;
    height: 115px;
    border: 5px solid #40444b;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    background-color: #1f2025;
}

#game-board td:hover {
    background-color: #2d2e35;
    transition: all 0.4s;
}

#game-board td img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block; /* Remove extra space */
    cursor: not-allowed;
}


#game-board tr:first-child td {
    border-top: none;
}

#game-board tr:last-child td {
    border-bottom: none;
}

#game-board td:first-child {
    border-left: none;
}

#game-board td:last-child {
    border-right: none;
}

#game-controls {
    margin-top: 20px;
}

@keyframes epicAnimation {
    0% { transform: scale(1); opacity: 0.5; }
    50% { transform: scale(1.5); opacity: 1; }
    100% { transform: scale(1); opacity: 0.5; }
}

#game-info.epic {
    animation: epicAnimation;
    animation-delay: 0.7s;
    animation-duration: 6s;
    color: gold;
    font-size: 24px;
    font-weight: bold;
    text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}

```

## game.js
```js=
const socket = io.connect('http://localhost:3000');

const gameBoard = document.getElementById('game-board');
const player1Info = document.getElementById('player1-info');
const player2Info = document.getElementById('player2-info');
const gameInfo = document.getElementById('game-info');

let currentPlayer = 'X';
let gameStarted = false;
let playerSymbol;
let gameOver = false;

// Extract roomId from the URL
const urlParams = new URLSearchParams(window.location.search);
const roomId = urlParams.get('roomId');
socket.emit('check-room-existence', {
    roomName: roomId
});

let username = localStorage.getItem('username');
let clientId = localStorage.getItem('clientId');

if (username && clientId) {
    socket.emit('reconnect', {
        username: username,
        clientId: clientId,
        roomName: roomId
    });
}

if (username) {
    console.log(`Emitting join-room with username: ${username} and room: ${roomId}`);

    socket.emit('join-room', {
        roomName: roomId,
        username: username
    });

}


gameBoard.addEventListener('click', (e) => {
    if (gameOver) {
        alert("The game is over!");
        return;
    }

    if (!gameStarted) {
        alert("The game hasn't started yet!");
        return;
    }

    if (currentPlayer !== playerSymbol) {
        alert("It's not your turn!");
        return;
    }

    const cell = e.target;
    if (cell.tagName === 'TD' && !cell.firstChild) {
        // Don't create and append the img here. Wait for the server's confirmation.
        socket.emit('make-move', {
            row: parseInt(cell.dataset.row),
            col: parseInt(cell.dataset.col),
            symbol: currentPlayer,
            roomName: roomId
        });
    } else {
        alert("X or O already in this cell...")
    }
});



// LISTENERS
socket.on('game-started', () => {
    gameStarted = true;
});

socket.on('player-symbol', (data) => {
    playerSymbol = data.symbol;
});

socket.on('room-existence-response', (data) => {
    if (data.exists) {
        player1Info.textContent = data.player1;
    } else {
        alert("The room you're trying to join doesn't exist!");
        window.location.href = 'index.html';
    }
});

socket.on('receive-move', (data) => {
    const cell = gameBoard.querySelector(`[data-row="${data.row}"][data-col="${data.col}"]`);
    const img = document.createElement('img');
    img.src = `images/${data.symbol.toLowerCase()}.png`;
    cell.appendChild(img);
    currentPlayer = data.nextPlayer;
    updateTurnInfo();
});

socket.on('players-ready', (data) => {
    console.log('Received players-ready event:', data);
    player1Info.textContent = data.player1 || '';
    player2Info.textContent = data.player2 || '';
    updateTurnInfo();
});

socket.on('disconnect', (reason) => {
    console.log('Socket disconnected due to:', reason);
});

socket.on('connect_error', (error) => {
    console.log('Socket connection error:', error);
});

socket.on('room-error', (msg) => {
    alert("Room error: " + msg);
});

socket.on('game-over', (result) => {
    gameOver = true;
    if (result === 'DRAW') {
        gameInfo.textContent = "DRAW!";
    } else {
        gameInfo.textContent = `${result === 'X' ? player1Info.textContent.trim() : player2Info.textContent.trim()} WINS!!!`.toUpperCase();
    }
    partyTime()
    gameInfo.classList.add('epic');
});

// functions
function updateTurnInfo() {
    if (!player2Info.textContent.trim()) { // Check if player2's name is not set
        gameInfo.textContent = "Waiting for second player";
    } else if (currentPlayer === 'X') {
        gameInfo.textContent = `${player1Info.textContent.trim()}'s turn (X)`;
    } else {
        gameInfo.textContent = `${player2Info.textContent.trim()}'s turn (O)`;
    }
}

function partyTime() {
    confetti({
        spread: 180
    });

    // do this for 5 seconds
    var duration = 5 * 1000;
    var end = Date.now() + duration;

    (function frame() {
        // launch a few confetti from the left edge
        confetti({
            particleCount: 7,
            angle: 60,
            spread: 55,
            origin: {
                x: 0
            }
        });
        // and launch a few from the right edge
        confetti({
            particleCount: 7,
            angle: 120,
            spread: 55,
            origin: {
                x: 1
            }
        });

        // keep going until we are out of time
        if (Date.now() < end) {
            requestAnimationFrame(frame);
        }
    }());
}
```




## Celi script.js (za index.html)
```js=
// document.addEventListener('DOMContentLoaded', function() {
//     const lobbiesList = document.getElementById('lobbies-list');
//     const sampleRooms = [
//         { id: 'room-12345', name: "Alice's Room", status: 'IN_PROGRESS' },
//         { id: 'room-67890', name: "Bob's Room", status: 'LOOKING_FOR_PLAYER' },
//         { id: 'room-11223', name: "Charlie's Room", status: 'IN_PROGRESS' },
//     ];

//     sampleRooms.forEach(room => {
//         const a = document.createElement('a');
//         a.href = `game.html?roomId=${room.id}`;
//         a.textContent = room.name + (room.status === 'IN_PROGRESS' ? ' (IN PROGRESS)' : ' (LOOKING FOR PLAYER)');
//         lobbiesList.appendChild(a);
//     });
// });

const socket = io.connect('http://localhost:3000');

const usernameInput = document.getElementById('username-input');
const setUsernameBtn = document.getElementById('set-username-btn');
const createLobbyBtn = document.getElementById('create-lobby-btn');
const lobbiesList = document.getElementById('lobbies-list');
const usernameStatus = document.getElementById('username-status');

let username = localStorage.getItem('username');
let clientId = localStorage.getItem('clientId');

document.addEventListener('DOMContentLoaded', () => {
    if (username && clientId) {
        setUsernameStatus(true, username);
        usernameInput.value = username;
        socket.emit('reclaim-username', { username, clientId });
    } else {
        setUsernameStatus(false, null);
    }
    socket.emit('get-rooms');
});

// Button click events
setUsernameBtn.addEventListener('click', () => {
    const newUsername = usernameInput.value.trim();

    let canChange = true;
    if (username) {
        canChange = confirm(`Are you sure you want to change your existing username "${username}"?`);
    }

    if (!canChange) {
        return;
    }

    if (newUsername == username) {
        alert("You are trying to change your username to the same name... can't do that");
        return;
    }

    if (newUsername.length < 3) {
        alert("Username must be at least 3 characters long");
        return;
    }

    if (newUsername.length > 7) {
        alert("Username must be max 7 characters long");
        return;
    }

    const existingClientId = localStorage.getItem('clientId') || null;

    socket.emit('set-username', { username: newUsername, clientId: existingClientId });
});

createLobbyBtn.addEventListener('click', () => {
    checkIfSocketConnected(socket);
    if (username) {
        // Prompt the user to select an image
        const imageInput = document.createElement('input');
        imageInput.type = 'file';
        imageInput.accept = 'image/*';
        imageInput.onchange = function() {
            // Upload the image
            const formData = new FormData();
            formData.append('lobbyImage', imageInput.files[0]);

            fetch('http://localhost:3000/upload', {
                method: 'POST',
                body: formData
            })
            .then(response => {
                if (!response.ok) {
                    return response.json().then(data => {
                        throw new Error(data.error);
                    });
                }
                return response.json();
            })
            .then(data => {
                socket.emit('create-room', {
                    username: username,
                    imageUrl: data.imageUrl
                });
            })
            .catch(error => {
                console.error('Error uploading image:', error);
                alert(error.message); // Display the error message to the user
            });
        };
        imageInput.click(); // This will open the file dialog
    } else {
        alert("Please set a username first.");
    }
});

// Extra...
toggleSpinner(false); // na zacetku izklopljen
// pocakamo 0.5 sekund...
let canShowSpinner = false;
setTimeout(() => {
    canShowSpinner = true;
}, 500);

//// SOCKET LISTENERS
socket.on('connect', () => {
    console.log('Socket connected!');    
    toggleSpinner(false);
});

socket.on('disconnect', (reason) => {
    console.log('Socket disconnected due to:', reason);    
    toggleSpinner(true);
});

socket.on('reconnect', (attemptNumber) => {
    console.log('Socket reconnected on attempt:', attemptNumber);
    toggleSpinner(false);
});

socket.on('room-error', (msg) => {
    alert("Room error: " + msg);
});

socket.on('username-status', (data) => {
    if (data.isUnique) {
        username = data.username; // Use the username sent from the server
        localStorage.setItem('username', username);
        
        // Store the clientId in localStorage
        localStorage.setItem('clientId', data.clientId);
        
        setUsernameStatus(true, username);
        enableCreateLobbyBtn();
    } else {
        alert(`Username ${username} is already in use or not available!`);
    }
});



socket.on('update-rooms', (rooms) => {
    lobbiesList.innerHTML = '';
    rooms.forEach(room => {
        const a = document.createElement('a');
        a.href = `game.html?roomId=${room.id}`;
        a.textContent = room.player1 + "'s Room" + (room.status === 'IN_PROGRESS' ? ' (IN PROGRESS)' : ' (LOOKING FOR PLAYER)');
        
        // Add the image next to the lobby name
        const img = document.createElement('img');
        img.src = "http://localhost:3000" + room.imageUrl;
        img.alt = "[Cant load room pic]";
        img.className = "lobbyPic"
        img.width = 42;
        a.prepend(img);

        // Add trash icon if the user is the owner of the lobby
        if (room.player1 === username) {
            const trashWrapper = document.createElement('div');
            trashWrapper.className = "trash-wrapper";
        
            const trashIcon = document.createElement('i');
            trashIcon.className = "fa fa-trash";
            trashIcon.style.color = "red";
            trashIcon.addEventListener('click', (e) => {
                e.preventDefault();
                socket.emit('delete-lobby', room.id);
            });
        
            trashWrapper.appendChild(trashIcon);
            a.appendChild(trashWrapper);
        }

        lobbiesList.appendChild(a);
    });
});


//// FUNCTIONS
function enableCreateLobbyBtn() {
    createLobbyBtn.classList.add('enabled');
    createLobbyBtn.disabled = false;
}

function setUsernameStatus(isSet, currentUsername) {
    usernameStatus.textContent = isSet ? `Username set (${currentUsername})` : "Username not set";
    usernameStatus.style.color = isSet ? "green" : "red";

    // Set the tooltip for the createLobbyBtn
    createLobbyBtn.setAttribute('title', isSet ? "Be sure to have a lobby pic ready" : "Please set a username first");
}

function checkIfSocketConnected(socket, customAlert = "") {
    let connected = socket.connected;
    if (!connected) {
        if (customAlert) {
            alert(customAlert)
        } else {
            alert("Connection not established / Connection lost with server's socket");
        }

    }
    return connected;
}

function toggleSpinner(toggleON) {
    if(toggleON) {
        document.querySelector('.spinner-container').style.display = 'flex';
        document.querySelector('.main-container').style.display = 'none';
    } else {
        document.querySelector('.spinner-container').style.display = 'none';
        document.querySelector('.main-container').style.display = 'flex';
    }
}
```



## HTML za nas HOME page (index.html)

```html=
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Lobbies</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" media="screen" href="style.css" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA=="
        crossorigin="anonymous" referrerpolicy="no-referrer" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js" integrity="sha512-zoJXRvW2gC8Z0Xo3lBbao5+AS3g6YWr5ztKqaicua11xHo+AvE1b0lT9ODgrHTmNUxeCw0Ry4BGRYZfXu70weg=="
        crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
    <div class="spinner-container">
        <h2>Connecting to server</h2>
        <div class="spinner"></div>
    </div>
    <div class="main-container">
        <div class="logo-container">
            <img src="images/logo.png" alt="Manjka slika" id="site-logo">
        </div>
        <div class="username-container">
            <div id="username-status" class="username-status"></div>
            <div class="username-box">
                <input type="text" id="username-input" placeholder="Enter username" autocomplete="off">
                <button id="set-username-btn">SET</button>
            </div>
            <button id="create-lobby-btn">CREATE LOBBY</button>
            <h4 id="lobbiesListh4">Lobbies List</h4>
            <div id="lobbies-list"></div>
        </div>
    </div>


    <script src="script.js"></script>
</body>

</html>
```



## CSS za lobbies index.html (poimenuj stil.css)
```css=
body {
    font-family: Arial, sans-serif;
    background-color: #36393f;
    margin: 0;
    padding: 0;
    color: #ffffff;
}

.spinner-container {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background-color: rgba(0, 0, 0, 0.7); 
    z-index: 1000; 
    height: 100vh;
}

.spinner {
    border: 16px solid #f3f3f3; 
    border-top: 16px solid #3498db; 
    border-radius: 50%;
    width: 120px;
    height: 120px;
    animation: spin 2s linear infinite;
}

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

.main-container {
     /* display: none; hide it initially */
}


.main-container {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100vh; /*full height of the viewport*/
}

.fa-trash {
    margin-left: 10px;
    transition: all 0.3s;
    border: 5px solid transparent; 
}

.fa-trash:hover {
    background-color: rgba(255, 5, 5, 0.219); 
    border-color: rgba(255, 5, 5, 0.219); 
    border-radius: 5px; 
}


.trash-wrapper {
    display: inline-block;
}


.trash-wrapper:hover .fa-trash {
    cursor: default !important; 
}

/* Override the <a> tag's cursor when hovering over the trash icon */
a:hover .trash-wrapper:hover {
    cursor: default;
}

.logo-container {
    position: absolute;
    top: 60px;
    text-align: center;
}

#site-logo {
    width: 260px;
    height: auto;
}

.username-container {
    text-align: center;
    background-color: #2f3136;
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}

.username-status {
    font-weight: bold;
    font-size: 14px;
    margin-bottom: 10px;
}

.username-box {
    display: inline-flex;
    align-items: center;
    margin-bottom: 10px;
}

#username-input {
    border: 2px solid #202225;
    padding: 10px;
    border-radius: 20px 0 0 20px;
    outline: none;
    width: 200px;
    background-color: #40444b;
    color: #ffffff;
}

#set-username-btn {
    border: 2px solid #202225;
    padding: 10px;
    border-radius: 0 20px 20px 0;
    background-color: #40444b;
    transition: background-color 0.3s;
    color: #ffffff;
}

#set-username-btn:hover {
    background-color: #50555b;
}

#set-username-btn:active {
    background-color: #30333b;
}

#create-lobby-btn {
    display: block;
    margin: 20px auto;
    padding: 15px 40px;
    border: none;
    border-radius: 30px;
    background-color: #202225;
    cursor: not-allowed;
    transition: background-color 0.3s;
    color: #ffffff;
    opacity: 0.4;
}

#create-lobby-btn.enabled {
    background-color: #7289da;
    cursor: pointer;
    opacity: 1;
}

#create-lobby-btn.enabled:hover {
    background-color: #5b6eae;
}

#lobbies-list {
    margin-top: 20px;
    max-height: 300px;
    overflow-y: scroll;
    overflow: auto;
    border: 1px solid #202225;
    border-radius: 10px;
    padding: 10px;
    background-color: #40444b;
}

a {
    text-decoration: none;
    color: #7289da;
    display: block;
    margin: 5px 0;
    padding: 5px;
    border-radius: 5px;
    transition: background-color 0.3s;
}

a:hover {
    background-color: #50555b;
}

.lobbyPic {
    border-radius: 50%;
    transition: border-radius 0.3s;
    vertical-align: middle;
    margin-right: 8px;
}

a:hover .lobbyPic {
    border-radius: 0%;
}

```





# SERVER CODE (server.js)
### run with `npm start` (google what npm is) (before start ... do `npm i`)
```js
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');
var createError = require('http-errors');
const app = express();
const server = http.createServer(app);
const multer = require('multer');
const rateLimit = require("express-rate-limit");
const fs = require('fs');
const {
    unlink
} = require('fs');
const path = require('path');
app.use(express.static(path.join(__dirname, 'public')));
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));

const uploadsDir = path.join(__dirname, 'uploads');

if (!fs.existsSync(uploadsDir)) {
    fs.mkdirSync(uploadsDir);
}

app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

const bodyParser = require('body-parser');
app.use(bodyParser.json());


app.use(cors({
    origin: "*",
    credentials: true
}));



// storage related
// Setup multer storage
const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        cb(null, './uploads/')
    },
    filename: function (req, file, cb) {
        cb(null, Date.now() + '-' + file.originalname)
    }
});

const upload = multer({
    storage: storage,
    limits: {
        fileSize: 5 * 1024 * 1024 // 5MB
    }
});

// Rate limiting middleware
const uploadLimiter = rateLimit({
    windowMs: 5 * 1000, // 5 seconds
    max: 1, // 1 request per windowMs
    handler: function (req, res) {
        res.status(429).json({
            error: "Too many images uploaded in the span of 5 seconds, please try again later."
        });
    }
});


app.use('/upload', uploadLimiter);

// Endpoint to handle image uploads
app.post('/upload', upload.single('lobbyImage'), (req, res, next) => {
    if (req.file) {
        res.json({
            imageUrl: '/uploads/' + req.file.filename
        });
    } else {
        next(new Error('Error uploading image.'));
    }
});

app.get('/works', (req, res) => {
    res.send("Works.")
})

// Error handling middleware
app.use((err, req, res, next) => {
    if (err instanceof multer.MulterError) {
        if (err.code === 'LIMIT_FILE_SIZE') {
            return res.status(400).json({
                error: 'Image size is too large. Please upload an image smaller than 5MB.'
            });
        }
        return res.status(400).json({
            error: 'Error uploading image.'
        });
    }
    // Handle other errors here if needed
    res.status(500).json({
        error: 'Internal Server Error.'
    });
});




///////////////
function setNewUsername(socket, newUsername, clientId) {
    // Only delete the old username if it's different from the new one
    if (socket.username && socket.username !== newUsername) {
        usernames.delete(socket.username);
    }

    usernames.add(newUsername);
    socket.username = newUsername; // Store the new username on the socket

    if (!clientId) {
        clientId = generateUUID();
    }

    userMappings[newUsername] = { id: clientId, timestamp: Date.now() };
    socket.emit('username-status', { isUnique: true, clientId, username: newUsername });
}



function generateUUID() {
    // Simple UUID generator
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = Math.random() * 16 | 0,
            v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

function checkWinner(board) {
    // Check rows, columns, and diagonals for a winner
    for (let i = 0; i < 3; i++) {
        if (board[i][0] && board[i][0] === board[i][1] && board[i][0] === board[i][2]) return board[i][0];
        if (board[0][i] && board[0][i] === board[1][i] && board[0][i] === board[2][i]) return board[0][i];
    }
    if (board[0][0] && board[0][0] === board[1][1] && board[0][0] === board[2][2]) return board[0][0];
    if (board[0][2] && board[0][2] === board[1][1] && board[0][2] === board[2][0]) return board[0][2];
    return null;
}

function getRooms() {
    return Object.entries(gameStates).map(([name, state]) => ({
        id: name,
        player1: state.player1,
        name: state.player1 + "'s Room",
        status: state.player2 ? 'IN_PROGRESS' : 'LOOKING_FOR_PLAYER',
        imageUrl: state.imageUrl
    }));
}



/// SOCKET RELATED

const io = socketIo(server, {
    cors: {
        origin: "*",
        methods: ["GET", "POST"],
        credentials: true
    }
});

const gameStates = {};
const usernames = new Set();
const userRooms = {};
const userMappings = {}; // { username: { id: 'some-uuid', timestamp: Date.now() } }


io.on('connection', (socket) => {
    console.log('A user connected');

    socket.on('get-rooms', () => {
        let rooms = getRooms();
        socket.emit('update-rooms', rooms);
    });


    socket.on('set-username', (data) => {
        const newUsername = data.username;
        const clientId = data.clientId;

        if (usernames.has(newUsername)) {
            // If the username is already in use, check if the clientId matches
            const existingUser = userMappings[newUsername];
            if (existingUser && existingUser.id === clientId && Date.now() - existingUser.timestamp <= 30000) {
                // If the clientId matches and the timestamp is within 30 seconds, allow reclaiming the username
                setNewUsername(socket, newUsername, clientId);
            } else {
                socket.emit('username-status', { isUnique: false });
            }
        } else {
            setNewUsername(socket, newUsername, clientId);
        }
    });

    socket.on('reconnect', (data) => {
        const { username, clientId, roomName } = data;
        const userMapping = userMappings[username];
        if (userMapping && userMapping.id === clientId) {
            const gameState = gameStates[roomName];
            if (gameState) {
                if (gameState.player1 === username) {
                    gameState.player1Connected = true;  // Set the connection flag for player1
                    socket.join(roomName);
                    console.log(`${username} has rejoined room ${roomName}`);
                    io.to(roomName).emit('players-ready', {
                        roomName,
                        player1: gameState.player1Connected ? gameState.player1 : null,
                        player2: gameState.player2Connected ? gameState.player2 : null,
                        currentPlayer: gameState.currentPlayer,
                    });
                    socket.emit('player-symbol', { symbol: 'X' });
                } else if (gameState.player2 === username) {
                    gameState.player2Connected = true;  // Set the connection flag for player2
                    socket.join(roomName);
                    console.log(`${username} has rejoined room ${roomName}`);
                    io.to(roomName).emit('players-ready', {
                        roomName,
                        player1: gameState.player1Connected ? gameState.player1 : null,
                        player2: gameState.player2Connected ? gameState.player2 : null,
                        currentPlayer: gameState.currentPlayer,
                    });
                    socket.emit('player-symbol', { symbol: 'O' });
                }
            }
        }
    });


    socket.on('reclaim-username', (data) => {
        const usernameToReclaim = data.username;
        const clientId = data.clientId;

        const userMapping = userMappings[usernameToReclaim];
        if (userMapping && userMapping.id === clientId) {
            setNewUsername(socket, usernameToReclaim, clientId);
        } else if (!usernames.has(usernameToReclaim)) {
            // If the username is not in use, set it as a new username
            setNewUsername(socket, usernameToReclaim, clientId);
        } else {
            socket.emit('username-status', { isUnique: false });
        }
    });



    socket.on('create-room', (data) => {
        const username = data.username;
        const imageUrl = data.imageUrl;

        if (!socket.username) {
            socket.emit('room-error', 'Please set a username first.');
            return;
        }

        // Check if user already has a room
        if (userRooms[socket.username]) {
            console.log(`User ${socket.username} already has an active lobby: ${userRooms[socket.username]}`);
            socket.emit('room-error', 'You already have an active lobby.');
            return;
        }

        const roomName = 'room-' + Math.random().toString(36).substr(2, 9);
        console.log(`Lobby ${roomName} created by ${socket.username}`);
        socket.join(roomName);
        console.log(`${username} has joined room ${roomName}`);
        const clientsInRoom = io.sockets.adapter.rooms.get(roomName);
        console.log(`Clients in room ${roomName}:`, [...clientsInRoom]);
        gameStates[roomName] = {
            player1: username,
            player1Connected: true,
            player2: null,
            player2Connected: false,
            board: Array(3).fill(null).map(() => Array(3).fill(null)),
            currentPlayer: 'X',
            imageUrl: imageUrl,
            gameInProgress: false
        };

        userRooms[socket.username] = roomName; // Store the room against the user's name
        io.emit('update-rooms', getRooms());
    });

    socket.on('delete-lobby', (roomName) => {
        if (!gameStates[roomName]) {
            socket.emit('room-error', 'The lobby you are trying to delete does not exist.');
            return;
        }

        if (gameStates[roomName].player1 !== socket.username) {
            socket.emit('room-error', 'You are not the owner of this lobby.');
            return;
        }

        delete gameStates[roomName];
        delete userRooms[socket.username]; // Ensure the user's lobby is deleted from userRooms
        console.log(`Lobby ${roomName} deleted by ${socket.username}`);
        io.emit('update-rooms', getRooms());
    });


    socket.on('join-room', (data) => {
        const roomName = data.roomName;
        const username = data.username;
        const gameState = gameStates[roomName];

        if (gameState && gameState.gameInProgress) {
            socket.emit('room-error', 'Game is already in progress.');
            return;
        }

        // Ensure the client joins the room
        socket.join(roomName);

        console.log(`Attempting to join room: ${roomName} with username: ${username}`);

        if (gameState) {
            console.log(`Current state of room ${roomName}:`, gameState);

            if (username !== gameState.player1 && !gameState.player2) {
                console.log(`Setting player2 as ${username}`);

                gameState.player2 = username;
                gameState.gameStarted = true;
                io.to(roomName).emit('game-started');
                io.to(roomName).emit('players-ready', {
                    roomName,
                    player1: gameState.player1,
                    player2: gameState.player2,
                    currentPlayer: gameState.currentPlayer,
                });
                socket.emit('player-symbol', { symbol: 'O' });
            } else if (username === gameState.player1 && !gameState.player2) {
                console.log(`Player1 ${username} rejoining the room.`);

                io.to(roomName).emit('players-ready', {
                    roomName,
                    player1: gameState.player1,
                    player2: gameState.player2,
                    currentPlayer: gameState.currentPlayer,
                });
                console.log(`Emitted players-ready event to room ${roomName}`);
                socket.emit('player-symbol', { symbol: 'X' });
            }
        } else {
            console.log(`Room ${roomName} does not exist.`);
        }
    });


    socket.on('check-room-existence', (data) => {
        const roomName = data.roomName;
        const gameState = gameStates[roomName];
        if (gameState) {
            socket.emit('room-existence-response', { exists: true, player1: gameState.player1 });
        } else {
            socket.emit('room-existence-response', { exists: false });
        }
    });

    socket.on('start-game', async (data) => {
        const roomName = data.roomName;
        const gameState = gameStates[roomName];
        if (gameState) {
            gameState.gameInProgress = true;
            io.to(roomName).emit('game-started');
        }
    });


    socket.on('make-move', (move) => {
        const gameState = gameStates[move.roomName];
        if (gameState && gameState.gameStarted && gameState.currentPlayer === move.symbol) {
            gameState.board[move.row][move.col] = move.symbol;
            gameState.currentPlayer = move.symbol === 'X' ? 'O' : 'X';
            io.to(move.roomName).emit('receive-move', {
                row: move.row,
                col: move.col,
                symbol: move.symbol,
                nextPlayer: gameState.currentPlayer,
            });

            const winner = checkWinner(gameState.board);
            let wasOver = false;
            if (winner) {
                io.to(move.roomName).emit('game-over', winner);
                wasOver = true;
            } else if (gameState.board.every(row => row.every(cell => cell))) {
                io.to(move.roomName).emit('game-over', 'DRAW');
                wasOver = true;
            }
            if (wasOver) {
                delete userRooms[gameState.player1];
                if (gameState.player2) {
                    delete userRooms[gameState.player2];
                }
                delete gameStates[move.roomName];
                io.emit('update-rooms', getRooms());
            }
        }
    });


    socket.on('end-lobby', (roomName) => {
        const gameState = gameStates[roomName];
        if (gameState) {
            // Delete the image associated with the room
            const imagePath = path.join(uploadsDir, path.basename(gameState.imageUrl));
            unlink(imagePath, (err) => {
                if (err) {
                    console.error('Error deleting the image:', err);
                } else {
                    console.log('Image deleted successfully');
                }
            });

            delete userRooms[gameState.player1];
            delete gameStates[roomName];
            console.log(`Lobby ${roomName} deleted by ${socket.username}`);
            io.emit('update-rooms', getRooms());
            socket.leave(roomName);
        }
    });

    socket.on('rematch', (roomName) => {
        const gameState = gameStates[roomName];
        if (gameState) {
            gameState.board = Array(3).fill(null).map(() => Array(3).fill(null));
            gameState.currentPlayer = 'X';
            io.to(roomName).emit('game-reset');
        }
    });

    socket.on('disconnect', () => {
        console.log('A user disconnected');
        const roomName = userRooms[socket.username];
        const gameState = gameStates[roomName];
        if (gameState) {
            if (gameState.player1 === socket.username) {
                gameState.player1Connected = false;
            } else if (gameState.player2 === socket.username) {
                gameState.player2Connected = false;
            }
        }
        if (socket.username) {
            const userMapping = userMappings[socket.username];
            if (userMapping) {
                userMapping.timestamp = Date.now();
            }
        }
    });


});
////////////////







/// DB RELATED

// const {
//     Pool
// } = require('pg');

// const pool = new Pool({
//     host: 'db', // Use service name defined in docker-compose.yml
//     port: 5432,
//     user: 'root',
//     password: 'rootpass',
//     database: 'tictactoe',
// });


// Example query
// pool.query('SELECT NOW()', (err, res) => {
//    console.log(err, res);
// });


/// REST

// POST endpoint to add game history
// app.post('/add-game-history', (req, res) => {
//     // Example request body
//     const {
//         krogec,
//         krizec,
//         result,
//         koenc
//     } = req.body;

//     const query = 'INSERT INTO game_history (krogec, krizec, result, koenc) VALUES ($1, $2, $3, $4)';
//     const values = [krogec, krizec, result, koenc];

//     pool.query(query, values, (err) => {
//         if (err) {
//             console.error(err);
//             res.status(500).send('Error inserting data');
//         } else {
//             res.status(200).send('Data inserted successfully');
//         }
//     });
// });




// const createTableQuery = `
//   CREATE TABLE IF NOT EXISTS game_history (
//     id SERIAL PRIMARY KEY,
//     krogec VARCHAR(255),
//     krizec VARCHAR(255),
//     result VARCHAR(255),
//     koenc TIMESTAMP
//   );
// `;

// pool.query(createTableQuery, (err) => {
//     if (err) {
//         console.error('Error creating table:', err);
//     } else {
//         console.log('Table game_history is ready');
//     }
// });






// catch 404 and forward to error handler
app.use(function (req, res, next) {
    next(createError(404));
});


server.listen(3000, () => {
     console.log('Server running on http://localhost:3000');
});

```
