์‚ฌ์šฉ์ž ๋„๊ตฌ

์‚ฌ์ดํŠธ ๋„๊ตฌ


wiki:it:dream_of_enc:metaverse:game_logic

๋ฌธ์„œ์˜ ์ด์ „ ํŒ์ž…๋‹ˆ๋‹ค!


๐Ÿง  ๋ฐ”๋‘‘ ๊ฒŒ์ž„ ๋กœ์ง

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
        };
    }
}

๐Ÿ“š ๊ด€๋ จ ๋ฌธ์„œ

โ€” ์ด ํŽ˜์ด์ง€๋Š” ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

wiki/it/dream_of_enc/metaverse/game_logic.1753682235.txt.gz ยท ๋งˆ์ง€๋ง‰์œผ๋กœ ์ˆ˜์ •๋จ: ์ €์ž 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki