Как вам игровая сессия с 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 ходов в среднем. Если вам известны другие безумные ходилки — скидывайте в комментариях. Если наберутся новые ещё более дикие, то я сделаю ещё подборку.
Источники
ссылка на оригинал статьи https://habr.com/ru/post/708498/
Добавить комментарий