diff --git a/Assets/Script/Renju/ForbiddenDetectorBase.cs b/Assets/Script/Renju/ForbiddenDetectorBase.cs index 24741c8..f954c1a 100644 --- a/Assets/Script/Renju/ForbiddenDetectorBase.cs +++ b/Assets/Script/Renju/ForbiddenDetectorBase.cs @@ -1,9 +1,20 @@ public class ForbiddenDetectorBase { + /// + /// 흑색 돌 + /// private protected Enums.PlayerType Black = Enums.PlayerType.PlayerA; + /// + /// 빈칸 + /// private protected Enums.PlayerType Space = Enums.PlayerType.None; - - // 8방향을 나타내는 델타 배열 (가로, 세로, 대각선 방향) + /// + /// 흰색 돌, 렌주룰 내에선 벽으로도 활용 + /// + private protected Enums.PlayerType White = Enums.PlayerType.PlayerB; + /// + /// 8방향을 나타내는 델타 배열 (가로, 세로, 대각선 방향) + /// private protected readonly int[,] Directions = new int[8, 2] { { 1, 0 }, // 오른쪽 @@ -16,11 +27,13 @@ { 1, -1 } // 오른쪽 위 }; - // 방향 쌍을 정의 (반대 방향끼리 쌍을 이룸) - // 0-4: 가로 방향 쌍 (동-서) - // 1-5: 대각선 방향 쌍 (남동-북서) - // 2-6: 세로 방향 쌍 (남-북) - // 3-7: 대각선 방향 쌍 (남서-북동) + /// + /// 방향 쌍을 정의 (반대 방향끼리 쌍을 이룸) + /// 0-4: 가로 방향 쌍 (동-서) + /// 1-5: 대각선 방향 쌍 (남동-북서) + /// 2-6: 세로 방향 쌍 (남-북) + /// 3-7: 대각선 방향 쌍 (남서-북동) + /// private protected readonly int[,] DirectionPairs = { { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 } }; // 15*15 보드 사이즈 diff --git a/Assets/Script/Renju/RenjuDoubleFourDetector.cs b/Assets/Script/Renju/RenjuDoubleFourDetector.cs index 8bc8b65..43a3ad3 100644 --- a/Assets/Script/Renju/RenjuDoubleFourDetector.cs +++ b/Assets/Script/Renju/RenjuDoubleFourDetector.cs @@ -30,12 +30,9 @@ public class RenjuDoubleFourDetector: ForbiddenDetectorBase /// private bool CheckDoubleFour(Enums.PlayerType[,] board, int row, int col) { - // 각각 두개의 라인에서 쌍사를 형성하는 경우 - if (FindDoubleLineFour(board, row, col)) return true; - - // true : 일직선으로 쌍사가 만들어지는 특수 패턴 // false : 모든 경우에도 쌍사가 만들어지지 않음 - return FindSingleLineDoubleFour(board, row, col); + return FindDoubleLineFour(board, row, col) || // 각각 두개의 라인에서 쌍사를 형성하는 경우 + FindSingleLineDoubleFour(board, row, col); // 일직선으로 쌍사가 만들어지는 특수 패턴 } private bool FindDoubleLineFour(Enums.PlayerType[,] board, int row, int col) @@ -136,7 +133,7 @@ public class RenjuDoubleFourDetector: ForbiddenDetectorBase } else { - linePattern[centerIndex + i] = Space; // 범위 밖은 빈칸으로 처리 + linePattern[centerIndex + i] = White; // 범위 밖은 백돌로 처리 } } @@ -152,7 +149,7 @@ public class RenjuDoubleFourDetector: ForbiddenDetectorBase } else { - linePattern[centerIndex - i] = Space; // 범위 밖은 빈칸으로 처리 + linePattern[centerIndex - i] = White; // 범위 밖은 백돌로 처리 } } @@ -181,7 +178,7 @@ public class RenjuDoubleFourDetector: ForbiddenDetectorBase } } - // 정확히 4개의 돌이 있고, 1개의 빈칸이 있으면 4로 판정 + // 4개의 돌이 있고, 1개의 빈칸이 있으면 4로 판정 // (현재 위치는 흑으로 이미 설정되어 있음) if (stoneCount == 4) { diff --git a/Assets/Script/Renju/RenjuDoubleFourDetector.cs.meta b/Assets/Script/Renju/RenjuDoubleFourDetector.cs.meta index abc7cef..54a3220 100644 --- a/Assets/Script/Renju/RenjuDoubleFourDetector.cs.meta +++ b/Assets/Script/Renju/RenjuDoubleFourDetector.cs.meta @@ -1,3 +1,11 @@ -fileFormatVersion: 2 -guid: 0daf1f2b8cbe4b19adc0e42db7a15991 -timeCreated: 1742270734 \ No newline at end of file +fileFormatVersion: 2 +guid: f997e95272e950240a6e9e2f8a99fdfa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Script/Renju/RenjuDoubleThreeDetector.cs b/Assets/Script/Renju/RenjuDoubleThreeDetector.cs index 943063e..1befe0d 100644 --- a/Assets/Script/Renju/RenjuDoubleThreeDetector.cs +++ b/Assets/Script/Renju/RenjuDoubleThreeDetector.cs @@ -1,8 +1,25 @@ using System.Collections.Generic; using UnityEngine; -public class RenjuDoubleThreeDetector: ForbiddenDetectorBase +/// +/// 렌주 쌍삼(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) 여부를 검사합니다. /// @@ -15,12 +32,635 @@ public class RenjuDoubleThreeDetector: ForbiddenDetectorBase // 임시로 돌 배치 board[row, col] = Black; - // 쌍삼 검사 - // bool isThreeThree = CheckThreeThree(board, row, col); + // 쌍삼 기본 검사 (열린 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; } } \ No newline at end of file diff --git a/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs b/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs index 88e97a3..995bc9b 100644 --- a/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs +++ b/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs @@ -1,11 +1,14 @@ using System.Collections.Generic; +using System.Text; using UnityEngine; -public class RenjuForbiddenMoveDetector +public class RenjuForbiddenMoveDetector : ForbiddenDetectorBase { // 렌주 룰 금수 감지기 생성 - private RenjuRuleChecker _ruleChecker = new RenjuRuleChecker(); + private RenjuOverlineDetector _overlineDetactor = new(); + private RenjuDoubleFourDetector _doubleFourDetactor = new(); + private RenjuDoubleThreeDetector _doubleThreeDetector = new(); /// /// 렌주 룰로 금수 리스트를 반환하는 함수 @@ -14,6 +17,120 @@ public class RenjuForbiddenMoveDetector /// 금수 좌표를 담은 리스트 public List RenjuForbiddenMove(Enums.PlayerType[,] board) { - return _ruleChecker.GetForbiddenMoves(board); + + var forbiddenCount = 0; + List forbiddenMoves = new(); + List tempForbiddenMoves = new(); + for (int row = 0; row < BoardSize; row++) + { + for (int col = 0; col < BoardSize; col++) + { + // ** 비어 있지 않으면 검사할 필요 없음 ** + if (!IsEmptyPosition(board, row, col)) continue; + + // 장목 검사 + if (_overlineDetactor.IsOverline(board, row, col)) + { + forbiddenCount++; + Debug.Log("장목 금수 좌표 X축 : " + row + ", Y축 : " + col); + forbiddenMoves.Add(new Vector2Int(row, col)); + continue; + } + + // 4-4 검사 + if (_doubleFourDetactor.IsDoubleFour(board, row, col)) + { + forbiddenCount++; + Debug.Log("사사 금수 좌표 X축 : " + row + ", Y축 : " + col); + forbiddenMoves.Add(new Vector2Int(row, col)); + continue; + } + + if(forbiddenCount > 0) continue; + + // 3-3 검사 + if (_doubleThreeDetector.IsDoubleThree(board, row, col)) + { + tempForbiddenMoves.Add(new Vector2Int(row, col)); + // if (!SimulateDoubleFour(tempBoard)) + // { + // Debug.Log("삼삼 금수 좌표 X축 : " + row + ", Y축 : " + col); + // forbiddenMoves.Add(new Vector2Int(row, col)); + // } + } + } + } + + foreach (var pos in tempForbiddenMoves) + { + board[pos.x, pos.y] = Black; + if (!SimulateDoubleFour(board)&& !SimulateOverline(board)) + { + Debug.Log("X: "+pos.x + "Y: "+ pos.y); + forbiddenMoves.Add(new Vector2Int(pos.x, pos.y)); + } + } + + + Debug.Log(DebugBoard(board)); + return forbiddenMoves; } -} \ No newline at end of file + + + + private bool SimulateDoubleFour(Enums.PlayerType[,] board) + { + for (int row = 0; row < BoardSize; row++) + { + for (int col = 0; col < BoardSize; col++) + { + if (_doubleFourDetactor.IsDoubleFour(board, row, col)) + return true; + } + } + return false; + } + + private bool SimulateOverline(Enums.PlayerType[,] board) + { + for (int row = 0; row < BoardSize; row++) + { + for (int col = 0; col < BoardSize; col++) + { + if (_overlineDetactor.IsOverline(board, row, col)) + { + return true; + } + } + } + return false; + } + + /// + /// 보드 상태를 시각적으로 출력하는 디버깅 함수 + /// + /// 현재 보드 상태 + /// 보드의 시각적 표현 문자열 + private string DebugBoard(Enums.PlayerType[,] board) + { + StringBuilder sb = new StringBuilder(); + + for (int row = 0; row < BoardSize; row++) + { + for (int col = 0; col < BoardSize; col++) + { + sb.Append(board[row, col] switch + { + Enums.PlayerType.None => "□", + Enums.PlayerType.PlayerA => "●", + Enums.PlayerType.PlayerB => "○", + _ => "?" + }); + } + sb.AppendLine(); // 줄바꿈 추가 + } + + return sb.ToString(); + } +} + diff --git a/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs.meta b/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs.meta index 511a9bd..05a239d 100644 --- a/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs.meta +++ b/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs.meta @@ -1,3 +1,11 @@ -fileFormatVersion: 2 -guid: 8618553c3e244abdb040fb7378dd4b65 -timeCreated: 1741939566 \ No newline at end of file +fileFormatVersion: 2 +guid: 4440d621b56f2ce459d819497911892b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Script/Renju/RenjuRuleChecker.cs b/Assets/Script/Renju/RenjuRuleChecker.cs deleted file mode 100644 index 8cdf082..0000000 --- a/Assets/Script/Renju/RenjuRuleChecker.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -/// -/// 렌주 규칙의 모든 금수 규칙(3-3, 4-4, 장목)을 검사하는 통합 클래스 -/// -public class RenjuRuleChecker: ForbiddenDetectorBase -{ - private RenjuOverlineDetector _overlineDetactor = new(); - private RenjuDoubleFourDetector _doubleFourDetactor = new(); - private RenjuDoubleThreeDetector _doubleThreeDetector = new(); - - public List GetForbiddenMoves(Enums.PlayerType[,] board) - { - List forbiddenMoves = new(); - for (int row = 0; row < BoardSize; row++) - { - for (int col = 0; col < BoardSize; col++) - { - // ** 비어 있지 않으면 검사할 필요 없음 ** - if (!IsEmptyPosition(board, row, col)) continue; - - // 장목 검사 - if (_overlineDetactor.IsOverline(board, row, col)) - { - Debug.Log("장목 금수 좌표 X축 : " + row + ", Y축 : " + col); - forbiddenMoves.Add(new Vector2Int(row, col)); - continue; - } - - // 4-4 검사 - if (_doubleFourDetactor.IsDoubleFour(board, row, col)) - { - Debug.Log("사사 금수 좌표 X축 : " + row + ", Y축 : " + col); - forbiddenMoves.Add(new Vector2Int(row, col)); - continue; - } - - // 3-3 검사 - if (_doubleThreeDetector.IsDoubleThree(board, row, col)) - { - Debug.Log("삼삼 금수 좌표 X축 : " + row + ", Y축 : " + col); - forbiddenMoves.Add(new Vector2Int(row, col)); - } - } - } - - return forbiddenMoves; - } -} - diff --git a/Assets/Script/Renju/RenjuRuleChecker.cs.meta b/Assets/Script/Renju/RenjuRuleChecker.cs.meta deleted file mode 100644 index a982a5d..0000000 --- a/Assets/Script/Renju/RenjuRuleChecker.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 083e8b9070ed407b8744d4cacd0d53dc -timeCreated: 1742256498 \ No newline at end of file diff --git a/Assets/Trash Can_JY/Renju.unity b/Assets/Trash Can_JY/Renju.unity index d70b9a6..b1afb5d 100644 --- a/Assets/Trash Can_JY/Renju.unity +++ b/Assets/Trash Can_JY/Renju.unity @@ -306,7 +306,11 @@ PrefabInstance: m_Modifications: - target: {fileID: 626368541760032086, guid: 65aac0e22fe25aa42b9cf7df776a854d, type: 3} propertyPath: m_Name - value: '[Canvas] Game UI' + value: Canvas + objectReference: {fileID: 0} + - target: {fileID: 1250546304786973426, guid: 65aac0e22fe25aa42b9cf7df776a854d, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 objectReference: {fileID: 0} - target: {fileID: 6113787613246818512, guid: 65aac0e22fe25aa42b9cf7df776a854d, type: 3} propertyPath: m_Pivot.x @@ -21706,6 +21710,7 @@ GameObject: m_Component: - component: {fileID: 1260188604546925059} - component: {fileID: 5677811662396631252} + - component: {fileID: 5677811662396631253} m_Layer: 0 m_Name: GameManager m_TagString: Untagged @@ -33884,10 +33889,104 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 35a4c6d5d3a97b444b968e68ec8bb9f7, type: 3} m_Name: m_EditorClassIdentifier: - signinPanel: {fileID: 0} - signupPanel: {fileID: 0} - canvas: {fileID: 0} - profileSprites: [] + panelManagerPrefab: {fileID: 3475740041361426276, guid: 085ca07ca90c92545b2594bd13412701, type: 3} + audioManagerPrefab: {fileID: 2946408323859178723, guid: e829818dce39a5d4383e061111bed871, type: 3} +--- !u!82 &5677811662396631253 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3626119060319719757} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 0} + m_audioClip: {fileID: 0} + m_PlayOnAwake: 1 + m_Volume: 1 + m_Pitch: 1 + Loop: 0 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 --- !u!61 &5684907091474785187 BoxCollider2D: m_ObjectHideFlags: 0