{"id":10598,"date":"2025-10-15T11:03:38","date_gmt":"2025-10-15T09:03:38","guid":{"rendered":"https:\/\/spgoo.org\/?page_id=10598"},"modified":"2025-10-15T11:22:43","modified_gmt":"2025-10-15T09:22:43","slug":"puissance-4-3d-three-js","status":"publish","type":"page","link":"https:\/\/spgoo.org\/?page_id=10598","title":{"rendered":"Puissance 4 &#8212; 3D Three.js"},"content":{"rendered":"    <style>       \r\n        #containerPuissance4 {\r\n            width: 1070px;\r\n            height: 800px;\r\n            cursor: pointer;\r\n        }\r\n        \r\n        #ui {\r\n            position: absolute;\r\n            top: 10px;\r\n            left: 50%;\r\n            transform: translateX(-50%);\r\n            background: rgba(0, 0, 0, 0.8);\r\n            padding: 10px 20px;\r\n            border-radius: 15px;\r\n            text-align: center;\r\n            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\r\n            backdrop-filter: blur(10px);\r\n            z-index: 10;\r\n        }\r\n        \r\n        #ui h1 {\r\n            font-size: 16px;\r\n            margin-bottom: 5px;\r\n            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);\r\n        }\r\n        \r\n        .subtitle {\r\n            font-size: 14px;\r\n            color: #aaa;\r\n            margin-bottom: 10px;\r\n        }\r\n        \r\n        #gameInfo {\r\n            font-size: 14px;\r\n            margin-top: 10px;\r\n        }\r\n        \r\n        .player-turn {\r\n            display: inline-flex;\r\n            align-items: center;\r\n\t\t\tcolor:white;\r\n            gap: 10px;\r\n            padding: 10px 20px;\r\n            background: rgba(255, 255, 255, 0.1);\r\n            border-radius: 10px;\r\n            margin-top: 10px;\r\n        }\r\n        \r\n        .player-indicator {\r\n            width: 20px;\r\n            height: 20px;\r\n            border-radius: 50%;\r\n            border: 2px solid white;\r\n        }\r\n        \r\n        .player1 { background: #ff4444; }\r\n        .player2 { background: #ffeb3b; }\r\n        \r\n        #winner {\r\n            font-size: 24px;\r\n            font-weight: bold;\r\n            margin-top: 15px;\r\n            padding: 15px;\r\n            background: rgba(76, 175, 80, 0.3);\r\n            border-radius: 10px;\r\n            display: none;\r\n        }\r\n        \r\n        #winner.show {\r\n            display: block;\r\n            animation: pulse 1s infinite;\r\n        }\r\n        \r\n        @keyframes pulse {\r\n            0%, 100% { transform: scale(1); }\r\n            50% { transform: scale(1.05); }\r\n        }\r\n        \r\n        #resetBtn {\r\n            margin-top: 15px;\r\n            padding: 12px 30px;\r\n            font-size: 16px;\r\n            background: #4CAF50;\r\n            color: white;\r\n            border: none;\r\n            border-radius: 8px;\r\n            cursor: pointer;\r\n            transition: all 0.3s;\r\n            font-weight: bold;\r\n        }\r\n        \r\n        #resetBtn:hover {\r\n            background: #45a049;\r\n            transform: translateY(-2px);\r\n            box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);\r\n        }\r\n        \r\n        #instructions {\r\n            position: absolute;\r\n            bottom: 20px;\r\n            left: 50%;\r\n            transform: translateX(-50%);\r\n            background: rgba(0, 0, 0, 0.7);\r\n            padding: 15px 30px;\r\n            border-radius: 10px;\r\n            font-size: 14px;\r\n            text-align: center;\r\n            max-width: 600px;\r\n        }\r\n        \r\n        .controls {\r\n            position: absolute;\r\n            top: 50%;\r\n            right: 20px;\r\n            transform: translateY(-50%);\r\n            background: rgba(0, 0, 0, 0.7);\r\n            padding: 15px;\r\n            border-radius: 10px;\r\n            font-size: 12px;\r\n        }\r\n        \r\n        .control-item {\r\n            margin: 8px 0;\r\n            display: flex;\r\n            align-items: center;\r\n            gap: 10px;\r\n        }\r\n        \r\n        .key {\r\n            background: rgba(255, 255, 255, 0.2);\r\n            padding: 5px 10px;\r\n            border-radius: 5px;\r\n            font-weight: bold;\r\n            min-width: 30px;\r\n            text-align: center;\r\n        }\r\n    <\/style>\r\n    <div id=\"containerPuissance4\"><\/div>\r\n    \r\n    <div id=\"ui\">\r\n        <h1>\ud83c\udfae Puissance 4 &#8211; 3D Gravitationnel<\/h1>\r\n        <div class=\"subtitle\">Grille 4\u00d74\u00d74 &#8211; Les jetons tombent vers le bas ! Alignez 4 jetons.<\/div>\r\n        <div id=\"gameInfo\">\r\n            <div class=\"player-turn\">\r\n                <div class=\"player-indicator\" id=\"currentPlayerIndicator\"><\/div>\r\n                <span id=\"currentPlayer\">Tour du Joueur 1<\/span>\r\n            <\/div>\r\n        <\/div>\r\n        <div id=\"winner\"><\/div>\r\n        <button id=\"resetBtn\">Nouvelle Partie<\/button>\r\n    <\/div>\r\n        \r\n    <div id=\"instructions\">\r\n        \ud83c\udfaf Cliquez sur une colonne (X, Z) pour faire tomber votre jeton<br>\r\n        \ud83d\udca1 Les jetons tombent vers le bas (Y) comme dans le vrai Puissance 4 !<br>\r\n        \ud83c\udfc6 Alignez 4 jetons dans n&#8217;importe quelle direction 3D pour gagner !\r\n    <\/div>\r\n\r\n    <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/three.js\/r128\/three.min.js\"><\/script>\r\n    <script>\r\n        \/\/ Configuration du jeu en 3D avec gravit\u00e9\r\n        const SIZE = 4; \/\/ Grille 4\u00d74\u00d74\r\n        const CELL_SIZE = 1;\r\n        const CELL_SPACING = 0.3;\r\n        \r\n        \/\/ Variables globales\r\n        let scene, camera, renderer;\r\n        let board = []; \/\/ Grille 3D [y][x][z] (0 = vide, 1 = joueur 1, 2 = joueur 2)\r\n        let currentPlayer = 1;\r\n        let gameOver = false;\r\n        let gridCells = []; \/\/ R\u00e9f\u00e9rences aux objets 3D\r\n        let tokens = []; \/\/ Jetons plac\u00e9s\r\n        let raycaster = new THREE.Raycaster();\r\n        let mouse = new THREE.Vector2();\r\n        let isDragging = false;\r\n        let previousMousePosition = { x: 0, y: 0 };\r\n        let columnIndicators = [];\r\n        let hoveredColumn = null;\r\n\r\n        \/\/ Initialiser la grille de jeu 3D\r\n        function initBoard() {\r\n            board = [];\r\n            for (let y = 0; y < SIZE; y++) {\r\n                board[y] = [];\r\n                for (let x = 0; x < SIZE; x++) {\r\n                    board[y][x] = [];\r\n                    for (let z = 0; z < SIZE; z++) {\r\n                        board[y][x][z] = 0;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\t\tconst innerWidth=1070;\r\n\t\tconst innerHeight=800;\r\n\r\n        \/\/ Initialiser Three.js\r\n        function init() {\r\n            const container = document.getElementById('containerPuissance4');\r\n            \/\/ Sc\u00e8ne\r\n            scene = new THREE.Scene();\r\n            scene.background = new THREE.Color(0x1a1a2e);\r\n            \r\n            \/\/ Cam\u00e9ra\r\n            camera = new THREE.PerspectiveCamera(60,innerWidth \/ innerHeight,0.1,1000);\r\n            camera.position.set(8, 12, 12);\r\n            camera.lookAt(0, 0, 0);\r\n            \r\n            \/\/ Renderer\r\n            renderer = new THREE.WebGLRenderer({ antialias: true });\r\n            renderer.setSize(innerWidth, innerHeight);\r\n            renderer.shadowMap.enabled = true;\r\n            container.appendChild(renderer.domElement);\r\n            \r\n            \/\/ Lumi\u00e8res\r\n            const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);\r\n            scene.add(ambientLight);\r\n            \r\n            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);\r\n            directionalLight.position.set(10, 15, 10);\r\n            directionalLight.castShadow = true;\r\n            scene.add(directionalLight);\r\n            \r\n            const pointLight1 = new THREE.PointLight(0x4444ff, 0.5);\r\n            pointLight1.position.set(-8, 5, 5);\r\n            scene.add(pointLight1);\r\n            \r\n            const pointLight2 = new THREE.PointLight(0xff4444, 0.5);\r\n            pointLight2.position.set(8, 5, -5);\r\n            scene.add(pointLight2);\r\n            \r\n            \/\/ Cr\u00e9er la grille 3D\r\n            createGrid();\r\n            \r\n            \/\/ Cr\u00e9er les indicateurs de colonnes\r\n            createColumnIndicators();\r\n            \r\n            \/\/ Sol pour mieux visualiser\r\n            const groundGeometry = new THREE.PlaneGeometry(15, 15);\r\n            const groundMaterial = new THREE.MeshPhongMaterial({ \r\n                color: 0x34495e,\r\n                side: THREE.DoubleSide\r\n            });\r\n            const ground = new THREE.Mesh(groundGeometry, groundMaterial);\r\n            ground.rotation.x = -Math.PI \/ 2;\r\n            ground.position.y = -(SIZE - 1) * (CELL_SIZE + CELL_SPACING) \/ 2 - 1.5;\r\n            scene.add(ground);\r\n            \r\n            \/\/ \u00c9v\u00e9nements\r\n            container.addEventListener('click', onMouseClick);\r\n            container.addEventListener('mousemove', onMouseMove);\r\n            container.addEventListener('mousedown', onMouseDown);\r\n            container.addEventListener('mouseup', onMouseUp);\r\n            container.addEventListener('wheel', onWheel, { passive: false });\r\n            window.addEventListener('resize', onWindowResize);\r\n            \r\n            \/\/ Bouton reset\r\n            document.getElementById('resetBtn').addEventListener('click', resetGame);\r\n            \r\n            \/\/ Initialiser\r\n            initBoard();\r\n            updateUI();\r\n            \r\n            \/\/ Animation\r\n            animate();\r\n        }\r\n\r\n        \/\/ Cr\u00e9er la grille 3D (cube avec gravit\u00e9 sur Y)\r\n        function createGrid() {\r\n            const offset = -(SIZE - 1) * (CELL_SIZE + CELL_SPACING) \/ 2;\r\n            \r\n            for (let y = 0; y < SIZE; y++) {\r\n                gridCells[y] = [];\r\n                for (let x = 0; x < SIZE; x++) {\r\n                    gridCells[y][x] = [];\r\n                    for (let z = 0; z < SIZE; z++) {\r\n                        \/\/ Cellule transparente\r\n                        const cellGeometry = new THREE.BoxGeometry(CELL_SIZE, CELL_SIZE, CELL_SIZE);\r\n                        const cellMaterial = new THREE.MeshPhongMaterial({\r\n                            color: 0x3498db,\r\n                            transparent: true,\r\n                            opacity: 0.2,\r\n                            shininess: 50\r\n                        });\r\n                        const cell = new THREE.Mesh(cellGeometry, cellMaterial);\r\n                        \r\n                        const posX = x * (CELL_SIZE + CELL_SPACING) + offset;\r\n                        const posY = y * (CELL_SIZE + CELL_SPACING) + offset;\r\n                        const posZ = z * (CELL_SIZE + CELL_SPACING) + offset;\r\n                        \r\n                        cell.position.set(posX, posY, posZ);\r\n                        cell.userData = { x, y, z };\r\n                        \r\n                        \/\/ Bordure de la cellule\r\n                        const edges = new THREE.EdgesGeometry(cellGeometry);\r\n                        const lineMaterial = new THREE.LineBasicMaterial({ \r\n                            color: 0x95a5a6,\r\n                            transparent: true,\r\n                            opacity: 0.25\r\n                        });\r\n                        const wireframe = new THREE.LineSegments(edges, lineMaterial);\r\n                        cell.add(wireframe);\r\n                        \r\n                        scene.add(cell);\r\n                        gridCells[y][x][z] = cell;\r\n                    }\r\n                }\r\n            }\r\n            \r\n            \/\/ Axes de r\u00e9f\u00e9rence\r\n            \/\/ const axesHelper = new THREE.AxesHelper(6);\r\n            \/\/ scene.add(axesHelper);\r\n            \r\n            \/\/ Ajouter des labels pour les axes\r\n            const textOffset = (SIZE) * (CELL_SIZE + CELL_SPACING) \/ 2 + 1;\r\n        }\r\n\r\n        \/\/ Cr\u00e9er les indicateurs de colonnes (au-dessus)\r\n        function createColumnIndicators() {\r\n            const offsetX = -(SIZE - 1) * (CELL_SIZE + CELL_SPACING) \/ 2;\r\n            const offsetZ = -(SIZE - 1) * (CELL_SIZE + CELL_SPACING) \/ 2;\r\n            const topY = (SIZE - 1) * (CELL_SIZE + CELL_SPACING) \/ 2 + 1;\r\n            \r\n            for (let x = 0; x < SIZE; x++) {\r\n                columnIndicators[x] = [];\r\n                for (let z = 0; z < SIZE; z++) {\r\n                    const geometry = new THREE.CylinderGeometry(0.3, 0.3, 0.2, 32);\r\n                    const material = new THREE.MeshPhongMaterial({\r\n                        color: 0x95a5a6,\r\n                        transparent: true,\r\n                        opacity: 0.5,\r\n                        emissive: 0x95a5a6,\r\n                        emissiveIntensity: 0.3\r\n                    });\r\n                    const indicator = new THREE.Mesh(geometry, material);\r\n                    \r\n                    const posX = x * (CELL_SIZE + CELL_SPACING) + offsetX;\r\n                    const posZ = z * (CELL_SIZE + CELL_SPACING) + offsetZ;\r\n                    indicator.position.set(posX, topY, posZ);\r\n                    indicator.userData = { x, z };\r\n                    indicator.visible = false;\r\n                    \r\n                    scene.add(indicator);\r\n                    columnIndicators[x][z] = indicator;\r\n                }\r\n            }\r\n        }\r\n\r\n        \/\/ Trouver la premi\u00e8re position libre dans une colonne (x, z)\r\n        function findEmptyY(x, z) {\r\n            for (let y = 0; y < SIZE; y++) {\r\n                if (board[y][x][z] === 0) {\r\n                    return y;\r\n                }\r\n            }\r\n            return -1; \/\/ Colonne pleine\r\n        }\r\n\r\n        \/\/ Cr\u00e9er un jeton avec animation de chute\r\n        function createToken(x, y, z, player) {\r\n            const geometry = new THREE.SphereGeometry(0.4, 32, 32);\r\n            const color = player === 1 ? 0xff4444 : 0xffeb3b;\r\n            const material = new THREE.MeshPhongMaterial({\r\n                color: color,\r\n                shininess: 80,\r\n                emissive: color,\r\n                emissiveIntensity: 0.3\r\n            });\r\n            const token = new THREE.Mesh(geometry, material);\r\n            \r\n            const offset = -(SIZE - 1) * (CELL_SIZE + CELL_SPACING) \/ 2;\r\n            const posX = x * (CELL_SIZE + CELL_SPACING) + offset;\r\n            const targetY = y * (CELL_SIZE + CELL_SPACING) + offset;\r\n            const posZ = z * (CELL_SIZE + CELL_SPACING) + offset;\r\n            \r\n            \/\/ Commencer au-dessus et tomber\r\n            const startY = (SIZE + 2) * (CELL_SIZE + CELL_SPACING) \/ 2;\r\n            token.position.set(posX, startY, posZ);\r\n            token.userData = { targetY, falling: true, velocity: 0 };\r\n            \r\n            scene.add(token);\r\n            tokens.push(token);\r\n            \r\n            return token;\r\n        }\r\n\r\n        \/\/ Animation des jetons qui tombent\r\n        function updateTokens() {\r\n            tokens.forEach(token => {\r\n                if (token.userData.falling) {\r\n                    token.userData.velocity += 0.02; \/\/ Gravit\u00e9\r\n                    token.position.y -= token.userData.velocity;\r\n                    \r\n                    if (token.position.y <= token.userData.targetY) {\r\n                        token.position.y = token.userData.targetY;\r\n                        token.userData.falling = false;\r\n                        token.userData.velocity = 0;\r\n                        \r\n                        \/\/ Petit rebond\r\n                        token.scale.set(1, 0.8, 1);\r\n                        setTimeout(() => {\r\n                            token.scale.set(1, 1, 1);\r\n                        }, 100);\r\n                    }\r\n                }\r\n            });\r\n        }\r\n\r\n        \/\/ Placer un jeton dans une colonne\r\n        function placeToken(x, z) {\r\n            if (gameOver) return false;\r\n            \r\n            const y = findEmptyY(x, z);\r\n            if (y === -1) return false; \/\/ Colonne pleine\r\n            \r\n            board[y][x][z] = currentPlayer;\r\n            createToken(x, y, z, currentPlayer);\r\n            \r\n            \/\/ Attendre que le jeton tombe avant de v\u00e9rifier\r\n            setTimeout(() => {\r\n                if (checkWin(x, y, z)) {\r\n                    gameOver = true;\r\n                    showWinner();\r\n                } else if (checkDraw()) {\r\n                    gameOver = true;\r\n                    showDraw();\r\n                } else {\r\n                    currentPlayer = currentPlayer === 1 ? 2 : 1;\r\n                    updateUI();\r\n                }\r\n            }, 500);\r\n            \r\n            return true;\r\n        }\r\n\r\n        \/\/ V\u00e9rifier la victoire (dans toutes les directions 3D)\r\n        function checkWin(x, y, z) {\r\n            const player = board[y][x][z];\r\n            \r\n            \/\/ Toutes les directions possibles en 3D\r\n            const directions = [\r\n                [1, 0, 0],   \/\/ X horizontal\r\n                [0, 1, 0],   \/\/ Y vertical\r\n                [0, 0, 1],   \/\/ Z profondeur\r\n                [1, 1, 0],   \/\/ XY diagonal montant\r\n                [1, -1, 0],  \/\/ XY diagonal descendant\r\n                [1, 0, 1],   \/\/ XZ diagonal\r\n                [1, 0, -1],  \/\/ XZ diagonal inverse\r\n                [0, 1, 1],   \/\/ YZ diagonal\r\n                [0, 1, -1],  \/\/ YZ diagonal inverse\r\n                [1, 1, 1],   \/\/ Diagonale 3D\r\n                [1, 1, -1],  \/\/ Diagonale 3D\r\n                [1, -1, 1],  \/\/ Diagonale 3D\r\n                [1, -1, -1]  \/\/ Diagonale 3D\r\n            ];\r\n            \r\n            for (const [dx, dy, dz] of directions) {\r\n                let count = 1;\r\n                \r\n                \/\/ Compter dans une direction\r\n                for (let i = 1; i < SIZE; i++) {\r\n                    const nx = x + i * dx;\r\n                    const ny = y + i * dy;\r\n                    const nz = z + i * dz;\r\n                    if (nx < 0 || nx >= SIZE || ny < 0 || ny >= SIZE || nz < 0 || nz >= SIZE) break;\r\n                    if (board[ny][nx][nz] !== player) break;\r\n                    count++;\r\n                }\r\n                \r\n                \/\/ Compter dans la direction oppos\u00e9e\r\n                for (let i = 1; i < SIZE; i++) {\r\n                    const nx = x - i * dx;\r\n                    const ny = y - i * dy;\r\n                    const nz = z - i * dz;\r\n                    if (nx < 0 || nx >= SIZE || ny < 0 || ny >= SIZE || nz < 0 || nz >= SIZE) break;\r\n                    if (board[ny][nx][nz] !== player) break;\r\n                    count++;\r\n                }\r\n                \r\n                if (count >= 4) return true;\r\n            }\r\n            \r\n            return false;\r\n        }\r\n\r\n        \/\/ V\u00e9rifier l'\u00e9galit\u00e9\r\n        function checkDraw() {\r\n            for (let y = 0; y < SIZE; y++) {\r\n                for (let x = 0; x < SIZE; x++) {\r\n                    for (let z = 0; z < SIZE; z++) {\r\n                        if (board[y][x][z] === 0) return false;\r\n                    }\r\n                }\r\n            }\r\n            return true;\r\n        }\r\n\r\n        \/\/ Afficher le gagnant\r\n        function showWinner() {\r\n            const winnerDiv = document.getElementById('winner');\r\n            winnerDiv.textContent = `\ud83c\udf89 Joueur ${currentPlayer} gagne ! \ud83c\udf89`;\r\n            winnerDiv.classList.add('show');\r\n        }\r\n\r\n        \/\/ Afficher l'\u00e9galit\u00e9\r\n        function showDraw() {\r\n            const winnerDiv = document.getElementById('winner');\r\n            winnerDiv.textContent = '\ud83e\udd1d \u00c9galit\u00e9 ! \ud83e\udd1d';\r\n            winnerDiv.classList.add('show');\r\n        }\r\n\r\n        \/\/ Mettre \u00e0 jour l'interface\r\n        function updateUI() {\r\n            const currentPlayerText = document.getElementById('currentPlayer');\r\n            const indicator = document.getElementById('currentPlayerIndicator');\r\n            \r\n            currentPlayerText.textContent = `Tour du Joueur ${currentPlayer}`;\r\n            indicator.className = `player-indicator player${currentPlayer}`;\r\n        }\r\n\r\n        \/\/ R\u00e9initialiser le jeu\r\n        function resetGame() {\r\n            tokens.forEach(token => scene.remove(token));\r\n            tokens = [];\r\n            initBoard();\r\n            currentPlayer = 1;\r\n            gameOver = false;\r\n            document.getElementById('winner').classList.remove('show');\r\n            updateUI();\r\n        }\r\n\r\n        \/\/ Gestion du clic\r\n        function onMouseClick(event) {\r\n            if (gameOver || isDragging) return;\r\n            console.log(event);\r\n            \/\/mouse.x = (event.clientX \/ innerWidth) * 2 - 1;\r\n            \/\/mouse.y = -(event.clientY \/ innerHeight) * 2 + 1;\r\n            \/\/mouse.x = (event.pageX \/ innerWidth) * 2 - 1;\r\n            \/\/mouse.y = -(event.pageY \/ innerHeight) * 2 + 1;\r\n            \/\/mouse.x = (event.layerX \/ innerWidth) * 2 - 1;\r\n            \/\/mouse.y = -(event.layerY \/ innerHeight) * 2 + 1;\r\n            mouse.x = (event.offsetX \/ innerWidth) * 2 - 1;\r\n            mouse.y = -(event.offsetY \/ innerHeight) * 2 + 1;\r\n\t\t\t\r\n            \r\n            raycaster.setFromCamera(mouse, camera);\r\n            const intersects = raycaster.intersectObjects(gridCells.flat(2));\r\n            \r\n            if (intersects.length > 0) {\r\n                const cell = intersects[0].object;\r\n                const { x, z } = cell.userData;\r\n                placeToken(x, z);\r\n            }\r\n        }\r\n\r\n        \/\/ Gestion du survol\r\n        function onMouseMove(event) {\r\n            const rect = event.target.getBoundingClientRect();\r\n            const x = event.clientX - rect.left;\r\n            const y = event.clientY - rect.top;\r\n\r\n            \/\/ Rotation de la cam\u00e9ra si on glisse\r\n            if (isDragging) {\r\n                const deltaX = event.clientX - previousMousePosition.x;\r\n                const deltaY = event.clientY - previousMousePosition.y;\r\n\r\n                const r = Math.sqrt(\r\n                    Math.pow(camera.position.x, 2) +\r\n                    Math.pow(camera.position.y, 2) +\r\n                    Math.pow(camera.position.z, 2)\r\n                );\r\n\r\n                const theta = Math.atan2(camera.position.z, camera.position.x);\r\n                const phi = Math.acos(camera.position.y \/ r);\r\n\r\n                const newTheta = theta - deltaX * 0.01;\r\n                const newPhi = Math.max(0.1, Math.min(Math.PI - 0.1, phi - deltaY * 0.01));\r\n\r\n                camera.position.x = r * Math.sin(newPhi) * Math.cos(newTheta);\r\n                camera.position.y = r * Math.cos(newPhi);\r\n                camera.position.z = r * Math.sin(newPhi) * Math.sin(newTheta);\r\n                camera.lookAt(0, 0, 0);\r\n\r\n                previousMousePosition = { x: event.clientX, y: event.clientY };\r\n                return;\r\n            }\r\n            \r\n            \/\/ Highlight des colonnes\r\n            if (gameOver) return;\r\n            \r\n            mouse.x = (x \/ event.target.clientWidth) * 2 - 1;\r\n            mouse.y = -(y \/ event.target.clientHeight) * 2 + 1;\r\n            \r\n            raycaster.setFromCamera(mouse, camera);\r\n            const intersects = raycaster.intersectObjects(gridCells.flat(2));\r\n            \r\n            \/\/ Cacher tous les indicateurs\r\n            columnIndicators.flat().forEach(ind => ind.visible = false);\r\n            \r\n            if (intersects.length > 0) {\r\n                const cell = intersects[0].object;\r\n                const { x, z } = cell.userData;\r\n                const yPos = findEmptyY(x, z);\r\n                \r\n                if (yPos !== -1) {\r\n                    const indicator = columnIndicators[x][z];\r\n                    indicator.visible = true;\r\n                    indicator.material.color.setHex(currentPlayer === 1 ? 0xff4444 : 0xffeb3b);\r\n                    indicator.material.emissive.setHex(currentPlayer === 1 ? 0xff4444 : 0xffeb3b);\r\n                }\r\n            }\r\n        }\r\n\r\n        function onMouseDown(event) {\r\n            isDragging = true;\r\n            previousMousePosition = { x: event.clientX, y: event.clientY };\r\n        }\r\n\r\n        function onMouseUp() {\r\n            isDragging = false;\r\n        }\r\n\r\n        function onWheel(event) {\r\n            event.preventDefault();\r\n            const delta = event.deltaY * 0.01;\r\n            const direction = new THREE.Vector3();\r\n            camera.getWorldDirection(direction);\r\n            camera.position.addScaledVector(direction, -delta);\r\n        }\r\n\r\n        function onWindowResize() {\r\n            camera.aspect = innerWidth \/ innerHeight;\r\n            camera.updateProjectionMatrix();\r\n            renderer.setSize(innerWidth, innerHeight);\r\n        }\r\n\r\n        \/\/ Animation\r\n        function animate() {\r\n            requestAnimationFrame(animate);\r\n            updateTokens();\r\n            renderer.render(scene, camera);\r\n        }\r\n\r\n        \/\/ D\u00e9marrer le jeu\r\n        init();\r\n    <\/script>\r\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-10598","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/10598","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spgoo.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10598"}],"version-history":[{"count":1,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/10598\/revisions"}],"predecessor-version":[{"id":10599,"href":"https:\/\/spgoo.org\/index.php?rest_route=\/wp\/v2\/pages\/10598\/revisions\/10599"}],"wp:attachment":[{"href":"https:\/\/spgoo.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10598"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}