gpt4 book ai didi

ruby - 此修改后的二十一点游戏的最佳获胜策略是什么?

转载 作者:数据小太阳 更新时间:2023-10-29 06:50:17 26 4
gpt4 key购买 nike

问题

有没有可以保持的最佳值(value),这样我才能赢得尽可能多的比赛?如果是这样,那是什么?

编辑:是否可以为给定的限制计算出确切的获胜概率,而与对手的所作所为无关? (自大学以来,我还没有做过概率和统计)。我有兴趣将其作为与模拟结果进行对比的答案。

编辑:修复了我算法中的错误,更新了结果表。

背景

我一直在玩改进的二十一点游戏,其中对标准规则进行了一些相当烦人的规则调整。我已将与标准二十一点规则不同的规则斜体化,并为不熟悉的人添加了二十一点规则。

修改二十一点规则

  • 正是两个人类玩家(经销商无关)
  • 每个玩家面朝下发两张牌
  • 双方玩家_ever_都不知道对手纸牌的_any_的值
  • 在_both_完成手牌
  • 之前,任何玩家都不知道对手的手牌值
  • 目标是尽可能接近21分。结果:
  • 如果玩家的A和B得分相同,则游戏为平局
  • 如果玩家的A和B得分均超过21(半身),则游戏为平局
  • 如果玩家A的得分小于等于21并且玩家B破产了,则玩家A会赢得
  • 如果玩家A的得分大于玩家B的得分,并且都没有失败,则玩家A赢得
  • 否则,玩家A输了(B赢了)。
  • 卡值得:
  • 卡2到10相当于相应的点数
  • 卡J,Q,K得10分
  • 卡牌A值得1或11点
  • 每个玩家都可以一次请求一张额外的纸牌,直到:
  • 玩家不再想要(保持)
  • 球员的得分(任何A都被计为1)超过21(失败)
  • 双方都不知道对方在任何时间使用了多少张纸牌。
  • 一旦两名球员都留下或被淘汰,则根据规则3确定获胜者
    以上。
  • 每局牌重新洗牌后,所有52张牌都重新投入使用

  • 什么是一副扑克牌?

    一副纸牌由52张纸牌组成,以下13个值中的每四个为四张纸牌:

    2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A



    卡的其他属性均不相关。

    Ruby的表示形式是:
    CARDS = ((2..11).to_a+[10]*3)*4

    算法

    我一直按如下方式进行处理:
  • 如果我的分数是2到11,我将一直想打,因为不可能破坏
  • 对于得分12到21中的每一个,我将模拟N手对抗对手
  • 对于这N只手,分数将是我的“极限”。一旦达到极限或更高,我将留下。
  • 我的对手将遵循完全相同的策略
  • 我将对每个集合(12..21),(12..21)
  • 的每个排列模拟N手
  • 打印每个排列的获利和损失之差以及净胜负差

  • 这是在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个套牌。快速Macbook pro 100,000大约需要5分钟。

    输出(N = 100000)
                                                           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上,所以我应该扔掉那些jackets_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 --

    一些其他评论:

    当前的设计是不灵活的。只需少量的重构,您就可以在游戏操作(交易,混洗,保持运行状态)和玩家决策之间实现清晰的分离。这将使您能够相互测试各种策略。当前,您的策略已嵌入循环中,这些循环都缠绕在游戏操作代码中。通过设计,您可以创建新的玩家并随意设置他们的策略,从而更好地满足您的实验需求。

    关于ruby - 此修改后的二十一点游戏的最佳获胜策略是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2301460/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com