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

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) {
}

도전 기록

결과 관전
방어 성공