ted_kim
목록으로공개 전적
1W 0L 0D
도전자 1명이 남긴 결과입니다. 같은 사람이 여러 번 도전하면 마지막 결과 하나만 셉니다.
브라우저에서 계산한 결과라 조작을 완전히 막지 못합니다. 공식 기록으로 쓰지 않습니다.
미보고 도전 0건
출전 코드
/**
* LEVEL 2 전략
*
* 두 가지를 함께 본다.
* 1. 상대가 최근 50턴 동안 무엇을 몇 번 냈는지로 점수 기대값을 계산한다.
* 이길 때 얻는 점수(가위 1, 바위 2, 보 3)가 다르므로 "많이 내는 수를
* 이기는 것"만으로는 부족하고, 상대에게 내주는 점수까지 빼서 본다.
* 2. 이전 턴의 승패에 따라 다음 수를 정하는 상대인지 판별한다. 그런
* 패턴이 보이면 그 상대가 낼 수에 지는 선택을 후보에서 뺀다.
*
* 최적수만 두면 읽히므로 기대값에 비례한 확률로 섞어서 낸다.
*/
var opHistory = []; // 상대가 낸 수의 이력
var rspCount = {}; // 최근 구간에서 상대가 가위·바위·보를 낸 횟수
var winSide = {1: 2, 2: 3, 3: 1}; // 나를 이기는 수 (가위<바위, 바위<보, 보<가위)
var loseSide = {1: 3, 2: 1, 3: 2}; // 내가 이기는 수 (가위>보, 바위>가위, 보>바위)
var recentMyChoice = 3; // 직전 턴의 내 선택
var recentMyChoice2 = 0; // 그보다 한 턴 전의 내 선택
var recentTurnWin = 0; // 직전 턴의 승패
var recentTurnWin2 = 0;
var patternByPrevWin = {}; // 이전 턴 결과별로, 상대의 다음 수가 내 이전 수와 갖는 상대값
var COUNT_WINDOW = 50; // 빈도를 세는 최근 턴 수
var PATTERN_WINDOW = 10; // 패턴 판별에 쓰는 최근 표본 수
function onGameStart() {
opHistory = [];
rspCount = {1: 0, 2: 0, 3: 0};
patternByPrevWin = {};
patternByPrevWin[-1] = [];
patternByPrevWin[0] = [];
patternByPrevWin[1] = [];
recentMyChoice = 3;
recentMyChoice2 = 0;
recentTurnWin = 0;
recentTurnWin2 = 0;
}
function onRoundStart() {
}
/**
* @return 1(가위) or 2(바위) or 3(보)
*/
function onTurnStart() {
var myChoice = 3;
var skipChoice = 0;
var samples = patternByPrevWin[recentTurnWin];
var i;
// 1. 이전 턴 결과로 다음 수를 정하는 상대인지 본다.
// 최근 표본의 절반을 넘는 값이 하나 있으면 패턴으로 인정한다.
if (samples.length > PATTERN_WINDOW) {
var patternCount = {0: 0, 1: 0, 2: 0};
for (i = 0; i < PATTERN_WINDOW; i += 1) {
patternCount[samples[samples.length - 1 - i]] += 1;
}
var patternValue = -1;
for (i = 0; i < 3; i += 1) {
if (patternCount[i] > PATTERN_WINDOW / 2) {
patternValue = i;
break;
}
}
if (patternValue >= 0) {
var expected = recentMyChoice + patternValue;
if (expected > 3) {
expected -= 3;
}
// 상대가 낼 것으로 보는 수에 지는 선택을 뺀다.
skipChoice = loseSide[expected];
}
}
// 2. 각 선택의 순이익을 점수로 계산한다.
// 내가 얻는 점수 = (내가 이기는 수를 상대가 낸 횟수) x (이길 때 내 점수)
// 내가 잃는 점수 = (나를 이기는 수를 상대가 낸 횟수) x (그때 상대 점수)
var diffs = {};
var minDiff = 999;
for (i = 1; i <= 3; i += 1) {
var myPoint = rspCount[loseSide[i]] * i;
var opPoint = rspCount[winSide[i]] * winSide[i];
diffs[i] = myPoint - opPoint;
if (diffs[i] < minDiff) {
minDiff = diffs[i];
}
}
if (skipChoice > 0) {
minDiff -= 10;
diffs[skipChoice] = minDiff;
}
// 3. 음수를 걷어 올린 뒤 기대값에 비례한 확률로 고른다.
// 항상 최적수만 내면 상대가 내 다음 수를 그대로 읽는다.
var diffSum = 0;
for (i = 1; i <= 3; i += 1) {
if (minDiff < 0) {
diffs[i] -= minDiff;
}
diffSum += diffs[i];
}
if (diffSum > 0) {
var rand = Math.floor(Math.random() * 100 + 1);
for (i = 1; i <= 3; i += 1) {
rand -= diffs[i] * 100 / diffSum;
if (rand <= 0) {
myChoice = i;
break;
}
}
}
recentMyChoice2 = recentMyChoice;
recentMyChoice = myChoice;
return myChoice;
}
/**
* @param result
* result.win: -1(졌을때) or 0(비겼을때) or 1(이겼을때)
* result.opponentchoice: 이번 턴에 상대가 리턴한 값
*/
function onTurnEnd(result) {
var opChoice = result.opponentchoice;
if (opChoice < 1 || opChoice > 3) {
return;
}
rspCount[opChoice] += 1;
opHistory.push(opChoice);
// 최근 구간만 본다. 게임 초반의 성향이 끝까지 남으면 상대가 전략을
// 바꿨을 때 따라가지 못한다.
if (opHistory.length > COUNT_WINDOW) {
rspCount[opHistory[opHistory.length - 1 - COUNT_WINDOW]] -= 1;
}
recentTurnWin2 = recentTurnWin;
recentTurnWin = result.win;
// 상대의 이번 수가 내 이전 수와 갖는 상대값을, 그 직전 턴의 결과별로
// 모은다. 이 값이 한쪽으로 쏠리면 결과에 반응하는 상대다.
if (recentMyChoice2 >= 1) {
var patternValue = opChoice - recentMyChoice2;
if (patternValue < 0) {
patternValue += 3;
}
patternByPrevWin[recentTurnWin2].push(patternValue);
}
}
function onRoundEnd(result) {
}
function onGameEnd(result) {
}
도전 기록
| 결과 | 관전 |
|---|---|
| 방어 성공 |
기록된 수를 되짚습니다. 코드를 다시 실행하지 않습니다. 각 라운드의 앞 20턴만 보여 줍니다.