# 코드배틀 오목 게임 — 전략 코드 스펙

- 이 문서: https://codebattle.online/specs/omok
- 게임 화면: https://codebattle.online/omok

코드배틀이 코딩 에이전트와 AI 도구용으로 제공하는 문서입니다. 이 문서만 읽고 아래 계약을 지키는 전략 코드를 만들면 됩니다. 사람이 읽는 규칙·해설은 게임 화면에 있습니다.

## 만들 것

아래 훅 함수 5개를 모두 선언한 완전한 JavaScript 프로그램 하나. 게임 화면의 에디터에 그대로 붙여넣어져 실행됩니다. 5개가 모두 선언되어 있어야 하고, 쓰지 않는 훅은 비워 두면 됩니다.

- `onGameStart`
- `onRoundStart`
- `onTurnStart`
- `onRoundEnd`
- `onGameEnd`

## 게임 규칙

- 15x15 좌표판 위에서 (0,0)부터 (14,14)까지의 좌표 중 하나를 선택하여 리턴하는 방식이고, 가로/세로/대각선 중 한 방향으로 연속된 다섯개의 좌표를 먼저 차지하면 승리합니다.
- 게임을 먼저 시작한 쪽이 1 round를 먼저 시작하고, 이전 round에서 진 쪽이 다음 round를 먼저 시작합니다.
- 총 6 round 중 4 round를 먼저 이기면 최종 승리하게 되며, 3대3이면 무승부 처리합니다.
- 나는 흰색, 상대방은 검정색입니다.

## 코드 계약

아래 샘플 코드가 계약의 단일 출처입니다. 주석이 각 훅의 호출 시점, 인자 구조, 반환값을 설명합니다. 위 목록에 없는 훅을 추가해도 호출되지 않습니다.

