게임 규칙
전략 해설 보기대결상대
TURN
ROUND
GAME
게임 로그
결과를 받고 무엇을 지울 것인가
숫자야구는 정보를 모으는 게임입니다. 서로 다른 세 자리 숫자는 720가지뿐이고, 매 턴 스트라이크와 볼이 돌아옵니다. 그 결과 하나가 후보 수백 개를 지웁니다. 지우지 않으면 720가지를 계속 찍는 것과 같습니다. 아래는 지우는 방식 셋과 각각이 실제로 낸 성적입니다.
1. 결과를 안 쓰고 계속 찍는다 (기본 샘플)
화면에 실려 있는 기본 코드가 이렇게 합니다. 매 턴 무작위로 세 자리 숫자를 만들고, 이미 낸 것만 피합니다. 스트라이크와 볼이 돌아오는데 아무것도 하지 않습니다.
RANDOM 반반 LEVEL 1·2·3 전패
720개를 무작위로 훑으면 30턴 안에 맞을 확률이 낮습니다. RANDOM과는 반반인데 둘 다 결과를 안 쓰기 때문입니다.
2. 결과와 어긋나는 후보를 지운다
이 게임의 기본기입니다. 720개를 다 들고 있다가, 방금 낸 추측을 각 후보에 대어 봅니다. 그 후보가 정답이었다면 나왔어야 할 결과와 실제로 받은 결과가 다르면 그 후보는 정답이 아닙니다. 지웁니다.
012 → 1S 0B
정답에 0·1·2 중 하나가 그 자리에 있다. 이 결과와 안 맞는 후보는 전부 버린다.
345 → 0S 1B
3·4·5 중 하나가 있지만 자리는 틀렸다. 이 한 줄로도 후보가 크게 줄어든다.
var left = null;
var lastGuess = null;
// 서로 다른 세 자리 숫자 720가지. 10 x 9 x 8 이다.
function allNumbers() {
var out = [];
var a, b, c;
for (a = 0; a <= 9; a += 1) {
for (b = 0; b <= 9; b += 1) {
for (c = 0; c <= 9; c += 1) {
if (a !== b && b !== c && a !== c) {
out.push("" + a + b + c);
}
}
}
}
return out;
}
// 추측을 어떤 정답에 대었을 때 나오는 [스트라이크, 볼]. 규칙 그대로다.
function judge(guess, secret) {
var strikes = 0, balls = 0;
var i, j;
for (i = 0; i < 3; i += 1) {
for (j = 0; j < 3; j += 1) {
if (guess.charAt(j) === secret.charAt(i)) {
if (i === j) { strikes += 1; } else { balls += 1; }
}
}
}
return [strikes, balls];
}
function onGameStart() {}
function onRoundStart() {
var pool = allNumbers();
left = allNumbers();
lastGuess = null;
// 내 숫자는 아무거나 고른다.
return pool[Math.floor(Math.random() * pool.length)];
}
function onTurnStart() {
lastGuess = left.length ? left[0] : "012";
return lastGuess;
}
// 방금 받은 결과와 어긋나는 후보를 모두 버린다. 이것이 이 게임의 기본기다.
function onTurnEnd(result) {
var got = result.result;
var kept = [];
var i, r;
for (i = 0; i < left.length; i += 1) {
r = judge(lastGuess, left[i]);
if (r[0] === got[0] && r[1] === got[1]) { kept.push(left[i]); }
}
left = kept;
}
function onRoundEnd(result) {}
function onGameEnd(result) {}
RANDOM 전승 LEVEL 1 반반 LEVEL 2·3 전패
RANDOM은 전승이고 LEVEL 1과 반반이 됩니다. 몇 턴이면 후보가 한두 개로 좁혀지니까요. 그런데 LEVEL 2·3에는 여전히 전패입니다. 상대도 같은 일을 하고 있고, 이 게임은 먼저 맞히는 쪽이 이기는 경쟁이기 때문입니다. 같은 속도로는 못 이깁니다.
3. 어느 쪽부터 훑을지 고른다
후보를 지우는 방식은 그대로 두고, 남은 후보 중 **무엇을 먼저 찍을지**만 바꿔 봅니다. 아래는 가장 큰 수부터 찍습니다. 코드에서 달라진 곳은 이 한 함수뿐입니다.
// 남은 후보 중 가장 큰 수를 찍는다. 순서만 바꾼 것이고 후보를 지우는
// 방식은 그대로다. 상대가 자기 숫자를 고르는 습관이 있다면, 그 습관 쪽을
// 먼저 훑는 것만으로 몇 턴을 아낀다.
function onTurnStart() {
var best = left.length ? left[0] : "012";
var i;
for (i = 1; i < left.length; i += 1) {
if (parseInt(left[i], 10) > parseInt(best, 10)) { best = left[i]; }
}
lastGuess = best;
return lastGuess;
}
RANDOM 전승 LEVEL 1 반반 LEVEL 2 우세 LEVEL 3 열세
이 한 줄 차이로 LEVEL 2가 전패에서 우세로 바뀝니다. 60판을 10회 반복해 재면 승률이 55~73% 사이이고 중앙값이 65%입니다. 상대가 자기 숫자를 고르는 방식에 치우침이 있어서, 그쪽부터 훑으면 몇 턴을 아끼기 때문입니다. 반면 LEVEL 3에는 거의 통하지 않습니다. 특정 상대의 습관을 노린 것이므로 그 습관이 없는 상대에게는 이득이 없습니다.
4. 여기서부터는 당신 차례
두 갈래가 열려 있습니다. 하나는 추측을 더 좋게 하는 것입니다. 남은 후보를 결과별로 나눌 때 가장 큰 덩어리가 작아지는 추측을 고르면 최악의 경우가 줄어듭니다. 다른 하나는 아직 손대지 않은 쪽입니다. onRoundStart가 돌려주는 내 숫자도 전략입니다. 상대가 어떤 순서로 훑는지 알면 늦게 걸리는 숫자를 고를 수 있습니다. 위 코드를 에디터에 붙여 넣고 여기서부터 고쳐 보세요.
대전은 이렇게 돕니다
- 코드배틀은 내가 작성한 코드와 상대가 작성한 코드가 일대일로 대결하는 방식이며, 언어는 JAVASCRIPT 입니다.
- 코드를 작성하고, 대결상대를 RANDOM, LEVEL1, LEVEL2, LEVEL3, 온라인 상대 중에서 선택한 후 게임시작 버튼을 누르면 대결이 시작됩니다.
- 대결은 game이며, 1 game은 31 round로 구성되고, 1 round는 숫자야구 한판을 의미하는데 누군가 상대 숫자를 맞추면 해당 round가 종료되고, 30번의 turn을 도는 동안 끝나지 않으면 더 큰 수를 가진 쪽이 승리하게 됩니다.
숫자야구는 훅이 여섯 개이고, onRoundStart가 서로 다른 세 자리 숫자를 문자열로 돌려줘야 합니다. 그것이 그 라운드의 내 숫자입니다. 위 2번 코드는 여섯 개를 모두 담고 있어 그대로 붙여 넣으면 돌아갑니다. 3번은 그 코드에서 달라진 함수 하나만 보여 줍니다.
성적은 상대별로 60판(라운드 1,860번)을 돌려 측정했고 저장소의 테스트가 실제 규칙 엔진으로 매번 다시 확인합니다. 상대 넷 모두에 무작위가 섞여 있어 판마다 달라집니다. 25판으로 재던 동안 같은 대결이 52%와 76% 사이를 오갔고, 그래서 60판으로 올렸습니다.