From aaa280341d8bd14b22479df604f3cce8ddf19272 Mon Sep 17 00:00:00 2001 From: fiore Date: Tue, 18 Mar 2025 20:06:52 +0900 Subject: [PATCH] =?UTF-8?q?DO-31=20Feat:=20=EC=9E=A5=EB=AA=A9,=204-4=20?= =?UTF-8?q?=EA=B8=88=EC=88=98=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Script/Renju/ForbiddenDetectorBase.cs | 32 +- .../Script/Renju/RenjuDoubleFourDetector.cs | 194 +++++ .../Renju/RenjuDoubleFourDetector.cs.meta | 3 + .../Script/Renju/RenjuDoubleThreeDetector.cs | 26 + .../Renju/RenjuDoubleThreeDetector.cs.meta | 3 + .../Renju/RenjuForbiddenMoveDetector.cs | 6 +- Assets/Script/Renju/RenjuOverlineDetector.cs | 102 +++ .../Renju/RenjuOverlineDetector.cs.meta | 3 + Assets/Script/Renju/RenjuRuleChecker.cs | 682 +----------------- 9 files changed, 374 insertions(+), 677 deletions(-) create mode 100644 Assets/Script/Renju/RenjuDoubleFourDetector.cs create mode 100644 Assets/Script/Renju/RenjuDoubleFourDetector.cs.meta create mode 100644 Assets/Script/Renju/RenjuDoubleThreeDetector.cs create mode 100644 Assets/Script/Renju/RenjuDoubleThreeDetector.cs.meta create mode 100644 Assets/Script/Renju/RenjuOverlineDetector.cs create mode 100644 Assets/Script/Renju/RenjuOverlineDetector.cs.meta diff --git a/Assets/Script/Renju/ForbiddenDetectorBase.cs b/Assets/Script/Renju/ForbiddenDetectorBase.cs index f770527..12c35a9 100644 --- a/Assets/Script/Renju/ForbiddenDetectorBase.cs +++ b/Assets/Script/Renju/ForbiddenDetectorBase.cs @@ -1,16 +1,28 @@ -using UnityEngine; - -public class ForbiddenDetectorBase +public class ForbiddenDetectorBase { - // 방향 배열 (가로, 세로, 대각선) - private protected Vector2Int[] directions = + private protected Enums.PlayerType Black = Enums.PlayerType.PlayerA; + private protected Enums.PlayerType Space = Enums.PlayerType.None; + + // 8방향을 나타내는 델타 배열 (가로, 세로, 대각선 방향) + private protected readonly int[,] Directions = new int[8, 2] { - new Vector2Int(1, 0), // 가로 - new Vector2Int(0, 1), // 세로 - new Vector2Int(1, 1), // 대각선 (우하향) - new Vector2Int(1, -1) // 대각선 (우상향) + { 1, 0 }, // 오른쪽 + { 1, 1 }, // 오른쪽 아래 + { 0, 1 }, // 아래 + { -1, 1 }, // 왼쪽 아래 + { -1, 0 }, // 왼쪽 + { -1, -1 }, // 왼쪽 위 + { 0, -1 }, // 위 + { 1, -1 } // 오른쪽 위 }; + // 방향 쌍을 정의 (반대 방향끼리 쌍을 이룸) + // 0-4: 가로 방향 쌍 (동-서) + // 1-5: 대각선 방향 쌍 (남동-북서) + // 2-6: 세로 방향 쌍 (남-북) + // 3-7: 대각선 방향 쌍 (남서-북동) + private protected readonly int[,] DirectionPairs = { { 0, 4 }, { 1, 5 }, { 2, 6 }, { 3, 7 } }; + // 15*15 보드 사이즈 private protected int _boardSize = Constants.BoardSize; @@ -34,4 +46,4 @@ public class ForbiddenDetectorBase return board[row, col] == Enums.PlayerType.None; } -} +} \ No newline at end of file diff --git a/Assets/Script/Renju/RenjuDoubleFourDetector.cs b/Assets/Script/Renju/RenjuDoubleFourDetector.cs new file mode 100644 index 0000000..8bc8b65 --- /dev/null +++ b/Assets/Script/Renju/RenjuDoubleFourDetector.cs @@ -0,0 +1,194 @@ +using System; +using System.Text; +using UnityEngine; + +public class RenjuDoubleFourDetector: ForbiddenDetectorBase +{ + /// + /// 쌍사 여부를 검사합니다. + /// + /// 현재 보드 상태 + /// 행 (y 좌표) + /// 열 (x 좌표) + /// 쌍사면 true, 아니면 false + public bool IsDoubleFour(Enums.PlayerType[,] board, int row, int col) + { + // 임시로 돌 배치 + board[row, col] = Black; + + // 쌍사 검사 + bool isDoubleFour = CheckDoubleFour(board, row, col); + + // 원래 상태로 되돌림 + board[row, col] = Space; + + return isDoubleFour; + } + + /// + /// 쌍사(4-4) 여부를 검사합니다. + /// + private bool CheckDoubleFour(Enums.PlayerType[,] board, int row, int col) + { + // 각각 두개의 라인에서 쌍사를 형성하는 경우 + if (FindDoubleLineFour(board, row, col)) return true; + + // true : 일직선으로 쌍사가 만들어지는 특수 패턴 + // false : 모든 경우에도 쌍사가 만들어지지 않음 + return FindSingleLineDoubleFour(board, row, col); + } + + private bool FindDoubleLineFour(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]; + + // 4가 몇 개 만들어지는지 확인 + if (CheckFourInDirection(board, row, col, dir1, dir2)) + { + fourCount++; + + // 이미 4가 2개 이상 발견되면 쌍사로 판정 + if (fourCount >= 2) + { + return true; + } + } + } + + return false; + } + + private bool FindSingleLineDoubleFour(Enums.PlayerType[,] board, int row, int col) + { + for (int i = 0; i < 4; i++) + { + int dir1 = DirectionPairs[i, 0]; + int dir2 = DirectionPairs[i, 1]; + + // 각 방향 라인 패턴 + Enums.PlayerType[] linePattern = ExtractLinePattern(board, row, col, dir1, dir2); + + // 패턴을 문자열로 변환 + StringBuilder temp = new StringBuilder(); + foreach (var cell in linePattern) + { + temp.Append(cell switch + { + Enums.PlayerType.None => "□", + Enums.PlayerType.PlayerA => "●", + Enums.PlayerType.PlayerB => "○", + _ => "?" + }); + } + + string patternStr = temp.ToString(); + + // 한줄로 발생하는 쌍사 패턴 검사 + if (patternStr.Contains("●●□●●□●●") || patternStr.Contains("●□●●●□●")) + { + return true; + } + } + + return false; + } + + /// + /// 특정 방향에서 4가 형성되는지 확인합니다. + /// 떨어져 있는 돌도 고려합니다 (한 칸 또는 두 칸 떨어진 패턴 포함). + /// + private bool CheckFourInDirection(Enums.PlayerType[,] board, int row, int col, int dir1, int dir2) + { + // 각 방향으로 최대 5칸까지의 범위를 검사 (현재 위치 포함 총 11칸) + Enums.PlayerType[] linePattern = ExtractLinePattern(board, row, col, dir1, dir2); + int centerIndex = 5; // 중앙 인덱스 (현재 위치) + + // 모든 가능한 패턴 확인 + return CheckFourOneGap(linePattern, centerIndex); + } + + /// + /// 라인 패턴을 추출합니다. (중복 코드를 방지하기 위한 헬퍼 메서드) + /// + 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] = Space; // 범위 밖은 빈칸으로 처리 + } + } + + // 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] = Space; // 범위 밖은 빈칸으로 처리 + } + } + + return linePattern; + } + + /// + /// 한 칸 떨어진 4 패턴을 확인합니다. + /// + private bool CheckFourOneGap(Enums.PlayerType[] linePattern, int centerIndex) + { + // 윈도우 슬라이딩으로 연속된 5칸을 검사 (한 칸 떨어진 패턴을 위해) + for (int start = 0; start <= 5; start++) + { + // 현재 위치가 이 윈도우에 포함되는지 확인 + bool currentPositionInWindow = (start <= centerIndex && centerIndex < start + 5); + if (!currentPositionInWindow) continue; + + // 윈도우 내의 돌 개수 세기 + int stoneCount = 0; + for (int i = 0; i < 5; i++) + { + if (linePattern[start + i] == Black) + { + stoneCount++; + } + } + + // 정확히 4개의 돌이 있고, 1개의 빈칸이 있으면 4로 판정 + // (현재 위치는 흑으로 이미 설정되어 있음) + if (stoneCount == 4) + { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/Assets/Script/Renju/RenjuDoubleFourDetector.cs.meta b/Assets/Script/Renju/RenjuDoubleFourDetector.cs.meta new file mode 100644 index 0000000..abc7cef --- /dev/null +++ b/Assets/Script/Renju/RenjuDoubleFourDetector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0daf1f2b8cbe4b19adc0e42db7a15991 +timeCreated: 1742270734 \ No newline at end of file diff --git a/Assets/Script/Renju/RenjuDoubleThreeDetector.cs b/Assets/Script/Renju/RenjuDoubleThreeDetector.cs new file mode 100644 index 0000000..943063e --- /dev/null +++ b/Assets/Script/Renju/RenjuDoubleThreeDetector.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using UnityEngine; + +public class RenjuDoubleThreeDetector: ForbiddenDetectorBase +{ + /// + /// 쌍삼(3-3) 여부를 검사합니다. + /// + /// 현재 보드 상태 + /// 행 좌표 + /// 열 좌표 + /// 쌍삼이면 true, 아니면 false + public bool IsDoubleThree(Enums.PlayerType[,] board, int row, int col) + { + // 임시로 돌 배치 + board[row, col] = Black; + + // 쌍삼 검사 + // bool isThreeThree = CheckThreeThree(board, row, col); + + // 원래 상태로 되돌림 + board[row, col] = Space; + + return false; + } +} \ No newline at end of file diff --git a/Assets/Script/Renju/RenjuDoubleThreeDetector.cs.meta b/Assets/Script/Renju/RenjuDoubleThreeDetector.cs.meta new file mode 100644 index 0000000..3ab4d04 --- /dev/null +++ b/Assets/Script/Renju/RenjuDoubleThreeDetector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1c3d6cdb2557431f82e7997bc5ad685c +timeCreated: 1742283793 \ No newline at end of file diff --git a/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs b/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs index 7833607..b6d6789 100644 --- a/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs +++ b/Assets/Script/Renju/RenjuForbiddenMoveDetector.cs @@ -13,7 +13,7 @@ public class RenjuForbiddenMoveDetector /// public RenjuForbiddenMoveDetector() { - // 각 감지기 초기화 + // 감지기 초기화 _ruleChecker = new RenjuRuleChecker(); } @@ -26,10 +26,6 @@ public class RenjuForbiddenMoveDetector { var doubleThreeList = _ruleChecker.GetForbiddenMoves(board); - foreach (var doubleThreePos in doubleThreeList) - { - Debug.Log("삼삼 금수 좌표 X축 : " + doubleThreePos.x + ", Y축 : " + doubleThreePos.y); - } return doubleThreeList; } diff --git a/Assets/Script/Renju/RenjuOverlineDetector.cs b/Assets/Script/Renju/RenjuOverlineDetector.cs new file mode 100644 index 0000000..f9646a6 --- /dev/null +++ b/Assets/Script/Renju/RenjuOverlineDetector.cs @@ -0,0 +1,102 @@ +public class RenjuOverlineDetector : ForbiddenDetectorBase +{ + /// + /// 장목 여부를 검사합니다. + /// + /// 현재 보드 상태 + /// 행 (y 좌표) + /// 열 (x 좌표) + /// 장목이면 true, 아니면 false + public bool IsOverline(Enums.PlayerType[,] board, int row, int col) + { + // 임시로 돌 놓기 + board[row, col] = Black; + + // 장목 검사 + bool isOverline = CheckOverline(board, row, col); + + + board[row, col] = Space; + + return isOverline; + } + + /// + /// 장목(6목 이상) 여부를 검사합니다. + /// + private bool CheckOverline(Enums.PlayerType[,] board, int row, int col) + { + // 4개의 방향 쌍에 대해 검사 + for (int i = 0; i < 4; i++) + { + int dir1 = DirectionPairs[i, 0]; + int dir2 = DirectionPairs[i, 1]; + + // 해당 방향 쌍에서 연속된 돌의 총 개수 계산 + int count = CountConsecutiveStones(board, row, col, dir1, dir2); + + // 6목 이상이면 장목 + if (count >= 6) + { + return true; + } + } + + return false; + } + + /// + /// 특정 방향 쌍에서 연속된 돌의 개수를 계산합니다. + /// + /// 현재 보드 상태 + /// 시작 행 + /// 시작 열 + /// 첫 번째 방향 인덱스 + /// 두 번째(반대) 방향 인덱스 + /// 연속된 돌의 총 개수 + private int CountConsecutiveStones(Enums.PlayerType[,] board, int row, int col, int dir1, int dir2) + { + int count = 1; // 현재 위치의 돌 포함 + + // 첫 번째 방향으로 연속된 돌 확인 + count += CountInDirection(board, row, col, dir1); + + // 두 번째(반대) 방향으로 연속된 돌 확인 + count += CountInDirection(board, row, col, dir2); + + return count; + } + + /// + /// 특정 방향으로의 연속된 돌 개수를 계산합니다. + /// + /// 현재 보드 상태 + /// 시작 행 + /// 시작 열 + /// 방향 인덱스 + /// 검사할 플레이어 타입 + /// 해당 방향의 연속된 돌 개수 + private int CountInDirection(Enums.PlayerType[,] board, int startRow, int startCol, int dirIndex) + { + int count = 0; + int dRow = Directions[dirIndex, 0]; + int dCol = Directions[dirIndex, 1]; + + for (int i = 1; i < _boardSize; i++) + { + int newRow = startRow + dRow * i; + int newCol = startCol + dCol * i; + + if (IsInBounds(newRow, newCol) && board[newRow, newCol] == Black) + { + count++; + } + else + { + break; + } + } + + return count; + } +} diff --git a/Assets/Script/Renju/RenjuOverlineDetector.cs.meta b/Assets/Script/Renju/RenjuOverlineDetector.cs.meta new file mode 100644 index 0000000..680b6a5 --- /dev/null +++ b/Assets/Script/Renju/RenjuOverlineDetector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bbb7c61178964d4b8aeb7fb8375bf285 +timeCreated: 1742265525 \ No newline at end of file diff --git a/Assets/Script/Renju/RenjuRuleChecker.cs b/Assets/Script/Renju/RenjuRuleChecker.cs index 3071293..7beab19 100644 --- a/Assets/Script/Renju/RenjuRuleChecker.cs +++ b/Assets/Script/Renju/RenjuRuleChecker.cs @@ -1,690 +1,48 @@ 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 } // 오른쪽 위 - }; + private RenjuOverlineDetector _overlineDetactor = new(); + private RenjuDoubleFourDetector _doubleFourDetactor = new(); + private RenjuDoubleThreeDetector _doubleThreeDetector = new(); - // 기본 삼(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++) + List forbiddenMoves = new(); + for (int row = 0; row < _boardSize; row++) { - for (int y = 0; y < height; y++) + for (int col = 0; col < _boardSize; col++) { - // 빈 칸만 검사 - if (board[x, y] != Enums.PlayerType.None) - continue; + // ** 비어 있지 않으면 검사할 필요 없음 ** + if (!IsEmptyPosition(board, row, col)) continue; - // 해당 위치에 흑돌을 임시로 놓았다고 가정 - Enums.PlayerType[,] simulatedBoard = (Enums.PlayerType[,])board.Clone(); - simulatedBoard[x, y] = Enums.PlayerType.PlayerA; - - // 장목(6목 이상) 확인 - if (IsOverline(simulatedBoard, x, y)) + // 장목 검사 + if (_overlineDetactor.IsOverline(board, row, col)) { - forbiddenMoves.Add(new Vector2Int(x, y)); + Debug.Log("장목 금수 좌표 X축 : " + row + ", Y축 : " + col); + forbiddenMoves.Add(new Vector2Int(row, col)); continue; } - // 모든 방향별 패턴 정보 수집 - PatternInfo patternInfo = CollectAllPatterns(simulatedBoard, x, y); - - // 4-3 패턴 확인 (이 위치에 놓았을 때 열린 4가 1개, 열린 3이 1개 이상 생성되면 4-3 패턴) - bool isFourThree = IsFourThreeAdvanced(patternInfo); - if (isFourThree) + // 4-4 검사 + if (_doubleFourDetactor.IsDoubleFour(board, row, col)) { - // 4-3 패턴은 금수가 아님 + Debug.Log("사사 금수 좌표 X축 : " + row + ", Y축 : " + col); + forbiddenMoves.Add(new Vector2Int(row, col)); continue; } - // 쌍삼(3-3) 또는 사사(4-4) 확인 - bool isThreeThree = IsThreeThreeAdvanced(patternInfo); - bool isFourFour = IsFourFourAdvanced(patternInfo); - - // 금수 여부 판정 - if (isThreeThree || isFourFour) + // 3-3 검사 + if (_doubleThreeDetector.IsDoubleThree(board, row, col)) { - forbiddenMoves.Add(new Vector2Int(x, y)); + Debug.Log("삼삼 금수 좌표 X축 : " + row + ", Y축 : " + col); + forbiddenMoves.Add(new Vector2Int(row, col)); } } } 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>(); - } }