Code viewer for World: Tic Tac Toe by Zohaib Ali
// Cloned by Zohaib Ali on 30 Jul 2024 from World "One Cube World (Three.js)" by Starter user 
// Please leave this clone trail here.

// Variables
const boardSize = 3;// Size of the Tic Tac Toe board (3x3)
let board = Array(boardSize).fill().map(() => Array(boardSize).fill(''));
let currentPlayer = 'X';
const cellSize = 100;
const spacing = 10;
let cells = []; // To store cell meshes for reference
let gameActive = true; // Track if the game is still active
let storage = sessionStorage; // Stores the win/tie count  across sessions
// Create the board
function createBoard3D() {
    // Calculate starting positions to center the grid on the screen
    const startX = -((boardSize - 1) * (cellSize + spacing)) / 2;
    const startY = -((boardSize - 1) * (cellSize + spacing)) / 2;
    
    // Loop through the grid and create each cell
    for (let i = 0; i < boardSize; i++) {
        cells[i] = [];
        for (let j = 0; j < boardSize; j++) {
            const cellGeometry = new THREE.BoxGeometry(cellSize, cellSize, 10);
            const cellMaterial = new THREE.MeshBasicMaterial({ color: 'white' });
            const cellMesh = new THREE.Mesh(cellGeometry, cellMaterial);

            // Position each cell in a grid
            cellMesh.position.x = startX + i * (cellSize + spacing);
            cellMesh.position.y = startY + j * (cellSize + spacing);
            cellMesh.position.z = 0;

            // Store row and column in userData
            cellMesh.userData = { row: i, col: j };

            // Store the cell for future reference
            cells[i][j] = cellMesh;

            // Add the cell to the scene
            ABWorld.scene.add(cellMesh);
        }
    }
}

// Handle mouse click in the 3D space
function handleClick(event) {
    if (!gameActive) return; // Ignore clicks if the game is not active

    event.preventDefault();

    // Calculate mouse position in normalized device coordinates (-1 to +1)
    const mouse = new THREE.Vector2();
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    // Raycasting to determine which cell was clicked
    const raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(mouse, ABWorld.camera);
    const intersects = raycaster.intersectObjects(ABWorld.scene.children); // Check which objects are intersected by the ray

    if (intersects.length > 0) {
        const intersectedCell = intersects[0].object;
        const row = intersectedCell.userData.row;
        const col = intersectedCell.userData.col;

        // Check if the cell is already occupied
        if (board[row][col] === '') {
            // Update the board state
            board[row][col] = currentPlayer;

            // Change cell color based on player and add X or O
            if (currentPlayer === 'X') {
                intersectedCell.material.color.set('blue');
                createTextMesh('X', intersectedCell.position); // Place X on the cell
            } else {
                intersectedCell.material.color.set('red');
                createTextMesh('O', intersectedCell.position); // Place O on the cell
            }

            // Check if the move resulted in a win
            if (checkWin(currentPlayer)) {
                const winner = currentPlayer === 'X' ? 'Blue' : 'Red';
                storage.setItem(winner, parseInt((storage.getItem(winner) == null ? 0 : storage.getItem(winner))) + 1)
                showWinMessage(`${winner} wins!`);
                gameActive = false; // End the game
                setTimeout(resetGame, 2000); // Automatically reset the game after 2 seconds to allow win msg to be seen
                return;
            } else if (checkTie()) {
                showWinMessage("It's a tie!");
                storage.setItem("Tie", parseInt((storage.getItem("Tie") == null ? 0 : storage.getItem("Tie"))) + 1)
                gameActive = false; // End the game
                setTimeout(resetGame, 2000); // Automatically reset the game after 2 seconds
                return;
            }

            // Switch to the other player
            currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
        }
    }
}


// Check for a winning condition
function checkWin(player) {
    // Check rows, columns, and diagonals for a win
    for (let i = 0; i < boardSize; i++) {
        // Check rows
        if (board[i].every(cell => cell === player)) return true;
        // Check columns
        if (board.map(row => row[i]).every(cell => cell === player)) return true;
    }
    // Check diagonals
    if (board.map((row, idx) => row[idx]).every(cell => cell === player)) return true;
    if (board.map((row, idx) => row[boardSize - 1 - idx]).every(cell => cell === player)) return true;

    return false; // No win detected
}

// Check for a tie
function checkTie() {
    return board.flat().every(cell => cell !== '');
}

// Create 3D text ("X" or "O") above the cell
function createTextMesh(text, position) {
    const loader = new THREE.FontLoader();
    // font to create text mesh
    loader.load('https://threejs.org/examples/fonts/helvetiker_regular.typeface.json', function(font) {
        const textGeometry = new THREE.TextGeometry(text, {
            font: font,
            size: cellSize / 2,
            height: 10,
            curveSegments: 12,
        });
        const textMaterial = new THREE.MeshBasicMaterial({ color: 'white' });
        const textMesh = new THREE.Mesh(textGeometry, textMaterial);

        // Center the text
        textGeometry.computeBoundingBox();
        const textWidth = textGeometry.boundingBox.max.x - textGeometry.boundingBox.min.x;
        const textHeight = textGeometry.boundingBox.max.y - textGeometry.boundingBox.min.y;

        textMesh.position.x = position.x - textWidth / 2;
        textMesh.position.y = position.y - textHeight / 2;
        textMesh.position.z = position.z + cellSize / 2 + 2; // Slightly above the cell

        ABWorld.scene.add(textMesh);
    });
}

// Create the UI for displaying the win message
function createUI() {
    const messageDiv = document.createElement('div');
    messageDiv.id = 'winMessage';
    messageDiv.style.position = 'absolute';
    messageDiv.style.top = '20px';
    messageDiv.style.left = '50%';
    messageDiv.style.transform = 'translateX(-50%)';
    messageDiv.style.color = 'white';
    messageDiv.style.fontSize = '24px';
    messageDiv.style.display = 'none'; // Hidden by default
    document.body.appendChild(messageDiv);

}

// Display the win message and update the win count
function showWinMessage(message) {
    const messageDiv = document.getElementById('winMessage');
    const RedCount = "Red Win Count: "+(storage.getItem("Red") == null ? 0 : storage.getItem("Red"))
    const BlueCount = "Blue Win Count: "+(storage.getItem("Blue") == null ? 0 : storage.getItem("Blue"))
    const TieCount = "Tie Count: "+(storage.getItem("Tie") == null ? 0 : storage.getItem("Tie"))
    messageDiv.innerText = message+"\n"+RedCount+"\n"+BlueCount+"\n"+TieCount;
    messageDiv.style.display = 'block';

}

// Reset the game
function resetGame() {
    // Clear the board state
    board = Array(boardSize).fill().map(() => Array(boardSize).fill(''));

    // Remove all text meshes and reset cell colors
    for (let i = 0; i < boardSize; i++) {
        for (let j = 0; j < boardSize; j++) {
            const cell = cells[i][j];
            cell.material.color.set('white');

            // Remove text meshes above cells
            ABWorld.scene.children
                .filter(obj => obj.type === 'Mesh' && obj.geometry.type === 'TextGeometry')
                .forEach(textMesh => ABWorld.scene.remove(textMesh));
        }
    }

    // Reset to player X
    currentPlayer = 'X';
}

// Initialize 3D world
AB.world.newRun = function() {
    // Set up the 3D scene
    ABWorld.init3d(1000, 5000, 'black');

    // Create and display the 3D Tic Tac Toe board
    createBoard3D();

    // Set the camera position for better visibility
    ABWorld.camera.position.set(0, 0, 500);
    ABWorld.camera.lookAt(0, 0, 0);

    // Click listener
    window.addEventListener('click', handleClick, false);

    // Create the win message
    createUI();
};

// Clean up when the world ends
AB.world.endRun = function() {
    ABWorld.clearScene();
    window.removeEventListener('click', handleClick, false);
};