前段时间玩了两次余姚麻将,规则里带有地方麻将常见的癞子规则(癞子牌被叫做龙牌)。边打边学的时候我了解到,七小对/七对子在这个规则里是最大的番种之一,也就是和牌的人可以把其他三家的点数全部赢到手里(每人开局发100点,任何一人把这100点输光这局就算结束)。根据之前打立直麻将的经验,七对子并不是一个很难和的牌,再加上有龙牌辅助,我当时就觉得它应该是余姚麻将的版本答案。可惜的是那两天有意试着做了几次,最多只实现了一次听牌。打完之后好奇我的猜测是否正确,写了个模拟脚本来检验七对子的收益有多高。

假设某玩家A在第一轮摸牌的时候发现手里的牌包含若干个对子和若干张龙牌,直接开始闷头做七对子。那么这把牌只有三种结局,一是玩家A和了牌,显然A的收益期望为300点,二是其他玩家和牌,此时A的收益期望为某个负值,三则是没有任何人和牌,A的收益期望为0。对于第二和第三种情况,综合考虑点炮、别家自摸、有人开杠这些因素,估计A的损失大约为20点,可能有些高估。

在上述条件下,假设在余姚麻将中平均的和牌轮数为X,A在此轮或之前和牌的概率为Y,那么A做七对子的收益即为300×Y-20×(1-Y)。X难以计算,先搁置。Y则可以通过模拟来统计。模拟的过程中,A的策略是把手牌中在场上出现次数最多的那张打出,以此增加和牌概率。为了简化,模拟中除了A以外的三个玩家都是无情的摸到哪张就打哪张的机器,也不会吃碰杠。

模拟代码如下:

import random


times_to_test = 10000
list_tiles_all = [j for j in range(34) for i in range(4)]
list_round = []
# 余姚麻将一共136张牌,垒完长城后一张牌要翻开来指示龙牌,它和被它压住的牌是不能被摸走的,那么第21轮只有两个玩家能摸牌,第22轮用来记录A在这一把没有和牌的情况
list_result = [[] for _ in range(23)]
for number_pair, number_loong in ((2, 1), (1, 2), (4, 0), (3, 1), (4, 1), (3, 2)):
    print(number_pair, number_loong)
    list_round.append({each: 0 for each in range(1, 23)})
    for _ in range(times_to_test):

        # 选定编号33是龙牌指示牌,编号0是龙牌,为了方便起见,假设龙牌指示牌和它压住的牌在牌堆最末尾
        list_tiles = list_tiles_all[:]
        list_tiles.remove(33)
        list_pairs = []
        list_loong = [list_tiles.pop(0) for _ in range(number_loong)]
        list_single = []
        while len(list_pairs) + number_loong + len(list_single)< 14:                  
            if each_tile := random.choice(list_tiles):
                if len(list_pairs) < 2 * number_pair and (each_tile < 33 or each_tile not in list_pairs):
                    for _ in range(2):
                        list_pairs.append(each_tile)
                        list_tiles.remove(each_tile)
                elif each_tile not in list_single:
                    list_single.append(each_tile)
                    list_tiles.remove(each_tile)
        dict_tiles_shown = {each: 0 for each in range(34)}

        random.shuffle(list_tiles)
        # 把A的手牌初始化后,从牌堆中删去其余三人的手牌,以及牌堆最末尾被龙牌指示牌压住的牌
        # 摸牌顺序随机,各自摸13张牌后在第一轮到A再摸一张,这时可能已经有人出过牌了,要先把它们记下,然后再出牌
        order = random.randint(0, 3)
        list_tiles = list_tiles[39:-1]
        for _ in range(order):
            dict_tiles_shown[list_tiles.pop(0)] += 1
        list_single.remove(sorted(list_single, key=lambda x: dict_tiles_shown[x],  reverse=True)[0])

        number_count = order
        number_round = 1
        win = False
        while list_tiles:
            number_count += 1
            each_tile = list_tiles.pop(0)
            dict_tiles_shown[each_tile] += 1
            if number_count % 4 == order:
                number_round += 1
                if each_tile == 0:
                    list_loong.append(each_tile)
                elif each_tile in list_single:
                    list_pairs.append(each_tile)
                    list_pairs.append(each_tile)
                    list_single.remove(each_tile)
                else:
                    list_single.append(each_tile)
                if len(list_loong) >= len(list_single):
                    win = True
                    break
                list_single.remove(sorted(list_single, key=lambda x: dict_tiles_shown[x],  reverse=True)[0])

        list_round[-1][number_round if win else 22] += 1

    probability_all = 0
    for i in range(1, 22):
        probability = list_round[-1][i] / times_to_test
        probability_all += probability
        list_result[i] += [probability, probability_all, probability_all * 300 - (1 - probability_all) * 20]

for each_round in list_result[1:22]:
    print(' '.join([str(each_value) for each_value in each_round]))
input()

以上是对A的起始手牌(第一轮摸完牌的状态)中包含2对+1龙/1对+2龙/4对+0龙/3对+1龙/4对+1龙/3对+2龙的这几种情况的模拟,模拟结果如下:

qdz.jpg

每种情况下的第一列是A在该轮的和牌概率,第二列是在该轮或之前和牌的概率,也就是前面提到的Y,第三列是X取整后为该轮数(显然X不会是一个整数)时A的收益期望。从这张图可以看到,前三种情况下,A的收益期望为正的前提是X大于12/11/11,后三种情况则只要求X大于9/6/5。

在立直麻将里,X一般在11到13之间,余姚麻将因为和牌更容易,X会稍小些,可能会在9和11之间。也就是说,只要初始手牌包含3对+1龙/4对+1龙/3对+2龙,那么做七对的收益就是划算的,和我的猜想基本一致,只不过我高估了4对+0龙的收益。当然,这个模拟并不严谨,图一乐罢了2333

标签: none

评论已关闭