DO-13-AI-난이도-조절

Do 13 AI 난이도 조절
This commit is contained in:
Sehyeon 2025-03-25 13:03:37 +09:00 committed by GitHub
commit e1dd037dc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 178 additions and 19790 deletions

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: d652b2a2f29c0c541983b529f66a5169
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: e61de5ff6b71b2e45b0878ccd8c8033a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -315,8 +315,8 @@ public static class AIEvaluator
return fourThreeCount; return fourThreeCount;
} }
// 깨진 패턴 (3-빈칸-1) 감지 // 깨진 패턴 (ex. 3-빈칸-1) 감지
private static (bool isDetected, int count, int openEnds) DetectBrokenPattern( public static (bool isDetected, int count, int openEnds) DetectBrokenPattern(
Enums.PlayerType[,] board, int row, int col, int[] dir, Enums.PlayerType player) Enums.PlayerType[,] board, int row, int col, int[] dir, Enums.PlayerType player)
{ {
int size = board.GetLength(0); int size = board.GetLength(0);
@ -403,15 +403,15 @@ public static class AIEvaluator
} }
else if (count == 4) // 깨진 4 else if (count == 4) // 깨진 4
{ {
return (openEnds == 2) ? PatternScore.OPEN_FOUR * 0.8f : return (openEnds == 2) ? PatternScore.OPEN_FOUR * 0.9f :
(openEnds == 1) ? PatternScore.HALF_OPEN_FOUR * 0.8f : (openEnds == 1) ? PatternScore.HALF_OPEN_FOUR * 0.9f :
PatternScore.CLOSED_FOUR * 0.7f; PatternScore.CLOSED_FOUR * 0.8f;
} }
else if (count == 3) // 깨진 3 else if (count == 3) // 깨진 3
{ {
return (openEnds == 2) ? PatternScore.OPEN_THREE * 0.7f : return (openEnds == 2) ? PatternScore.OPEN_THREE * 0.9f :
(openEnds == 1) ? PatternScore.HALF_OPEN_THREE * 0.7f : (openEnds == 1) ? PatternScore.HALF_OPEN_THREE * 0.9f :
PatternScore.CLOSED_THREE * 0.6f; PatternScore.CLOSED_THREE * 0.8f;
} }
return 0; return 0;
@ -443,24 +443,24 @@ public static class AIEvaluator
float normalScore = 0; float normalScore = 0;
if (count >= 4) if (count >= 4)
{ {
normalScore = PatternScore.FIVE_IN_A_ROW / 10; normalScore = PatternScore.FIVE_IN_A_ROW / 9;
} }
else if (count == 3) else if (count == 3)
{ {
normalScore = (openEnds == 2) ? PatternScore.OPEN_THREE / 3 : normalScore = (openEnds == 2) ? PatternScore.OPEN_THREE / 2.5f :
(openEnds == 1) ? PatternScore.HALF_OPEN_THREE / 5 : (openEnds == 1) ? PatternScore.HALF_OPEN_THREE / 3.5f :
PatternScore.CLOSED_THREE / 5; PatternScore.CLOSED_THREE / 4.5f;
} }
else if (count == 2) else if (count == 2)
{ {
normalScore = (openEnds == 2) ? PatternScore.OPEN_TWO / 2 : normalScore = (openEnds == 2) ? PatternScore.OPEN_TWO / 3.0f :
(openEnds == 1) ? PatternScore.HALF_OPEN_TWO / 3 : (openEnds == 1) ? PatternScore.HALF_OPEN_TWO / 4.0f :
PatternScore.CLOSED_TWO / 5; PatternScore.CLOSED_TWO / 5.0f;
} }
else if (count == 1) else if (count == 1)
{ {
normalScore = (openEnds == 2) ? PatternScore.OPEN_ONE : normalScore = (openEnds == 2) ? PatternScore.OPEN_ONE / 4.0f :
PatternScore.CLOSED_ONE; PatternScore.CLOSED_ONE / 5.0f;
} }
// 깨진 패턴 평가 // 깨진 패턴 평가
@ -469,7 +469,7 @@ public static class AIEvaluator
if (isBroken) if (isBroken)
{ {
brokenScore = EvaluateBrokenPattern(brokenCount, brokenOpenEnds); brokenScore = EvaluateBrokenPattern(brokenCount, brokenOpenEnds) * 1.5f;
} }
directionScore = Math.Max(normalScore, brokenScore); directionScore = Math.Max(normalScore, brokenScore);
@ -491,27 +491,28 @@ public static class AIEvaluator
opponentPatterns.Add((dir, count, openEnds)); opponentPatterns.Add((dir, count, openEnds));
float normalScore = 0; float normalScore = 0;
// 상대 패턴 차단에 대한 가치 (약간 낮은 가중치) AI는 공격지향적으로
if (count >= 4) if (count >= 4)
{ {
normalScore = PatternScore.FIVE_IN_A_ROW / 12.5f; normalScore = PatternScore.FIVE_IN_A_ROW / 8.5f;
} }
else if (count == 3) else if (count == 3)
{ {
normalScore = (openEnds == 2) ? PatternScore.OPEN_THREE / 3.75f : // 일관된 분모 사용 (방어 가중치는 유지)
(openEnds == 1) ? PatternScore.HALF_OPEN_THREE / 6.25f : normalScore = (openEnds == 2) ? PatternScore.OPEN_THREE / 1.3f :
PatternScore.CLOSED_THREE / 6.25f; (openEnds == 1) ? PatternScore.HALF_OPEN_THREE / 3.2f :
PatternScore.CLOSED_THREE / 4.2f;
} }
else if (count == 2) else if (count == 2)
{ {
normalScore = (openEnds == 2) ? PatternScore.OPEN_TWO / 2.5f : normalScore = (openEnds == 2) ? PatternScore.OPEN_TWO / 2.7f :
(openEnds == 1) ? PatternScore.HALF_OPEN_TWO / 3.75f : (openEnds == 1) ? PatternScore.HALF_OPEN_TWO / 3.7f :
PatternScore.CLOSED_TWO / 5f; PatternScore.CLOSED_TWO / 4.7f;
} }
else if (count == 1) else if (count == 1)
{ {
normalScore = (openEnds == 2) ? PatternScore.OPEN_ONE / 1.25f : normalScore = (openEnds == 2) ? PatternScore.OPEN_ONE / 3.7f :
PatternScore.CLOSED_ONE; PatternScore.CLOSED_ONE / 4.7f;
} }
var (isBroken, brokenCount, brokenOpenEnds) = DetectBrokenPattern(board, row, col, dir, opponentPlayer); var (isBroken, brokenCount, brokenOpenEnds) = DetectBrokenPattern(board, row, col, dir, opponentPlayer);
@ -520,7 +521,7 @@ public static class AIEvaluator
if (isBroken) if (isBroken)
{ {
// 깨진 패턴은 일반 패턴보다 좀 더 높은 가중치 할당 // 깨진 패턴은 일반 패턴보다 좀 더 높은 가중치 할당
brokenScore = EvaluateBrokenPattern(brokenCount, brokenOpenEnds) * 0.9f; brokenScore = EvaluateBrokenPattern(brokenCount, brokenOpenEnds) * 1.2f;
} }
directionScore = Math.Max(normalScore, brokenScore); directionScore = Math.Max(normalScore, brokenScore);
@ -561,7 +562,7 @@ public static class AIEvaluator
if (!AreParallelDirections(openThrees[i].dir, openThrees[j].dir)) if (!AreParallelDirections(openThrees[i].dir, openThrees[j].dir))
{ {
float threeThreeScore = PatternScore.DOUBLE_THREE / 4; // 복합 패턴 가중치 float threeThreeScore = PatternScore.DOUBLE_THREE / 4; // 복합 패턴 가중치
score += isAI ? threeThreeScore : threeThreeScore * 1.1f; score += isAI ? threeThreeScore * 1.1f : threeThreeScore * 1.3f;
break; break;
} }
} }
@ -578,7 +579,7 @@ public static class AIEvaluator
if (!AreParallelDirections(fours[i].dir, fours[j].dir)) if (!AreParallelDirections(fours[i].dir, fours[j].dir))
{ {
float fourFourScore = PatternScore.DOUBLE_FOUR / 4; float fourFourScore = PatternScore.DOUBLE_FOUR / 4;
score += isAI ? fourFourScore : fourFourScore * 1.2f; score += isAI ? fourFourScore * 1.2f : fourFourScore * 1.5f;
break; break;
} }
} }
@ -589,7 +590,7 @@ public static class AIEvaluator
if (fours.Count > 0 && openThrees.Count > 0) if (fours.Count > 0 && openThrees.Count > 0)
{ {
float fourThreeScore = PatternScore.FOUR_THREE / 4; float fourThreeScore = PatternScore.FOUR_THREE / 4;
score += isAI ? fourThreeScore : fourThreeScore * 1.2f; score += isAI ? fourThreeScore * 1.1f : fourThreeScore * 1.4f;
} }
return score; return score;

