๋ฌธ์์ ์ด์ ํ์ ๋๋ค!
๋ชฉ์ฐจ
๐ง ๋ฐ๋ ๊ฒ์ ๋ก์ง
Phaser Baduk Metaverse ํ๋ก์ ํธ์ ๋ฐ๋ ๊ฒ์ ๋ก์ง๊ณผ ๊ท์น ๊ตฌํ์ ๋ํด ์ค๋ช ํฉ๋๋ค.
๐ ๊ฐ์
๋ฐ๋ ๊ฒ์์ ํต์ฌ ๊ท์น๊ณผ ๋ก์ง์ JavaScript๋ก ๊ตฌํํ์ฌ ์ ํํ ๋ฐ๋ ๊ฒ์์ ์ ๊ณตํฉ๋๋ค.
๐ฏ ๊ธฐ๋ณธ ๊ท์น
๋ฐ๋์ ๊ธฐ๋ณธ ๊ท์น: 1. ์ฐฉ์: ํ๋๋ถํฐ ์์ํ์ฌ ๋ฒ๊ฐ์๊ฐ๋ฉฐ ๋์ ๋์ต๋๋ค. 2. ์์ ๋: ๋์ ์ต์ ํ๋์ ์์ ๋๋ฅผ ๊ฐ์ ธ์ผ ํฉ๋๋ค. 3. ์ฐฉ์: ์๋๋ฐฉ ๋์ ์์ ๋๋ฅผ ๋ชจ๋ ์์ ๋ฉด ๊ทธ ๋์ ์ ๊ฑฐํ ์ ์์ต๋๋ค. 4. ์์ถฉ: ์์ ์ ๋์ ์์ ๋ ์์ด ๋์ ์ ์์ต๋๋ค. 5. ๊ณ๊ฐ: ๊ฒ์ ์ข ๋ฃ ํ ์ ์๋ฅผ ๊ณ์ฐํฉ๋๋ค.
๐๏ธ ๊ฒ์ ์ํ ๊ด๋ฆฌ
๊ฒ์ ์ํ ํด๋์ค:
export default class BadukGameState { constructor() { this.board = Array(19).fill().map(() => Array(19).fill(null)); this.currentPlayer = 'black'; // 'black' ๋๋ 'white' this.capturedStones = { black: 0, white: 0 }; this.territory = { black: 0, white: 0 }; this.gamePhase = 'playing'; // 'playing', 'counting', 'finished' this.moveHistory = []; this.lastMove = null; } // ๊ฒ์ ์ํ ์ด๊ธฐํ reset() { this.board = Array(19).fill().map(() => Array(19).fill(null)); this.currentPlayer = 'black'; this.capturedStones = { black: 0, white: 0 }; this.territory = { black: 0, white: 0 }; this.gamePhase = 'playing'; this.moveHistory = []; this.lastMove = null; } // ํ์ฌ ์ํ๋ฅผ JSON์ผ๋ก ์ง๋ ฌํ toJSON() { return { board: this.board, currentPlayer: this.currentPlayer, capturedStones: this.capturedStones, territory: this.territory, gamePhase: this.gamePhase, moveHistory: this.moveHistory, lastMove: this.lastMove }; } // JSON์์ ์ํ ๋ณต์ fromJSON(data) { this.board = data.board; this.currentPlayer = data.currentPlayer; this.capturedStones = data.capturedStones; this.territory = data.territory; this.gamePhase = data.gamePhase; this.moveHistory = data.moveHistory; this.lastMove = data.lastMove; } }
๐ฎ ์ด๋ ๊ฒ์ฆ
์ ํจํ ์ด๋ ํ์ธ:
export default class BadukMoveValidator { constructor(gameState) { this.gameState = gameState; } // ์ด๋์ด ์ ํจํ์ง ํ์ธ isValidMove(x, y, player) { // 1. ๋ฐ๋ํ ๋ฒ์ ํ์ธ if (!this.isInBounds(x, y)) { return { valid: false, reason: '๋ฐ๋ํ ๋ฒ์๋ฅผ ๋ฒ์ด๋ฌ์ต๋๋ค.' }; } // 2. ์ด๋ฏธ ๋์ด ๋์ธ ์์น์ธ์ง ํ์ธ if (this.gameState.board[y][x] !== null) { return { valid: false, reason: '์ด๋ฏธ ๋์ด ๋์ธ ์์น์ ๋๋ค.' }; } // 3. ์์ถฉ ๊ท์น ํ์ธ if (this.isSuicide(x, y, player)) { return { valid: false, reason: '์์ถฉ์ ํ์ฉ๋์ง ์์ต๋๋ค.' }; } // 4. ์ฝ ๊ท์น ํ์ธ (๊ฐ์ ์์น์ ์ฐ์์ผ๋ก ๋์ ์ ์์) if (this.isKoViolation(x, y)) { return { valid: false, reason: '์ฝ ๊ท์น ์๋ฐ์ ๋๋ค.' }; } return { valid: true, reason: '์ ํจํ ์ด๋์ ๋๋ค.' }; } // ๋ฐ๋ํ ๋ฒ์ ํ์ธ isInBounds(x, y) { return x >= 0 && x < 19 && y >= 0 && y < 19; } // ์์ถฉ ํ์ธ isSuicide(x, y, player) { // ์์๋ก ๋์ ๋์๋ณด๊ณ ์์ ๋ ํ์ธ this.gameState.board[y][x] = player; const hasLiberties = this.hasLiberties(x, y); const canCapture = this.canCaptureOpponent(x, y, player); // ์๋ ์ํ๋ก ๋ณต์ this.gameState.board[y][x] = null; // ์์ ๋๊ฐ ์๊ณ ์๋๋ฐฉ์ ์ก์ ์ ์๋ค๋ฉด ์์ถฉ return !hasLiberties && !canCapture; } // ์์ ๋ ํ์ธ hasLiberties(x, y) { const color = this.gameState.board[y][x]; if (!color) return false; const visited = new Set(); return this.checkGroupLiberties(x, y, color, visited); } // ๊ทธ๋ฃน์ ์์ ๋ ํ์ธ checkGroupLiberties(x, y, color, visited) { const key = `${x},${y}`; if (visited.has(key)) return false; visited.add(key); const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]; for (const [dx, dy] of directions) { const nx = x + dx; const ny = y + dy; if (!this.isInBounds(nx, ny)) continue; if (this.gameState.board[ny][nx] === null) { return true; // ์์ ๋ ๋ฐ๊ฒฌ } if (this.gameState.board[ny][nx] === color) { if (this.checkGroupLiberties(nx, ny, color, visited)) { return true; } } } return false; } // ์๋๋ฐฉ ๋์ ์ก์ ์ ์๋์ง ํ์ธ canCaptureOpponent(x, y, player) { const opponent = player === 'black' ? 'white' : 'black'; const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]; for (const [dx, dy] of directions) { const nx = x + dx; const ny = y + dy; if (!this.isInBounds(nx, ny)) continue; if (this.gameState.board[ny][nx] === opponent) { if (!this.hasLiberties(nx, ny)) { return true; } } } return false; } // ์ฝ ๊ท์น ํ์ธ isKoViolation(x, y) { if (!this.gameState.lastMove) return false; // ๋ง์ง๋ง ์ด๋์ด ์ฐฉ์์๋ค๋ฉด, ๊ฐ์ ์์น์ ๋์ ์ ์์ if (this.gameState.lastMove.type === 'capture') { return x === this.gameState.lastMove.capturedX && y === this.gameState.lastMove.capturedY; } return false; } }
๐ฏ ์ฐฉ์ ๋ก์ง
์ฐฉ์ ์ฒ๋ฆฌ:
export default class BadukCaptureLogic { constructor(gameState) { this.gameState = gameState; } // ๋ ๋๊ธฐ ๋ฐ ์ฐฉ์ ์ฒ๋ฆฌ placeStone(x, y, player) { const validator = new BadukMoveValidator(this.gameState); const validation = validator.isValidMove(x, y, player); if (!validation.valid) { return { success: false, reason: validation.reason }; } // ๋ ๋๊ธฐ this.gameState.board[y][x] = player; // ์ฐฉ์ ํ์ธ ๋ฐ ์ฒ๋ฆฌ const captured = this.processCapture(x, y, player); // ์ด๋ ๊ธฐ๋ก this.gameState.moveHistory.push({ x: x, y: y, player: player, captured: captured, timestamp: Date.now() }); this.gameState.lastMove = { x: x, y: y, player: player, type: captured > 0 ? 'capture' : 'place' }; // ํ๋ ์ด์ด ๋ณ๊ฒฝ this.gameState.currentPlayer = player === 'black' ? 'white' : 'black'; return { success: true, captured: captured }; } // ์ฐฉ์ ์ฒ๋ฆฌ processCapture(x, y, player) { const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]; let totalCaptured = 0; const capturedPositions = []; for (const [dx, dy] of directions) { const nx = x + dx; const ny = y + dy; if (!this.isInBounds(nx, ny)) continue; const opponent = player === 'black' ? 'white' : 'black'; if (this.gameState.board[ny][nx] === opponent) { if (!this.hasLiberties(nx, ny)) { const captured = this.removeGroup(nx, ny); totalCaptured += captured; capturedPositions.push({ x: nx, y: ny, count: captured }); } } } // ์ฐฉ์ ๊ธฐ๋ก if (capturedPositions.length > 0) { this.gameState.lastMove.capturedPositions = capturedPositions; } this.gameState.capturedStones[player] += totalCaptured; return totalCaptured; } // ๊ทธ๋ฃน ์ ๊ฑฐ removeGroup(x, y) { const color = this.gameState.board[y][x]; if (!color) return 0; let count = 0; const stack = [[x, y]]; while (stack.length > 0) { const [cx, cy] = stack.pop(); if (this.gameState.board[cy][cx] === color) { this.gameState.board[cy][cx] = null; count++; const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]; for (const [dx, dy] of directions) { const nx = cx + dx; const ny = cy + dy; if (this.isInBounds(nx, ny) && this.gameState.board[ny][nx] === color) { stack.push([nx, ny]); } } } } return count; } // ์์ ๋ ํ์ธ (BadukMoveValidator์ ๋์ผ) hasLiberties(x, y) { const color = this.gameState.board[y][x]; if (!color) return false; const visited = new Set(); return this.checkGroupLiberties(x, y, color, visited); } checkGroupLiberties(x, y, color, visited) { const key = `${x},${y}`; if (visited.has(key)) return false; visited.add(key); const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]; for (const [dx, dy] of directions) { const nx = x + dx; const ny = y + dy; if (!this.isInBounds(nx, ny)) continue; if (this.gameState.board[ny][nx] === null) { return true; } if (this.gameState.board[ny][nx] === color) { if (this.checkGroupLiberties(nx, ny, color, visited)) { return true; } } } return false; } isInBounds(x, y) { return x >= 0 && x < 19 && y >= 0 && y < 19; } }
๐ฏ ํจ์ค ๋ฐ ํญ๋ณต
ํจ์ค ์ฒ๋ฆฌ:
export default class BadukPassLogic { constructor(gameState) { this.gameState = gameState; this.consecutivePasses = 0; } // ํจ์ค ์ฒ๋ฆฌ pass(player) { this.consecutivePasses++; this.gameState.moveHistory.push({ type: 'pass', player: player, timestamp: Date.now() }); this.gameState.currentPlayer = player === 'black' ? 'white' : 'black'; // ์ฐ์ ํจ์ค๊ฐ 2๋ฒ์ด๋ฉด ๊ฒ์ ์ข ๋ฃ if (this.consecutivePasses >= 2) { this.endGame(); } return { success: true, consecutivePasses: this.consecutivePasses }; } // ํญ๋ณต ์ฒ๋ฆฌ surrender(player) { const winner = player === 'black' ? 'white' : 'black'; this.gameState.gamePhase = 'finished'; this.gameState.winner = winner; this.gameState.surrender = true; this.gameState.moveHistory.push({ type: 'surrender', player: player, timestamp: Date.now() }); return { success: true, winner: winner }; } // ๊ฒ์ ์ข ๋ฃ endGame() { this.gameState.gamePhase = 'counting'; this.calculateTerritory(); this.calculateFinalScore(); } // ์์ญ ๊ณ์ฐ calculateTerritory() { const visited = Array(19).fill().map(() => Array(19).fill(false)); for (let y = 0; y < 19; y++) { for (let x = 0; x < 19; x++) { if (!visited[y][x] && this.gameState.board[y][x] === null) { this.analyzeTerritory(x, y, visited); } } } } // ์์ญ ๋ถ์ analyzeTerritory(x, y, visited) { const territory = []; const stack = [[x, y]]; let blackStones = 0; let whiteStones = 0; while (stack.length > 0) { const [cx, cy] = stack.pop(); if (visited[cy][cx]) continue; visited[cy][cx] = true; if (this.gameState.board[cy][cx] === null) { territory.push([cx, cy]); const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]]; for (const [dx, dy] of directions) { const nx = cx + dx; const ny = cy + dy; if (this.isInBounds(nx, ny) && !visited[ny][nx]) { stack.push([nx, ny]); } } } else if (this.gameState.board[cy][cx] === 'black') { blackStones++; } else if (this.gameState.board[cy][cx] === 'white') { whiteStones++; } } // ์์ญ ์์ ์ ๊ฒฐ์ if (territory.length > 0) { const owner = this.determineTerritoryOwner(blackStones, whiteStones); if (owner) { this.gameState.territory[owner] += territory.length; } } } // ์์ญ ์์ ์ ๊ฒฐ์ determineTerritoryOwner(blackStones, whiteStones) { if (blackStones > 0 && whiteStones === 0) { return 'black'; } else if (whiteStones > 0 && blackStones === 0) { return 'white'; } return null; // ์ค๋ฆฝ ์์ญ } // ์ต์ข ์ ์ ๊ณ์ฐ calculateFinalScore() { const blackScore = this.gameState.territory.black + this.gameState.capturedStones.black; const whiteScore = this.gameState.territory.white + this.gameState.capturedStones.white + 6.5; // ๋ฐฑ์ ๋ฐ์ง this.gameState.finalScore = { black: blackScore, white: whiteScore, winner: blackScore > whiteScore ? 'black' : 'white' }; } isInBounds(x, y) { return x >= 0 && x < 19 && y >= 0 && y < 19; } }
๐ ๊ฒ์ ํต๊ณ
๊ฒ์ ํต๊ณ ๊ด๋ฆฌ:
export default class BadukStatistics { constructor() { this.stats = { totalGames: 0, blackWins: 0, whiteWins: 0, averageGameLength: 0, totalMoves: 0, captures: { black: 0, white: 0 }, passes: 0, surrenders: 0 }; } // ๊ฒ์ ๊ฒฐ๊ณผ ๊ธฐ๋ก recordGameResult(gameState) { this.stats.totalGames++; if (gameState.surrender) { this.stats.surrenders++; } else { if (gameState.finalScore.winner === 'black') { this.stats.blackWins++; } else { this.stats.whiteWins++; } } // ํ๊ท ๊ฒ์ ๊ธธ์ด ์ ๋ฐ์ดํธ const gameLength = gameState.moveHistory.length; this.stats.averageGameLength = (this.stats.averageGameLength * (this.stats.totalGames - 1) + gameLength) / this.stats.totalGames; // ์ด ์ด๋ ์ ์ ๋ฐ์ดํธ this.stats.totalMoves += gameLength; // ์ฐฉ์ ํต๊ณ ์ ๋ฐ์ดํธ this.stats.captures.black += gameState.capturedStones.black; this.stats.captures.white += gameState.capturedStones.white; // ํจ์ค ํต๊ณ ์ ๋ฐ์ดํธ const passCount = gameState.moveHistory.filter(move => move.type === 'pass').length; this.stats.passes += passCount; } // ํต๊ณ ๊ฐ์ ธ์ค๊ธฐ getStats() { return { ...this.stats, winRate: { black: this.stats.totalGames > 0 ? (this.stats.blackWins / this.stats.totalGames * 100).toFixed(1) : 0, white: this.stats.totalGames > 0 ? (this.stats.whiteWins / this.stats.totalGames * 100).toFixed(1) : 0 }, averageCaptures: { black: this.stats.totalGames > 0 ? (this.stats.captures.black / this.stats.totalGames).toFixed(1) : 0, white: this.stats.totalGames > 0 ? (this.stats.captures.white / this.stats.totalGames).toFixed(1) : 0 } }; } // ํต๊ณ ์ด๊ธฐํ reset() { this.stats = { totalGames: 0, blackWins: 0, whiteWins: 0, averageGameLength: 0, totalMoves: 0, captures: { black: 0, white: 0 }, passes: 0, surrenders: 0 }; } }
๐ ๊ด๋ จ ๋ฌธ์
โ ์ด ํ์ด์ง๋ ์๋์ผ๋ก ์์ฑ๋์์ต๋๋ค.