목차

🧠 바둑 게임 로직

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) 메서드는 플레이어가 특정 위치에 돌을 놓으려 할 때 호출되는 핵심 함수입니다.

    // 돌 놓기 및 착수 처리
    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() {
        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를 호출하여 최신 통계 데이터를 가져온 후, 가독성 높은 형태로 각 지표를 나열합니다. 실제 애플리케이션에서는 이 데이터를 사용자 인터페이스에 표시하는 데 활용될 수 있습니다.