프로젝트 개요
십목은 오목을 기반으로 한 변형 보드게임이다. 유니티 게임 개발 역량을 키우기 위해 만든 연습용 프로젝트로, 19×19 그리드와 턴제 로직 위에 간단한 휴리스틱 알고리즘을 적용한 AI 상대를 붙였다.
| 항목 | 내용 |
|---|---|
| 엔진 | Unity 2021.3.45f2 |
| 기여도 | 100% (1인 개발) |
| 플랫폼 | Android |
| 리소스 | 인터넷에서 구한 에셋으로 바둑판과 돌 이미지를 구성 |
| 결과 | 완성 |
게임 제작의 기초인 2차원 배열 활용과 턴제 시스템, 그리고 AI와의 대결을 직접 구현해 보는 것이 목표였다.

보드를 무엇으로 들고 있는가
보드 상태는 int[,] 2차원 배열 하나로 관리한다. 각 칸이 빈칸인지, 흑돌인지, 백돌인지를 정수로 들고 돌을 놓을 때마다 갱신한다.
승패 판정
돌을 둔 직후 그 위치를 기준으로 가로 · 세로 · 양방향 대각선 네 축을 검사해 연속된 돌의 개수를 센다. 당시 코드 중 가로 판정 부분이다.
private bool Is_Victory(int i, int j)
{
int k;
int count = 1;
// 가로 판정
for (k = -4; k <= 4; k++)
{
if (i + k >= 0 && i + k < 19)
{
if (posi.stone_postion[i, j] == posi.stone_postion[i + k, j])
{
count++;
if (count > 5) return true;
}
else
{
count = 1;
}
}
}
// (세로, 대각선 로직 동일 구조로 생략)
return false;
}
놓은 칸을 중심으로 좌우 4칸씩, 총 9칸을 한 번에 훑는다. 같은 색이면 count 를 올리고 다르면 1로 되돌리는 단순한 구조다.
지금 이 코드를 읽으면 보이는 것
count가 1에서 시작한다. 일치할 때마다 오르므로count > 5는 일치 6번이 아니라 연속된 돌 5개를 뜻한다.count = 0으로 시작했다면 조건이count >= 5가 되어 읽기 쉬웠을 것이다.k = 0일 때 자기 자신과 비교한다. 항상 참이라 낭비되는 비교인데, 위의 시작값 1과 맞물려 결과적으로는 맞아떨어진다. 의도한 것인지 우연인지는 코드만 봐서는 알 수 없다.- 범위를 벗어난 칸에서는
count를 리셋하지 않는다.if안에 들어가지 못하고 그냥 넘어가므로, 보드 경계 근처에서 끊긴 구간이 이어져 세어질 여지가 있다. 경계는else로 명시해 리셋하는 편이 안전하다. stone_postion은position의 오타다. 동작에는 영향이 없지만 그대로 남아 있다.
AI가 둘 자리를 고르는 방법
탐색 트리를 쓰지 않는다. 보드의 빈칸마다 8방향으로 늘어선 돌의 개수를 세어 가중치를 매기고, 가장 높은 좌표에 둔다. 이때 자기 돌을 잇는 값과 상대 돌을 막는 값을 각각 구해 둘 중 무엇을 우선할지 판단한다.
// 위치별 우선순위 가중치 탐색 및 결정 로직의 일부
if (max_ai < 3 && max_player < 3)
{
if (max_player > max_ai)
{
max_posi_drx[2, 0] = max_posi_drx[0, 0];
max_posi_drx[2, 1] = max_posi_drx[0, 1];
}
else
{
max_posi_drx[2, 0] = max_posi_drx[1, 0];
max_posi_drx[2, 1] = max_posi_drx[1, 1];
}
}
// 위와 같은 조건 분기를 통해 최적의 위치를 max_posi_drx[2] 배열에 저장 후 돌을 둠
putting.Put(max_posi_drx[2, 0], max_posi_drx[2, 1], 2);
| 코드 | 읽는 법 |
|---|---|
max_player | 플레이어 쪽에서 가장 위험한 자리의 가중치 |
max_ai | AI 쪽에서 가장 좋은 자리의 가중치 |
max_posi_drx[0] / [1] | 각각 방어 후보 좌표 / 공격 후보 좌표 |
max_posi_drx[2] | 실제로 둘 좌표 |
< 3 분기 | 양쪽 다 아직 급하지 않은 국면일 때의 처리 |
즉 공격과 방어를 따로 계산해 두고 더 큰 쪽을 고르는 구조다. 양쪽 가중치가 3 미만이면 아직 승부처가 아니라고 보고 그 안에서 다시 비교한다.
max_posi_drx[0], [1], [2] 처럼 인덱스로 의미를 구분하면 나중에 읽을 때 매번 되짚어야 한다. defenseBest, attackBest, chosen 같은 이름의 Vector2Int 로 두면 위 분기가 그대로 읽힌다. 가중치 기준값 3 도 상수로 빼두면 난이도를 조절할 지점이 명확해진다.스크립트 구성

에디터 화면을 보면 AI, GameManager, Put_Stone, Stone_Field, Turn_System, Winning 등으로 스크립트가 나뉘어 있다. 보드의 각 칸은 Square 오브젝트로 깔아 두고, AI_Object 의 Field 슬롯에 Stone_Field 를 연결해 배열을 넘긴다.
정리
이 프로젝트에서 얻은 것은 2차원 배열 제어와 상태 관리다. 게임 로직 구현의 기본기에 해당하는 부분이고, 특히 컴퓨터가 규칙에 맞게 타당한 자리에 돌을 두도록 휴리스틱 알고리즘을 직접 설계하고 값을 조절해 본 것이 가장 큰 수확이었다.
동시에 남은 숙제도 분명하다. 구현 당시에는 기능이 동작하는 데 초점을 맞추다 보니 연산 로직이 한곳에 몰렸다. 다음에 비슷한 것을 만든다면 클래스의 책임을 나누고 유지보수하기 좋은 구조로 가야 한다.