코드 아레나
코드배틀
In English, please.

ted_kim

목록으로

공개 전적

0W 0L 0D

도전자 0명이 남긴 결과입니다. 같은 사람이 여러 번 도전하면 마지막 결과 하나만 셉니다.

브라우저에서 계산한 결과라 조작을 완전히 막지 못합니다. 공식 기록으로 쓰지 않습니다.

미보고 도전 0건

출전 코드

/**
 * LEVEL 2 전략 (오목)
 *
 * 판 전체를 보지 않는다. 이미 놓인 돌의 이웃만 후보로 두고, 후보마다 점수를
 * 매겨 가장 높은 자리에 놓는다.
 *
 * 점수는 두 갈래로 낸다.
 *   1. 그 자리에 내 돌을 놓았다고 가정하고 내 모양을 찾는다(공격).
 *   2. 같은 자리에 상대 돌을 놓았다고 가정하고 상대 모양을 찾는다(수비).
 * 둘을 더하므로 "내가 이기는 자리"와 "상대를 막아야 하는 자리"를 한 자에
 * 올려 비교한다. 내 5목이 100000이고 상대 5목이 24000인 것은 이기는 쪽을
 * 막는 쪽보다 앞세운다는 뜻이다.
 *
 * 같은 점수가 나오면 일정 확률로 바꿔 고른다. 항상 같은 자리를 두면 상대가
 * 내 다음 수를 그대로 읽는다.
 */

var VAL_EMPTY = 0;
var VAL_ME = 1;
var VAL_OPPONENT = 2;
// 모양에서 "비어 있지만 않으면 된다"를 뜻하는 자리표다. 판에는 쓰지 않는다.
var VAL_OCCUPIED = 3;
var BOARD_SIZE = 15;

var turnMap = null;
var candidates = null;

// 내 돌로 만드는 모양과 점수.
var PATTERNS_ME = [
	{pattern: [1, 1, 1, 1, 1], score: 100000},
	{pattern: [0, 1, 1, 1, 1, 0], score: 5900},
	{pattern: [1, 0, 1, 1, 1, 0], score: 750}, {pattern: [0, 1, 1, 1, 0, 1], score: 750},
	{pattern: [1, 0, 1, 1, 1, 2], score: 710}, {pattern: [2, 1, 1, 1, 0, 1], score: 710},
	{pattern: [0, 1, 1, 1, 1, 2], score: 710}, {pattern: [2, 1, 1, 1, 1, 0], score: 710},
	{pattern: [1, 1, 0, 1, 1], score: 710},
	{pattern: [0, 1, 1, 1, 0], score: 600},
	{pattern: [0, 1, 0, 1, 1, 0], score: 500}, {pattern: [0, 1, 1, 0, 1, 0], score: 500},
	{pattern: [2, 1, 1, 1, 0, 0], score: 100}, {pattern: [0, 0, 1, 1, 1, 2], score: 100},
	{pattern: [2, 1, 0, 1, 1, 0], score: 100}, {pattern: [0, 1, 1, 0, 1, 2], score: 100},
	{pattern: [2, 1, 1, 0, 1, 0], score: 90}, {pattern: [0, 1, 0, 1, 1, 2], score: 90},
	{pattern: [0, 1, 1, 0, 0], score: 50}, {pattern: [0, 0, 1, 1, 0], score: 50},
	{pattern: [0, 1, 0, 1, 0], score: 40},
	{pattern: [2, 1, 1, 0, 0, 0], score: 10}, {pattern: [0, 0, 0, 1, 1, 2], score: 10},
	{pattern: [2, 1, 0, 1, 0, 0], score: 6}, {pattern: [0, 0, 1, 0, 1, 2], score: 6}
];

// 상대 돌로 만들어지는 모양과 점수. 막을 값어치다.
var PATTERNS_OPPONENT = [
	{pattern: [2, 2, 2, 2, 2], score: 24000},
	{pattern: [0, 2, 2, 2, 2, 0], score: 1400},
	{pattern: [2, 0, 2, 2, 2, 0], score: 590}, {pattern: [0, 2, 2, 2, 0, 2], score: 590},
	{pattern: [2, 0, 2, 2, 2, 1], score: 500}, {pattern: [1, 2, 2, 2, 0, 2], score: 500},
	{pattern: [0, 2, 2, 2, 2, 1], score: 500}, {pattern: [1, 2, 2, 2, 2, 0], score: 500},
	{pattern: [2, 2, 0, 2, 2], score: 500},
	{pattern: [0, 2, 2, 2, 0], score: 400},
	{pattern: [0, 2, 0, 2, 2, 0], score: 300}, {pattern: [0, 2, 2, 0, 2, 0], score: 300},
	{pattern: [1, 2, 2, 2, 0, 0], score: 50}, {pattern: [0, 0, 2, 2, 2, 1], score: 50},
	{pattern: [1, 2, 0, 2, 2, 0], score: 50}, {pattern: [0, 2, 2, 0, 2, 1], score: 50},
	{pattern: [1, 2, 2, 0, 2, 0], score: 40}, {pattern: [0, 2, 0, 2, 2, 1], score: 40},
	{pattern: [0, 2, 2, 0, 0], score: 30}, {pattern: [0, 0, 2, 2, 0], score: 30},
	{pattern: [0, 2, 0, 2, 0], score: 20},
	{pattern: [1, 2, 2, 0, 0, 0], score: 2}, {pattern: [0, 0, 0, 2, 2, 1], score: 2},
	{pattern: [1, 2, 0, 2, 0, 0], score: 1}, {pattern: [0, 0, 2, 0, 2, 1], score: 1}
];

