이번 회차에서는 오목여부를 판단하는 방법에 대해 구현해 보겠습니다.
오목을 판단하기 위해 오목판의 착수정보를 추출하고 패턴을 확인하는 로직은 오목판단뿐만 아니라 이후에 각 포인트의 우선순위를 분석하는데 이용하게 됩니다.
오목여부 판단
오목여부 판단은 오목판의 착수정보를 추출하여 패턴을 확인하는 방법으로 진행합니다.
오목판 착수정보 추출
최종 착수점을 기준으로 가로, 세로, 좌대각선, 우대각선 각각에 대해 검색 후 착수된 모든 정보를 추출합니다.
착수정보는 흑돌, 백돌, 공백, 오목판 외부 정보를 모두 구분하여 수집합니다.
각 포인트 정보는 다음의 알파벳으로 구분합니다.
- B: 흑돌
- W: 백돌
- S: 공백
- X: 오목판 외부
추출함수는 COmok클래스의 멤버함수로 구현하며 4개의 방향에 대해 동일한 로직이므로 하나의 함수로 인자값을 변경하여 4개 방향을 모두 추출할 수 있도록 합니다.
인자값은 각각 아래와 같습니다.
- point : 최종적으로 착수한 포인트 객체 (속성 : x, y, color, order)
- dx : 가로방향의 증감을 조정하기 위한 변수 (1이면 증가, 0이면 고정, -1이면 감소)
- dy : 세로방향의 증감을 조정하기 위한 변수 (1이면 증가, 0이면 고정, -1이면 감소)
private StoneInfo GetStoneInfo(CPoint point, int dx, int dy)
{
int x = 0;
int y = 0;
int pos = 0;
int spaceCount = 0;
StoneInfo stoneInfo;
stoneInfo.info = "";
stoneInfo.no = 0;
do
{
x = point.X + pos * dx;
y = point.Y + pos * dy;
if (x < 1 || x > boardSize || y < 1 || y > boardSize)
{
stoneInfo.info += 'X';
break;
}
if (mainBoard[x, y] == 'B' || mainBoard[x, y] == 'W')
{
stoneInfo.info += mainBoard[x, y];
spaceCount = 0;
if (mainBoard[x, y] != point.Color) break;
}
else
{
stoneInfo.info += 'E';
spaceCount++;
if (spaceCount == 3) break;
}
pos++;
} while (x >= 1 && x <= boardSize & y >= 1 && y <= boardSize);
spaceCount = 0;
pos = 1;
do
{
stoneInfo.no = pos;
x = point.X + pos * dx * (-1);
y = point.Y + pos * dy * (-1);
if (x < 1 || x > boardSize || y < 1 || y > boardSize)
{
stoneInfo.info = 'X' + stoneInfo.info;
break;
}
if (mainBoard[x, y] == 'B' || mainBoard[x, y] == 'W')
{
stoneInfo.info = mainBoard[x, y] + stoneInfo.info;
spaceCount = 0;
if (mainBoard[x, y] != point.Color) break;
}
else
{
stoneInfo.info = 'E' + stoneInfo.info;
spaceCount++;
if (spaceCount == 3) break;
}
pos++;
} while (x >= 1 && x <= boardSize & y >= 1 && y <= boardSize);
return stoneInfo;
}
각 방향별 검색 시 다음과 같은 3가지 조건중 하나라도 해당되면 그 방향의 검색은 중지합니다.
- 다른 돌을 만난 경우
- 연속된 공백이 3개인 경우
- 오목판을 벗어난 경우
이렇게 해서 4개의 방향으로 추출한 예시를 보면 아래와 같은 형태로 나오게 됩니다.
- 가로: SSSBSSS
- 세로: WBSSS
- 우 대각선: SSSBBBSSS
- 좌 대각선: SSSBBW
각 방향별 검색 시 다음과 같은 3가지 조건중 하나라도 해당되면 그 방향의 검색은 중지합니다.
패턴 확인 : 오목패턴과 비교
가로, 세로, 좌대각선, 우대각선 4개의 방향으로 검색해서 추출한 착수정보에 대해 각 방향별로 오목패턴과 비교하여 오목여부를 판단합니다.
오목패턴은 아래와 같이 배열로 관리하여 추출한 착수정보 패턴과 비교하여 오목여부를 판정하게 됩니다.
오목패턴배열은 오목게임의 Rule에 맞게 필요에 따라 조정하면 자동으로 비교 로직에서 참조할 수 있도록 관리합니다.
readonly string[][] omokPattern = new string[2][]
{
new string[] { "EBBBBBE", "EBBBBBW", "EBBBBBX", "WBBBBBE", "WBBBBBW", "WBBBBBX", "XBBBBBE", "XBBBBBW", "XBBBBBX" },
new string[] { "WWWWW" }
};
흑의 경우 6목 이상의 장목은 승리로 인정하지 않기 때문에 좌우에 백돌, 공백, 오목판 모서리 등으로 막혀 있는지를 확인하기 위해 구체적인 패턴을 정의했고, 백은 장목인 경우에도 승리하는 룰이므로 백돌 5개가 연속으로 포함되어 있는지만 확인하면 되기 때문에 한 가지 패턴으로 정리합니다.
추출한 착수정보가 오목패턴배열에 정의된 패턴을 포함하면 오목이 되기 때문에 패턴을 찾는 별도의 Omok클래스의 멤버함수를 만들어 줍니다.
인자로는 추출한 착수정보(stoneInfo)와 오목패턴배열 중 흑돌과 백돌을 구분하여 넘겨주고, 매칭되는 패턴이 있으면 result를 true로 return 하게 됩니다.
private bool FindPattern(StoneInfo stoneInfo, string[] pattern)
{
bool result = false;
foreach (var item in pattern)
{
if (stoneInfo.info.Contains(item))
{
if (stoneInfo.info.IndexOf(item) <= stoneInfo.no && (stoneInfo.no < (stoneInfo.info.IndexOf(item) + item.Length)))
{
result = true; ;
}
}
}
return result;
}
findPattern함수를 통해 체크하는 방법은 아래와 같습니다.
//오목 체크
if (FindPattern(stoneInfo, omokPattern[point.Color == 'B' ? 0 : 1]))
{
omokPatternFlag.omokFlag[direction - 1] = true;
return stoneInfo;
}
//오목 체크
if (this.findPattern(pattern, this.omokPattern[checkStone])) {
this.omokFlag[direction] = true;
return;
}
- stoneInfo: 추출한 착수패턴정보를 전달합니다.
- omokPattern[point.Color == 'B' ? 0 : 1] : 흑돌 순서이면 0을 전달하고, 백돌 순서이면 1을 전달하여 해당 오목패턴배열을 참조하게 합니다.
- omokPatternFlag.omokFlag[direction - 1] = true; : 4개의 방향 중에 오목이 되는 방향을 direction에 세팅하여 omokFlag에 true를 세팅합니다.
- omokPatternFlag 객체는 omokFlag 등의 각종 Flag를 관리하기 위한 클래스의 객체입니다.
분석함수 작성
착수정보 추출함수와 패턴확인함수를 이용하여 최종적으로 4개의 방향에 대해 분석하는 함수를 작성합니다.
4개의 방향에 대해 동일한 로직을 사용할 수 있도록 인자값으로 방향을 전달하는 형태로 구현하겠습니다.
public StoneInfo AnalyzePoint(CPoint point, int dx, int dy, int direction)
{
omokPatternFlag.Initialize(direction - 1);
StoneInfo stoneInfo = GetStoneInfo(point, dx, dy);
//오목 체크
if (FindPattern(stoneInfo, omokPattern[point.Color == 'B' ? 0 : 1]))
{
omokPatternFlag.omokFlag[direction - 1] = true;
return stoneInfo;
}
return stoneInfo;
}
인자값은 아래와 같습니다.
- point : 착수정보객체
- dx, dy : 4개의 방향을 분석하기 위해 조정하는 변수 (1이면 증가, 0이면 고정, -1이면 감소)
- direction : 4개 방향에 대한 구분자(1~4)
4개의 방향에 대해 오목여부를 체크하여 omokPatternFlag객체 있는 omokFlag 배열에 각각 세팅합니다. 이후 처리에서 오목여부는 omokFlag배열로 체크하면 됩니다.
착수정보 저장 멤버함수(putStone) 반영
매 착수시마다 오목여부를 판단하기 위해 지금까지 작성한 내용을 착수정보 저장 멤버함수에 반영합니다.
public void PutStone(int x, int y)
{
char nextColor = 'B';
//착수돌 색깔 구하기
if (mainList.Count > 0)
{
nextColor = mainList[mainList.Count - 1].Color == 'B' ? 'W' : 'B';
}
//착수정보List(mainList)에 착수정보 추가
mainList.Add(new CPoint(GetBoardPosition(x, y).X, GetBoardPosition(x, y).Y, nextColor));
//오목판 배열(mainBoard) 초기화 후 갱신
for (int i = 0; i < boardSize + 1; i++)
{
for (int j = 0; j < boardSize + 1; j++)
{
if (i == 0 || j == 0)
mainBoard[i, j] = 'X';
else
mainBoard[i, j] = 'E';
}
}
//착수정보List 기준으로 오목판 배열(mainBoard) 갱신
foreach (CPoint p in mainList)
{
mainBoard[p.X, p.Y] = p.Color;
}
//착수점 분석
stoneinfo[0] = AnalyzePoint(new CPoint(GetBoardPosition(x, y).X, GetBoardPosition(x, y).Y, nextColor), 1, 0, 1);
stoneinfo[1] = AnalyzePoint(new CPoint(GetBoardPosition(x, y).X, GetBoardPosition(x, y).Y, nextColor), 0, 1, 2);
stoneinfo[2] = AnalyzePoint(new CPoint(GetBoardPosition(x, y).X, GetBoardPosition(x, y).Y, nextColor), 1, 1, 3);
stoneinfo[3] = AnalyzePoint(new CPoint(GetBoardPosition(x, y).X, GetBoardPosition(x, y).Y, nextColor), -1, 1, 4);
}
착수가 정상적으로 되면 4개의 방향에 대해 오목여부를 판단하여 각각 omokFlag에 세팅하고, 다른 처리 시에 오목여부에 대한 판단은 omokFlag로 처리하게 됩니다.
app.js 로직 구현
위에서 작성한 Omok 클래스의 멤버함수를 이용하여 착수 이벤트 시에 오목인 경우 Alert 메시지를 보여주는 로직을 추가해 보겠습니다.
//사람 착수 처리
canvas.addEventListener('click', e => {
//마우스 클릭위치를 오목포인트로 변환
let { omokX, omokY } = omokGame.getOmokPosition(e.layerX, e.layerY);
//오목판을 벗어나면 return(무시)
if (omokX < 1 || omokX > boardSize || omokY < 1 || omokY > boardSize) {
return;
}
//클릭 포인트에 이미 돌이 있는 경우 return(무시)
if (omokGame.checkOccupied(omokX, omokY)) {
return;
}
//착수정보 저장(추가)
omokGame.putStone(omokX, omokY);
//오목판 그리기
omokGame.drawBoard(context);
//착수 소리 재생
playSound.play();
//오목여부 체크
if (omokGame.omokFlag[1] || omokGame.omokFlag[2] || omokGame.omokFlag[3] || omokGame.omokFlag[4]) {
setTimeout(() => {
alert('오목!!! 다음 게임을 계속하려면 시작버튼을 누르세요.');
});
return;
}
});
오목분석 멤버함수에서 오목인 경우 omokFlag에 true로 세팅하기 때문에 여기에서는 omokFlag배열 중 한 개라도 true이면 오목이 됩니다.
실행결과 확인
오목이 되었을 때 아래와 같이 Alert가 뜨게 한 결과입니다.
'개발노트 > 오목게임_C#' 카테고리의 다른 글
C# 오목 게임 개발 #9 (0) | 2023.01.30 |
---|---|
C# 오목 게임 개발 #7 (0) | 2023.01.30 |
C# 오목 게임 개발 #6 (0) | 2023.01.30 |
C# 오목 게임 개발 #5 (0) | 2023.01.30 |
C# 오목 게임 개발 #4 (0) | 2023.01.30 |
댓글