바둑 메타버스 내에서 게임 플레이를 담당하는 game-worker.js
파일은 오목과 바둑이라는 두 가지 보드 게임의 인공지능(AI) 로직을 포함하고 있습니다. 이 문서는 각 게임의 AI가 어떤 원리와 전략을 사용하여 최적의 수를 찾아내는지에 대해 상세히 설명합니다. 특히, 오목 AI는 '미니맥스' 알고리즘과 '알파-베타 가지치기'를 통해 승리 경로를 탐색하며, 바둑 AI는 복합적인 '휴리스틱' 평가 함수를 통해 바둑의 다양한 전술적, 전략적 요소를 고려하여 착수합니다.
오목 AI의 핵심 목표는 가장 빠른 승리 경로를 찾거나 상대방의 승리를 확실하게 저지하는 것입니다. 이를 위해 게임 트리 탐색 기법인 미니맥스(Minimax) 알고리즘과 그 효율을 높이는 알파-베타 가지치기(Alpha-Beta Pruning)를 활용합니다.
오목 AI는 보드의 특정 부분을 '창(Window)'으로 보고 해당 창 내의 돌 배치를 평가하여 점수를 부여합니다. 이는 각 수가 가져올 수 있는 잠재적 가치를 수치화하는 과정입니다.
항목 | 점수 | 설명 |
---|---|---|
FIVE | 100000 | 5목 완성 (승리) |
OPEN_FOUR | 10000 | 양쪽이 열린 4 (다음 수에 바로 승리 가능) |
FOUR | 5000 | 한쪽이 막힌 4 |
OPEN_THREE | 100 | 양쪽이 열린 3 (두 수 안에 5목 가능) |
THREE | 50 | 한쪽이 막힌 3 |
OPEN_TWO | 10 | 양쪽이 열린 2 |
TWO | 5 | 한쪽이 막힌 2 |
ONE | 1 | 돌 하나 |
color
)와 상대방 돌의 개수, 빈 공간의 개수를 파악합니다. -SCORES.FOUR
) 및 (-SCORES.THREE
)를 부여하여 AI가 방어하도록 유도합니다.이 함수는 미니맥스 알고리즘이 특정 보드 상태의 가치를 판단하는 휴리스틱 함수입니다.
evaluateWindow
함수를 적용하여 점수를 합산합니다.오목 AI의 핵심 의사결정 로직입니다.
depth
)에 도달하면 scorePosition
함수로 현재 국면의 가치를 평가합니다.SEARCH_DEPTH
가 2로 설정되어 있어, AI는 자신의 다음 수와 그에 대한 상대방의 응수까지 총 2수를 예측합니다.alpha
(최대화 플레이어가 보장할 수 있는 최저 점수)와 beta
(최소화 플레이어가 보장할 수 있는 최고 점수) 값을 유지하며 탐색합니다.alpha
가 beta
보다 커지거나 같아지면, 해당 경로를 더 이상 탐색할 필요가 없다고 판단하여 가지치기합니다. 이는 불필요한 계산을 대폭 줄여줍니다.아래는 미니맥스 및 알파-베타 가지치기 알고리즘의 개념적인 코드 예시입니다.
function minimax(board, depth, alpha, beta, maximizingPlayer, aiColor) { // 게임 종료 또는 최대 깊이 도달 시 보드 평가 if (depth === 0 || checkWin(board, /* any color */) || checkDraw(board)) { return scorePosition(board, aiColor); } // AI 턴 (최대화 플레이어) if (maximizingPlayer) { let maxEval = -Infinity; for (const move of getPossibleMoves(board)) { const newBoard = applyMove(board, move, aiColor); const evaluation = minimax(newBoard, depth - 1, alpha, beta, false, aiColor); maxEval = Math.max(maxEval, evaluation); alpha = Math.max(alpha, evaluation); if (beta <= alpha) { // 알파-베타 가지치기 break; } } return maxEval; } else { // 상대방 턴 (최소화 플레이어) let minEval = Infinity; for (const move of getPossibleMoves(board)) { const newBoard = applyMove(board, move, getOpponentColor(aiColor)); const evaluation = minimax(newBoard, depth - 1, alpha, beta, true, aiColor); minEval = Math.min(minEval, evaluation); beta = Math.min(beta, evaluation); if (beta <= alpha) { // 알파-베타 가지치기 break; } } return minEval; } }
checkWin
은 checkGomokuWin
을 호출하여 전체 보드에서 승리 여부를 판정합니다. 미니맥스 탐색의 종료 조건 중 하나로 사용됩니다.AI가 실제로 착수할 위치를 결정하는 함수입니다.
minimax
함수를 호출하여 SEARCH_DEPTH
(현재 2)까지 탐색하여 가장 유리하다고 판단되는 위치에 착수합니다.오목 AI의 착수 결정 우선순위는 다음 표와 같습니다.
우선순위 | 설명 | 호출 함수 |
---|---|---|
1순위 | AI의 즉시 승리 지점 확인 및 착수 | 내부 로직 (checkGomokuWin 활용) |
2순위 | 상대방의 즉시 승리 방어 지점 확인 및 착수 | 내부 로직 (checkGomokuWin 활용) |
3순위 | minimax 알고리즘을 통한 최적의 수 탐색 및 착수 | minimax |
4순위 | 바둑판이 비어있을 경우 중앙(9,9)에 착수 | 내부 로직 |
5순위 | 비상시 임의의 빈칸에 착수 | 내부 로직 |
processMove
함수에서 착수 유효성 검사 시 사용됩니다.바둑은 오목보다 훨씬 복잡한 게임이므로, 단순한 승리/패배 예측 대신 각 착수 위치가 가져올 수 있는 다양한 가치(집, 세력, 사활, 연결 등)를 종합적으로 평가하는 휴리스틱 기반 접근 방식을 사용합니다.
이 함수는 바둑판의 특정 위치(x
, y
)에 특정 색깔(color
)의 돌을 놓았을 때의 잠재적 가치를 점수로 환산합니다. stoneCount
를 통해 게임 진행 단계를 파악하여 평가 가중치를 조절합니다.
phaseMultiplier
변수를 통해 stoneCount
에 따라 게임 초반에는 전략적 가치를 높게, 후반에는 낮게 평가합니다.
게임 단계별 phaseMultiplier
가중치 및 적용 목표는 다음과 같습니다.
게임 단계 (stoneCount ) | phaseMultiplier 가중치 | 적용 목표 |
---|---|---|
0 ~ 30 (초반) | 높음 (전략적 가치 > 전술적 가치) | 넓은 자리, 귀 선점, 모양 형성 |
31 ~ 150 (중반) | 중간 (전술적 가치와 균형) | 전투, 침투, 연결, 끊기 |
151+ (후반) | 낮음 (전술적 가치 > 전술적 가치) | 사활, 끝내기, 집 계산 |
다음은 바둑 착점 가치 평가 함수 getBadukMoveScore
의 개념적인 구조를 보여주는 코드 예시입니다.
function getBadukMoveScore(boardState, x, y, color, stoneCount) { let score = 0; const opponentColor = (color === 'black') ? 'white' : 'black'; // 0. 자충수 확인 if (isSuicide(boardState, x, y, color) && !canCapture(boardState, x, y, color)) { return -Infinity; // 자충수는 매우 낮은 점수 } // 1. 전술적 평가 (높은 가중치) if (canCapture(boardState, x, y, color)) { score += getCaptureScore(boardState, x, y, color); } if (canSaveOwnStones(boardState, x, y, color)) { score += getSaveScore(boardState, x, y, color); } if (canPutOpponentInAtari(boardState, x, y, color)) { score += getAtariScore(boardState, x, y, color); } // 2. 전략적 평가 (게임 단계별 가중치) const phaseMultiplier = calculatePhaseMultiplier(stoneCount); score += evaluateStrategicValue(boardState, x, y, color) * phaseMultiplier; // 3. 모양(패턴) 평가 score += evaluateGoodPatterns(boardState, x, y, color); score -= evaluateBadPatterns(boardState, x, y, color); // 4. 영역적 가치 평가 score += evaluateTerritorialValue(boardState, x, y, color); return score; }
바둑 AI는 복잡한 규칙과 상태를 처리하기 위해 다양한 헬퍼 함수들을 활용합니다. 다음 표는 주요 헬퍼 함수들의 역할과 활용처를 보여줍니다.
함수명 | 설명 | 주요 활용처 |
---|---|---|
getGroupInfo(boardState, x, y, color) | 특정 위치 돌 그룹의 활로 및 포함 돌 개수 계산 | 사활, 따내기, 연결성 평가 |
countTotalStones(board) | 바둑판의 총 돌 개수를 세어 게임 진행 단계 추정 | getBadukMoveScore 내 phaseMultiplier 계산 |
getCornerInfluence(board, cornerX, cornerY) | 특정 귀 주변의 세력 및 영향력 분석 | 귀 차지, 걸침, 굳힘 평가 |
isNearCorner(x, y, cornerX, cornerY) | 특정 위치가 귀 근처인지 확인 | 귀 관련 착수 평가 |
getSideExpansionValue(board, x, y, color) | 변에서의 집 또는 세력 확장 가치 평가 | 변 확장 전략 평가 |
isSpaciousOpeningMove(point, boardState) | 포석 단계에서 주변에 돌 없는 넓은 공간인지 확인 | 포석 단계의 전략적 착수 유도 |
getOpeningMoveScore(x, y, boardState) | 바둑 포석 단계 착수 가치 평가 (실리/세력, 귀, 연계 등) | (현재 AI 로직에서 직접 사용되진 않으나) 포석 전략 참고 |
바둑 AI가 실제로 착수할 위치를 결정하는 함수입니다.
getBadukMoveScore
함수를 호출하여 점수를 계산합니다.
다음은 바둑 AI가 최적의 착수 위치를 결정하는 calculateBadukAIMove
함수의 개념적인 코드 예시입니다.
function calculateBadukAIMove(boardState) { let bestScore = -Infinity; let bestMoves = []; const aiColor = 'white'; // 또는 현재 AI의 색상 for (let x = 0; x < 19; x++) { for (let y = 0; y < 19; y++) { if (boardState[y][x] === null) { // 빈 칸인 경우 const stoneCount = countTotalStones(boardState); const currentScore = getBadukMoveScore(boardState, x, y, aiColor, stoneCount); if (currentScore > bestScore) { bestScore = currentScore; bestMoves = [{ x, y }]; } else if (currentScore === bestScore) { bestMoves.push({ x, y }); } } } } // 최적의 수가 여러 개일 경우 무작위 선택 if (bestMoves.length > 0) { const randomIndex = Math.floor(Math.random() * bestMoves.length); return bestMoves[randomIndex]; } return null; // 유효한 수가 없을 경우 }
game-worker.js
는 Node.js의 worker_threads
를 사용하여 메인 스레드와 통신하며 게임 로직 및 AI 계산을 비동기적으로 수행합니다.
메인 스레드로부터 메시지를 받아 다음과 같은 작업을 수행합니다.
메시지 타입 | 송신 주체 | 수신 주체 | 설명 |
---|---|---|---|
PLACE_STONE | 메인 스레드 | 워커 스레드 | 플레이어 또는 AI의 착수 요청 |
CALCULATE_AI_MOVE | 메인 스레드 | 워커 스레드 | AI에게 최적의 수 계산 요청 |
CALCULATE_SCORE | 메인 스레드 | 워커 스레드 | 바둑 종국 후 집 계산 요청 |
BOARD_UPDATED | 워커 스레드 | 메인 스레드 | 착수 처리 후 변경된 보드 상태 및 게임 정보 전송 |
AI_MOVE_CALCULATED | 워커 스레드 | 메인 스레드 | AI의 계산된 최적의 수 전송 |
GOMOKU_WIN | 워커 스레드 | 메인 스레드 | 오목 승리 조건 충족 알림 |
다음은 game-worker.js
에서 메인 스레드로부터 메시지를 처리하는 개념적인 코드 예시입니다.
parentPort.on('message', (message) => { const { type, payload } = message; switch (type) { case 'PLACE_STONE': // payload.boardState, payload.x, payload.y, payload.color const result = processMove(payload.boardState, payload.x, payload.y, payload.color); parentPort.postMessage({ type: 'BOARD_UPDATED', payload: result }); if (result.gomokuWin) { // 오목 승리 시 추가 메시지 parentPort.postMessage({ type: 'GOMOKU_WIN', payload: { winner: result.winner } }); } break; case 'CALCULATE_AI_MOVE': // payload.gameType, payload.boardState const aiMove = (payload.gameType === 'gomoku') ? calculateGomokuAIMove(payload.boardState) : calculateBadukAIMove(payload.boardState); parentPort.postMessage({ type: 'AI_MOVE_CALCULATED', payload: { move: aiMove } }); break; case 'CALCULATE_SCORE': // payload.boardState const finalScore = calculateScore(payload.boardState); // 가상의 함수 parentPort.postMessage({ type: 'SCORE_CALCULATED', payload: { score: finalScore } }); break; default: console.warn(''알 수 없는 메시지 타입: ${type}''); } });
processMove
함수를 통해 돌 놓기, 상대방 돌 따내기, 자살수 검사, 패(Ko) 규칙 적용, 오목 승리 판정 등의 핵심 게임 규칙을 처리합니다.GOMOKU_WIN
메시지를 별도로 전송합니다.calculateGomokuAIMove
또는 calculateBadukAIMove
함수를 호출하여 AI의 최적 수를 계산합니다.AI_MOVE_CALCULATED
메시지를 통해 메인 스레드로 전달합니다. 실제 보드에 돌을 놓는 작업은 메인 스레드에서 PLACE_STONE
요청을 다시 보내는 방식으로 이루어집니다.calculateScore
함수는 제공된 코드에 명시적으로 나타나 있지 않으나, 메시지 처리 로직에 존재합니다.)이 함수는 플레이어나 AI의 착수가 발생했을 때, 해당 착수가 게임 규칙에 부합하는지 검증하고 보드 상태를 업데이트하는 역할을 합니다.
processMove
함수에서 수행하는 주요 유효성 검사 항목은 다음과 같습니다.
검사 항목 | 설명 | 결과 |
---|---|---|
이미 돌이 있는 칸 | 이미 돌이 놓인 위치에 착수 시도 | 착수 거부 |
패 규칙 위반 | 직전 상대방이 따낸 패 위치에 즉시 되따내기 시도 | 착수 거부 |
자살수 | 자신이 둔 돌이 바로 잡히는 수 (상대 돌 따내지 않는 경우) | 착수 거부 |
3-3 규칙 (오목 흑돌) | 한 번의 착수로 두 개 이상의 '열린 3' 형성 | 착수 거부 |
5목 완성 (오목) | 착수 후 바로 5목 완성 | 승리 판정 |
capturedStones
목록에 추가합니다.newKoInfo
를 업데이트합니다.
이 game-worker.js
파일은 바둑 메타버스 내에서 오목과 바둑 게임의 핵심 로직과 AI 기능을 효율적으로 구현하고 있습니다. 오목 AI는 '미니맥스 + 알파-베타 가지치기'를 통해 승리/방어에 초점을 맞춘 강력한 수읽기 능력을 보여주는 반면, 바둑 AI는 '휴리스틱 기반의 복합적인 가치 평가 시스템'을 통해 바둑의 다양한 전략적, 전술적 요소를 고려한 지능적인 착수를 가능하게 합니다. 특히 바둑 AI는 사활, 모양, 영역, 게임 단계별 전략 등 실제 바둑의 중요한 개념들을 잘 반영하고 있어, 초급 및 중급 사용자에게 충분히 도전적이고 흥미로운 대국 경험을 제공할 수 있을 것입니다. 이러한 AI 로직은 게임의 깊이를 더하고 사용자들에게 몰입감 있는 플레이를 선사합니다.