omok2 / index.html
aicoding101's picture
Add 3 files
7435da3 verified
<!DOCTYPE html>
<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>