// 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);
};