View File

@ -10,12 +10,16 @@ public static class MiniMaxAIController
private static int[][] _directions = AIConstants.Directions; private static int[][] _directions = AIConstants.Directions;
private static int _playerLevel = 1; // 급수 설정 private static int _playerRating = 18; // 급수. 기본값 18(최하)
private static float _mistakeMove;
private static Enums.PlayerType _AIPlayerType = Enums.PlayerType.PlayerB; private static Enums.PlayerType _AIPlayerType = Enums.PlayerType.PlayerB;
private static System.Random _random = new System.Random(); // 랜덤 실수용 Random 함수 // 실수 관련 변수
private static float _baselineMistakeProb; // 기본 실수 확률
private static float _mistakeSeverity; // 실수의 심각도 (높을수록 더 나쁜 수를 둘 확률 증가)
private static int _consecutiveGoodMoves = 0; // 연속으로 좋은 수를 둔 횟수
private static int _turnsPlayed = 0; // 진행된 턴 수
private static System.Random _random = new System.Random();
// 중복 계산을 방지하기 위한 캐싱 데이터. 위치 기반 (그리드 기반 해시맵) // 중복 계산을 방지하기 위한 캐싱 데이터. 위치 기반 (그리드 기반 해시맵)
private static Dictionary<(int row, int col), Dictionary<(int dirX, int dirY), (int count, int openEnds)>> private static Dictionary<(int row, int col), Dictionary<(int dirX, int dirY), (int count, int openEnds)>>
@ -28,18 +32,17 @@ public static class MiniMaxAIController
} }
// 급수 설정 -> 실수 넣을 때 계산 // 급수 설정 -> 실수 넣을 때 계산
public static void SetLevel(int level) public static void SetRating(int level)
{ {
_playerLevel = level; _playerRating = level;
// 레벨에 따른 실수율? 설정 GetMistakeProbability(_playerRating);
_mistakeMove = GetMistakeProbability(_playerLevel);
} }
// 실수 확률 계산 함수 // 실수 확률 계산 함수
private static float GetMistakeProbability(int level) private static void GetMistakeProbability(int level)
{ {
// 레벨이 1일 때 실수 확률 0%, 레벨이 18일 때 실수 확률 50% _baselineMistakeProb = Math.Max(0.01f, (level - 1) / 17f * 0.34f); //1급 1%, 18급 35%
return (level - 1) / 17f * 0.5f; _mistakeSeverity = 1f - ((level - 1) / 17f); // 레벨이 낮을수록 심각한 실수를 함
} }
// return 값이 null 일 경우 == 보드에 칸 꽉 참 // return 값이 null 일 경우 == 보드에 칸 꽉 참
@ -50,7 +53,7 @@ public static class MiniMaxAIController
float bestScore = float.MinValue; float bestScore = float.MinValue;
(int row, int col)? bestMove = null; (int row, int col)? bestMove = null;
(int row, int col)? secondBestMove = null; List<(int row, int col)>? fiveInARowMoves = null;
List<(int row, int col, float score)> validMoves = GetValidMoves(board); List<(int row, int col, float score)> validMoves = GetValidMoves(board);
// 보드에 놓을 수 있는 자리가 있는지 확인 // 보드에 놓을 수 있는 자리가 있는지 확인
@ -59,11 +62,22 @@ public static class MiniMaxAIController
return null; return null;
} }
// 5연승 가능한 자리를 먼저 찾아서 우선적으로 설정 // 즉시 승리 가능한 자리를 먼저 찾아서 우선적으로 설정
List<(int row, int col)> fiveInARowMoves = GetFiveInARowCandidateMoves(board); fiveInARowMoves = GetFiveInARowCandidateMoves(board, _AIPlayerType);
if (fiveInARowMoves.Count > 0) if (fiveInARowMoves != null & fiveInARowMoves.Count > 0)
{ {
bestMove = fiveInARowMoves[0]; bestMove = fiveInARowMoves[0];
_turnsPlayed++;
return bestMove;
}
// 즉시 패배 가능한 자리를 먼저 찾아서 우선적으로 설정
// var oppositePlayer = _AIPlayerType == Enums.PlayerType.PlayerB ? Enums.PlayerType.PlayerA : Enums.PlayerType.PlayerB;
fiveInARowMoves = GetFiveInARowCandidateMoves(board, Enums.PlayerType.PlayerA);
if (fiveInARowMoves != null & fiveInARowMoves.Count > 0)
{
bestMove = fiveInARowMoves[0];
_turnsPlayed++;
return bestMove; return bestMove;
} }
@ -76,26 +90,82 @@ public static class MiniMaxAIController
if (score > bestScore) if (score > bestScore)
{ {
bestScore = score; bestScore = score;
if (bestMove != null)
{
secondBestMove = bestMove;
}
bestMove = (row, col); bestMove = (row, col);
} }
} }
// 랜덤 실수 // 랜덤 실수
if (secondBestMove != null && _random.NextDouble() < _mistakeMove) float currentMistakeProb = CalculateDynamicMistakeProb(board, validMoves); // 실수 확률 동적 조정
// 실수 확률에 따라 실수 여부 결정
if (_random.NextDouble() < currentMistakeProb)
{ {
Debug.Log("AI Mistake"); int moveIndex = SelectMistakeMove(validMoves);
return secondBestMove; _consecutiveGoodMoves = 0; // 실수했으므로 연속 카운터 리셋
var mistakeMove = (validMoves[moveIndex].row, validMoves[moveIndex].col);
// Debug.Log($"AI Mistake: 최적 점수 {validMoves[0].score}대신 {validMoves[moveIndex].score} 선택 (실수 확률: {currentMistakeProb:P2})");
_turnsPlayed++;
return mistakeMove;
} }
// 실수X
_consecutiveGoodMoves++;
_turnsPlayed++;
return bestMove; return bestMove;
} }
#region Mistake Code
// 동적 실수 확률 계산 (게임 상황에 따라 조정)
private static float CalculateDynamicMistakeProb(Enums.PlayerType[,] board, List<(int row, int col, float score)> validMoves)
{
float mistakeProb = _baselineMistakeProb;
// 1. 턴 수에 따라 조정
if (_turnsPlayed < 5) // 초반에는 실수 확률 감소
{
mistakeProb *= 0.5f;
}
// 2. 연속적인 좋은 수에 따른 조정 (집중력 저하 효과)
if (_consecutiveGoodMoves > 3)
{
// 연속으로 좋은 수를 두면 집중력이 저하되어 실수 확률 증가
mistakeProb += (_consecutiveGoodMoves - 3) * 0.03f;
}
// 3. 게임 후반 집중력 향상 (마지막 몇 수는 집중)
if (_turnsPlayed > 78) {
mistakeProb *= 0.7f; // 종반에는 더 집중
}
return Math.Min(mistakeProb, 0.8f); // 최대 80%로 제한
}
// 실수 선택(경미 ~ 심각)
private static int SelectMistakeMove(List<(int row, int col, float score)> moves)
{
int moveCount = moves.Count;
// _mistakeSeverity가 높을수록(레벨이 낮을수록) 더 심각한 실수를 할 확률 증가
float severityFactor = (float)Math.Pow(_random.NextDouble(), 1 / _mistakeSeverity);
// 상위 수들 중에서는 약간 안 좋은 수를, 하위 수들 중에서는 매우 안 좋은 수를 선택
int mistakeIndex = Math.Min(1 + (int)(severityFactor * (moveCount - 1)), moveCount - 1);
// 가끔 완전히 랜덤한 수 선택 (매우 낮은 확률)
if (_random.NextDouble() < 0.05 * _mistakeSeverity)
{
mistakeIndex = _random.Next(1, moveCount);
}
return mistakeIndex;
}
#endregion
private static float DoMinimax(Enums.PlayerType[,] board, int depth, bool isMaximizing, float alpha, float beta, private static float DoMinimax(Enums.PlayerType[,] board, int depth, bool isMaximizing, float alpha, float beta,
int recentRow, int recentCol) int recentRow, int recentCol)
{ {
@ -136,6 +206,8 @@ public static class MiniMaxAIController
private static List<(int row, int col, float score)> GetValidMoves(Enums.PlayerType[,] board) private static List<(int row, int col, float score)> GetValidMoves(Enums.PlayerType[,] board)
{ {
List<(int, int, float)> validMoves = new List<(int, int, float)>(); List<(int, int, float)> validMoves = new List<(int, int, float)>();
List<(int, int, float)> allMoves = new List<(int, int, float)>();
int size = board.GetLength(0); int size = board.GetLength(0);
for (int row = 0; row < size; row++) for (int row = 0; row < size; row++)
@ -146,16 +218,26 @@ public static class MiniMaxAIController
{ {
// 보드 전체가 아닌 해당 돌에 대해서만 Score 계산 // 보드 전체가 아닌 해당 돌에 대해서만 Score 계산
float score = AIEvaluator.EvaluateMove(board, row, col, _AIPlayerType); float score = AIEvaluator.EvaluateMove(board, row, col, _AIPlayerType);
validMoves.Add((row, col, score)); allMoves.Add((row, col, score));
} }
} }
} }
// score가 높은 순으로 정렬 -> 더 좋은 수 먼저 계산하도록 함 // score가 높은 순으로 정렬 -> 더 좋은 수 먼저 계산하도록 함
validMoves.Sort((a, b) => b.Item3.CompareTo(a.Item3)); allMoves.Sort((a, b) => b.Item3.CompareTo(a.Item3));
// 시간 단축을 위해 상위 10-15개만 고려. int topCount = Math.Min(8, allMoves.Count); // 상위 8개 (또는 가능한 최대)
return validMoves.Take(10).ToList(); validMoves.AddRange(allMoves.Take(topCount));
// 중간 범위의 점수를 가진 수도 일부 포함 (전략적 게임을 위해서)
if (allMoves.Count > topCount + 10) // 10개 이상의 후보가 있을 때만
{
var middleIndex = allMoves.Count / 2;
var middleMoves = allMoves.Skip(middleIndex - 1).Take(2); // 중간 부분에서 2개 선택
validMoves.AddRange(middleMoves);
}
return validMoves;
} }
private static bool HasNearbyStones(Enums.PlayerType[,] board, int row, int col, int distance = 3) private static bool HasNearbyStones(Enums.PlayerType[,] board, int row, int col, int distance = 3)
@ -286,7 +368,7 @@ public static class MiniMaxAIController
} }
// 5목이 될 수 있는 위치 찾기 // 5목이 될 수 있는 위치 찾기
private static List<(int row, int col)> GetFiveInARowCandidateMoves(Enums.PlayerType[,] board) private static List<(int row, int col)> GetFiveInARowCandidateMoves(Enums.PlayerType[,] board, Enums.PlayerType currentPlayer)
{ {
List<(int row, int col)> fiveInARowMoves = new List<(int, int)>(); List<(int row, int col)> fiveInARowMoves = new List<(int, int)>();
int size = board.GetLength(0); int size = board.GetLength(0);
@ -299,12 +381,21 @@ public static class MiniMaxAIController
foreach (var dir in _directions) foreach (var dir in _directions)
{ {
var (count, openEnds) = CountStones(board, row, col, dir, _AIPlayerType); var (count, openEnds) = CountStones(board, row, col, dir, currentPlayer, false);
if (count == 4 && openEnds > 0) if (count + 1 == WIN_COUNT && openEnds > 0) // 일반 패턴 (연속 4돌)
{ {
fiveInARowMoves.Add((row, col)); return new List<(int row, int col)> { (row, col) }; // 하나 나오면 바로 return (시간 단축)
break; // 하나 나오면 바로 break (시간 단축) }
if (count >= 2) // 깨진 패턴 평가
{
var (isBroken, brokenCount, _) = AIEvaluator.DetectBrokenPattern(board, row, col, dir, currentPlayer);
if (isBroken && brokenCount + 1 >= WIN_COUNT) // 자기 자신 포함
{
return new List<(int row, int col)> { (row, col) };
}
} }
} }
} }

View File

@ -1,8 +1,8 @@
using System; using System;
using UnityEngine;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine.SceneManagement;
public class OmokAI : MonoBehaviour public class OmokAI : Singleton<OmokAI>
{ {
public static OmokAI Instance; public static OmokAI Instance;
@ -11,6 +11,17 @@ public class OmokAI : MonoBehaviour
Instance = this; Instance = this;
} }
// AI가 Player B가 아닌 경우 해당 메서드로 설정. 기본값은 PlayerB
public void SetAIPlayerType(Enums.PlayerType AIPlayerType)
{
MiniMaxAIController.SetAIPlayerType(AIPlayerType);
}
public void SetRating(int level)
{
MiniMaxAIController.SetRating(level);
}
public async void StartBestMoveSearch(Enums.PlayerType[,] board, Action<(int, int)?> callback) public async void StartBestMoveSearch(Enums.PlayerType[,] board, Action<(int, int)?> callback)
{ {
(int row, int col)? bestMove = await Task.Run(() => MiniMaxAIController.GetBestMove(board)); (int row, int col)? bestMove = await Task.Run(() => MiniMaxAIController.GetBestMove(board));
@ -23,4 +34,6 @@ public class OmokAI : MonoBehaviour
bool isWin = MiniMaxAIController.CheckGameWin(player, board, row, col, false); bool isWin = MiniMaxAIController.CheckGameWin(player, board, row, col, false);
return isWin; return isWin;
} }
protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode) { }
} }

