From ff855399e3c6b9b2e6d3c07dca16fa7bbba87298 Mon Sep 17 00:00:00 2001 From: Sehyeon Date: Fri, 21 Mar 2025 11:26:29 +0900 Subject: [PATCH] =?UTF-8?q?DO-4=20[Fix]=20=EC=A0=90=EC=88=98=20=ED=8F=89?= =?UTF-8?q?=EA=B0=80=20=EA=B3=84=EC=82=B0=20=EB=B0=A9=EB=B2=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Script/AI/AIEvaluator.cs | 347 +++++++++++++----------- Assets/Script/AI/MiniMaxAIController.cs | 287 +------------------- 2 files changed, 186 insertions(+), 448 deletions(-) diff --git a/Assets/Script/AI/AIEvaluator.cs b/Assets/Script/AI/AIEvaluator.cs index e250d47..578245a 100644 --- a/Assets/Script/AI/AIEvaluator.cs +++ b/Assets/Script/AI/AIEvaluator.cs @@ -315,165 +315,6 @@ public static class AIEvaluator return fourThreeCount; } - #endregion - - #region Evaluate Move Position - // 이동 평가 함수 - public static float EvaluateMove(Enums.PlayerType[,] board, int row, int col, Enums.PlayerType AIPlayer) - { - float score = 0; - Enums.PlayerType opponentPlayer = (AIPlayer == Enums.PlayerType.PlayerA) ? - Enums.PlayerType.PlayerB : Enums.PlayerType.PlayerA; - - // 복합 패턴 감지를 위한 위치 저장 리스트 - List<(int[] dir, int count, int openEnds)> aiPatterns = new List<(int[], int, int)>(); - List<(int[] dir, int count, int openEnds)> opponentPatterns = new List<(int[], int, int)>(); - - // AI 관점에서 평가 - board[row, col] = AIPlayer; - - foreach (var dir in Directions) - { - // 평가를 위한 가상 보드이기에 캐시 데이터에 저장X - var (count, openEnds) = MiniMaxAIController.CountStones(board, row, col, dir, AIPlayer, false); - aiPatterns.Add((dir, count, openEnds)); - - if (count >= 4) - { - score += PatternScore.FIVE_IN_A_ROW / 10; - } - else if (count == 3) - { - score += (openEnds == 2) ? PatternScore.OPEN_THREE / 3 : - (openEnds == 1) ? PatternScore.HALF_OPEN_THREE / 5 : - PatternScore.CLOSED_THREE / 5; - } - else if (count == 2) - { - score += (openEnds == 2) ? PatternScore.OPEN_TWO / 2 : - (openEnds == 1) ? PatternScore.HALF_OPEN_TWO / 3 : - PatternScore.CLOSED_TWO / 5; - } - else if (count == 1) - { - score += (openEnds == 2) ? PatternScore.OPEN_ONE : - PatternScore.CLOSED_ONE; - } - - // 깨진 패턴 평가 - var (isBroken, brokenCount, brokenOpenEnds) = DetectBrokenPattern(board, row, col, dir, AIPlayer); - - if (isBroken) - { - float brokenScore = EvaluateBrokenPattern(brokenCount, brokenOpenEnds); - score = Math.Max(score, brokenScore); - } - } - - // AI 복합 패턴 점수 계산 (새로 추가) - score += EvaluateComplexMovePatterns(aiPatterns, true); - - // 상대 관점에서 평가 (방어 가치) - board[row, col] = opponentPlayer; - - foreach (var dir in Directions) - { - var (count, openEnds) = MiniMaxAIController.CountStones(board, row, col, dir, opponentPlayer, false); - opponentPatterns.Add((dir, count, openEnds)); - - // 상대 패턴 차단에 대한 가치 (약간 낮은 가중치) - if (count >= 4) - { - score += PatternScore.FIVE_IN_A_ROW / 12.5f; - } - else if (count == 3) - { - score += (openEnds == 2) ? PatternScore.OPEN_THREE / 3.75f : - (openEnds == 1) ? PatternScore.HALF_OPEN_THREE / 6.25f : - PatternScore.CLOSED_THREE / 6.25f; - } - else if (count == 2) - { - score += (openEnds == 2) ? PatternScore.OPEN_TWO / 2.5f : - (openEnds == 1) ? PatternScore.HALF_OPEN_TWO / 3.75f : - PatternScore.CLOSED_TWO / 5f; - } - else if (count == 1) - { - score += (openEnds == 2) ? PatternScore.OPEN_ONE / 1.25f : - PatternScore.CLOSED_ONE; - } - } - - score += EvaluateComplexMovePatterns(opponentPatterns, false); - - // 원래 상태로 복원 - board[row, col] = Enums.PlayerType.None; - - // 중앙에 가까울수록 추가 점수 - int size = board.GetLength(0); - float centerDistance = Math.Max( - Math.Abs(row - (size - 1) / 2.0f), - Math.Abs(col - (size - 1) / 2.0f) - ); - float centerBonus = 1.0f - (centerDistance / ((size - 1) / 2.0f)) * 0.3f; // 30% 가중치 - - return score * centerBonus; - } - - // 복합 패턴 평가를 위한 새로운 함수 - private static float EvaluateComplexMovePatterns(List<(int[] dir, int count, int openEnds)> patterns, bool isAI) - { - float score = 0; - - // 열린 3 패턴 및 4 패턴 찾기 - var openThrees = patterns.Where(p => p.count == 3 && p.openEnds == 2).ToList(); - var fours = patterns.Where(p => p.count == 4 && p.openEnds >= 1).ToList(); - - // 3-3 패턴 감지 - if (openThrees.Count >= 2) - { - for (int i = 0; i < openThrees.Count; i++) - { - for (int j = i + 1; j < openThrees.Count; j++) - { - if (!AreParallelDirections(openThrees[i].dir, openThrees[j].dir)) - { - float threeThreeScore = PatternScore.DOUBLE_THREE / 4; // 복합 패턴 가중치 - score += isAI ? threeThreeScore : threeThreeScore; - break; - } - } - } - } - - // 4-4 패턴 감지 - if (fours.Count >= 2) - { - for (int i = 0; i < fours.Count; i++) - { - for (int j = i + 1; j < fours.Count; j++) - { - if (!AreParallelDirections(fours[i].dir, fours[j].dir)) - { - float fourFourScore = PatternScore.DOUBLE_FOUR / 4; - score += isAI ? fourFourScore : fourFourScore; - break; - } - } - } - } - - // 4-3 패턴 감지 - if (fours.Count > 0 && openThrees.Count > 0) - { - float fourThreeScore = PatternScore.FOUR_THREE / 4; - score += isAI ? fourThreeScore : fourThreeScore; - } - - return score; - } - // 깨진 패턴 (3-빈칸-1) 감지 private static (bool isDetected, int count, int openEnds) DetectBrokenPattern( Enums.PlayerType[,] board, int row, int col, int[] dir, Enums.PlayerType player) @@ -552,7 +393,7 @@ public static class AIEvaluator return (isDetected, totalStones, openEnds); } - + // 깨진 패턴 점수 계산 함수 private static float EvaluateBrokenPattern(int count, int openEnds) { @@ -563,18 +404,196 @@ public static class AIEvaluator else if (count == 4) // 깨진 4 { return (openEnds == 2) ? PatternScore.OPEN_FOUR * 0.8f : - (openEnds == 1) ? PatternScore.HALF_OPEN_FOUR * 0.8f : - PatternScore.CLOSED_FOUR * 0.7f; + (openEnds == 1) ? PatternScore.HALF_OPEN_FOUR * 0.8f : + PatternScore.CLOSED_FOUR * 0.7f; } else if (count == 3) // 깨진 3 { return (openEnds == 2) ? PatternScore.OPEN_THREE * 0.7f : - (openEnds == 1) ? PatternScore.HALF_OPEN_THREE * 0.7f : - PatternScore.CLOSED_THREE * 0.6f; + (openEnds == 1) ? PatternScore.HALF_OPEN_THREE * 0.7f : + PatternScore.CLOSED_THREE * 0.6f; } return 0; } #endregion + + #region Evaluate Move Position + // 이동 평가 함수 + public static float EvaluateMove(Enums.PlayerType[,] board, int row, int col, Enums.PlayerType AIPlayer) + { + float score = 0; + Enums.PlayerType opponentPlayer = (AIPlayer == Enums.PlayerType.PlayerA) ? + Enums.PlayerType.PlayerB : Enums.PlayerType.PlayerA; + + // 복합 패턴 감지를 위한 위치 저장 리스트 + List<(int[] dir, int count, int openEnds)> aiPatterns = new List<(int[], int, int)>(); + List<(int[] dir, int count, int openEnds)> opponentPatterns = new List<(int[], int, int)>(); + + board[row, col] = AIPlayer; + + foreach (var dir in Directions) + { + float directionScore = 0; + + var (count, openEnds) = MiniMaxAIController.CountStones(board, row, col, dir, AIPlayer, false); + aiPatterns.Add((dir, count, openEnds)); + + float normalScore = 0; + if (count >= 4) + { + normalScore = PatternScore.FIVE_IN_A_ROW / 10; + } + else if (count == 3) + { + normalScore = (openEnds == 2) ? PatternScore.OPEN_THREE / 3 : + (openEnds == 1) ? PatternScore.HALF_OPEN_THREE / 5 : + PatternScore.CLOSED_THREE / 5; + } + else if (count == 2) + { + normalScore = (openEnds == 2) ? PatternScore.OPEN_TWO / 2 : + (openEnds == 1) ? PatternScore.HALF_OPEN_TWO / 3 : + PatternScore.CLOSED_TWO / 5; + } + else if (count == 1) + { + normalScore = (openEnds == 2) ? PatternScore.OPEN_ONE : + PatternScore.CLOSED_ONE; + } + + // 깨진 패턴 평가 + var (isBroken, brokenCount, brokenOpenEnds) = DetectBrokenPattern(board, row, col, dir, AIPlayer); + float brokenScore = 0; + + if (isBroken) + { + brokenScore = EvaluateBrokenPattern(brokenCount, brokenOpenEnds); + } + + directionScore = Math.Max(normalScore, brokenScore); + + score += directionScore; // 공격 점수 누적 + } + + // AI 복합 패턴 점수 계산 + score += EvaluateComplexMovePatterns(aiPatterns, true); + + // 상대 관점에서 평가 (방어 가치) + board[row, col] = opponentPlayer; + + foreach (var dir in Directions) + { + float directionScore = 0; + + var (count, openEnds) = MiniMaxAIController.CountStones(board, row, col, dir, opponentPlayer, false); + opponentPatterns.Add((dir, count, openEnds)); + + float normalScore = 0; + // 상대 패턴 차단에 대한 가치 (약간 낮은 가중치) AI는 공격지향적으로 + if (count >= 4) + { + normalScore = PatternScore.FIVE_IN_A_ROW / 12.5f; + } + else if (count == 3) + { + normalScore = (openEnds == 2) ? PatternScore.OPEN_THREE / 3.75f : + (openEnds == 1) ? PatternScore.HALF_OPEN_THREE / 6.25f : + PatternScore.CLOSED_THREE / 6.25f; + } + else if (count == 2) + { + normalScore = (openEnds == 2) ? PatternScore.OPEN_TWO / 2.5f : + (openEnds == 1) ? PatternScore.HALF_OPEN_TWO / 3.75f : + PatternScore.CLOSED_TWO / 5f; + } + else if (count == 1) + { + normalScore = (openEnds == 2) ? PatternScore.OPEN_ONE / 1.25f : + PatternScore.CLOSED_ONE; + } + + var (isBroken, brokenCount, brokenOpenEnds) = DetectBrokenPattern(board, row, col, dir, opponentPlayer); + float brokenScore = 0; + + if (isBroken) + { + // 깨진 패턴은 일반 패턴보다 좀 더 높은 가중치 할당 + brokenScore = EvaluateBrokenPattern(brokenCount, brokenOpenEnds) * 0.9f; + } + + directionScore = Math.Max(normalScore, brokenScore); + + score += directionScore; // 방어 점수 누적 + } + + score += EvaluateComplexMovePatterns(opponentPatterns, false); + + board[row, col] = Enums.PlayerType.None; // 복원 + + int size = board.GetLength(0); + float centerDistance = Math.Max( + Math.Abs(row - (size - 1) / 2.0f), // 중앙 위치 계산 + Math.Abs(col - (size - 1) / 2.0f) + ); + float centerBonus = 1.0f - (centerDistance / ((size - 1) / 2.0f)) * 0.3f; // 중앙과 가장자리의 점수 차이를 30%로 설정 + + return score * centerBonus; + } + + // 복합 패턴 평가를 위한 새로운 함수 + private static float EvaluateComplexMovePatterns(List<(int[] dir, int count, int openEnds)> patterns, bool isAI) + { + float score = 0; + + // 열린 3 패턴 및 4 패턴 찾기 + var openThrees = patterns.Where(p => p.count == 3 && p.openEnds == 2).ToList(); + var fours = patterns.Where(p => p.count == 4 && p.openEnds >= 1).ToList(); + + // 3-3 패턴 감지 + if (openThrees.Count >= 2) + { + for (int i = 0; i < openThrees.Count; i++) + { + for (int j = i + 1; j < openThrees.Count; j++) + { + if (!AreParallelDirections(openThrees[i].dir, openThrees[j].dir)) + { + float threeThreeScore = PatternScore.DOUBLE_THREE / 4; // 복합 패턴 가중치 + score += isAI ? threeThreeScore : threeThreeScore; + break; + } + } + } + } + + // 4-4 패턴 감지 + if (fours.Count >= 2) + { + for (int i = 0; i < fours.Count; i++) + { + for (int j = i + 1; j < fours.Count; j++) + { + if (!AreParallelDirections(fours[i].dir, fours[j].dir)) + { + float fourFourScore = PatternScore.DOUBLE_FOUR / 4; + score += isAI ? fourFourScore : fourFourScore; + break; + } + } + } + } + + // 4-3 패턴 감지 + if (fours.Count > 0 && openThrees.Count > 0) + { + float fourThreeScore = PatternScore.FOUR_THREE / 4; + score += isAI ? fourThreeScore : fourThreeScore; + } + + return score; + } + + #endregion } diff --git a/Assets/Script/AI/MiniMaxAIController.cs b/Assets/Script/AI/MiniMaxAIController.cs index 76e1c74..5580e39 100644 --- a/Assets/Script/AI/MiniMaxAIController.cs +++ b/Assets/Script/AI/MiniMaxAIController.cs @@ -5,7 +5,7 @@ using UnityEngine; public static class MiniMaxAIController { - private const int SEARCH_DEPTH = 3; // 탐색 깊이 제한 (3 = 빠른 응답, 4 = 좀 더 강한 AI 그러나 느린) + private const int SEARCH_DEPTH = 4; // 탐색 깊이 제한 (3 = 빠른 응답, 4 = 좀 더 강한 AI 그러나 느린) private const int WIN_COUNT = 5; private static int[][] _directions = AIConstants.Directions; @@ -154,8 +154,8 @@ public static class MiniMaxAIController // score가 높은 순으로 정렬 -> 더 좋은 수 먼저 계산하도록 함 validMoves.Sort((a, b) => b.Item3.CompareTo(a.Item3)); - // 시간 단축을 위해 상위 10-15개만 고려. 일단 15개 - return validMoves.Take(15).ToList(); + // 시간 단축을 위해 상위 10-15개만 고려. + return validMoves.Take(10).ToList(); } private static bool HasNearbyStones(Enums.PlayerType[,] board, int row, int col, int distance = 3) @@ -312,285 +312,4 @@ public static class MiniMaxAIController return fiveInARowMoves; } -/* - #region Evaluate Score - - // 특정 위치의 Score를 평가하는 새로운 함수 - private static float EvaluateMove(Enums.PlayerType[,] board, int row, int col) - { - float score = 0; - board[row, col] = _AIPlayerType; - - foreach (var dir in _directions) - { - // CountStones를 사용하나 캐시에 저장X, 가상 계산이기 때문.. - var (count, openEnds) = CountStones(board, row, col, dir, _AIPlayerType, false); - - if (count >= 4) - { - score += 10000; - } - else if (count == 3) - { - score += (openEnds == 2) ? 1000 : (openEnds == 1) ? 100 : 10; - } - else if (count == 2) - { - score += (openEnds == 2) ? 50 : (openEnds == 1) ? 10 : 5; - } - else if (count == 1) - { - score += (openEnds == 2) ? 10 : (openEnds == 1) ? 5 : 1; - } - } - - // 상대 돌로 바꿔서 평가 - board[row, col] = Enums.PlayerType.PlayerB; - - foreach (var dir in _directions) - { - // 캐시 저장X - var (count, openEnds) = CountStones(board, row, col, dir, Enums.PlayerType.PlayerB, false); - - // 상대 패턴 차단에 대한 가치 (방어 점수) - if (count >= 4) - { - score += 8000; - } - else if (count == 3) - { - score += (openEnds == 2) ? 800 : (openEnds == 1) ? 80 : 8; - } - else if (count == 2) - { - score += (openEnds == 2) ? 40 : (openEnds == 1) ? 8 : 4; - } - else if (count == 1) - { - score += (openEnds == 2) ? 8 : (openEnds == 1) ? 4 : 1; - } - } - - // 원래 상태로 복원 - board[row, col] = Enums.PlayerType.None; - - // 중앙에 가까울수록 추가 점수 - int size = board.GetLength(0); - float centerDistance = Math.Max( - Math.Abs(row - (size - 1) / 2.0f), - Math.Abs(col - (size - 1) / 2.0f) - ); - float centerBonus = 1.0f - (centerDistance / ((size - 1) / 2.0f)) * 0.3f; // 30% 가중치 - - return score * centerBonus; - } - - // 현재 보드 평가 함수 - private static float EvaluateBoard(Enums.PlayerType[,] board) - { - float score = 0; - int size = board.GetLength(0); - - // 복합 패턴 감지를 위한 위치 저장 리스트 - List<(int row, int col, int[] dir)> aiOpen3Positions = new List<(int, int, int[])>(); - List<(int row, int col, int[] dir)> playerOpen3Positions = new List<(int, int, int[])>(); - List<(int row, int col, int[] dir)> ai4Positions = new List<(int, int, int[])>(); - List<(int row, int col, int[] dir)> player4Positions = new List<(int, int, int[])>(); - - - for (int row = 0; row < size; row++) - { - for (int col = 0; col < size; col++) - { - if (board[row, col] == Enums.PlayerType.None) continue; - - Enums.PlayerType player = board[row, col]; - int playerScore = (player == _AIPlayerType) ? 1 : -1; // AI는 양수, 플레이어는 음수 - - // 위치 가중치 계산. 중앙 중심으로 돌을 두도록 함 - float positionWeight = CalculatePositionWeight(row, col, size); - - foreach (var dir in _directions) - { - var (count, openEnds) = CountStones(board, row, col, dir, player); - - // 점수 계산 - float patternScore = 0; - - if (count >= 5) - { - Debug.Log("over 5 counts. count amount: " + count); - patternScore = 100000; - } - else if (count == 4) - { - patternScore = (openEnds == 2) ? 15000 : (openEnds == 1) ? 5000 : 500; - - // 4 패턴 위치 저장 - if (openEnds >= 1) - { - if (player == _AIPlayerType) - ai4Positions.Add((row, col, dir)); - else - player4Positions.Add((row, col, dir)); - } - } - else if (count == 3) - { - patternScore = (openEnds == 2) ? 3000 : (openEnds == 1) ? 500 : 50; - - // 3 패턴 위치 저장 - if (openEnds == 2) - { - if (player == _AIPlayerType) - aiOpen3Positions.Add((row, col, dir)); - else - playerOpen3Positions.Add((row, col, dir)); - } - } - else if (count == 2) - { - patternScore = (openEnds == 2) ? 100 : (openEnds == 1) ? 30 : 10; - } - else if (count == 1) - { - patternScore = (openEnds == 2) ? 10 : 1; - } - - // 위치 가중치 적용 - patternScore *= positionWeight; - - // 최종 점수 적용 (플레이어는 음수) - score += playerScore * patternScore; - } - } - } - - // 2. 복합 패턴 감지 및 점수 부여 (4,4 / 3,3 / 4,3) - int aiThreeThree = DetectDoubleThree(aiOpen3Positions); - int playerThreeThree = DetectDoubleThree(playerOpen3Positions); - - int aiFourFour = DetectDoubleFour(ai4Positions); - int playerFourFour = DetectDoubleFour(player4Positions); - - int aiFourThree = DetectFourThree(ai4Positions, aiOpen3Positions); - int playerFourThree = DetectFourThree(player4Positions, playerOpen3Positions); - - // 복합 패턴 점수 추가 - score += aiThreeThree * 8000; - score -= playerThreeThree * 8000; - - score += aiFourFour * 12000; - score -= playerFourFour * 12000; - - score += aiFourThree * 10000; - score -= playerFourThree * 10000; - - return score; - } - - // 위치 가중치 계산 함수 - private static float CalculatePositionWeight(int row, int col, int size) - { - float boardCenterPos = (size - 1) / 2.0f; - - // 현재 위치와 중앙과의 거리 계산 (0~1 사이 값) - float distance = Math.Max(Math.Abs(row - boardCenterPos), Math.Abs(col - boardCenterPos)) / boardCenterPos; - - // 중앙(거리 0)은 1.2배, 가장자리(거리 1)는 0.8배 - return 1.2f - (0.4f * distance); - } - - // 삼삼(3-3) 감지 함수 - private static int DetectDoubleThree(List<(int row, int col, int[] dir)> openThreePositions) - { - int doubleThreeCount = 0; - var checkedPairs = new HashSet<(int, int)>(); - - for (int i = 0; i < openThreePositions.Count; i++) - { - var (row1, col1, dir1) = openThreePositions[i]; - - for (int j = i + 1; j < openThreePositions.Count; j++) - { - var (row2, col2, dir2) = openThreePositions[j]; - - // 같은 돌에서 다른 방향으로 두 개의 열린 3이 형성된 경우 - if (row1 == row2 && col1 == col2 && !AreParallelDirections(dir1, dir2)) - { - if (!checkedPairs.Contains((row1, col1))) - { - doubleThreeCount++; - checkedPairs.Add((row1, col1)); - } - } - } - } - - return doubleThreeCount; - } - - // 방향이 평행한지 확인하는 함수 - private static bool AreParallelDirections(int[] dir1, int[] dir2) - { - return (dir1[0] == dir2[0] && dir1[1] == dir2[1]) || - (dir1[0] == -dir2[0] && dir1[1] == -dir2[1]); - } - - // 사사(4-4) 감지 함수 - private static int DetectDoubleFour(List<(int row, int col, int[] dir)> fourPositions) - { - int doubleFourCount = 0; - var checkedPairs = new HashSet<(int, int)>(); - - for (int i = 0; i < fourPositions.Count; i++) - { - var (row1, col1, dir1) = fourPositions[i]; - - for (int j = i + 1; j < fourPositions.Count; j++) - { - var (row2, col2, dir2) = fourPositions[j]; - - if (row1 == row2 && col1 == col2 && !AreParallelDirections(dir1, dir2)) - { - if (!checkedPairs.Contains((row1, col1))) - { - doubleFourCount++; - checkedPairs.Add((row1, col1)); - } - } - } - } - - return doubleFourCount; - } - - // 사삼(4-3) 감지 함수 - private static int DetectFourThree(List<(int row, int col, int[] dir)> fourPositions, - List<(int row, int col, int[] dir)> openThreePositions) - { - int fourThreeCount = 0; - var checkedPairs = new HashSet<(int, int)>(); - - foreach (var (row1, col1, _) in fourPositions) - { - foreach (var (row2, col2, _) in openThreePositions) - { - // 같은 돌에서 4와 열린 3이 동시에 형성된 경우 - if (row1 == row2 && col1 == col2) - { - if (!checkedPairs.Contains((row1, col1))) - { - fourThreeCount++; - checkedPairs.Add((row1, col1)); - } - } - } - } - - return fourThreeCount; - } - -#endregion -*/ }