```js
/**
 * 코드는 JAVASCRIPT 로 작성해 주세요.
 */
var turnMap = null;		// 15x15 오목판의 상태를 나타내는 2차원 배열
var candidates = null;	// 다음 턴에 선택할 수도 있는 위치값들의 후보들
var val_notyet = 0;		// 오목판 특정위치의 상태값 중 아직 선택되지 않은 상태
var val_me = 1;			// 오목판 특정위치의 상태값 중 내가 선택한 경우
var val_opponent = 2;	// 오목판 특정위치의 상태값 중 상대가 선택한 경우

/**
 * 게임이 시작되면 호출됩니다.
 * 게임 중에 사용할 변수들의 초기화를 여기서 합니다.
 */
function onGameStart() {
	// printLog(string)는 플레이 영역 하단의 게임 로그 영역에 텍스트를 출력하는 함수입니다.
	printLog("onGameStart!");
}
/**
 * 라운드가 시작되면 호출됩니다.
 * 1번의 게임은 6번의 라운드를 실행합니다.
 * 1번의 라운드가 오목 한판입니다.
 */
function onRoundStart() {
	printLog("onRoundStart!");
	// 15x15 오목판 초기화
	turnMap = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
            ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]];
    candidates = [];
}
/**
 * [row,col] 위치값을 4자리 숫자로 변경
 */
function _locToNum(loc) {
	return loc[0]*100 + loc[1];
}
/**
 * 4자리 숫자를 다시 [row,col] 위치값으로 변경
 */
function _numToLoc(num) {
	return [Math.floor(num/100), num%100];
}
/**
 * 다음턴에 선택할수도 있는 후보 위치들 업데이트
 * 바둑과 달리 오목은 놓여져 있는 돌들 근처에 다음돌을 두게 될 확률이 높다는 가정합니다.
 */
function _updateCandidates(loc) {
	var diffs = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];
	candidates.splice(candidates.indexOf(_locToNum(loc)), 1);
	diffs.forEach(function(diff) {
		var r = loc[0] + diff[0];
		var c = loc[1] + diff[1];
		var num = _locToNum([r,c]);
		if (r>=0 && r<15 && c>=0 && c<15 && turnMap[r][c]==0 && candidates.indexOf(num)<0) {
			candidates.push(num);
		}
	});
}
/**
 * 턴이 시작되면 호출됩니다.
 * @param data
 *   data.turncnt: 턴이 실행된 횟수
 *   data.opponentchoice: 상대가 마지막에 선택한 위치. 없으면 첫턴임을 의미합니다.
 * @return [row,col]
 */
function onTurnStart(data) {
	var loc;
	if (data.opponentchoice) {
		loc = data.opponentchoice;
		turnMap[loc[0]][loc[1]] = val_opponent;
		_updateCandidates(loc);
		loc = _numToLoc(candidates[Math.floor(Math.random()*candidates.length)]);
		turnMap[loc[0]][loc[1]] = val_me;
		_updateCandidates(loc);
	} else {
		// data가 없는 경우는, 첫번째 수를 두는 경우임을 의미합니다.
		loc = [7,7];
		turnMap[loc[0]][loc[1]] = val_me;
		_updateCandidates(loc);
	}
	printLog("onTurnStart: op-"+data.opponentchoice+", me-"+loc);
	return loc;
}
/**
 * 라운드가 끝나면 호출되며, 라운드의 결과를 받아봅니다.
 * @param result
 *   result.win: -1(졌을때) or 0(비겼을때) or 1(이겼을때)
 *   result.wincnt: 이번 게임에서 몇번의 라운드를 이겼는가
 *   result.losecnt: 이번 게임에서 몇번의 라운드를 졌는가
 *   result.turncnt: 턴이 실행된 횟수
 *   result.opponentchoice: 
 *     상대가 마지막에 선택한 위치. 
 *     상대가 해당 라운드를 이긴 경우 onTurnStart를 실행하지 않고 onRoundEnd가 호출되므로 이 값이 필요합니다.
 */
function onRoundEnd(result) {
	printLog("onRoundEnd! win:"+result.win+", wincnt:"+result.wincnt+", losecnt:"+result.losecnt);
}
/**
 * 게임이 끝나면 호출되며, 게임의 결과를 받아봅니다.
 * @param result
 *   result.win: -1(졌을때) or 0(비겼을때) or 1(이겼을때)
 */
function onGameEnd(result) {
	printLog("onGameEnd! win:"+result.win);
	printLog("----------------------------");
}
```

## 실행 환경과 제약

- 코드는 브라우저의 Dedicated Worker 안에서 실행되고, 네트워크 접근은 차단되어 있습니다.
- 각 훅은 2초 안에 반환해야 합니다. 넘기면 오류로 처리됩니다.
- `import`/`export`/`require` 없이 전역 `function` 선언으로 작성합니다.
- `printLog(문자열)`로 게임 화면 하단의 로그 영역에 출력할 수 있습니다.
- `Math.random`을 쓸 수 있습니다.

## 이 게임의 함정

- 이 게임에는 `onTurnEnd` 훅이 없습니다(훅 다섯 개). 상대의 마지막 수는 `onTurnStart`의 `data.opponentchoice`로 옵니다.
- `data.opponentchoice`가 없으면 첫 턴, 즉 내가 선입니다.

## 제공되는 상대

RANDOM(easy), LEVEL 1, LEVEL 2, LEVEL 3. 게임 화면의 대결상대 드롭다운에서 고릅니다.

## 만든 코드 시험하기

1. https://codebattle.online/omok 화면의 에디터에 코드 전체를 붙여넣습니다.
2. 대결상대를 고르고 게임시작 버튼을 누릅니다. 로그인 없이 됩니다.
3. 화면의 점수판과 로그로 결과를 확인하고 고칩니다.

## 더 보기

- 전략 해설(사람이 읽는 분석): https://codebattle.online/omok 화면 아래에 있습니다.
- 코드 아레나: 로그인하면 대표 코드를 https://codebattle.online/arena/omok에 출전시켜 공개 전적을 쌓을 수 있습니다.