function onGameStart() {
}

function onRoundStart() {
	var row;

	turnMap = [];
	for (row = 0; row < BOARD_SIZE; row += 1) {
		turnMap.push([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
	}
	candidates = [];
}

// 좌표와 숫자를 오간다. 후보 목록에서 중복을 걸러야 해서 숫자로 들고 있다.
function locToNum(loc) {
	return loc[0] * 100 + loc[1];
}

function numToLoc(num) {
	return [Math.floor(num / 100), num % 100];
}

function isValidLine(value) {
	return value >= 0 && value < BOARD_SIZE;
}

function isMatchVal(mapValue, patternValue) {
	if (patternValue === VAL_OCCUPIED) {
		return mapValue !== VAL_EMPTY;
	}
	return mapValue === patternValue;
}

// 방금 놓인 자리를 후보에서 빼고, 그 이웃 여덟 칸을 후보에 넣는다.
// 오목은 놓인 돌 근처에 다음 돌이 놓일 확률이 높다는 가정이다.
function updateCandidates(loc) {
	var diffs = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]];
	var index = candidates.indexOf(locToNum(loc));

	if (index >= 0) {
		candidates.splice(index, 1);
	}
	diffs.forEach(function (diff) {
		var r = loc[0] + diff[0];
		var c = loc[1] + diff[1];
		var num = locToNum([r, c]);

		if (isValidLine(r) && isValidLine(c) && turnMap[r][c] === VAL_EMPTY
				&& candidates.indexOf(num) < 0) {
			candidates.push(num);
		}
	});
}

// 시작점에서 끝점까지 한 방향으로 훑으며 모양이 있는지 본다.
function hasPattern(startLoc, endLoc, pattern) {
	var loop = Math.max(
		Math.abs(endLoc[0] - startLoc[0]),
		Math.abs(endLoc[1] - startLoc[1])
	) - pattern.length + 2;
	var drow = endLoc[0] > startLoc[0] ? 1 : (endLoc[0] < startLoc[0] ? -1 : 0);
	var dcol = endLoc[1] > startLoc[1] ? 1 : (endLoc[1] < startLoc[1] ? -1 : 0);
	var i;
	var j;

	for (i = 0; i < loop; i += 1) {
		var found = true;
		var r = startLoc[0] + drow * i;
		var c = startLoc[1] + dcol * i;

		for (j = 0; j < pattern.length; j += 1) {
			var rr = r + drow * j;
			var cc = c + dcol * j;

			if (!isValidLine(rr) || !isValidLine(cc)
					|| !isMatchVal(turnMap[rr][cc], pattern[j])) {
				found = false;
				break;
			}
		}
		if (found) {
			return true;
		}
	}
	return false;
}

// 후보 중 점수가 가장 높은 자리를 고른다. 네 방향(가로·세로·대각선 둘)을
// 각각 본다.
function getNextLoc() {
	var selectedLoc;
	var selectedScore = -1;
	var sdiff = [[0, -1], [-1, 0], [-1, -1], [-1, 1]];
	var ediff = [[0, 1], [1, 0], [1, 1], [1, -1]];

	candidates.forEach(function (candidate) {
		var cloc = numToLoc(candidate);
		var score = 0;
		var scoreOne = function (po) {
			var plen = po.pattern.length - 1;
			var i;

			for (i = 0; i < 4; i += 1) {
				var sl = [cloc[0] + sdiff[i][0] * plen, cloc[1] + sdiff[i][1] * plen];
				var el = [cloc[0] + ediff[i][0] * plen, cloc[1] + ediff[i][1] * plen];

				if (hasPattern(sl, el, po.pattern)) {
					score += po.score;
				}
			}
			// 같은 점수면 가끔 바꿔 고른다. 항상 같은 자리를 두면 읽힌다.
			if (score > selectedScore
					|| (score === selectedScore && Math.random() > 0.8)) {
				selectedLoc = cloc;
				selectedScore = score;
			}
		};

		// 내 돌을 놓아 보고 공격 점수, 상대 돌을 놓아 보고 수비 점수를 낸다.
		// 본 뒤에는 반드시 비운다.
		turnMap[cloc[0]][cloc[1]] = VAL_ME;
		PATTERNS_ME.forEach(scoreOne);
		turnMap[cloc[0]][cloc[1]] = VAL_OPPONENT;
		PATTERNS_OPPONENT.forEach(scoreOne);
		turnMap[cloc[0]][cloc[1]] = VAL_EMPTY;
	});
	return selectedLoc;
}

/**
 * @param data
 *   data.turncnt: 턴이 실행된 횟수
 *   data.opponentchoice: 상대가 마지막에 놓은 좌표. 없으면 내가 첫 수다.
 * @return [row, col]
 */
function onTurnStart(data) {
	var loc;

	if (data && data.opponentchoice) {
		loc = data.opponentchoice;
		turnMap[loc[0]][loc[1]] = VAL_OPPONENT;
		updateCandidates(loc);
		loc = getNextLoc();
		turnMap[loc[0]][loc[1]] = VAL_ME;
		updateCandidates(loc);
	} else {
		// 첫 수는 판 중앙이다. 사방으로 뻗을 수 있어 가장 값이 크다.
		loc = [7, 7];
		turnMap[loc[0]][loc[1]] = VAL_ME;
		updateCandidates(loc);
	}
	return loc;
}

function onRoundEnd(result) {
}

function onGameEnd(result) {
}

도전 기록

아직 도전받은 적이 없습니다.