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) {
}
도전 기록
아직 도전받은 적이 없습니다.
기록된 수를 되짚습니다. 코드를 다시 실행하지 않습니다. 라운드마다 최종 판을 보여 주고, 마지막 수에 테두리를 칩니다.