Есть ли лучшее значение, на котором стоит остановиться, чтобы выиграть наибольший процент игр? Если да, то какое?
Редактировать: Существует ли точная вероятность выигрыша, которую можно рассчитать для данного лимита, независимо от того, что делает соперник? (Я не занимался вероятностью и статистикой со времен колледжа). Мне было бы интересно увидеть этот ответ, чтобы сравнить его с моими смоделированными результатами.
Edit: Исправлены ошибки в моем алгоритме, обновлена таблица результатов.
Я играл в модифицированную игру блэкджек с некоторыми довольно раздражающими изменениями правил по сравнению со стандартными правилами. Я выделил курсивом правила, которые отличаются от стандартных правил блэкджека, а также включил правила блэкджека для тех, кто не знаком с ними.
Колода карт состоит из 52 карт, по четыре каждого из следующих 13 значений:
2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A
Никакие другие свойства карт не имеют значения.
Рубиновое представление этого:
CARDS = ((2..11).to_a+[10]*3)*4
Я подхожу к этому следующим образом:
Вот алгоритм, реализованный в Ruby:
#!/usr/bin/env ruby
class Array
def shuffle
sort_by { rand }
end
def shuffle!
self.replace shuffle
end
def score
sort.each_with_index.inject(0){|s,(c,i)|
s+c > 21 - (size - (i + 1)) && c==11 ? s+1 : s+c
}
end
end
N=(ARGV[0]||100_000).to_i
NDECKS = (ARGV[1]||1).to_i
CARDS = ((2..11).to_a+[10]*3)*4*NDECKS
CARDS.shuffle
my_limits = (12..21).to_a
opp_limits = my_limits.dup
puts " " * 55 + "opponent_limit"
printf "my_limit |"
opp_limits.each do |result|
printf "%10s", result.to_s
end
printf "%10s", "net"
puts
printf "-" * 8 + " |"
print " " + "-" * 8
opp_limits.each do |result|
print " " + "-" * 8
end
puts
win_totals = Array.new(10)
win_totals.map! { Array.new(10) }
my_limits.each do |my_limit|
printf "%8s |", my_limit
$stdout.flush
opp_limits.each do |opp_limit|
if my_limit == opp_limit # will be a tie, skip
win_totals[my_limit-12][opp_limit-12] = 0
print " --"
$stdout.flush
next
elsif win_totals[my_limit-12][opp_limit-12] # if previously calculated, print
printf "%10d", win_totals[my_limit-12][opp_limit-12]
$stdout.flush
next
end
win = 0
lose = 0
draw = 0
N.times {
cards = CARDS.dup.shuffle
my_hand = [cards.pop, cards.pop]
opp_hand = [cards.pop, cards.pop]
# hit until I hit limit
while my_hand.score < my_limit
my_hand << cards.pop
end
# hit until opponent hits limit
while opp_hand.score < opp_limit
opp_hand << cards.pop
end
my_score = my_hand.score
opp_score = opp_hand.score
my_score = 0 if my_score > 21
opp_score = 0 if opp_score > 21
if my_hand.score == opp_hand.score
draw += 1
elsif my_score > opp_score
win += 1
else
lose += 1
end
}
win_totals[my_limit-12][opp_limit-12] = win-lose
win_totals[opp_limit-12][my_limit-12] = lose-win # shortcut for the inverse
printf "%10d", win-lose
$stdout.flush
end
printf "%10d", win_totals[my_limit-12].inject(:+)
puts
end
Использование
ruby blackjack.rb [num_iterations] [num_decks]
Скрипт по умолчанию использует 100 000 итераций и 4 колоды. 100 000 занимает около 5 минут на быстром macbook pro.
opponent_limit
my_limit | 12 13 14 15 16 17 18 19 20 21 net
-------- | -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- --------
12 | -- -7666 -13315 -15799 -15586 -10445 -2299 12176 30365 65631 43062
13 | 7666 -- -6962 -11015 -11350 -8925 -975 10111 27924 60037 66511
14 | 13315 6962 -- -6505 -9210 -7364 -2541 8862 23909 54596 82024
15 | 15799 11015 6505 -- -5666 -6849 -4281 4899 17798 45773 84993
16 | 15586 11350 9210 5666 -- -6149 -5207 546 11294 35196 77492
17 | 10445 8925 7364 6849 6149 -- -7790 -5317 2576 23443 52644
18 | 2299 975 2541 4281 5207 7790 -- -11848 -7123 8238 12360
19 | -12176 -10111 -8862 -4899 -546 5317 11848 -- -18848 -8413 -46690
20 | -30365 -27924 -23909 -17798 -11294 -2576 7123 18848 -- -28631 -116526
21 | -65631 -60037 -54596 -45773 -35196 -23443 -8238 8413 28631 -- -255870
Здесь я испытываю трудности. Я не совсем понимаю, как интерпретировать эти данные. На первый взгляд кажется, что всегда оставаться на уровне 16 или 17 - это правильный путь, но я не уверен, что все так просто. Я думаю, что маловероятно, что реальный человеческий противник останется на 12, 13 и, возможно, 14, так что мне следует отбросить эти значения opponent_limit? Также, как я могу изменить это, чтобы учесть изменчивость реального человеческого оппонента? Например, реальный человек, скорее всего, останется на 15, просто основываясь на "чувстве", и может также попасть на 18, основываясь на "чувстве"
Я с подозрением отношусь к вашим результатам. Например, если противник стремится к 19, ваши данные говорят, что лучший способ победить его - бить до тех пор, пока вы не достигнете 20. Это не проходит элементарную проверку на запах. Вы уверены, что у вас нет ошибки? Если мой оппонент стремится к 19 или лучше, моя стратегия будет заключаться в том, чтобы любой ценой избегать бустинга: оставайтесь на уровне 13 или выше (может быть, даже 12?). Стремление к 20 должно быть ошибочным - и не просто с небольшим перевесом, а с большим.
Откуда я знаю, что ваши данные плохие? Потому что игра в блэкджек, в которую вы играете, не является необычной. В большинстве казино дилер играет так: дилер бьет до определенной цели, а затем останавливается, независимо от того, что у других игроков на руках. Что это за цель? Стоять на хард 17 и бить софт 17. Когда вы избавитесь от ошибок в своем скрипте, это должно подтвердить, что казино знают свое дело.
Когда я сделаю следующие замены в вашем коде:
# Replace scoring method.
def score
s = inject(0) { |sum, c| sum + c }
return s if s < 21
n_aces = find_all { |c| c == 11 }.size
while s > 21 and n_aces > 0
s -= 10
n_aces -= 1
end
return s
end
# Replace section of code determining hand outcome.
my_score = my_hand.score
opp_score = opp_hand.score
my_score = 0 if my_score > 21
opp_score = 0 if opp_score > 21
if my_score == opp_score
draw += 1
elsif my_score > opp_score
win += 1
else
lose += 1
end
Результаты согласуются с поведением дилеров казино: 17 - оптимальная цель.
n=10000
opponent_limit
my_limit | 12 13 14 15 16 17 18 19 20 21 net
-------- | -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- --------
12 | -- -843 -1271 -1380 -1503 -1148 -137 1234 3113 6572
13 | 843 -- -642 -1041 -1141 -770 -93 1137 2933 6324
14 | 1271 642 -- -498 -784 -662 93 1097 2977 5945
15 | 1380 1041 498 -- -454 -242 -100 898 2573 5424
16 | 1503 1141 784 454 -- -174 69 928 2146 4895
17 | 1148 770 662 242 174 -- 38 631 1920 4404
18 | 137 93 -93 100 -69 -38 -- 489 1344 3650
19 | -1234 -1137 -1097 -898 -928 -631 -489 -- 735 2560
20 | -3113 -2933 -2977 -2573 -2146 -1920 -1344 -735 -- 1443
21 | -6572 -6324 -5945 -5424 -4895 -4404 -3650 -2560 -1443 --
Несколько разных замечаний:
Текущая конструкция негибкая. С помощью небольшого рефакторинга можно было бы добиться чистого разделения между работой игры (дилинг, тасовка, ведение статистики) и принятием решений игроком. Это позволило бы вам тестировать различные стратегии друг против друга. В настоящее время ваши стратегии встроены в циклы, которые запутаны в коде работы игры. Для экспериментов лучше подойдет дизайн, позволяющий создавать новых игроков и задавать их стратегию по своему усмотрению.
Два комментария:
Похоже, что не существует ни одной доминирующей стратегии, основанной на "пределе попадания":
2. Вы не упоминаете, могут ли игроки видеть, сколько карт вытянул их противник (я бы предположил, что да). Я бы ожидал, что эта информация будет включена в "лучшую" стратегию. (ответ)
При отсутствии информации о решениях других игроков игра становится проще. Но поскольку явно не существует доминирующей "чистой" стратегии, оптимальной стратегией будет "смешанная" стратегия. То есть: набор вероятностей для каждого счета от 12 до 21 для того, следует ли вам остановиться или взять еще одну карту (EDIT: вам понадобятся разные вероятности для данного счета без тузов и счета с тузами). Выполнение стратегии требует случайного выбора (в соответствии с вероятностями), остановиться или продолжить после каждого нового розыгрыша. После этого можно найти равновесие Нэша для игры.
Конечно, если вы задаете более простой вопрос: какова оптимальная стратегия выигрыша против субоптимальных игроков (например, тех, которые всегда останавливаются на 16, 17, 18 или 19), то вы задаете совершенно другой вопрос, и вам придется точно указать, в чем именно другой игрок ограничен по сравнению с вами.
Вот некоторые мысли о собранных вами данных:
Чтобы показать данные другим способом, первое число - это лимит вашего противника, а вторая группа чисел - это лимиты, которые вы можете выбрать и выиграть. То, что отмечено звездочкой, является "самым выигрышным" выбором:
12: 13, 14, 15, 16*, 17, 18
13: 14, 15, 16*, 17, 18, 19
14: 15, 16, 17*, 18, 19
15: 16, 17*, 18, 19
16: 17, 18*, 19
17: 18*, 19
18: 19*, 20
19: 12, 20*
20: 12*, 13, 14, 15, 16, 17
21: 12*, 13, 14, 15, 16, 17, 18, 19, 20
Отсюда видно, что хит-лимит 17 или 18 является самым безопасным вариантом, если противник следует случайной стратегии выбора "хит-лимита", потому что 17 и 18 побьют 7/10 "хит-лимитов" противника.
Конечно, если ваш оппонент - человек, вы не сможете ответить на его самонавязывание "хит-лимита" меньше 18 или больше 19, так что это полностью сводит на нет предыдущие расчеты. Тем не менее, я считаю эти цифры полезными:
Я согласен, что для любой отдельной руки вы можете быть достаточно уверены, что у вашего оппонента есть лимит, после которого он перестанет бить, и он останется. Если вы можете угадать этот предел, вы можете выбрать свой собственный предел, основываясь на этой оценке.
Если вы думаете, что они оптимистичны или готовы рискнуть, выберите предел 20 - вы победите их в долгосрочной перспективе, если их предел выше 17. Если вы действительно уверены в себе, выбирайте лимит 12 - вы выиграете, если их лимит выше 18, и выигрыши здесь будут гораздо чаще.
Если вы считаете, что они консервативны или не склонны к риску, выберите лимит 18. Это позволит выиграть, если они сами не превышают 18.
Для нейтральной позиции подумайте о том, каким был бы ваш предел без внешнего влияния. Обычно вы бы сделали 16? 17?
Короче говоря, вы можете только догадываться о том, каков предел вашего противника, но если вы угадаете хорошо, то сможете победить его в долгосрочной перспективе с такой статистикой.