λ¬Έμμ μ΄μ νμ λλ€!
λͺ©μ°¨
π§ λ°λ κ²μ λ‘μ§
Phaser Baduk Metaverse νλ‘μ νΈμ λ°λ κ²μ λ‘μ§κ³Ό κ·μΉ ꡬνμ λν΄ μ€λͺ ν©λλ€.
π κ°μ
λ°λ κ²μμ ν΅μ¬ κ·μΉκ³Ό λ‘μ§μ JavaScriptλ‘ κ΅¬ννμ¬ μ νν λ°λ κ²μμ μ 곡ν©λλ€.
π― κΈ°λ³Έ κ·μΉ
λ°λμ κΈ°λ³Έ κ·μΉ:
μ°©μ
: νλλΆν° μμνμ¬ λ²κ°μκ°λ©° λμ λμ΅λλ€.μμ λ
: λμ μ΅μ νλμ μμ λλ₯Ό κ°μ ΈμΌ ν©λλ€.μ°©μ
: μλλ°© λμ μμ λλ₯Ό λͺ¨λ μμ λ©΄ κ·Έ λμ μ κ±°ν μ μμ΅λλ€.μμΆ©
: μμ μ λμ μμ λ μμ΄ λμ μ μμ΅λλ€.κ³κ°
: κ²μ μ’ λ£ ν μ μλ₯Ό κ³μ°ν©λλ€.
1. κ²μ μν κ΄λ¦¬
μ΄ μΉμ
μμλ λ°λ κ²μμ νμ¬ μνλ₯Ό κ΄λ¦¬νκ³ μ μ₯νλ BadukGameState
ν΄λμ€μ λν΄ μ€λͺ
ν©λλ€. μ΄ ν΄λμ€λ λ°λνμ λ λ°°μΉ, νμ¬ νλ μ΄μ΄, μ‘μ λμ μ, κ²μ λ¨κ³ λ± κ²μμ λͺ¨λ μ€μν μ 보λ₯Ό ν¬ν¨νκ³ μμ΅λλ€.
1) μμ±μ (Constructor) - κ²μ μ΄κΈ°ν
μ΄ μ½λλ BadukGameState
κ°μ²΄κ° μμ±λ λ νΈμΆλλ©°, κ²μμ μ΄κΈ° μνλ₯Ό μ€μ ν©λλ€. λ°λνμ 19×19 격μλ‘ μ΄κΈ°ννκ³ , νμ¬ νλ μ΄μ΄λ₯Ό νλλ‘ μ€μ νλ©°, μ‘μ λμ μμ μμ μ μλ₯Ό 0μΌλ‘ λ§λλλ€. κ²μ λ¨κ³λ 'playing'μΌλ‘ μμν©λλ€.
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; }
2) μν μ¬μ€μ (Resetting State)
reset()
λ©μλλ κ²μ μνλ₯Ό μ²μ μμνμ λμ λμΌν μ΄κΈ° μνλ‘ λλ립λλ€. μλ‘μ΄ κ²μμ μμν λλ κ²μμ λ€μ νλ μ΄ν λ μ μ©νκ² μ¬μ©λ©λλ€.
// κ²μ μν μ΄κΈ°ν 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; }
3) μν μ§λ ¬ν (Serialization - toJSON)
toJSON()
λ©μλλ νμ¬ κ²μ μνλ₯Ό μΌλ° JavaScript κ°μ²΄λ‘ λ³νν©λλ€. μ΄λ κ²μ μνλ₯Ό μ μ₯νκ±°λ λ€νΈμν¬λ₯Ό ν΅ν΄ μ μ‘ν λ μ μ©ν©λλ€. 볡μ‘ν ν΄λμ€ μΈμ€ν΄μ€ λμ κ°λ¨ν λ°μ΄ν° κ΅¬μ‘°λ‘ λ³ννμ¬ μ½κ² μ²λ¦¬ν μ μκ² ν©λλ€.
// νμ¬ μνλ₯Ό JSONμΌλ‘ μ§λ ¬ν toJSON() { return { board: this.board, currentPlayer: this.currentPlayer, capturedStones: this.capturedStones, territory: this.territory, gamePhase: this.gamePhase, moveHistory: this.moveHistory, lastMove: this.lastMove }; }
4) μν 볡μ (Deserialization - fromJSON)
fromJSON()
λ©μλλ toJSON()
μΌλ‘ μ§λ ¬νλ λ°μ΄ν° κ°μ²΄λ₯Ό λ°μ BadukGameState
μΈμ€ν΄μ€μ μνλ₯Ό ν΄λΉ λ°μ΄ν°λ‘ μ
λ°μ΄νΈν©λλ€. μ μ₯λ κ²μμ λΆλ¬μ€κ±°λ λ€λ₯Έ ν΄λΌμ΄μΈνΈλ‘λΆν° κ²μ μνλ₯Ό λκΈ°νν λ μ¬μ©λ©λλ€.
// 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; } }
2. μ΄λ κ²μ¦
μ΄ μΉμ
μμλ νλ μ΄μ΄μ μ°©μκ° μ ν¨νμ§ νμΈνλ BadukMoveValidator
ν΄λμ€μ λν΄ μ€λͺ
ν©λλ€. λ°λμ 볡μ‘ν κ·μΉ(μμ λ, μμΆ©, μ½ λ±)μ μ μ©νμ¬ μ¬λ°λ₯Έ μ°©μλ§ νμ©νλλ‘ ν©λλ€.
1) μμ±μ (Constructor)
BadukMoveValidator
λ νμ¬ κ²μ μν(gameState
)λ₯Ό μ°Έμ‘°νμ¬ μ ν¨μ± κ²μ¬λ₯Ό μνν©λλ€. μ΄λ₯Ό ν΅ν΄ λ°λνμ νμ¬ μν©μ κΈ°λ°μΌλ‘ μ°©μμ μ ν¨μ±μ νλ¨ν μ μμ΅λλ€.
export default class BadukMoveValidator { constructor(gameState) { this.gameState = gameState; }
2) μ ν¨ν μ΄λ νμΈ (isValidMove)
isValidMove(x, y, player)
λ©μλλ μ£Όμ΄μ§ μ’ν (x, y)
μ player
κ° λμ λμμ λ μ ν¨ν μ°©μμΈμ§ μ¬λΆλ₯Ό νμΈνλ ν΅μ¬ λ‘μ§μ
λλ€. λ°λμ μ¬λ¬ κ·μΉ(λ°λν λ²μ, μ΄λ―Έ λμ΄ λμΈ κ³³, μμΆ©, μ½ κ·μΉ)μ μμ°¨μ μΌλ‘ κ²μ¬ν©λλ€.
// μ΄λμ΄ μ ν¨νμ§ νμΈ 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: 'μ ν¨ν μ΄λμ λλ€.' }; }
3) λ°λν λ²μ νμΈ (isInBounds)
μ΄ κ°λ¨ν μ νΈλ¦¬ν° λ©μλλ μ£Όμ΄μ§ μ’ν (x, y)
κ° 19×19 λ°λνμ μ ν¨ν λ²μ λ΄μ μλμ§ νμΈν©λλ€. λͺ¨λ μ°©μ λ° μ£Όλ³ νμ μ μ νμμ μΌλ‘ μνλ©λλ€.
// λ°λν λ²μ νμΈ isInBounds(x, y) { return x >= 0 && x < 19 && y >= 0 && y < 19; }
4) μμΆ© νμΈ (isSuicide)
μμΆ©μ λ°λμμ μμ μ λμ λμμ λ κ·Έ λμ΄ μ¦μ μ‘νκ² λλ μλ₯Ό μλ―Έν©λλ€. μ΄ λ©μλλ μ£Όμ΄μ§ μμΉμ μμλ‘ λμ λμλ³΄κ³ , ν΄λΉ λμ΄ μμ λλ₯Ό κ°μ§λμ§ λλ μλλ°© λμ μ‘μ μ μλμ§ νμΈνμ¬ μμΆ© μ¬λΆλ₯Ό νλ¨ν©λλ€. νμΈ νμλ λ°λνμ μλ μνλ‘ λλ립λλ€.
// μμΆ© νμΈ 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; }
5) λ κ·Έλ£Ήμ μμ λ νμΈ (hasLiberties & checkGroupLiberties)
λ°λμμ λμ΄ μ΄κΈ° μν΄μλ 'μμ λ'(μ¨κ΅¬λ©)κ° νμν©λλ€. hasLiberties()
λ νΉμ λμ΄ μν κ·Έλ£Ή μ μ²΄κ° μμ λλ₯Ό κ°μ§κ³ μλμ§ νμΈνλ μμμ μ΄λ©°, checkGroupLiberties()
λ μ¬κ·μ μΌλ‘ λλ μ€νμ μ¬μ©νμ¬ λ κ·Έλ£Ήμ νμνλ©° λΉ κ³΅κ°(μμ λ)μ μ°Ύμ΅λλ€. visited
μ§ν©μ μ΄λ―Έ λ°©λ¬Έν λμ μΆμ νμ¬ λ¬΄ν 루νλ₯Ό λ°©μ§ν©λλ€.
// μμ λ νμΈ 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; }
6) μλλ°© λ μ‘μ μ μλμ§ νμΈ (canCaptureOpponent)
μ΄ λ©μλλ νΉμ μμΉμ λμ λμμ λ, κ·Έ λ μ£Όλ³μ μλ μλλ°© λ κ·Έλ£Ήμ μμ λκ° λͺ¨λ μ¬λΌμ Έ μ‘μ μ μλμ§ νμΈν©λλ€. μ°©μ μ§ν μλλ°© λμ μ κ±°νλ λ° μ¬μ©λλ μ€μν λ‘μ§μ λλ€.
// μλλ°© λμ μ‘μ μ μλμ§ νμΈ 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; }
7) μ½ κ·μΉ μλ° νμΈ (isKoViolation)
'μ½'λ λ°λμμ κ°μ ννκ° λ°λ³΅λλ κ²μ λ°©μ§νλ κ·μΉμ λλ€. μ΄ λ©μλλ λ§μ§λ§ μ΄λμ΄ μλλ°©μ λμ νλ μ‘λ 'λ¨μ'μμ κ²½μ°, κ·Έ μμΉμ μ¦μ λ€μ λμ λλ κ²μ κΈμ§νμ¬ λ¬΄ν λ°λ³΅μ λ§μ΅λλ€.
// μ½ κ·μΉ νμΈ 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; } }
3. μ°©μ λ‘μ§
μ΄ μΉμ
μμλ μ€μ λ‘ λ°λνμ λμ λκ³ , λμ΄ μ‘νμ λ μ΄λ₯Ό μ²λ¦¬νλ BadukCaptureLogic
ν΄λμ€μ λν΄ μ€λͺ
ν©λλ€.
1) μμ±μ (Constructor)
BadukCaptureLogic
ν΄λμ€λ κ²μ μν(gameState
) κ°μ²΄λ₯Ό λ°μ νμ¬ κ²μμ λ°λνκ³Ό κΈ°ν μ 보λ₯Ό μ‘°μν©λλ€.
export default class BadukCaptureLogic { constructor(gameState) { this.gameState = gameState; }
2) λ λκΈ° λ° μ°©μ μ²λ¦¬ (placeStone)
placeStone(x, y, player)
λ©μλλ νλ μ΄μ΄κ° νΉμ μμΉμ λμ λμΌλ € ν λ νΈμΆλλ ν΅μ¬ ν¨μμ
λλ€.
- λ¨Όμ
BadukMoveValidator
λ₯Ό μ¬μ©νμ¬ μ°©μκ° μ ν¨νμ§ νμΈν©λλ€. - μ ν¨νλ©΄ λ°λνμ λμ λκ³ , κ·Έ κ²°κ³Όλ‘ μ£Όλ³μ μλλ°© λμ΄ μ‘νλμ§
processCapture()
λ₯Ό ν΅ν΄ νμΈν©λλ€. - λͺ¨λ μ°©μμ μ°©μ κ²°κ³Όλ
moveHistory
μ κΈ°λ‘λ©λλ€. - λ§μ§λ§μΌλ‘ νμ¬ νλ μ΄μ΄λ₯Ό λ€μ νλ μ΄μ΄(ν β λ°±, λ°± β ν)λ‘ λ³κ²½ν©λλ€.
// λ λκΈ° λ° μ°©μ μ²λ¦¬ 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 }; }
3) μ°©μ μ€ν (processCapture)
λμ λμ ν, μ΄ λ©μλλ μλ‘ λμΈ λ μ£Όλ³μ 4λ°©ν₯μ νμΈν©λλ€. λ§μ½ μΈμ ν μλλ°© λ κ·Έλ£Ήμ΄ μμ λλ₯Ό λͺ¨λ μμλ€λ©΄, removeGroup()
λ©μλλ₯Ό νΈμΆνμ¬ ν΄λΉ κ·Έλ£Ήμ λͺ¨λ λμ λ°λνμμ μ κ±°νκ³ , μ‘μ λμ μλ₯Ό μ
λ°μ΄νΈν©λλ€.
// μ°©μ μ²λ¦¬ 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; }
4) λ κ·Έλ£Ή μ κ±° (removeGroup)
μ΄ λ©μλλ μ‘ν λ κ·Έλ£Ήμ νΉμ λ (x, y)
λ₯Ό μμμ μΌλ‘ μΌμ, ν΄λΉ κ·Έλ£Ήμ μν λͺ¨λ λμ λ°λνμμ μ κ±°ν©λλ€. μ€ν(Stack)μ μ΄μ©ν κΉμ΄ μ°μ νμ(DFS) λ°©μμΌλ‘ κ·Έλ£Ή μ 체λ₯Ό μννλ©° λμ null
λ‘ μ€μ νκ³ , μ κ±°λ λμ μ΄ κ°μλ₯Ό λ°νν©λλ€.
// κ·Έλ£Ή μ κ±° 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; }
5) μμ λ νμΈ μ νΈλ¦¬ν° (hasLiberties, checkGroupLiberties, isInBounds)
μ΄ λ©μλλ€μ BadukMoveValidator
ν΄λμ€μ μλ κ²κ³Ό λμΌν μ νΈλ¦¬ν° ν¨μμ
λλ€. λ κ·Έλ£Ήμ μμ λλ₯Ό νμΈνκ³ λ°λν λ²μ λ΄μ μλμ§ νμΈνλ λ° μ¬μ©λ©λλ€. μ½λ μ€λ³΅μ νΌνκΈ° μν΄ λ³λμ μ νΈλ¦¬ν° λͺ¨λλ‘ λΆλ¦¬ν μλ μμ§λ§, μ¬κΈ°μλ ν΄λΉ λ‘μ§ λ΄μμ μ§μ ν¬ν¨λμ΄ μ¬μ©λ©λλ€.
// μμ λ νμΈ (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; } }
4. ν¨μ€ λ° ν볡
μ΄ μΉμ
μμλ νλ μ΄μ΄κ° λμ λλ λμ 'ν¨μ€'νκ±°λ 'ν볡'νλ κ²½μ°μ λ‘μ§, κ·Έλ¦¬κ³ κ²μμ΄ μ’
λ£λ λ μμκ³Ό μ μλ₯Ό κ³μ°νλ BadukPassLogic
ν΄λμ€μ λν΄ μ€λͺ
ν©λλ€.
1) μμ±μ (Constructor)
BadukPassLogic
ν΄λμ€λ νμ¬ κ²μ μν(gameState
)λ₯Ό μ°Έμ‘°νλ©°, μ°μμΌλ‘ ν¨μ€ν νμλ₯Ό μΆμ νλ consecutivePasses
λ³μλ₯Ό μ΄κΈ°νν©λλ€.
export default class BadukPassLogic { constructor(gameState) { this.gameState = gameState; this.consecutivePasses = 0; }
2) ν¨μ€ μ²λ¦¬ (pass)
νλ μ΄μ΄κ° 'ν¨μ€' λ²νΌμ λλ μ λ νΈμΆλ©λλ€. consecutivePasses
νμλ₯Ό μ¦κ°μν€κ³ , ν¨μ€ κΈ°λ‘μ moveHistory
μ μΆκ°ν©λλ€. μ°μμΌλ‘ λ λ² ν¨μ€νλ©΄ κ²μμ΄ μ’
λ£λ©λλ€.
// ν¨μ€ μ²λ¦¬ 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 }; }
3) ν볡 μ²λ¦¬ (surrender)
νλ μ΄μ΄κ° 'ν볡'νμ λ νΈμΆλ©λλ€. ν볡ν νλ μ΄μ΄μ μλλ°©μ μΉμλ‘ μ μΈνκ³ , κ²μ λ¨κ³λ₯Ό 'finished'λ‘ λ³κ²½νμ¬ μ¦μ κ²μμ μ’
λ£ν©λλ€. ν볡 κΈ°λ‘ λν moveHistory
μ μΆκ°λ©λλ€.
// ν볡 μ²λ¦¬ 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 }; }
β
4) κ²μ μ’ λ£ (endGame)
λ νλ μ΄μ΄κ° μ°μμΌλ‘ ν¨μ€νκ±°λ ν νλ μ΄μ΄κ° ν볡νμ¬ κ²μμ΄ μ’
λ£λ λ νΈμΆλ©λλ€. κ²μ λ¨κ³λ₯Ό 'counting'(κ³κ°)μΌλ‘ λ³κ²½νκ³ , calculateTerritory()
λ° calculateFinalScore()
λ©μλλ₯Ό νΈμΆνμ¬ μ΅μ’
μ μλ₯Ό κ³μ°ν©λλ€.
// κ²μ μ’ λ£ endGame() { this.gameState.gamePhase = 'counting'; this.calculateTerritory(); this.calculateFinalScore(); }
5) μμ κ³μ° (calculateTerritory & analyzeTerritory)
λ°λνμ λΉ κ³΅κ°(μ§)μ νμνμ¬ κ° μμμ΄ μ΄λ νλ μ΄μ΄μ μμ μΈμ§ κ³μ°ν©λλ€.
calculateTerritory()
λ λ°λνμ λͺ¨λ λΉ κ³΅κ°μ μννλ©° μμ§ λ°©λ¬Ένμ§ μμ λΉ μ§μ μ μ°ΎμanalyzeTerritory()
λ₯Ό νΈμΆν©λλ€.analyzeTerritory()
λ μμ μ§μ μΌλ‘λΆν° μ°κ²°λ λΉ κ³΅κ°λ€μ νμνκ³ , ν΄λΉ μμμ λλ¬μΈκ³ μλ λμ΄ νλλ§ μλμ§ λ°±λλ§ μλμ§ νμΈν©λλ€. μ΄λ₯Ό ν΅ν΄ μμμ μμ μλ₯Ό νλ¨νκ³ , ν΄λΉ μμμ ν¬κΈ°λ₯Ό μ μμ λ°μν©λλ€.
// μμ κ³μ° 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; } } }
6) μμ μμ μ κ²°μ (determineTerritoryOwner)
μ΄ λ©μλλ νΉμ λΉ μμμ λλ¬μΈκ³ μλ λμ μκΉμ λΆμνμ¬ ν΄λΉ μμμ μμ μλ₯Ό κ²°μ ν©λλ€. μμ μ£Όλ³μ νλλ§ μμΌλ©΄ νμ μμμΌλ‘, λ°±λλ§ μμΌλ©΄ λ°±μ μμμΌλ‘ νλ¨ν©λλ€. λ§μ½ νλκ³Ό λ°±λμ΄ λͺ¨λ μ‘΄μ¬νκ±°λ λμ΄ μ ν μλ κ²½μ° (μ: μΈν€), ν΄λΉ μμμ μμ μκ° μλ κ²μΌλ‘ κ°μ£Όλ©λλ€.
// μμ μμ μ κ²°μ determineTerritoryOwner(blackStones, whiteStones) { if (blackStones > 0 && whiteStones === 0) { return 'black'; } else if (whiteStones > 0 && blackStones === 0) { return 'white'; } else { return null; // μμ μ μμ (μ: μΈν€ λλ κ³΅ν΅ μμ) } }
π² λ°λ κ²μ ν΅μ¬ κΈ°λ₯
1. κ²μ μν λ° μ μ κ³μ°
μ΄ μΉμ μμλ λ°λ κ²μμ νμ¬ μνλ₯Ό κ΄λ¦¬νκ³ , κ²μ μ’ λ£ μ μ΅μ’ μ μλ₯Ό κ³μ°νλ©°, λ°λν λ΄ μ ν¨ν μμΉλ₯Ό νμΈνλ ν΅μ¬ ν¨μμ λν΄ μ€λͺ ν©λλ€.
1) μ΅μ’ μ μ κ³μ°
calculateFinalScore
ν¨μλ κ²μμ΄ μ’
λ£λ ν νκ³Ό λ°±μ μ΅μ’
μ μλ₯Ό κ³μ°ν©λλ€. μ΄ ν¨μλ κ° νλ μ΄μ΄κ° μ°¨μ§ν μν μ νλν ν¬λ‘μ μλ₯Ό ν©μ°νμ¬ μ΄μ μ κ²°μ ν©λλ€. λ°±μκ²λ μΆκ°λ‘ 6.5μ§μ λ°μ§(λ€, komi)μ΄ λΆμ¬λ©λλ€.
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' }; }
2) 보λ λ΄ μμΉ μ ν¨μ± κ²μ¬
isInBounds
ν¨μλ μ£Όμ΄μ§ μ’ν (x, y)κ° λ°λνμ μ ν¨ν λ²μ(0λΆν° 18κΉμ§) λ΄μ μλμ§ νμΈν©λλ€. μ΄λ λμ λκ±°λ νΉμ μμΉλ₯Ό μ°Έμ‘°ν λ νμμ μΈ κ²μ¬μ
λλ€.
isInBounds(x, y) { return x >= 0 && x < 19 && y >= 0 && y < 19; }
π κ²μ ν΅κ³
μ΄ μΉμ μμλ λ°λ κ²μμ λ€μν ν΅κ³λ₯Ό κΈ°λ‘νκ³ κ΄λ¦¬νλ λ°©λ²μ λν΄ μ€λͺ ν©λλ€.
1. κ²μ ν΅κ³ κ΄λ¦¬ ν΄λμ€ (BadukStatistics)
BadukStatistics
ν΄λμ€λ λ°λ κ²μμ μ 체μ μΈ ν΅κ³λ₯Ό κ΄λ¦¬νλ λ° μ¬μ©λ©λλ€. μ΄ ν΄λμ€λ μ΄ κ²μ μ, μΉν¨ κΈ°λ‘, νκ· κ²μ κΈΈμ΄, μ°©μ λ° ν¬νλ λμ μ λ± λ€μν κ²μ κ΄λ ¨ λ°μ΄ν°λ₯Ό μΆμ νκ³ , ν΅κ³μ μ κ·Όνκ±°λ μ΄κΈ°ννλ κΈ°λ₯μ μ 곡ν©λλ€.
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 } }; } // ν΅κ³ μ΄κΈ°ν resetStats() { this.stats = { totalGames: 0, blackWins: 0, whiteWins: 0, averageGameLength: 0, totalMoves: 0, captures: { black: 0, white: 0 }, passes: 0, surrenders: 0 }; } // ν΅κ³ μΆλ ₯ (μμ) displayStats() { const currentStats = this.getStats(); console.log("--- λ°λ κ²μ ν΅κ³ ---"); console.log(`μ΄ κ²μ μ: ${currentStats.totalGames}`); console.log(`ν μΉλ¦¬: ${currentStats.blackWins} (${currentStats.winRate.black}%)`); console.log(`λ°± μΉλ¦¬: ${currentStats.whiteWins} (${currentStats.winRate.white}%)`); console.log(`νκ· κ²μ κΈΈμ΄ (μ): ${currentStats.averageGameLength.toFixed(1)}`); console.log(`μ΄ μ°©μ μ: ${currentStats.totalMoves}`); console.log(`νκ· ν ν¬ν μ: ${currentStats.averageCaptures.black}`); console.log(`νκ· λ°± ν¬ν μ: ${currentStats.averageCaptures.white}`); console.log(`ν¨μ€ μ: ${currentStats.passes}`); console.log(`κΈ°κΆ μ: ${currentStats.surrenders}`); } }
1) κ²μ κ²°κ³Ό κΈ°λ‘ (recordGameResult)
recordGameResult
λ©μλλ κ²μμ΄ μ’
λ£λ λ νΈμΆλμ΄ ν΄λΉ κ²μμ κ²°κ³Όλ₯Ό ν΅κ³μ λ°μν©λλ€. μ΄λ μ΄ κ²μ μ, μΉν¨ μ¬λΆ, νκ· κ²μ κΈΈμ΄, μ΄ μ°©μ μ, ν¬νλ λμ μ, ν¨μ€ λ° κΈ°κΆ νμ λ±μ μ
λ°μ΄νΈν©λλ€.
2) ν΅κ³ κ°μ Έμ€κΈ° (getStats)
getStats
λ©μλλ νμ¬κΉμ§ κΈ°λ‘λ λͺ¨λ ν΅κ³ λ°μ΄ν°λ₯Ό κ°μ²΄ ννλ‘ λ°νν©λλ€. μ΄ κ°μ²΄μλ μ΄ κ²μ μ, μΉν¨ κΈ°λ‘, νκ· κ²μ κΈΈμ΄, μ΄ μ°©μ μ, ν¬ν μ λ± κΈ°λ³Έ ν΅κ³ μΈμλ νλ°±μ μΉλ₯ λ° νκ· ν¬ν μμ κ°μ κ³μ°λ μ§νκ° ν¬ν¨λ©λλ€.
3) ν΅κ³ μ΄κΈ°ν (resetStats)
resetStats
λ©μλλ λͺ¨λ κ²μ ν΅κ³ λ°μ΄ν°λ₯Ό μ΄κΈ° μνλ‘ λ¦¬μ
ν©λλ€. μ΄λ μλ‘μ΄ μΈμ
μ΄λ ν
μ€νΈλ₯Ό μμν λ μ μ©νλ©°, λͺ¨λ ν΅κ³ μμΉλ₯Ό 0μΌλ‘ λλ립λλ€.
4) ν΅κ³ μΆλ ₯ (displayStats)
displayStats
λ©μλλ νμ¬ κΈ°λ‘λ ν΅κ³λ₯Ό μ½μμ μΆλ ₯νλ μμ κΈ°λ₯μ μ 곡ν©λλ€. μ΄ λ©μλλ getStats
λ₯Ό νΈμΆνμ¬ μ΅μ ν΅κ³ λ°μ΄ν°λ₯Ό κ°μ Έμ¨ ν, κ°λ
μ± λμ ννλ‘ κ° μ§νλ₯Ό λμ΄ν©λλ€. μ€μ μ ν리μΌμ΄μ
μμλ μ΄ λ°μ΄ν°λ₯Ό μ¬μ©μ μΈν°νμ΄μ€μ νμνλ λ° νμ©λ μ μμ΅λλ€.