From 3eb595b143b436e8633bef6a91b4e826e6f7b773 Mon Sep 17 00:00:00 2001 From: Sehyeon Date: Wed, 19 Mar 2025 15:02:35 +0900 Subject: [PATCH] =?UTF-8?q?DO-4=20[Style]=20=EA=B3=B5=EC=9C=A0=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=B3=80=EC=88=98=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Script/AI/AIConstants.cs | 12 ++++ Assets/Script/AI/AIConstants.cs.meta | 3 + Assets/Script/AI/AIEvaluator.cs | 82 +++++++++++++++++++++---- Assets/Script/AI/MiniMaxAIController.cs | 24 ++++---- Assets/Script/Game/GameLogic.cs | 5 +- 5 files changed, 100 insertions(+), 26 deletions(-) create mode 100644 Assets/Script/AI/AIConstants.cs create mode 100644 Assets/Script/AI/AIConstants.cs.meta diff --git a/Assets/Script/AI/AIConstants.cs b/Assets/Script/AI/AIConstants.cs new file mode 100644 index 0000000..647b089 --- /dev/null +++ b/Assets/Script/AI/AIConstants.cs @@ -0,0 +1,12 @@ +// AI에서만 사용하는 상수 모음 +public class AIConstants +{ + // 방향 상수 + public static readonly int[][] Directions = new int[][] + { + new int[] {1, 0}, // 수직 + new int[] {0, 1}, // 수평 + new int[] {1, 1}, // 대각선 ↘ ↖ + new int[] {1, -1} // 대각선 ↙ ↗ + }; +} \ No newline at end of file diff --git a/Assets/Script/AI/AIConstants.cs.meta b/Assets/Script/AI/AIConstants.cs.meta new file mode 100644 index 0000000..75205fc --- /dev/null +++ b/Assets/Script/AI/AIConstants.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: de0993e48b9548668a73768a38c11b6d +timeCreated: 1742362879 \ No newline at end of file diff --git a/Assets/Script/AI/AIEvaluator.cs b/Assets/Script/AI/AIEvaluator.cs index c215406..3ad13f6 100644 --- a/Assets/Script/AI/AIEvaluator.cs +++ b/Assets/Script/AI/AIEvaluator.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEngine; public static class AIEvaluator { // 패턴 가중치 상수 - public static class PatternScore + public struct PatternScore { // AI 패턴 점수 public const float FIVE_IN_A_ROW = 100000f; @@ -30,15 +31,8 @@ public static class AIEvaluator public const float CENTER_WEIGHT = 1.2f; public const float EDGE_WEIGHT = 0.8f; } - - // 방향 상수 -> public으로 빼기 - private static readonly int[][] Directions = new int[][] - { - new int[] {1, 0}, // 수직 - new int[] {0, 1}, // 수평 - new int[] {1, 1}, // 대각선 ↘ ↖ - new int[] {1, -1} // 대각선 ↙ ↗ - }; + + private static readonly int[][] Directions = AIConstants.Directions; // 보드 전체 상태 평가 public static float EvaluateBoard(Enums.PlayerType[,] board, Enums.PlayerType aiPlayer) @@ -102,7 +96,7 @@ public static class AIEvaluator // 위치 가중치 적용 patternScore *= positionWeight; - // 최종 점수 적용 (플레이어는 음수) + // 최종 점수 (플레이어는 음수) score += playerScore * patternScore; } } @@ -310,19 +304,24 @@ public static class AIEvaluator return fourThreeCount; } - // 이동 평가 함수 (EvaluateMove 대체) + // 이동 평가 함수 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) { var (count, openEnds) = MiniMaxAIController.CountStones(board, row, col, dir, AIPlayer, false); + aiPatterns.Add((dir, count, openEnds)); if (count >= 4) { @@ -347,12 +346,16 @@ public static class AIEvaluator } } + // 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) @@ -378,6 +381,8 @@ public static class AIEvaluator } } + score += EvaluateComplexMovePatterns(opponentPatterns, false); + // 원래 상태로 복원 board[row, col] = Enums.PlayerType.None; @@ -391,4 +396,57 @@ public static class AIEvaluator 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; + } } diff --git a/Assets/Script/AI/MiniMaxAIController.cs b/Assets/Script/AI/MiniMaxAIController.cs index 87494d9..a514523 100644 --- a/Assets/Script/AI/MiniMaxAIController.cs +++ b/Assets/Script/AI/MiniMaxAIController.cs @@ -8,19 +8,14 @@ public static class MiniMaxAIController private const int SEARCH_DEPTH = 3; // 탐색 깊이 제한 (3 = 빠른 응답, 4 = 좀 더 강한 AI 그러나 느린) private const int WIN_COUNT = 5; - private static int[][] _directions = new int[][] - { - new int[] {1, 0}, // 수직 - new int[] {0, 1}, // 수평 - new int[] {1, 1}, // 대각선 ↘ ↖ - new int[] {1, -1} // 대각선 ↙ ↗ - }; + private static int[][] _directions = AIConstants.Directions; private static int _playerLevel = 1; // 급수 설정 private static float _mistakeMove; + private static Enums.PlayerType _AIPlayerType = Enums.PlayerType.PlayerB; - private static System.Random _random = new System.Random(); + private static System.Random _random = new System.Random(); // 랜덤 실수용 Random 함수 // 중복 계산을 방지하기 위한 캐싱 데이터. 위치 기반 (그리드 기반 해시맵) private static Dictionary<(int row, int col), Dictionary<(int dirX, int dirY), (int count, int openEnds)>> @@ -36,7 +31,7 @@ public static class MiniMaxAIController public static void SetLevel(int level) { _playerLevel = level; - + // 레벨에 따른 실수률? 설정 _mistakeMove = GetMistakeProbability(_playerLevel); } @@ -158,7 +153,10 @@ public static class MiniMaxAIController // score가 높은 순으로 정렬 -> 더 좋은 수 먼저 계산하도록 함 validMoves.Sort((a, b) => b.Item3.CompareTo(a.Item3)); - return validMoves; + + // 상위 10-15개만 고려. 일단 15개 + return validMoves.Take(15).ToList(); + // return validMoves; } private static bool HasNearbyStones(Enums.PlayerType[,] board, int row, int col, int distance = 3) @@ -274,11 +272,13 @@ public static class MiniMaxAIController #endregion // 최근에 둔 돌 위치 기반으로 게임 승리를 판별하는 함수 - public static bool CheckGameWin(Enums.PlayerType player, Enums.PlayerType[,] board, int row, int col) + // !!!!!!MinimaxAIController 밖의 cs파일은 호출 시 맨 마지막을 false로 지정해야 합니다.!!!!!! + public static bool CheckGameWin(Enums.PlayerType player, Enums.PlayerType[,] board, + int row, int col, bool isSavedCache = true) { foreach (var dir in _directions) { - var (count, _) = CountStones(board, row, col, dir, player); + var (count, _) = CountStones(board, row, col, dir, player, isSavedCache); // 자기 자신 포함하여 5개 이상일 시 true 반환 if (count + 1 >= WIN_COUNT) diff --git a/Assets/Script/Game/GameLogic.cs b/Assets/Script/Game/GameLogic.cs index 688b390..1a29333 100644 --- a/Assets/Script/Game/GameLogic.cs +++ b/Assets/Script/Game/GameLogic.cs @@ -261,7 +261,7 @@ public class GameLogic : MonoBehaviour ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.Black, row, col); //기보 데이터 저장 break; case Enums.PlayerType.PlayerB: - /* + // AI 테스트 시작 OmokAI.Instance.StartBestMoveSearch(_board, (bestMove) => { @@ -277,14 +277,15 @@ public class GameLogic : MonoBehaviour } }); // AI 테스트 끝 - */ + /* stoneController.SetStoneType(Enums.StoneType.White, row, col); stoneController.SetStoneState(Enums.StoneState.LastPositioned, row, col); _board[row, col] = Enums.PlayerType.PlayerB; LastNSelectedSetting(row, col); ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.White, row, col); + */ break; } }