DO-13 [Fix] 가중치 재조정
This commit is contained in:
parent
871218b8ce
commit
bc2bd0df6a
@ -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
@ -1,7 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: e61de5ff6b71b2e45b0878ccd8c8033a
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
@ -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;
|
||||||
|
@ -10,7 +10,7 @@ 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 = 1; // 급수 설정
|
||||||
private static float _mistakeMove;
|
private static float _mistakeMove;
|
||||||
|
|
||||||
private static Enums.PlayerType _AIPlayerType = Enums.PlayerType.PlayerB;
|
private static Enums.PlayerType _AIPlayerType = Enums.PlayerType.PlayerB;
|
||||||
@ -28,11 +28,11 @@ public static class MiniMaxAIController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 급수 설정 -> 실수 넣을 때 계산
|
// 급수 설정 -> 실수 넣을 때 계산
|
||||||
public static void SetLevel(int level)
|
public static void SetRating(int level)
|
||||||
{
|
{
|
||||||
_playerLevel = level;
|
_playerRating = level;
|
||||||
// 레벨에 따른 실수율? 설정
|
// 레벨에 따른 실수율? 설정
|
||||||
_mistakeMove = GetMistakeProbability(_playerLevel);
|
_mistakeMove = GetMistakeProbability(_playerRating);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 실수 확률 계산 함수
|
// 실수 확률 계산 함수
|
||||||
@ -51,6 +51,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;
|
(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,9 +60,17 @@ 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];
|
||||||
|
return bestMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 즉시 패배 가능한 자리를 먼저 찾아서 우선적으로 설정
|
||||||
|
fiveInARowMoves = GetFiveInARowCandidateMoves(board, _AIPlayerType);
|
||||||
|
if (fiveInARowMoves != null & fiveInARowMoves.Count > 0)
|
||||||
{
|
{
|
||||||
bestMove = fiveInARowMoves[0];
|
bestMove = fiveInARowMoves[0];
|
||||||
return bestMove;
|
return bestMove;
|
||||||
@ -136,6 +145,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 +157,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 +307,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,7 +320,7 @@ 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 == 4 && openEnds > 0)
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,16 @@ public class OmokAI : MonoBehaviour
|
|||||||
Instance = this;
|
Instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
@ -179,14 +179,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)
|
||||||
{
|
{
|
||||||
//보드 초기화
|
//보드 초기화
|
||||||
@ -236,6 +228,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);
|
||||||
SetState(firstPlayerState);
|
SetState(firstPlayerState);
|
||||||
break;
|
break;
|
||||||
case Enums.GameType.MultiPlay:
|
case Enums.GameType.MultiPlay:
|
||||||
@ -352,17 +346,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)
|
||||||
@ -427,7 +413,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);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user