DO-4 [Style] 공유되는 변수 정리

This commit is contained in:
Sehyeon 2025-03-19 15:02:35 +09:00
parent b382e82059
commit 3eb595b143
5 changed files with 100 additions and 26 deletions

View File

@ -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} // 대각선 ↙ ↗
};
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: de0993e48b9548668a73768a38c11b6d
timeCreated: 1742362879

View File

@ -1,11 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using UnityEngine; using UnityEngine;
public static class AIEvaluator public static class AIEvaluator
{ {
// 패턴 가중치 상수 // 패턴 가중치 상수
public static class PatternScore public struct PatternScore
{ {
// AI 패턴 점수 // AI 패턴 점수
public const float FIVE_IN_A_ROW = 100000f; 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 CENTER_WEIGHT = 1.2f;
public const float EDGE_WEIGHT = 0.8f; public const float EDGE_WEIGHT = 0.8f;
} }
// 방향 상수 -> public으로 빼기 private static readonly int[][] Directions = AIConstants.Directions;
private static readonly int[][] Directions = new int[][]
{
new int[] {1, 0}, // 수직
new int[] {0, 1}, // 수평
new int[] {1, 1}, // 대각선 ↘ ↖
new int[] {1, -1} // 대각선 ↙ ↗
};
// 보드 전체 상태 평가 // 보드 전체 상태 평가
public static float EvaluateBoard(Enums.PlayerType[,] board, Enums.PlayerType aiPlayer) public static float EvaluateBoard(Enums.PlayerType[,] board, Enums.PlayerType aiPlayer)
@ -102,7 +96,7 @@ public static class AIEvaluator
// 위치 가중치 적용 // 위치 가중치 적용
patternScore *= positionWeight; patternScore *= positionWeight;
// 최종 점수 적용 (플레이어는 음수) // 최종 점수 (플레이어는 음수)
score += playerScore * patternScore; score += playerScore * patternScore;
} }
} }
@ -310,19 +304,24 @@ public static class AIEvaluator
return fourThreeCount; return fourThreeCount;
} }
// 이동 평가 함수 (EvaluateMove 대체) // 이동 평가 함수
public static float EvaluateMove(Enums.PlayerType[,] board, int row, int col, Enums.PlayerType AIPlayer) public static float EvaluateMove(Enums.PlayerType[,] board, int row, int col, Enums.PlayerType AIPlayer)
{ {
float score = 0; float score = 0;
Enums.PlayerType opponentPlayer = (AIPlayer == Enums.PlayerType.PlayerA) ? Enums.PlayerType opponentPlayer = (AIPlayer == Enums.PlayerType.PlayerA) ?
Enums.PlayerType.PlayerB : 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 관점에서 평가 // AI 관점에서 평가
board[row, col] = AIPlayer; board[row, col] = AIPlayer;
foreach (var dir in Directions) foreach (var dir in Directions)
{ {
var (count, openEnds) = MiniMaxAIController.CountStones(board, row, col, dir, AIPlayer, false); var (count, openEnds) = MiniMaxAIController.CountStones(board, row, col, dir, AIPlayer, false);
aiPatterns.Add((dir, count, openEnds));
if (count >= 4) if (count >= 4)
{ {
@ -347,12 +346,16 @@ public static class AIEvaluator
} }
} }
// AI 복합 패턴 점수 계산 (새로 추가)
score += EvaluateComplexMovePatterns(aiPatterns, true);
// 상대 관점에서 평가 (방어 가치) // 상대 관점에서 평가 (방어 가치)
board[row, col] = opponentPlayer; board[row, col] = opponentPlayer;
foreach (var dir in Directions) foreach (var dir in Directions)
{ {
var (count, openEnds) = MiniMaxAIController.CountStones(board, row, col, dir, opponentPlayer, false); var (count, openEnds) = MiniMaxAIController.CountStones(board, row, col, dir, opponentPlayer, false);
opponentPatterns.Add((dir, count, openEnds));
// 상대 패턴 차단에 대한 가치 (약간 낮은 가중치) // 상대 패턴 차단에 대한 가치 (약간 낮은 가중치)
if (count >= 4) if (count >= 4)
@ -378,6 +381,8 @@ public static class AIEvaluator
} }
} }
score += EvaluateComplexMovePatterns(opponentPatterns, false);
// 원래 상태로 복원 // 원래 상태로 복원
board[row, col] = Enums.PlayerType.None; board[row, col] = Enums.PlayerType.None;
@ -391,4 +396,57 @@ public static class AIEvaluator
return score * centerBonus; 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;
}
} }

View File

@ -8,19 +8,14 @@ public static class MiniMaxAIController
private const int SEARCH_DEPTH = 3; // 탐색 깊이 제한 (3 = 빠른 응답, 4 = 좀 더 강한 AI 그러나 느린) private const int SEARCH_DEPTH = 3; // 탐색 깊이 제한 (3 = 빠른 응답, 4 = 좀 더 강한 AI 그러나 느린)
private const int WIN_COUNT = 5; private const int WIN_COUNT = 5;
private static int[][] _directions = new int[][] private static int[][] _directions = AIConstants.Directions;
{
new int[] {1, 0}, // 수직
new int[] {0, 1}, // 수평
new int[] {1, 1}, // 대각선 ↘ ↖
new int[] {1, -1} // 대각선 ↙ ↗
};
private static int _playerLevel = 1; // 급수 설정 private static int _playerLevel = 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;
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)>> 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) public static void SetLevel(int level)
{ {
_playerLevel = level; _playerLevel = level;
// 레벨에 따른 실수률? 설정
_mistakeMove = GetMistakeProbability(_playerLevel); _mistakeMove = GetMistakeProbability(_playerLevel);
} }
@ -158,7 +153,10 @@ public static class MiniMaxAIController
// score가 높은 순으로 정렬 -> 더 좋은 수 먼저 계산하도록 함 // score가 높은 순으로 정렬 -> 더 좋은 수 먼저 계산하도록 함
validMoves.Sort((a, b) => b.Item3.CompareTo(a.Item3)); 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) private static bool HasNearbyStones(Enums.PlayerType[,] board, int row, int col, int distance = 3)
@ -274,11 +272,13 @@ public static class MiniMaxAIController
#endregion #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) foreach (var dir in _directions)
{ {
var (count, _) = CountStones(board, row, col, dir, player); var (count, _) = CountStones(board, row, col, dir, player, isSavedCache);
// 자기 자신 포함하여 5개 이상일 시 true 반환 // 자기 자신 포함하여 5개 이상일 시 true 반환
if (count + 1 >= WIN_COUNT) if (count + 1 >= WIN_COUNT)

View File

@ -261,7 +261,7 @@ public class GameLogic : MonoBehaviour
ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.Black, row, col); //기보 데이터 저장 ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.Black, row, col); //기보 데이터 저장
break; break;
case Enums.PlayerType.PlayerB: case Enums.PlayerType.PlayerB:
/*
// AI 테스트 시작 // AI 테스트 시작
OmokAI.Instance.StartBestMoveSearch(_board, (bestMove) => OmokAI.Instance.StartBestMoveSearch(_board, (bestMove) =>
{ {
@ -277,14 +277,15 @@ public class GameLogic : MonoBehaviour
} }
}); });
// AI 테스트 끝 // AI 테스트 끝
*/
/*
stoneController.SetStoneType(Enums.StoneType.White, row, col); stoneController.SetStoneType(Enums.StoneType.White, row, col);
stoneController.SetStoneState(Enums.StoneState.LastPositioned, row, col); stoneController.SetStoneState(Enums.StoneState.LastPositioned, row, col);
_board[row, col] = Enums.PlayerType.PlayerB; _board[row, col] = Enums.PlayerType.PlayerB;
LastNSelectedSetting(row, col); LastNSelectedSetting(row, col);
ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.White, row, col); ReplayManager.Instance.RecordStonePlaced(Enums.StoneType.White, row, col);
*/
break; break;
} }
} }