这个修改过的二十一点游戏的最佳获胜策略是什么?

问题

是否有最好的价值继续保持下去,以便我赢得尽可能多的比赛? 如果是这样,那是什么?

编辑:是否有一个确切的获胜概率,可以计算一个给定的限制,独立于任何对手的行为? (自大学以来我没有做过概率统计)。 我有兴趣看到这个答案与我的模拟结果进行对比。

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

背景

我一直在玩一个修改过的二十一点游戏,其中有一些来自标准规则的烦人的规则调整。 我已经制定了与标准二十一点规则不同的规则,还包括那些不熟悉的二十一点规则。

修改的21点规则

  • 恰好两个人类玩家(经销商无关)
  • 每个玩家都面朝下打两张牌
  • 玩家都不知道对手的牌的价值
  • 在双手完成之前,双方都不知道对手的价值
  • 目标是尽可能接近21分。 成果:
  • 如果玩家的A和B具有相同的分数,则游戏是平局
  • 如果玩家的A和B的得分超过21(半场),比赛是平局
  • 如果玩家A的得分<= 21并且玩家B已经被击中,则玩家A获胜
  • 如果玩家A的分数大于玩家B的分数,并且都没有被破坏,则玩家A获胜
  • 否则,玩家A输了(B赢了)。
  • 卡值得:
  • 第2到第10张牌值得相应的点数
  • 牌J,Q,K值10分
  • Card Ace值1或11分
  • 每个玩家可以一次申请一张附加卡,直到:
  • 玩家不再需要(停留)
  • 玩家的得分,任何A的点数都为1,超过21(半身像)
  • 玩家都无法知道对方随时使用了多少张牌
  • 一旦双方球员都停赛或被淘汰,获胜者将按照上述规则3进行确定。
  • 每手牌后整个牌组都重新洗牌,所有52张牌再次出场
  • 什么是一副牌?

    一副扑克牌由52张牌组成,其中四种值分别为以下13个值:

    1,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手,得分将成为我的“限制”。 一旦达到极限或更高,我会留下。
  • 我的对手会遵循完全相同的策略
  • 我将模拟N组每个排列(12..21),(12..21)
  • 打印每个排列的胜负差异以及净赢输入差异
  • 这是在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快速的MacBook Pro需要大约5分钟。

    产出(N = 100 000)

                                                           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可能,所以我应该抛出这些对手限制值? 另外,如何修改这个以考虑真实人类对手的变化? 例如,真正的人类可能仅仅基于“感觉”而保持在15,并且也可能基于“感觉”


    我怀疑你的结果。 例如,如果对手的目标是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        --
    

    一些其他的评论

    目前的设计是不灵活的。 只需要很少的重构,就可以在游戏操作(交易,洗牌,保持运行状态)和玩家决策之间实现清晰的分离。 这将允许您针对彼此测试各种策略。 目前,你的策略被嵌入到所有与游戏操作代码纠结在一起的循环中。 您的实验可以更好地通过设计让您可以随意创建新玩家并制定战略。


    两点评论:

  • 看起来没有一个基于“命中限制”的统治策略:

  • 如果你选择16,你的对手可以选择17
  • 如果你选择17,你的对手可以选择18
  • 如果你选择18,你的对手可以选择19
  • 如果你选择19,你的对手可以选择20
  • 如果你选择20,你的对手可以选择12
  • 如果你选择12,你的对手可以选择16。
  • 2.你没有提到玩家是否可以看到他们的对手画了多少张牌(我想是这样)。 我希望将这些信息纳入“最佳”战略。 (回答)


    由于没有关于其他玩家决定的信息,游戏变得更简单。 但是由于显然没有统治性的“纯粹”策略,所以最优策略将是一个“混合”策略。 也就是说:对于每个得分从12到21的一组概率,您是否应该停止或抽出另一张牌(编辑:对于给定得分,您将需要不同的概率,而对于没有对手的对手,则需要不同的概率)。然后执行策略需要您可以随机选择(根据概率)每次新抽签后是停止还是继续。 然后你可以找到游戏的纳什均衡。

    当然,如果你只是问一个比较简单的问题:针对次优球员的最佳赢球策略是什么(例如那些总是停在16,17,18或19的球员),你会问一个完全不同的问题,你将不得不指定与其他玩家相比,你的确切方式是有限的。


    以下是关于您收集的数据的一些想法:

  • 这对于告诉你你的“命中限制”应该是多少有用,但前提是你知道你的对手正在遵循类似的“命中限制”策略。
  • 即使如此,如果你知道你的对手的“命中限制”是或可能是什么,它才会非常有用。 你可以选择一个限制,让你赢得比他们更多的胜利。
  • 您可以或多或少地忽略表中的实际值。 无论他们是正面还是负面都很重要。
  • 要以另一种方式展示您的数据,第一个数字是您的对手的限制,第二组数字是您可以选择并赢得的限制。 带星号的是“winningest”选项:

    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?

    总之,你只能猜测你的对手的限制是什么,但如果你猜对了,你可以用这些统计数据长期打败他们。

    链接地址: http://www.djcxy.com/p/30695.html

    上一篇: What is the optimal winning strategy for this modified blackjack game?

    下一篇: Modular game engine: DLL circular dependencies