View File

@ -184,14 +184,6 @@ public class GameLogic : MonoBehaviour
private List<Vector2Int> _forbiddenMoves = new List<Vector2Int>(); private List<Vector2Int> _forbiddenMoves = new List<Vector2Int>();
#endregion #endregion
private static int[][] _directions = new int[][]
{
new int[] {1, 0}, // 수직
new int[] {0, 1}, // 수평
new int[] {1, 1}, // 대각선 ↘ ↖
new int[] {1, -1} // 대각선 ↙ ↗
};
public GameLogic(StoneController stoneController, Enums.GameType gameType, FioTimer fioTimer = null) public GameLogic(StoneController stoneController, Enums.GameType gameType, FioTimer fioTimer = null)
{ {
//보드 초기화 //보드 초기화
@ -238,6 +230,8 @@ public class GameLogic : MonoBehaviour
case Enums.GameType.SinglePlay: case Enums.GameType.SinglePlay:
firstPlayerState = new PlayerState(true); firstPlayerState = new PlayerState(true);
secondPlayerState = new AIState(); secondPlayerState = new AIState();
// AI 난이도 설정(급수 설정)
OmokAI.Instance.SetRating(UserManager.Instance.Rating);
//유저 이름 사진 초기화 //유저 이름 사진 초기화
GameManager.Instance.InitPlayersName(UserManager.Instance.Nickname, "AIPlayer"); GameManager.Instance.InitPlayersName(UserManager.Instance.Nickname, "AIPlayer");
@ -365,17 +359,9 @@ public class GameLogic : MonoBehaviour
//승리 확인 함수 //승리 확인 함수
public bool CheckGameWin(Enums.PlayerType player, int row, int col) public bool CheckGameWin(Enums.PlayerType player, int row, int col)
{ {
foreach (var dir in _directions) return OmokAI.Instance.CheckGameWin(player, _board, row, col);
{
var (count, _) = CountStones(_board, row, col, dir, player);
// 자기 자신 포함하여 5개 이상일 시 true 반환
if (count + 1 >= Constants.WIN_COUNT)
return true;
}
return false;
} }
// 특정 방향으로 같은 돌 개수와 열린 끝 개수를 계산하는 함수 // 특정 방향으로 같은 돌 개수와 열린 끝 개수를 계산하는 함수
private (int count, int openEnds) CountStones( private (int count, int openEnds) CountStones(
Enums.PlayerType[,] board, int row, int col, int[] direction, Enums.PlayerType player) Enums.PlayerType[,] board, int row, int col, int[] direction, Enums.PlayerType player)
@ -440,7 +426,7 @@ public class GameLogic : MonoBehaviour
{ {
if (tempBoard[row, col] != Enums.PlayerType.None) continue; if (tempBoard[row, col] != Enums.PlayerType.None) continue;
tempBoard[row, col] = player; tempBoard[row, col] = player;
foreach (var dir in _directions) foreach (var dir in AIConstants.Directions)
{ {
var (count, _) = CountStones(tempBoard, row, col, dir, player); var (count, _) = CountStones(tempBoard, row, col, dir, player);