Как вам игровая сессия с 1000+ ходами в обычной ходилке? А такое вполне реально.
До этого я уже проанализировал одну немного бесячую настольную игру ходилку через эмуляции [1] [2]. В комментариях мне накидали кучу других запомнившихся игр с предложением и их потыкать. Ну вот я и потыкал. Для этого немного оптимизировал код эмулятора через javascript, чтобы он мог запускать по 100 миллионов игр. Скрипты выложены на гитхабе [3].
Вокруг света
В качестве механики большого отбрасывания (аналог чёрной дыры из прошлой статьи) я учитывал две позиции: 100->46, 107->37. А вот отбрасывание на начало 21->0 я не стал считать аналогом чёрной дыры, т.к. возврат на 21 ход примерно равнозначен обычным "стрелкам-назад". Статистика [4] вышла такая:
среднее число ходов 36;
максимальное число ходов 235;
минимальное число ходов 11;
число игр с попаданием хотя бы в одну отбрасывалку 54%, при этом игр с неравным числом попаданий в ловушки 43%;
вероятность проигрыша при более частом попадании в отбрасывалку 88%;
частота победы у первого игрока 50,85%.
Что интересного тут можно увидеть.
Плюсы:
- Красивая картинка, которую интересно разглядывать.
- Средняя длина игрового поля, очень долгая игровая сессия случается редко. Игра на 235 ходов случилась лишь однажды из 100 миллионов игр.
- Преимущество первого хода с 50,85% весьма небольшое.
Минусы:
- Мега отбрасывания, как всегда, подбешивают, но есть механика для камбека, так как оппонент сам может попасть в одну из двух ловушек у самого финиша.
- Если кого-то отбросило ловушкой чаще (что происходит с частотой 43%), то он проиграет с очень большой вероятностью: 88%.
Веселое путешествие
Здесь два отбрасывания в начало. При этом первая ловушка отбрасывает недалеко, поэтому её рассматривать как критическую я не стал. Поэтому ловушками я посчитал следующие комбинации: 63->0, 75->35. Статистика [5] вышла такая:
среднее число ходов 35;
максимальное число ходов 271;
минимальное число ходов 11;
число игр с попаданием хотя бы в одну отбрасывалку 50%, при этом игр с неравным числом попаданий в ловушки 41%;
вероятность проигрыша при более частом попадании в отбрасывалку 81%;
частота победы у первого игрока 50.78%.
Что интересного тут можно увидеть.
Статистически игра очень похожа на предыдущую, и даже немного лучше сбалансирована. Хотя на глазок, я думал, что будет наоборот, и в этой игре окажется кошмарный баланс.
Большое космическое путешествие (гребаный поезд)
Как подсказал, один из комментаторов AlexKoz1980, настоящее название этой игры - гребаный поезд. В качестве больших ловушек я считал за точки: 57, 70, 77, 88, 90. И судя по статистике такое название он полностью оправдывает [6].
среднее число ходов 102;
максимальное число ходов 1615;
минимальное число ходов 10;
число игр с попаданием хотя бы в одну отбрасывалку 92%, при этом игр с неравным числом попаданий в ловушки 70%;
вероятность проигрыша при более частом попадании в отбрасывалку 77%;
частота победы у первого игрока 50.14%.
Это самая несбалансированная игра из тех, что я видел. Помимо 5 самых опасных ловушек, тут есть ещё и мелкие ловушки, откатывающие на 1-2 этажа. Проблема в том, что после мелкой ловушки сбрасывается риск попадания в одну из опасных ловушек. И сохраняется он до самого конца. К 100-ому ходу становится уже неважно кто победит, лишь бы хоть кто-нибудь игру закончил.
Javascript для эмуляции использовался такой (можно запускать в консоли F12 в любой вкладке в любом браузере)
const finishStep = 93;
const countOfEmulatedGames = 100000000;
const bonusTurn = {
9: true,
24: true,
43: true,
56: true,
82: true,
84: true,
};
const moveBack = {};
const skipTurn = {
2: true,
8: true,
11: true,
21: true,
23: true,
28: true,
29: true,
32: true,
39: true,
44: true,
45: true,
49: true,
58: true,
59: true,
68: true,
73: true,
};
const instaDeath = {
12: true,
};
const arrowMoves = {
3: 5,
4: 9,
6: 27,
13: 14,
16: 18,
19: 20,
26: 46,
30: 33,
31: 36,
34: 35,
37: 38,
40: 43,
41: 46,
51: 36,
53: 54,
57: 10,
60: 47,
61: 63,
64: 67,
65: 67,
66: 46,
69: 67,
70: 10,
72: 55,
74: 78,
75: 55,
76: 78,
77: 10,
79: 78,
83: 84,
86: 87,
88: 48,
90: 10,
91: 93,
};
const bigBack = {
57: true,
70: true,
77: true,
88: true,
90: true,
};
let Stats = {
totalGames: countOfEmulatedGames,
iterationGames: 1000000,
checkedGames: 0,
turnsToGames: {},
turnsToGamesPoints: {},
catchedGames: 0,
catchedGamesUnfair: 0,
catchedMoreLoseGames: 0,
firstPlayerWinCount: 0,
totalTurns: 0,
maxCountOfTurns: 0,
minCountOfTurns: 999999,
};
function main() {
let newCountOfGames = Math.min(Stats.checkedGames + Stats.iterationGames, Stats.totalGames);
for (0; Stats.checkedGames < newCountOfGames; Stats.checkedGames++) {
let game = emulateGame();
Stats.totalTurns += game.turn;
if (typeof Stats.turnsToGames[game.turn] === 'undefined') {
Stats.turnsToGames[game.turn] = 0;
}
Stats.turnsToGames[game.turn]++;
Stats.maxCountOfTurns = Math.max(Stats.maxCountOfTurns, game.turn);
Stats.minCountOfTurns = Math.min(Stats.minCountOfTurns, game.turn);
if (game.p1Catched > 0 || game.p2Catched > 0) {
Stats.catchedGames++;
if (game.p1Catched != game.p2Catched) {
Stats.catchedGamesUnfair++;
}
}
if (game.p1Catched > game.p2Catched && game.winner == 'p2') {
Stats.catchedMoreLoseGames++;
} else if (game.p1Catched < game.p2Catched && game.winner == 'p1') {
Stats.catchedMoreLoseGames++;
}
if (game.winner == 'p1') {
Stats.firstPlayerWinCount++;
}
}
if (Stats.checkedGames >= Stats.totalGames) {
console.log('Progress: 100% Done');
Object.keys(Stats.turnsToGames).forEach(key => {
Stats.turnsToGamesPoints[key] = 100*Stats.turnsToGames[key]/Stats.totalGames;
});
console.log('Count of games: ' + Stats.totalGames.toLocaleString());
console.log('Average count of turns: ' + Math.round(100*Stats.totalTurns/Stats.totalGames)/100);
console.log(JSON.stringify(Stats.turnsToGamesPoints));
console.log('Max count of turns: ' + Stats.maxCountOfTurns);
console.log('Min count of turns: ' + Stats.minCountOfTurns);
console.log('--------------------');
console.log('Percent of games with at least one big-back: ' + formatedRound(Stats.catchedGames/Stats.totalGames) + '%');
console.log('Percent of unfair games with big-back: ' + formatedRound(Stats.catchedGamesUnfair/Stats.totalGames) + '%');
console.log('If step to big-back more times then lose: ' + formatedRound(Stats.catchedMoreLoseGames/Stats.catchedGamesUnfair) + '%');
console.log('--------------------');
console.log('First player win rate: ' + formatedRound(Stats.firstPlayerWinCount/Stats.totalGames) + '%');
} else {
setTimeout(
function() {
console.log('Progress: ' + formatedRound(Stats.checkedGames/Stats.totalGames) + '%');
main();
},
0
);
}
}
function emulateGame() {
let game = {
'p1': 0,
'p2': 0,
'winner': null,
'p1Catched': 0,
'p2Catched': 0,
'turn': 0,
}
while(true) {
game.turn++;
game.p1 += getDice();
game = checkMove(game, 'p1');
if (game.p1 >= finishStep) {
game.winner = 'p1';
break;
}
game.p2 += getDice();
game = checkMove(game, 'p2');
if (game.p2 >= finishStep) {
game.winner = 'p2';
break;
}
}
return game;
}
function checkMove(game, player) {
let anotherPlayer = 'p1';
if (player == anotherPlayer) {
anotherPlayer = 'p2';
}
if (bigBack[game[player]]) {
game[player + 'Catched']++;
}
if (bonusTurn[game[player]]) {
game[player] += getDice();
game = checkMove(game, player);
}
if (moveBack[game[player]]) {
game[player] -= getDice();
game = checkMove(game, player);
}
if (skipTurn[game[player]]) {
game[anotherPlayer] += getDice();
game = checkMove(game, anotherPlayer);
game.turn++;
}
if (instaDeath[game[player]]) {
game[player] = 0;
//game[player + 'Catched']++; // skiped because zero return here almost at the start
}
if (typeof arrowMoves[game[player]] !== 'undefined') {
game[player] = arrowMoves[game[player]];
}
return game;
}
function formatedRound(value) {
return Math.round(10000*value)/100;
}
function getDice() {
return Math.floor(Math.random() * 6 + 1);
}
main();
Космос от шестилетнего ребенка с дедушкой
А дальше идёт ходилка "Космическое приключение с чёрными дырами и кротовыми норами", созданная под руководством ребёнка. Картинка немного доработана, чтобы появились числа на шагах и легенда к игре.
Весьма типичная особенность новичка - циклопических размеров игровая карта, аж на 509 шагов. По первости часто кажется, что чем больше тем лучше, но это почти всегда не так.
Вторая особенность - наличие механики кротовой норы, в результате попадания в которую игрок моментально побеждает. На карте 4 кротовые норы и 4 чёрные дыры (возврат в начало).
Эмуляция [7] на 100 миллионов игр дало следующие результаты:
среднее число ходов 35;
максимальное число ходов 232;
минимальное число ходов 3;
число игр с попаданием хотя бы в одну чёрную дыру 65%, при этом игр с неравным числом попаданий в чёрные дыры 57%;
вероятность проигрыша при более частом попадании в чёрную дыру 54%;
побед через кротовую нору 83,5%;
частота победы у первого игрока 50,48%.
Что интересного тут можно увидеть.
Плюсы:
- Влияние чёрных дыр почти полностью нивелировано. 54% вероятности проиграть если ты попадал в чёрную дыру чаще оппонента - почти 50/50.
- Довольно часто игры заканчиваются до 20 ходов, быстрые игровые сессии это хорошо.
- Преимущество первого хода с 50,48% минимальное.
Минусы:
- Огромный путь в 509 шагов приводит к тому, что чаще всего игра очень сильно затягивается. Обычно это сильно утомляет. Рецепт простой - уменьшать карту до ~100 шагов и меньше.
- Победа почти всегда происходит за счёт попадания в кротовую нору. Поэтому, как вариант, следовало по максимуму использовать эту механику и многократно увеличить число кротовых нор при удалении от старта.
Заключение
Среди проверенных игр лишь гребаный поезд оказался сильно перекошенным. Остальные, на удивление, примерно одинаково проходятся за 35 ходов в среднем. Если вам известны другие безумные ходилки - скидывайте в комментариях. Если наберутся новые ещё более дикие, то я сделаю ещё подборку.