Spaces:
Running
Running
| <html lang="ko"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>3D ์ค๋ชฉ ๊ฒ์</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| font-family: 'Arial', sans-serif; | |
| } | |
| #game-container { | |
| position: relative; | |
| width: 100%; | |
| height: 100vh; | |
| } | |
| #ui { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| z-index: 100; | |
| background-color: rgba(255, 255, 255, 0.8); | |
| padding: 15px; | |
| border-radius: 10px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| #restart-btn { | |
| background-color: #4CAF50; | |
| color: white; | |
| border: none; | |
| padding: 8px 16px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| font-weight: bold; | |
| transition: background-color 0.3s; | |
| } | |
| #restart-btn:hover { | |
| background-color: #45a049; | |
| } | |
| .stone-count { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 10px; | |
| } | |
| .stone-icon { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| margin-right: 10px; | |
| } | |
| .black-stone { | |
| background-color: #FF69B4; /* ํํฌ์์ผ๋ก ๋ณ๊ฒฝ */ | |
| border: 1px solid #FF1493; | |
| } | |
| .white-stone { | |
| background-color: #fff; | |
| border: 1px solid #ddd; | |
| } | |
| #winner-message { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background-color: rgba(0, 0, 0, 0.8); | |
| color: white; | |
| padding: 20px 40px; | |
| border-radius: 10px; | |
| font-size: 24px; | |
| display: none; | |
| z-index: 200; | |
| text-align: center; | |
| } | |
| #close-winner { | |
| background-color: #f44336; | |
| color: white; | |
| border: none; | |
| padding: 8px 16px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| margin-top: 15px; | |
| font-weight: bold; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <div id="ui"> | |
| <h1 class="text-xl font-bold mb-4">3D ์ค๋ชฉ ๊ฒ์</h1> | |
| <div class="stone-count"> | |
| <div class="stone-icon black-stone"></div> | |
| <span id="black-count">ํํฌ๋: 0</span> <!-- ํ ์คํธ ๋ณ๊ฒฝ --> | |
| </div> | |
| <div class="stone-count"> | |
| <div class="stone-icon white-stone"></div> | |
| <span id="white-count">๋ฐฑ๋: 0</span> | |
| </div> | |
| <p id="current-player" class="font-bold mt-2">ํ์ฌ ์ฐจ๋ก: ํํฌ๋</p> <!-- ์ด๊ธฐ ํ ์คํธ ๋ณ๊ฒฝ --> | |
| <button id="restart-btn" class="mt-4">๊ฒ์ ์ฌ์์</button> | |
| </div> | |
| <div id="winner-message"> | |
| <h2 id="winner-text"></h2> | |
| <button id="close-winner">๋ซ๊ธฐ</button> | |
| </div> | |
| </div> | |
| <script> | |
| // ๊ฒ์ ๋ณ์ | |
| const BOARD_SIZE = 15; | |
| let currentPlayer = 1; // 1: ํํฌ๋, 2: ๋ฐฑ๋ | |
| let gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(0)); | |
| let gameOver = false; | |
| let blackStones = 0; | |
| let whiteStones = 0; | |
| // Three.js ์ด๊ธฐํ | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0xf0f0f0); | |
| const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| camera.position.set(0, 20, 20); | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.shadowMap.enabled = true; | |
| document.getElementById('game-container').appendChild(renderer.domElement); | |
| // ์ปจํธ๋กค ์ถ๊ฐ | |
| const controls = new THREE.OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| controls.dampingFactor = 0.05; | |
| controls.minDistance = 15; | |
| controls.maxDistance = 50; | |
| controls.maxPolarAngle = Math.PI / 2; | |
| // ์กฐ๋ช ์ถ๊ฐ | |
| const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); | |
| directionalLight.position.set(10, 20, 10); | |
| directionalLight.castShadow = true; | |
| directionalLight.shadow.mapSize.width = 2048; | |
| directionalLight.shadow.mapSize.height = 2048; | |
| scene.add(directionalLight); | |
| // ์ค๋ชฉํ ์์ฑ | |
| const boardGeometry = new THREE.BoxGeometry(BOARD_SIZE, 0.2, BOARD_SIZE); | |
| const boardMaterial = new THREE.MeshStandardMaterial({ | |
| color: 0x8B4513, | |
| roughness: 0.7, | |
| metalness: 0.2 | |
| }); | |
| const board = new THREE.Mesh(boardGeometry, boardMaterial); | |
| board.position.y = -0.1; | |
| board.receiveShadow = true; | |
| scene.add(board); | |
| // ์ค๋ชฉํ ์ ๊ทธ๋ฆฌ๊ธฐ | |
| const lineMaterial = new THREE.LineBasicMaterial({ color: 0x000000 }); | |
| // ๊ฐ๋ก์ | |
| for (let i = 0; i < BOARD_SIZE; i++) { | |
| const points = []; | |
| points.push(new THREE.Vector3(-BOARD_SIZE/2 + 0.5, 0.01, -BOARD_SIZE/2 + 0.5 + i)); | |
| points.push(new THREE.Vector3(BOARD_SIZE/2 - 0.5, 0.01, -BOARD_SIZE/2 + 0.5 + i)); | |
| const lineGeometry = new THREE.BufferGeometry().setFromPoints(points); | |
| const line = new THREE.Line(lineGeometry, lineMaterial); | |
| scene.add(line); | |
| } | |
| // ์ธ๋ก์ | |
| for (let i = 0; i < BOARD_SIZE; i++) { | |
| const points = []; | |
| points.push(new THREE.Vector3(-BOARD_SIZE/2 + 0.5 + i, 0.01, -BOARD_SIZE/2 + 0.5)); | |
| points.push(new THREE.Vector3(-BOARD_SIZE/2 + 0.5 + i, 0.01, BOARD_SIZE/2 - 0.5)); | |
| const lineGeometry = new THREE.BufferGeometry().setFromPoints(points); | |
| const line = new THREE.Line(lineGeometry, lineMaterial); | |
| scene.add(line); | |
| } | |
| // ์ ํ์ (5์ค๋ง๋ค) | |
| const dotGeometry = new THREE.SphereGeometry(0.1, 16, 16); | |
| const dotMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 }); | |
| const positions = [ | |
| [3, 3], [3, 11], [7, 7], [11, 3], [11, 11] | |
| ]; | |
| positions.forEach(pos => { | |
| const dot = new THREE.Mesh(dotGeometry, dotMaterial); | |
| dot.position.set( | |
| -BOARD_SIZE/2 + 0.5 + pos[0], | |
| 0.02, | |
| -BOARD_SIZE/2 + 0.5 + pos[1] | |
| ); | |
| scene.add(dot); | |
| }); | |
| // ๋ ๊ทธ๋ฃน | |
| const stoneGroup = new THREE.Group(); | |
| scene.add(stoneGroup); | |
| // ๋ ์์ฑ ํจ์ (ํํฌ์์ผ๋ก ๋ณ๊ฒฝ) | |
| function createStone(x, z, player) { | |
| const stoneGeometry = new THREE.SphereGeometry(0.4, 32, 32); | |
| const stoneMaterial = new THREE.MeshStandardMaterial({ | |
| color: player === 1 ? 0xFF69B4 : 0xffffff, // ํํฌ์ ์ ์ฉ | |
| roughness: 0.2, | |
| metalness: player === 1 ? 0.7 : 0.5, | |
| emissive: player === 1 ? 0xFF1493 : 0x000000, | |
| emissiveIntensity: 0.1 | |
| }); | |
| const stone = new THREE.Mesh(stoneGeometry, stoneMaterial); | |
| stone.position.set( | |
| -BOARD_SIZE/2 + 0.5 + x, | |
| 0.4, | |
| -BOARD_SIZE/2 + 0.5 + z | |
| ); | |
| stone.castShadow = true; | |
| stone.receiveShadow = true; | |
| // ๋์ ํ๋ ์ด์ด ์ ๋ณด ์ ์ฅ | |
| stone.userData = { player, x, z }; | |
| stoneGroup.add(stone); | |
| return stone; | |
| } | |
| // ๋ ๋๊ธฐ | |
| function placeStone(x, z) { | |
| if (gameBoard[x][z] !== 0 || gameOver) return false; | |
| gameBoard[x][z] = currentPlayer; | |
| createStone(x, z, currentPlayer); | |
| // ๋ ๊ฐ์ ์ ๋ฐ์ดํธ | |
| if (currentPlayer === 1) { | |
| blackStones++; | |
| document.getElementById('black-count').textContent = `ํํฌ๋: ${blackStones}`; | |
| } else { | |
| whiteStones++; | |
| document.getElementById('white-count').textContent = `๋ฐฑ๋: ${whiteStones}`; | |
| } | |
| // ์น๋ฆฌ ํ์ธ | |
| if (checkWin(x, z)) { | |
| gameOver = true; | |
| const winner = currentPlayer === 1 ? 'ํํฌ๋' : '๋ฐฑ๋'; // ํ ์คํธ ๋ณ๊ฒฝ | |
| document.getElementById('winner-text').textContent = `${winner} ์น๋ฆฌ!`; | |
| document.getElementById('winner-message').style.display = 'block'; | |
| return true; | |
| } | |
| // ์ฐจ๋ก ๋ณ๊ฒฝ | |
| currentPlayer = currentPlayer === 1 ? 2 : 1; | |
| document.getElementById('current-player').textContent = `ํ์ฌ ์ฐจ๋ก: ${currentPlayer === 1 ? 'ํํฌ๋' : '๋ฐฑ๋'}`; | |
| return true; | |
| } | |
| // ์น๋ฆฌ ์กฐ๊ฑด ํ์ธ | |
| function checkWin(x, z) { | |
| const directions = [ | |
| [1, 0], // ๊ฐ๋ก | |
| [0, 1], // ์ธ๋ก | |
| [1, 1], // ๋๊ฐ์ โ | |
| [1, -1] // ๋๊ฐ์ โ | |
| ]; | |
| for (const [dx, dz] of directions) { | |
| let count = 1; | |
| // ์๋ฐฉํฅ์ผ๋ก ํ์ธ | |
| for (let i = 1; i < 5; i++) { | |
| const nx = x + i * dx; | |
| const nz = z + i * dz; | |
| if (nx < 0 || nx >= BOARD_SIZE || nz < 0 || nz >= BOARD_SIZE || | |
| gameBoard[nx][nz] !== currentPlayer) { | |
| break; | |
| } | |
| count++; | |
| } | |
| for (let i = 1; i < 5; i++) { | |
| const nx = x - i * dx; | |
| const nz = z - i * dz; | |
| if (nx < 0 || nx >= BOARD_SIZE || nz < 0 || nz >= BOARD_SIZE || | |
| gameBoard[nx][nz] !== currentPlayer) { | |
| break; | |
| } | |
| count++; | |
| } | |
| if (count >= 5) return true; | |
| } | |
| return false; | |
| } | |
| // ๊ฒ์ ์ฌ์ค์ | |
| function resetGame() { | |
| // ๊ฒ์ ๋ณด๋ ์ด๊ธฐํ | |
| gameBoard = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(0)); | |
| // ๋ ์ ๊ฑฐ | |
| while (stoneGroup.children.length > 0) { | |
| stoneGroup.remove(stoneGroup.children[0]); | |
| } | |
| // ๊ฒ์ ์ํ ์ด๊ธฐํ | |
| currentPlayer = 1; | |
| gameOver = false; | |
| blackStones = 0; | |
| whiteStones = 0; | |
| // UI ์ ๋ฐ์ดํธ | |
| document.getElementById('black-count').textContent = 'ํํฌ๋: 0'; | |
| document.getElementById('white-count').textContent = '๋ฐฑ๋: 0'; | |
| document.getElementById('current-player').textContent = 'ํ์ฌ ์ฐจ๋ก: ํํฌ๋'; | |
| document.getElementById('winner-message').style.display = 'none'; | |
| } | |
| // ๋ง์ฐ์ค ํด๋ฆญ ์ด๋ฒคํธ ์ฒ๋ฆฌ | |
| function onMouseClick(event) { | |
| if (gameOver) return; | |
| // ๋ง์ฐ์ค ์์น ์ ๊ทํ | |
| const mouse = new THREE.Vector2(); | |
| mouse.x = (event.clientX / window.innerWidth) * 2 - 1; | |
| mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; | |
| // ๋ ์ด์บ์คํ | |
| const raycaster = new THREE.Raycaster(); | |
| raycaster.setFromCamera(mouse, camera); | |
| // ์ค๋ชฉํ๊ณผ์ ๊ต์ฐจ์ ํ์ธ | |
| const intersects = raycaster.intersectObject(board); | |
| if (intersects.length > 0) { | |
| const point = intersects[0].point; | |
| // ๋ณด๋ ์ขํ๋ก ๋ณํ | |
| const boardX = Math.round(point.x + BOARD_SIZE/2 - 0.5); | |
| const boardZ = Math.round(point.z + BOARD_SIZE/2 - 0.5); | |
| // ์ ํจํ ์์น์ธ์ง ํ์ธ | |
| if (boardX >= 0 && boardX < BOARD_SIZE && boardZ >= 0 && boardZ < BOARD_SIZE) { | |
| placeStone(boardX, boardZ); | |
| } | |
| } | |
| } | |
| // UI ์ด๋ฒคํธ ๋ฆฌ์ค๋ | |
| document.getElementById('restart-btn').addEventListener('click', resetGame); | |
| document.getElementById('close-winner').addEventListener('click', () => { | |
| document.getElementById('winner-message').style.display = 'none'; | |
| }); | |
| // ์ฐฝ ํฌ๊ธฐ ์กฐ์ | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| // ๋ง์ฐ์ค ํด๋ฆญ ์ด๋ฒคํธ ๋ฑ๋ก | |
| window.addEventListener('click', onMouseClick); | |
| // ์ ๋๋ฉ์ด์ ๋ฃจํ | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| controls.update(); | |
| renderer.render(scene, camera); | |
| } | |
| animate(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - ๐งฌ <a href="https://enzostvs-deepsite.hf.space?remix=aicoding101/omok2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |