using System.Collections.Generic; using System.Linq; using UnityEngine; public class RenjuRuleChecker: ForbiddenDetectorBase { // 8방향을 나타내는 델타 배열 (가로, 세로, 대각선 방향) private static readonly int[,] directions = new int[8, 2] { { 1, 0 }, // 오른쪽 { 1, 1 }, // 오른쪽 아래 { 0, 1 }, // 아래 { -1, 1 }, // 왼쪽 아래 { -1, 0 }, // 왼쪽 { -1, -1 }, // 왼쪽 위 { 0, -1 }, // 위 { 1, -1 } // 오른쪽 위 }; // 기본 삼(3) 패턴 (흑돌 = 1, 빈칸 = 0) private static readonly int[][] threePatterns = new int[][] { new int[] { 0, 1, 1, 1, 0 }, // _OOO_ new int[] { 0, 1, 1, 0, 1, 0 }, // _OO_O_ new int[] { 0, 1, 0, 1, 1, 0 } // _O_OO_ }; // 기본 사(4) 패턴 (흑돌 = 1, 빈칸 = 0) private static readonly int[][] fourPatterns = new int[][] { new int[] { 0, 1, 1, 1, 1, 0 }, // _OOOO_ new int[] { 0, 1, 1, 1, 0, 1, 0 }, // _OOO_O_ new int[] { 0, 1, 1, 0, 1, 1, 0 }, // _OO_OO_ new int[] { 0, 1, 0, 1, 1, 1, 0 } // _O_OOO_ }; // 디버그 모드 활성화 (문제 해결 시 사용) private bool debugMode = false; /// /// 현재 보드 상태에서 흑돌(PlayerA)이 둘 수 없는 금수 위치 목록을 반환합니다. /// /// 현재 보드 상태 /// 금수 위치 목록 public List GetForbiddenMoves(Enums.PlayerType[,] board) { int width = board.GetLength(0); int height = board.GetLength(1); List forbiddenMoves = new List(); // 빈 칸마다 금수인지 확인 for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { // 빈 칸만 검사 if (board[x, y] != Enums.PlayerType.None) continue; // 해당 위치에 흑돌을 임시로 놓았다고 가정 Enums.PlayerType[,] simulatedBoard = (Enums.PlayerType[,])board.Clone(); simulatedBoard[x, y] = Enums.PlayerType.PlayerA; // 장목(6목 이상) 확인 if (IsOverline(simulatedBoard, x, y)) { forbiddenMoves.Add(new Vector2Int(x, y)); continue; } // 모든 방향별 패턴 정보 수집 PatternInfo patternInfo = CollectAllPatterns(simulatedBoard, x, y); // 4-3 패턴 확인 (이 위치에 놓았을 때 열린 4가 1개, 열린 3이 1개 이상 생성되면 4-3 패턴) bool isFourThree = IsFourThreeAdvanced(patternInfo); if (isFourThree) { // 4-3 패턴은 금수가 아님 continue; } // 쌍삼(3-3) 또는 사사(4-4) 확인 bool isThreeThree = IsThreeThreeAdvanced(patternInfo); bool isFourFour = IsFourFourAdvanced(patternInfo); // 금수 여부 판정 if (isThreeThree || isFourFour) { forbiddenMoves.Add(new Vector2Int(x, y)); } } } return forbiddenMoves; } /// /// 한 위치에서 모든 방향의 패턴 정보를 수집 /// private PatternInfo CollectAllPatterns(Enums.PlayerType[,] board, int x, int y) { int width = board.GetLength(0); int height = board.GetLength(1); PatternInfo patternInfo = new PatternInfo(); // 각 방향별로 패턴 수집 for (int dir = 0; dir < 4; dir++) { // 해당 방향에서 연속된 돌의 배열 얻기 List line = GetLineOfStones(board, x, y, dir, width, height, 8); // 열린 3 패턴 찾기 List threePatterns = FindOpenThreePatterns(line, x, y, dir); patternInfo.OpenThreePatterns.AddRange(threePatterns); // 열린 4 패턴 찾기 List fourPatterns = FindOpenFourPatterns(line, x, y, dir); patternInfo.OpenFourPatterns.AddRange(fourPatterns); // 방향별 돌 배열 저장 (디버깅용) if (debugMode) { patternInfo.LinesByDirection[dir] = line; } } return patternInfo; } /// /// 고도화된 4-3 패턴 확인 로직 /// private bool IsFourThreeAdvanced(PatternInfo patternInfo) { // 열린 4와 열린 3 패턴 확인 List fourPatterns = patternInfo.OpenFourPatterns; List threePatterns = patternInfo.OpenThreePatterns; if (fourPatterns.Count == 0 || threePatterns.Count == 0) { // 열린 4 또는 열린 3이 없으면 4-3 패턴이 아님 return false; } // 방향별로 패턴 그룹화 var fourPatternsByDir = fourPatterns.GroupBy(p => p.Direction).ToList(); var threePatternsByDir = threePatterns.GroupBy(p => p.Direction).ToList(); // 열린 4가 있는 각 방향에 대해 foreach (var fourGroup in fourPatternsByDir) { int fourDir = fourGroup.Key; // 열린 3이 있는 각 방향에 대해 foreach (var threeGroup in threePatternsByDir) { int threeDir = threeGroup.Key; // 서로 다른 방향이면 (열린 4와 열린 3이 다른 방향에 있으면) if (fourDir != threeDir) { // 4와 3 패턴이 서로 중첩되지 않는지 확인 bool patternsOverlap = false; foreach (var fourPattern in fourGroup) { foreach (var threePattern in threeGroup) { // 중심 돌 외에 다른 공유 돌이 있는지 확인 if (PatternsShareStones(fourPattern, threePattern)) { patternsOverlap = true; break; } } if (patternsOverlap) break; } // 중첩되지 않는 4-3 패턴 발견됨 if (!patternsOverlap) { return true; } } } } return false; } /// /// 두 패턴이 중심 돌 외에 다른 돌을 공유하는지 확인 /// private bool PatternsShareStones(OpenFourPattern fourPattern, OpenThreePattern threePattern) { // 중심 돌 외에 공유하는 돌이 있는지 확인 foreach (Vector2Int pos4 in fourPattern.StonePositions) { foreach (Vector2Int pos3 in threePattern.StonePositions) { // 두 패턴이 같은 좌표의 돌을 공유하고, 그것이 중심 돌이 아니면 if (pos4.x == pos3.x && pos4.y == pos3.y && !(pos4.x == fourPattern.CenterX && pos4.y == fourPattern.CenterY)) { return true; } } } return false; } /// /// 고도화된 삼삼 확인 로직 /// private bool IsThreeThreeAdvanced(PatternInfo patternInfo) { List threePatterns = patternInfo.OpenThreePatterns; // 열린 3 패턴이 2개 미만이면 삼삼이 아님 if (threePatterns.Count < 2) return false; // 방향별로 패턴 그룹화 var patternsByDir = threePatterns.GroupBy(p => p.Direction).ToList(); // 서로 다른 방향의 패턴 쌍을 검사 for (int i = 0; i < patternsByDir.Count; i++) { for (int j = i + 1; j < patternsByDir.Count; j++) { // 첫 번째 방향의 모든 패턴 var patternsDir1 = patternsByDir[i].ToList(); // 두 번째 방향의 모든 패턴 var patternsDir2 = patternsByDir[j].ToList(); // 두 방향의 패턴들이 서로 중첩되지 않는지 확인 bool foundNonOverlapping = false; foreach (var pattern1 in patternsDir1) { if (foundNonOverlapping) break; foreach (var pattern2 in patternsDir2) { // 중심 돌 외에 다른 공유 돌이 없으면 비중첩 삼삼 if (!PatternsOverlap(pattern1, pattern2)) { foundNonOverlapping = true; break; } } } // 비중첩 삼삼 발견 if (foundNonOverlapping) { return true; } } } return false; } /// /// 두 열린 3 패턴이 중첩되는지 확인 /// private bool PatternsOverlap(OpenThreePattern pattern1, OpenThreePattern pattern2) { // 중심 돌 외에 공유하는 돌이 있는지 확인 foreach (Vector2Int pos1 in pattern1.StonePositions) { foreach (Vector2Int pos2 in pattern2.StonePositions) { // 두 패턴이 같은 좌표의 돌을 공유하고, 그것이 중심 돌이 아니면 중첩 if (pos1.x == pos2.x && pos1.y == pos2.y && !(pos1.x == pattern1.CenterX && pos1.y == pattern1.CenterY)) { return true; } } } return false; } /// /// 고도화된 사사 확인 로직 /// private bool IsFourFourAdvanced(PatternInfo patternInfo) { List fourPatterns = patternInfo.OpenFourPatterns; // 열린 4 패턴이 2개 미만이면 사사가 아님 if (fourPatterns.Count < 2) return false; // 방향별로 패턴 그룹화 var patternsByDir = fourPatterns.GroupBy(p => p.Direction).ToList(); // 서로 다른 방향의 패턴이 2개 이상이면 사사 if (patternsByDir.Count >= 2) return true; // 같은 방향에서도 중첩되지 않는 패턴이 있는지 확인 foreach (var group in patternsByDir) { var patterns = group.ToList(); for (int i = 0; i < patterns.Count - 1; i++) { for (int j = i + 1; j < patterns.Count; j++) { // 중심 돌 외에 다른 공유 돌이 없으면 비중첩 사사 if (!FourPatternsOverlap(patterns[i], patterns[j])) { return true; } } } } return false; } /// /// 두 열린 4 패턴이 중첩되는지 확인 /// private bool FourPatternsOverlap(OpenFourPattern pattern1, OpenFourPattern pattern2) { // 중심 돌 외에 공유하는 돌이 있는지 확인 foreach (Vector2Int pos1 in pattern1.StonePositions) { foreach (Vector2Int pos2 in pattern2.StonePositions) { // 두 패턴이 같은 좌표의 돌을 공유하고, 그것이 중심 돌이 아니면 중첩 if (pos1.x == pos2.x && pos1.y == pos2.y && !(pos1.x == pattern1.CenterX && pos1.y == pattern1.CenterY)) { return true; } } } return false; } /// /// 한 방향의 돌 배열 얻기 (지정된 범위만큼 확장) /// private List GetLineOfStones(Enums.PlayerType[,] board, int x, int y, int dirIndex, int width, int height, int extensionRange = 5) { List line = new List(); // 중앙에 놓인 현재 돌 추가 line.Add(new Stone { Type = board[x, y], X = x, Y = y, RelativePosition = 0 }); // 양방향으로 지정된 칸수만큼 확장 for (int extendDir = 0; extendDir < 2; extendDir++) { int actualDirIndex = extendDir == 0 ? dirIndex : dirIndex + 4; // 반대 방향으로도 확장 int dx = directions[actualDirIndex, 0]; int dy = directions[actualDirIndex, 1]; // 지정된 칸수만큼 확장 for (int i = 1; i <= extensionRange; i++) { int nx = x + (dx * i); int ny = y + (dy * i); if (!IsInBounds(nx, ny)) break; Stone stone = new Stone { Type = board[nx, ny], X = nx, Y = ny, RelativePosition = extendDir == 0 ? i : -i }; if (extendDir == 0) line.Add(stone); // 정방향은 뒤에 추가 else line.Insert(0, stone); // 역방향은 앞에 추가 } } return line; } /// /// 주어진 라인에서 열린 3 패턴 찾기 (백돌로 막혀있는 경우 고려) /// private List FindOpenThreePatterns(List line, int centerX, int centerY, int dirIndex) { List result = new List(); int lineLength = line.Count; // 각 열린 3 패턴에 대해 검사 foreach (int[] pattern in threePatterns) { int patternLength = pattern.Length; // 라인을 슬라이딩 윈도우로 검사 for (int i = 0; i <= lineLength - patternLength; i++) { bool patternMatches = true; List stonePositions = new List(); List stoneRelativePositions = new List(); // 패턴과 라인이 일치하는지 확인 for (int j = 0; j < patternLength; j++) { Stone stone = line[i + j]; // 패턴의 1은 흑돌(PlayerA)과 일치해야 함 if (pattern[j] == 1) { if (stone.Type != Enums.PlayerType.PlayerA) { patternMatches = false; break; } stonePositions.Add(new Vector2Int(stone.X, stone.Y)); stoneRelativePositions.Add(stone.RelativePosition); } // 패턴의 0은 빈 칸(None)과 일치해야 함 else if (pattern[j] == 0 && stone.Type != Enums.PlayerType.None) { patternMatches = false; break; } } // 패턴이 일치하고, 중심 돌이 패턴에 포함되는 경우에만 열린 3으로 인정 if (patternMatches && stonePositions.Any(pos => pos.x == centerX && pos.y == centerY)) { // 패턴 양쪽의 확장성 검사 (백돌로 막혀있는지 확인) bool isReallyOpen = IsPatternReallyOpen(line, i, i + patternLength - 1); // 진짜 열린 3인 경우에만 추가 if (isReallyOpen) { result.Add(new OpenThreePattern { Direction = dirIndex, StonePositions = stonePositions, RelativePositions = stoneRelativePositions, CenterX = centerX, CenterY = centerY }); } } } } return result; } /// /// 주어진 라인에서 열린 4 패턴 찾기 (백돌로 막혀있는 경우 고려) /// private List FindOpenFourPatterns(List line, int centerX, int centerY, int dirIndex) { List result = new List(); int lineLength = line.Count; // 각 열린 4 패턴에 대해 검사 foreach (int[] pattern in fourPatterns) { int patternLength = pattern.Length; // 라인을 슬라이딩 윈도우로 검사 for (int i = 0; i <= lineLength - patternLength; i++) { bool patternMatches = true; List stonePositions = new List(); // 패턴과 라인이 일치하는지 확인 for (int j = 0; j < patternLength; j++) { Stone stone = line[i + j]; // 패턴의 1은 흑돌(PlayerA)과 일치해야 함 if (pattern[j] == 1) { if (stone.Type != Enums.PlayerType.PlayerA) { patternMatches = false; break; } stonePositions.Add(new Vector2Int(stone.X, stone.Y)); } // 패턴의 0은 빈 칸(None)과 일치해야 함 else if (pattern[j] == 0 && stone.Type != Enums.PlayerType.None) { patternMatches = false; break; } } // 패턴이 일치하고, 중심 돌이 패턴에 포함되는 경우에만 열린 4로 인정 if (patternMatches && stonePositions.Any(pos => pos.x == centerX && pos.y == centerY)) { // 패턴 양쪽의 확장성 검사 (백돌로 막혀있는지 확인) bool isReallyOpen = IsPatternReallyOpen(line, i, i + patternLength - 1); // 진짜 열린 4인 경우에만 추가 if (isReallyOpen) { result.Add(new OpenFourPattern { Direction = dirIndex, StonePositions = stonePositions, CenterX = centerX, CenterY = centerY }); } } } } return result; } /// /// 패턴이 정말로 열려있는지 확인 (백돌로 막혀있지 않은지) /// private bool IsPatternReallyOpen(List line, int startIdx, int endIdx) { int lineLength = line.Count; bool leftSideOpen = false; bool rightSideOpen = false; // 좌측 확장성 검사 int leftCheckIdx = startIdx - 1; int leftExtendableSpaces = 0; while (leftCheckIdx >= 0) { if (line[leftCheckIdx].Type == Enums.PlayerType.None) { leftExtendableSpaces++; leftCheckIdx--; } else if (line[leftCheckIdx].Type == Enums.PlayerType.PlayerB) { // 백돌로 막혀있음 break; } else { // 흑돌이 있음 (이미 다른 패턴의 일부일 수 있음) break; } } // 우측 확장성 검사 int rightCheckIdx = endIdx + 1; int rightExtendableSpaces = 0; while (rightCheckIdx < lineLength) { if (line[rightCheckIdx].Type == Enums.PlayerType.None) { rightExtendableSpaces++; rightCheckIdx++; } else if (line[rightCheckIdx].Type == Enums.PlayerType.PlayerB) { // 백돌로 막혀있음 break; } else { // 흑돌이 있음 (이미 다른 패턴의 일부일 수 있음) break; } } // 양쪽이 모두 백돌로 막혀있다면 진짜 열린 3이 아님 if (leftCheckIdx >= 0 && rightCheckIdx < lineLength && line[leftCheckIdx].Type == Enums.PlayerType.PlayerB && line[rightCheckIdx].Type == Enums.PlayerType.PlayerB) { return false; } // 적어도 한쪽에 빈 공간이 있으면 열린 패턴으로 간주 leftSideOpen = leftCheckIdx < 0 || line[leftCheckIdx].Type != Enums.PlayerType.PlayerB; rightSideOpen = rightCheckIdx >= lineLength || line[rightCheckIdx].Type != Enums.PlayerType.PlayerB; // 한쪽이라도 열려있어야 열린 패턴으로 인정 return leftSideOpen || rightSideOpen; } /// /// 장목(6목 이상) 금수 검사 /// private bool IsOverline(Enums.PlayerType[,] board, int x, int y) { int width = board.GetLength(0); int height = board.GetLength(1); // 4방향(가로, 세로, 대각선1, 대각선2)에 대해 6목 이상 검사 for (int dir = 0; dir < 4; dir++) { int count = 1; // 현재 위치 포함 // 정방향 탐색 int dx = directions[dir, 0]; int dy = directions[dir, 1]; int nx = x + dx; int ny = y + dy; while (IsInBounds(nx, ny) && board[nx, ny] == Enums.PlayerType.PlayerA) { count++; nx += dx; ny += dy; } // 역방향 탐색 dx = directions[dir + 4, 0]; dy = directions[dir + 4, 1]; nx = x + dx; ny = y + dy; while (IsInBounds(nx, ny) && board[nx, ny] == Enums.PlayerType.PlayerA) { count++; nx += dx; ny += dy; } // 6목 이상이면 장목(금수) if (count >= 6) return true; } return false; } /// /// 돌 정보를 저장하는 구조체 /// private struct Stone { public Enums.PlayerType Type; public int X; public int Y; public int RelativePosition; // 중심 돌로부터의 상대적 위치 } /// /// 열린 3 패턴 정보를 저장하는 클래스 /// private class OpenThreePattern { public int Direction; public List StonePositions; public List RelativePositions; // 중심 돌로부터의 상대적 위치 public int CenterX; public int CenterY; } /// /// 열린 4 패턴 정보를 저장하는 클래스 /// private class OpenFourPattern { public int Direction; public List StonePositions; public int CenterX; public int CenterY; } /// /// 한 위치의 모든 패턴 정보를 저장하는 클래스 /// private class PatternInfo { public List OpenThreePatterns = new List(); public List OpenFourPatterns = new List(); public Dictionary> LinesByDirection = new Dictionary>(); } }