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