Delphi [따라하기] 인공지능 오목게임(7) - 오목게임 승자(5돌) 결정 지능프로그램
2017.10.17 02:29
[따라하기] 인공지능 오목게임 [7] 오목게임 승자(5돌) 결정 지능프로그램
안녕하세요 델파이 프로그래머님들!
이번 인공지능 오목게임 7번째 세션에서는 다음의 과정을 해보도록 하겠습니다.
1. 마우스클릭에서 호출되는 IsThereFiveToStopGame (5개연속있는가!) 함수(function)의 의미는?
2. 좌표상 서->동 수평축(West->East) 상 5돌 연속 있는가를 확인하기 위한 기본 이해
3. 좌표상 서->동 수평축(West->East) 과 동->서 수평축 등 연속 5돌을 확인하기 위한
모든 경우의 수를 생각해보고 나열식(비효율적)으로 1차적 프로그램 하기
4. 연속 5돌을 확인하기 위한 좌표상 8개 방향을 이용하는 효율적 방식 프로그램 하기
5. 게임 종료후 GameOver를 사용하기
6. 다음 세션들 중에 하게될 컴퓨터와 사람과의 프로그램 논리흐름도 이해하기
1. 마우스클릭에서 호출되는 IsThereFiveToStopGame (5개연속있는가?) 함수(function)의 의미는?
지난 세션에서 마우스클릭에서 IsThereFiveToStopGame 함수(function) 호출한 것 기억하시죠?
이 프로시져를 호출시 3개의 값 (흑백여부, Column값, Row값) 을 보냅니다.
procedure TMainForm.TTTBoardMouseUp( , , , );
begin
, , ,
if IsThereFiveToStopGame(StonePiece[Column, Row].WhiteOrBlack, Column, Row) then
MessageDlg('백색의 5개 연속입니다!', mtInformation, [mbOk], 0);
end;
function TMainForm.IsThereFiveToStopGame(ForWho : char; X, Y: integer): boolean;
begin
// 5개 연속있는가를 결정함
end;
이렇게 함수(function)에서 ForWho, X, Y 변수이름으로 받아들입니다.
여기서 ForWho의 값은 W, B, ‘ ’ 3개중 하나입니다. 그리고 매번 놓여진 돌의 X,Y좌표 값을 승자를
확인하기 위해 전합니다
2. 좌표상 동-서 수평축(West-East) 상 5돌 연속있는가를 확인하기 위한 기본 이해
위 그림처럼 각 돌이 화살표 방향으로 순서대로 놓여진다고 가정하면 좌표 점의 변화는 다음과 같습니다.
1번: (1,1), (2,1), (3,1), (4,1), (5,1) : X 축만 1씩 증가
2번: (11,1), (11,2), (11,3), (11,4), (11,5) : Y 축만 1씩 증가
3번: (1,10), (2,11), (3,12), (4,13), (5,14) : X, Y 축 1씩 증가
4번: (19,10), (18,11), (17,12), (16,13), (15,14) : X는 1씩 감소, Y는 1씩 증가
물론 화살표 반대방향으로 놓여지게 될 때 좌표 점의 변화도 알수 있겠죠.
3. 좌표상 동-서 수평축(West-East) 상 5돌 연속 확인하기 위한 프로그램 하기
연속 5돌을 확인하기 위해 프로그램을 하는데는 2가지 방법이 있습니다.
1) 코딩을 길게 나열해서 재사용을 못하는 비효율적이 방법이 있고
2) 코딩의 재사용이 가능한 효율적이 방법이 있습니다.
물론 프로그래머가 2번째 방법으로 처음부터 시작하면 좋겠지만 그게 잘 안될때도 있기에
1번째 방법으로 문제를 일단 해결하고 2번째 방법으로 개선하는 것도 좋을 것 같습니다.
그래서 이번 따라하기에선 먼저 1번째 방법으로 해보고 2번째 방법으로 변환하는 것을 함께 해보도록 하겠습니다.
function TMainForm.IsThereFiveToStopGame(ForWho : char; X, Y: integer): boolean;
var count : integer;
begin
Count := 0;
//코딩을 길게 나열하는 비효율적으로 하자면 이런방식입니다.
if StonePiece[X , Y ].WhiteOrBlack = ForWho then
Inc(Count);
if StonePiece[X + 1 , Y ].WhiteOrBlack = ForWho then
Inc(Count);
if StonePiece[X + 2 , Y ].WhiteOrBlack = ForWho then
Inc(Count);
if StonePiece[X + 3 , Y ].WhiteOrBlack = ForWho then
Inc(Count);
if StonePiece[X + 4 , Y ].WhiteOrBlack = ForWho then
Inc(Count);
// 나머지 모든 경우의 수를 반영해야합니다.
if (Count = 5) then
Result := true
else Result := false;
end;
여기 StonePiece[X , Y ]
StonePiece[X + 1 , Y ]
, , ,
StonePiece[X + 4 , Y ] 처럼
X좌표값을 1씩 증가하며 같은색의 돌의 Count 가 5개가 되는지 확인하고 함수의 Result 겂을 전달합니다.
그런데 프로그램을 실행해서 위 그림처럼 (1,1), (2,1), (3,1), (4,1), (5,1) 순서로 놓으면 무슨 일이 일어날까요?
게임의 승자를 선언할까요?
아닙니다!
그럼 이제 위 그림의 1번 화살표의 반대방향으로 순서대로 (5,1), (4,1), (3,1), (2,1), (1,1) 에 놓아보죠!
이럴 경우 게임 승자를 정상적으로 선언하죠!
왜냐면 맨 마지막 놓여진 X, Y 좌표 값에서부터 검사를 하기 때문입니다.
그래서 우리는 마지막 놓여진 돌의 좌표점을 기준으로 양방향으로 5개의 연속돌이 있나를 확인해야 합니다.
그러므로 다음처럼 TMainForm.IsThereFiveToStopGame을 추가합니다.
function TMainForm.IsThereFiveToStopGame(ForWho : char; X, Y: integer): boolean;
begin
, , ,
//2차로 반대 방향으로 검사하기
if (Count <> 5) then
begin
Count := 0;
if StonePiece[X , Y ].WhiteOrBlack = ForWho then
Inc(Count);
if StonePiece[X - 1 , Y ].WhiteOrBlack = ForWho then
Inc(Count);
if StonePiece[X - 2 , Y ].WhiteOrBlack = ForWho then
Inc(Count);
if StonePiece[X - 3 , Y ].WhiteOrBlack = ForWho then
Inc(Count);
if StonePiece[X - 4 , Y ].WhiteOrBlack = ForWho then
Inc(Count);
end;
, , ,
end;
즉 StonePiece[X , Y ], StonePiece[X - 1 , Y ] , , , StonePiece[X - 4 , Y ] 로
X좌표값이 1씩 감소해면서 반대방향으로 검색을 할 수 있습니다.
그래서 이렇게 (4개의 경우 방향 x 반대방향 ) 의 총 8개의 경우 수를 비효율적으로 프로그램 해야 합니다.
4. 연속 5돌을 확인하기 위한 좌표상 8개 방향을 확인하는 효율적 프로그램 하기
이제 코딩의 재사용이 가능한 효율적 프로그래밍 방법을 알아 보겠습니다.
[마지막 포석점을 기준으로 한 8 방향의 검사를 나타낸 그림]
A. 위 그림처럼 빨간색 마지막 포석 점을 기준으로 다음 논리를 생각해보고 프로그램을 해봅니다.
1번. 빨간점 기준 –> 1번 방향으로 5돌 검색 (X, Y-1) 하고
연속5돌이 없으면
빨간점 기준 –> 5번 방향으로 검색 (X, Y+1) 함.
2번. 빨간점 기준 –> 2번 방향으로 5돌 검색 (X+1, Y-1) 하고
연속5돌이 없으면
빨간점 기준 –> 6번 방향으로 검색 (X-1, Y+1) 함.
3번. 빨간점 기준 –> 3번 방향으로 5돌 검색 (X+1, Y) 하고
연속5돌이 없으면
빨간점 기준 –> 7번 방향으로 검색 (X-1, Y) 함.
4번. 빨간점 기준 –> 4번 방향으로 5돌 검색 (X+1, Y+1) 하고
연속5돌이 없으면
빨간점 기준 –> 8번 방향으로 검색 (X-1, Y-1) 함.
B. 이런 기준을 세우고 IsThereFiveToStopGame 함수를 코딩을 수정 해보겠습니다.
function TMainForm.IsThereFiveToStopGame(ForWho : char; X, Y: integer): boolean;
begin
, , ,
if CheckFourDirForFive(ForWho, 1, X, Y) then
Result := true
else if CheckFourDirForFive(ForWho, 2, X, Y) then
Result := true
else if CheckFourDirForFive(ForWho, 3, X, Y) then
Result := true
else if CheckFourDirForFive(ForWho, 4, X, Y) then
Result := true
else Result := false;
end;
여기서 CheckFourDirForFive(ForWho, 1, X, Y) 함수를 호출하는데
A 에서 언급한 것처럼 포석 점을 기준으로 1번 부터 ~ 4번까지의 방향으로 검사를 합니다.
function TMainForm.CheckFourDirForFive(ForWho: char; WhichWay, X, Y: integer): boolean;
var
XFactor, YFactor, SquareCount, Count, EmptyCount : integer;
begin
case WhichWay of
1 : begin XFactor := 0; YFactor := -1; end;
2 : begin XFactor := 1; YFactor := -1; end;
3 : begin XFactor := 1; YFactor := 0; end;
4 : begin XFactor := 1; YFactor := 1; end;
5 : begin XFactor := 0; YFactor := 1; end;
6 : begin XFactor := -1; YFactor := 1; end;
7 : begin XFactor := -1; YFactor := 0; end;
8 : begin XFactor := -1; YFactor := -1; end;
end;
여기서 선언된 XFactor, YFactor 는 4-A에서 설명된 좌표점 변화를 구현한 것입니다.
( 이 내용은 나중에는 GetXYFactor(WhichWay, XFactor, YFactor)라는 함수로 분리할 예정입니다.)
C. 아래의 그림에서는 3가지 경우를 생각해야 합니다.
1) 다른 색 돌로 막혀있을 경우?
그림의 1번처럼 빨간 포석점부터 카운트를 하다 검은돌로 막히면
더 이상 진행하지않고 반대방향으로 검사합니다.
이를 코딩에선 BlockedBy 변수를 통해 구현 합니다.
2) 빈 자리가 있을 경우?
그림의 2번 처럼 카운트를 하다 빈 자리가 나오면 더 이상 진행하지 않고
반대방향으로 검사합니다. 이를 코딩에선 EmptyCount 로 확인합니다.
3) 좌표의 이동을 반영하려면?
SquareCount 는 실제 검사한 좌표 점 의 값을 저장하는 변수이며 빈 좌표점일 경우도
값이 올라갑니다. 이는 (XFactor * SquareCount) 와 (YFactor * SquareCount)
형태로 사용되어 좌표 값의 이동을 반영합니다.
그럼 다음의 코딩 결과를 얻을 수 있습니다.
function TMainForm.CheckFourDirForFive(ForWho: char; WhichWay, X, Y: integer): boolean;
begin
Count := 0;
SquareCount := 0;
EmptyCount := 0;
, , ,
While ((Not BlockedBy) and (SquareCount < 5) and (EmptyCount = 0)) do
begin
if ((X + (XFactor * SquareCount) > 0) and (X + (XFactor * SquareCount) < 20)
and (Y + (YFactor * SquareCount) > 0) and (Y + (YFactor * SquareCount) < 20) ) then
begin
if StonePiece[X + (XFactor * SquareCount), Y + (YFactor * SquareCount)].WhiteOrBlack = ForWho then
begin
Inc(Count); inc(SquareCount);
end
else if StonePiece[X + (XFactor * SquareCount), Y + (YFactor * SquareCount)].WhiteOrBlack = ' ' then
begin
inc(EmptyCount); Inc(SquareCount);
end
else if StonePiece[X + (XFactor * SquareCount), Y + (YFactor * SquareCount)].WhiteOrBlack <> ForWho then
BlockedBy := true;
end;
end;
여기서 다음 코딩은 바둑판 경계를 벋어나지 않을 때만 수행하도록 제한합니다.
if ((X + (XFactor * SquareCount) > 0) and (X + (XFactor * SquareCount) < 20)
and (Y + (YFactor * SquareCount) > 0) and (Y + (YFactor * SquareCount) < 20) ) then
begin
게임을 해보시면 아시겠지만 [그림- 8 방향의 검사를 나타낸 그림]에서 언급한거처럼
1번 방향, 2번 방향, 3번 방향, 4번 방향은 정상 작동하지만
5, 6, 7, 8 번 방향은 아직 미완이죠!
그래서 이제 1, 2, 3, 4 번의 반대 방향으로 검사하도록 해야합니다.
D. GetXYFactor 프로시져를 선언해서 함수의 재활용이 가능히게 하고 XFactor, YFactor 값 받아오려고 합니다.
아래의 선언된 GetXYFactor 프로시져 파라미터를 보면 var 선언을 하면 XFactor, YFactor 값을
전달할 수 있습니다.
procedure TMainForm.GetXYFactor(WhichWay: integer; var XFactor, YFactor: integer);
begin
case WhichWay of
1 : begin XFactor := 0; YFactor := -1; end;
2 : begin XFactor := 1; YFactor := -1; end;
3 : begin XFactor := 1; YFactor := 0; end;
4 : begin XFactor := 1; YFactor := 1; end;
5 : begin XFactor := 0; YFactor := 1; end;
6 : begin XFactor := -1; YFactor := 1; end;
7 : begin XFactor := -1; YFactor := 0; end;
8 : begin XFactor := -1; YFactor := -1; end;
end;
end;
그리고 위에 선언된 GetXYFactor 프로시져를 아래의 CheckFourDirForFive 안에서 호출하여
좌표방향에 따른 XFactor, YFactor 값을 전달 받습니다.
그리고 1, 2, 3, 4 방향으로 연속5개가 있나 검사한 후 연속5개가 없는 경우에는
반대방향인 5, 6, 7, 8 방향으로 다시 연속5개가 있나 검사합니다.
function TMainForm.CheckFourDirForFive( , , , ): boolean;
begin
, , ,
//대칭 방향으로 검사하기
if (count < 5) then
begin
GetXYFactor(WhichWay + 4, XFactor, YFactor); // WhichWay + 4는 대칭 방향으로 검사하기위함
count := count - 1; // 마지막 포석점은 이미 위에서 카운트 했기에 -1 함
BlockedBy := false;
SquareCount := 0; EmptyCount := 0;
, , ,
end;
if (Count = 5) then
begin
GameOver := true;
Result := true
end
else Result := false;
end;
5. 게임 종료후 GameOver를 사용하기
그리고 마지막으로 게임이 끝났으면 GameOver 변수를 True롤 선언하고 계속 진행할수 없게
다음처럼 TTTBoardMouseUp에서 GameOver 상태를 검사하도록 변경합니다.
procedure TMainForm.TTTBoardMouseUp( , ,, )
begin
, , ,
if Not GameOver then
begin
, , ,
end;
end;
6. 논리흐름도 이해하기
ps 다음 논리 흐름도는 컴퓨터와 사람의 게임을 위한 논리를 구현했던 것입니다.
고민해 보시죠!
다음은 논리 흐름도상의 내용입니다.
1번: 5개의 있는지 검사하기
2번: 컴퓨터가 5개를 만들 경우가 있는지 검사
3번: 사람이 4개가 있는지 검사
4번: 컴퓨터가 4개를 만들 경우가 있는지 검사
5번: 사람이 3개가 있는지 검사
6번: 사람이 함정 수가 있는지 검사
7번: 컴퓨터가 함정 수를 만들 경우가 있는지 검사
8번: 컴퓨터가 3개를 만들 경우가 있는지 검사
내용이 길었습니다. 설명하는 능력이 모자라서 이해가 되셨는지 모르겠습니다.
첨부 파일을 실행해서 연구하다 보면 도움이 많이 되리라 생각됩니다.
그럼 좋은 하루 보내세요.