using System.Collections.Generic; using UnityEngine; /// /// 렌주 쌍삼(3-3) 금수 판정을 위한 개선된 클래스 /// 렌주 국제 규칙 9.3에 따라 쌍삼의 예외 상황까지 정확히 판별 /// public class RenjuDoubleThreeDetector : ForbiddenDetectorBase { // 열린 3 패턴 정보를 저장하는 구조체 private struct OpenThreeInfo { public int direction; // 방향 인덱스 public List emptyPositions; // 빈 좌표들 (4를 만들 수 있는 위치) public OpenThreeInfo(int dir) { direction = dir; emptyPositions = new List(); } } /// /// 쌍삼(3-3) 여부를 검사합니다. /// /// 현재 보드 상태 /// 행 좌표 /// 열 좌표 /// 쌍삼이면 true, 아니면 false public bool IsDoubleThree(Enums.PlayerType[,] board, int row, int col) { // 임시로 돌 배치 board[row, col] = Black; // 쌍삼 기본 검사 (열린 3이 2개 이상인지) List openThrees = FindAllOpenThrees(board, row, col); // 원래 상태로 되돌림 board[row, col] = Space; // 열린 3이 2개 미만이면 쌍삼이 아님 if (openThrees.Count < 2) return false; // 렌주 규칙 9.3에 따른 예외 케이스 확인 return !CheckDoubleThreeExceptions(board, row, col, openThrees); } /// /// 모든 방향에서 열린 3을 찾아 반환합니다. /// private List FindAllOpenThrees(Enums.PlayerType[,] board, int row, int col) { List openThrees = new List(); // 4개의 방향 쌍에 대해 검사 (대각선 및 직선 포함) for (int i = 0; i < 4; i++) { int dir1 = DirectionPairs[i, 0]; int dir2 = DirectionPairs[i, 1]; // 열린 3 정보 획득 OpenThreeInfo threeInfo = new OpenThreeInfo(i); if (FindOpenThreeInDirection(board, row, col, dir1, dir2, ref threeInfo)) { openThrees.Add(threeInfo); } } return openThrees; } /// /// 특정 방향에서 열린 3을 찾고 관련 정보를 채웁니다. /// private bool FindOpenThreeInDirection(Enums.PlayerType[,] board, int row, int col, int dir1, int dir2, ref OpenThreeInfo threeInfo) { // 라인 패턴 추출 Enums.PlayerType[] linePattern = ExtractLinePattern(board, row, col, dir1, dir2); int centerIndex = 5; // 중앙 인덱스 (현재 위치) // 연속된 열린 3 또는 한 칸 떨어진 열린 3 확인 if (FindConsecutiveOpenThree(linePattern, centerIndex, ref threeInfo, row, col, dir1, dir2) || FindGappedOpenThree(linePattern, centerIndex, ref threeInfo, row, col, dir1, dir2)) { // 열린 3이 발견됨 return true; } return false; } /// /// 라인 패턴을 추출합니다. /// private Enums.PlayerType[] ExtractLinePattern(Enums.PlayerType[,] board, int row, int col, int dir1, int dir2) { Enums.PlayerType[] linePattern = new Enums.PlayerType[11]; int centerIndex = 5; // 중앙 인덱스 (현재 위치) // 현재 위치 설정 linePattern[centerIndex] = Black; // dir1 방향으로 패턴 채우기 for (int i = 1; i <= 5; i++) { int newRow = row + Directions[dir1, 0] * i; int newCol = col + Directions[dir1, 1] * i; if (IsInBounds(newRow, newCol)) { linePattern[centerIndex + i] = board[newRow, newCol]; } else { linePattern[centerIndex + i] = White; // 범위 밖은 벽으로 처리하여 일관성 유지 } } // dir2 방향으로 패턴 채우기 for (int i = 1; i <= 5; i++) { int newRow = row + Directions[dir2, 0] * i; int newCol = col + Directions[dir2, 1] * i; if (IsInBounds(newRow, newCol)) { linePattern[centerIndex - i] = board[newRow, newCol]; } else { linePattern[centerIndex - i] = White; // 범위 밖은 벽으로 처리하여 일관성 유지 } } return linePattern; } /// /// 연속된 열린 3 패턴을 찾고 관련 정보를 채웁니다. /// private bool FindConsecutiveOpenThree(Enums.PlayerType[] linePattern, int centerIndex, ref OpenThreeInfo threeInfo, int row, int col, int dir1, int dir2) { // 연속된 3개의 돌 패턴 (●●●) for (int start = centerIndex - 2; start <= centerIndex; start++) { // 범위 체크 if (start < 0 || start + 2 >= linePattern.Length) { continue; } // 3개의 연속된 돌 확인 bool isConsecutiveThree = true; for (int i = 0; i < 3; i++) { if (linePattern[start + i] != Black) { isConsecutiveThree = false; break; } } if (isConsecutiveThree) { // 양쪽이 모두 열려있는지 확인 bool isLeftOpen = (start - 1 >= 0) && (linePattern[start - 1] == Space); bool isRightOpen = (start + 3 < linePattern.Length) && (linePattern[start + 3] == Space); // 양쪽이 모두 열려있으면 열린 3 if (isLeftOpen && isRightOpen) { // 추가 검증: 더 확장해서 열려있는지 확인 bool isExtendedLeftOpen = IsExtendedOpen(linePattern, start - 1, -1); bool isExtendedRightOpen = IsExtendedOpen(linePattern, start + 3, 1); if (isExtendedLeftOpen && isExtendedRightOpen) { // 4를 만들 수 있는 위치 저장 if (isLeftOpen) { int leftRow = row + Directions[dir2, 0] * (centerIndex - (start - 1)); int leftCol = col + Directions[dir2, 1] * (centerIndex - (start - 1)); if (IsInBounds(leftRow, leftCol)) { threeInfo.emptyPositions.Add(new Vector2Int(leftCol, leftRow)); } } if (isRightOpen) { int rightRow = row + Directions[dir1, 0] * ((start + 3) - centerIndex); int rightCol = col + Directions[dir1, 1] * ((start + 3) - centerIndex); if (IsInBounds(rightRow, rightCol)) { threeInfo.emptyPositions.Add(new Vector2Int(rightCol, rightRow)); } } return true; } } } } return false; } /// /// 한 칸 떨어진 열린 3 패턴을 찾고 관련 정보를 채웁니다. /// private bool FindGappedOpenThree(Enums.PlayerType[] linePattern, int centerIndex, ref OpenThreeInfo threeInfo, int row, int col, int dir1, int dir2) { // 한 칸 떨어진 패턴 확인 (●●○● 또는 ●○●●) for (int start = Mathf.Max(0, centerIndex - 3); start <= Mathf.Min(linePattern.Length - 4, centerIndex); start++) { // 패턴 내에 돌과 빈칸 개수 확인 int stoneCount = 0; int gapCount = 0; int gapPosition = -1; for (int i = 0; i < 4; i++) { if (linePattern[start + i] == Black) { stoneCount++; } else if (linePattern[start + i] == Space) { gapCount++; gapPosition = start + i; } else { // 상대 돌이나 벽이 있으면 패턴이 깨짐 stoneCount = 0; break; } } // 3개의 돌과 1개의 빈칸으로 구성된 패턴 if (stoneCount == 3 && gapCount == 1) { // 양쪽이 모두 열려있는지 확인 bool isLeftOpen = (start - 1 >= 0) && (linePattern[start - 1] == Space); bool isRightOpen = (start + 4 < linePattern.Length) && (linePattern[start + 4] == Space); // 한쪽이라도 열려있으면 잠재적 열린 3 if (isLeftOpen || isRightOpen) { // 빈칸에 돌을 놓았을 때 열린 4가 되는지 확인 if (CheckIfCreatesOpenFour(linePattern, gapPosition)) { // 4를 만들 수 있는 위치 저장 (빈칸 위치) int gapRow = row; int gapCol = col; // 빈칸의 보드 좌표 계산 int offset = gapPosition - centerIndex; if (offset > 0) { gapRow += Directions[dir1, 0] * offset; gapCol += Directions[dir1, 1] * offset; } else if (offset < 0) { gapRow += Directions[dir2, 0] * (-offset); gapCol += Directions[dir2, 1] * (-offset); } if (IsInBounds(gapRow, gapCol)) { threeInfo.emptyPositions.Add(new Vector2Int(gapCol, gapRow)); } // 장목이 되는지 확인 (장목이 되면 열린 3이 아님) if (CheckIfCreatesOverline(linePattern, gapPosition)) { return false; } return true; } } } } return false; } /// /// 특정 방향으로 추가로 열려있는지 확인합니다. /// private bool IsExtendedOpen(Enums.PlayerType[] linePattern, int startPos, int direction) { // 한 칸 더 확장해서 확인 int nextPos = startPos + direction; if (nextPos >= 0 && nextPos < linePattern.Length) { // 다음 칸이 상대 돌이나 벽이면 확장 불가 if (linePattern[nextPos] == White) { return false; } } else { // 범위를 벗어나면 확장 불가 return false; } return true; } /// /// 빈칸에 돌을 놓았을 때 열린 4가 되는지 확인합니다. /// private bool CheckIfCreatesOpenFour(Enums.PlayerType[] linePattern, int position) { // 시뮬레이션: 빈칸에 돌을 놓아봄 Enums.PlayerType[] testPattern = new Enums.PlayerType[linePattern.Length]; System.Array.Copy(linePattern, testPattern, linePattern.Length); testPattern[position] = Black; // 놓은 위치를 포함해 연속된 4가 있는지 확인 for (int start = Mathf.Max(0, position - 3); start <= position; start++) { // 범위 체크 if (start + 3 >= testPattern.Length) { continue; } // 4개의 연속된 돌 확인 bool isConsecutiveFour = true; for (int i = 0; i < 4; i++) { if (testPattern[start + i] != Black) { isConsecutiveFour = false; break; } } if (isConsecutiveFour) { // 양쪽이 모두 열려있는지 확인 (열린 4) bool isLeftOpen = (start - 1 >= 0) && (testPattern[start - 1] == Space); bool isRightOpen = (start + 4 < testPattern.Length) && (testPattern[start + 4] == Space); if (isLeftOpen || isRightOpen) { return true; // 열린 4나 반열린 4가 됨 } } } return false; } /// /// 빈칸에 돌을 놓았을 때 장목(6목 이상)이 되는지 확인합니다. /// private bool CheckIfCreatesOverline(Enums.PlayerType[] linePattern, int position) { // 시뮬레이션: 빈칸에 돌을 놓아봄 Enums.PlayerType[] testPattern = new Enums.PlayerType[linePattern.Length]; System.Array.Copy(linePattern, testPattern, linePattern.Length); testPattern[position] = Black; // 놓은 위치 주변의 최대 연속 돌 수 계산 int maxLength = 1; // 놓은 돌 포함 // 오른쪽 방향 연속 돌 세기 for (int i = position + 1; i < testPattern.Length && testPattern[i] == Black; i++) { maxLength++; } // 왼쪽 방향 연속 돌 세기 for (int i = position - 1; i >= 0 && testPattern[i] == Black; i--) { maxLength++; } // 6목 이상이면 장목 return maxLength >= 6; } /// /// 렌주 규칙 9.3에 따른 쌍삼 예외 케이스를 확인합니다. /// private bool CheckDoubleThreeExceptions(Enums.PlayerType[,] board, int row, int col, List openThrees) { // 예외 케이스 1: 하나의 삼만 열린 사가 될 수 있는 경우 (9.3 a항) bool canFormOpenFourWithoutDoubleFour = CheckExceptionCanFormOneFour(board, row, col, openThrees); // 예외 케이스 2: 9.3 b항 (복잡한 연쇄 체크) bool isMeetExceptionB = CheckExceptionB(board, row, col, openThrees); // 어느 하나라도 예외 조건을 만족하면 쌍삼이 아님 return canFormOpenFourWithoutDoubleFour || isMeetExceptionB; } /// /// 예외 케이스 1: 하나의 삼만 열린 사가 될 수 있고 쌍사가 형성되지 않는 경우 (9.3 a항) /// private bool CheckExceptionCanFormOneFour(Enums.PlayerType[,] board, int row, int col, List openThrees) { int canFormFourCount = 0; // 각 열린 3에 대해, 4를 만들 수 있는지 확인 foreach (var threeInfo in openThrees) { foreach (var emptyPos in threeInfo.emptyPositions) { // 빈 위치에 돌을 놓았을 때 열린 4가 되는지 확인 board[emptyPos.y, emptyPos.x] = Black; // 쌍사가 형성되는지 확인 bool formsDoubleFour = CheckDoubleFour(board, emptyPos.y, emptyPos.x); // 원래 상태로 복원 board[emptyPos.y, emptyPos.x] = Space; // 쌍사 없이 4를 만들 수 있으면 카운트 증가 if (!formsDoubleFour) { canFormFourCount++; // 디버깅 // Debug.Log($"Can form four at ({emptyPos.x}, {emptyPos.y}) without double four"); } } } // 하나의 삼만 쌍사 없이 4로 만들 수 있는 경우 return canFormFourCount == 1; } /// /// 예외 케이스 2: 9.3 b항의 복잡한 연쇄 체크 /// private bool CheckExceptionB(Enums.PlayerType[,] board, int row, int col, List openThrees) { // 이 부분은 매우 복잡한 렌주 규칙 9.3 b항을 구현해야 합니다. // 기본적인 구현만 제공하며, 필요에 따라 확장 가능합니다. // 각 열린 3에 대해, 4를 만들 때 다른 쌍삼이 형성되는지 확인 foreach (var threeInfo in openThrees) { bool canFormFourWithoutChainDoubleThree = false; foreach (var emptyPos in threeInfo.emptyPositions) { // 빈 위치에 돌을 놓았을 때 board[emptyPos.y, emptyPos.x] = Black; // 다른 쌍삼이 형성되는지 확인 (연쇄 체크) bool formsOtherDoubleThree = false; // 다른 모든 빈 위치에 대해 쌍삼 체크 for (int r = 0; r < BoardSize; r++) { for (int c = 0; c < BoardSize; c++) { if (board[r, c] == Space) { // 임시로 돌 배치하여 쌍삼 체크 board[r, c] = Black; bool isDoubleThree = CheckSimpleDoubleThree(board, r, c); board[r, c] = Space; if (isDoubleThree) { formsOtherDoubleThree = true; break; } } } if (formsOtherDoubleThree) break; } // 원래 상태로 복원 board[emptyPos.y, emptyPos.x] = Space; // 연쇄 쌍삼이 형성되지 않으면 예외 조건 만족 if (!formsOtherDoubleThree) { canFormFourWithoutChainDoubleThree = true; break; } } // 하나의 삼이라도 연쇄 쌍삼 없이 4를 만들 수 있으면 예외 조건 만족 if (canFormFourWithoutChainDoubleThree) { return true; } } return false; } /// /// 단순 쌍삼 체크 (연쇄 검사용, 재귀 호출 방지) /// private bool CheckSimpleDoubleThree(Enums.PlayerType[,] board, int row, int col) { int openThreeCount = 0; // 4개의 방향 쌍에 대해 검사 for (int i = 0; i < 4; i++) { int dir1 = DirectionPairs[i, 0]; int dir2 = DirectionPairs[i, 1]; // 간단한 열린 3 체크 if (CheckSimpleOpenThree(board, row, col, dir1, dir2)) { openThreeCount++; if (openThreeCount >= 2) { return true; } } } return false; } /// /// 단순 열린 3 체크 (연쇄 검사용) /// private bool CheckSimpleOpenThree(Enums.PlayerType[,] board, int row, int col, int dir1, int dir2) { Enums.PlayerType[] linePattern = ExtractLinePattern(board, row, col, dir1, dir2); int centerIndex = 5; // 연속된 열린 3 패턴 체크 for (int start = centerIndex - 2; start <= centerIndex; start++) { if (start < 0 || start + 2 >= linePattern.Length) { continue; } bool isConsecutiveThree = true; for (int i = 0; i < 3; i++) { if (linePattern[start + i] != Black) { isConsecutiveThree = false; break; } } if (isConsecutiveThree) { bool isLeftOpen = (start - 1 >= 0) && (linePattern[start - 1] == Space); bool isRightOpen = (start + 3 < linePattern.Length) && (linePattern[start + 3] == Space); if (isLeftOpen && isRightOpen) { return true; } } } // 한 칸 떨어진 열린 3 패턴 체크 (간단 구현) for (int start = centerIndex - 3; start <= centerIndex; start++) { if (start < 0 || start + 3 >= linePattern.Length) { continue; } int stoneCount = 0; int gapCount = 0; for (int i = 0; i < 4; i++) { if (linePattern[start + i] == Black) { stoneCount++; } else if (linePattern[start + i] == Space) { gapCount++; } else { stoneCount = 0; break; } } if (stoneCount == 3 && gapCount == 1) { return true; } } return false; } /// /// 쌍사 여부를 확인합니다 (예외 처리용) /// private bool CheckDoubleFour(Enums.PlayerType[,] board, int row, int col) { int fourCount = 0; // 4개의 방향 쌍에 대해 검사 for (int i = 0; i < 4; i++) { int dir1 = DirectionPairs[i, 0]; int dir2 = DirectionPairs[i, 1]; if (CheckFourInDirection(board, row, col, dir1, dir2)) { fourCount++; if (fourCount >= 2) { return true; } } } return false; } /// /// 특정 방향에서 4가 형성되는지 확인합니다. /// private bool CheckFourInDirection(Enums.PlayerType[,] board, int row, int col, int dir1, int dir2) { Enums.PlayerType[] linePattern = ExtractLinePattern(board, row, col, dir1, dir2); int centerIndex = 5; // 윈도우 슬라이딩으로 연속된 4를 검사 for (int start = 0; start <= 7; start++) { // 현재 위치가 이 윈도우에 포함되는지 확인 bool currentPositionInWindow = (start <= centerIndex && centerIndex < start + 4); if (!currentPositionInWindow) continue; // 윈도우 내의 돌 개수 세기 int stoneCount = 0; for (int i = 0; i < 4; i++) { if (linePattern[start + i] == Black) { stoneCount++; } } // 4개의 돌이 있으면 4로 판정 if (stoneCount == 4) { return true; } } return false; } }