麻将对对碰游戏:规则与模拟实现
麻将对对碰游戏:规则与模拟实现
麻将对对碰是一种基于麻将牌的趣味游戏,结合了抽牌、许愿和对子结算的机制。本文将介绍游戏规则,并通过 Python 实现一个模拟程序,帮助大家更好地理解游戏玩法。
游戏规则
-
牌堆:
- 使用标准麻将的 136 张牌,包括万子、条子、筒子、风牌和箭牌。
- 牌堆在游戏开始前会被随机打乱。
-
初始翻牌:
- 玩家选择初始翻牌数量(例如 5 张),每翻一张牌需要支付 1 元。
-
许愿机制:
- 玩家从万子、条子、筒子中选择一个“许愿”种类。
- 每当翻出的牌与许愿种类一致时,玩家可以多抽一张牌。
-
对子结算:
- 每当手牌中形成一个对子(两张相同的牌),对子会被移入结算池,并多抽一张牌。
- 游戏结束时,结算池中的每个对子会为玩家带来 1 元收益。
-
游戏结束:
- 当牌堆被抽完或没有抽牌动作时,游戏结束。
- 最终收益 = 对子数量 - 初始翻牌数量。
模拟实现
以下是使用 Python 实现的麻将对对碰游戏模拟程序:
import random
# 定义标准麻将牌堆(136张)
tiles = (
['万' + str(i) for i in range(1, 10)] * 4 + # 万子
['条' + str(i) for i in range(1, 10)] * 4 + # 条子
['筒' + str(i) for i in range(1, 10)] * 4 + # 筒子
['东', '南', '西', '北'] * 4 + # 风牌
['中', '发', '白'] * 4 # 箭牌
)
# 打乱牌堆
random.shuffle(tiles)
def draw_tiles(num_tiles, wish):
drawn_tiles = [] # 当前手牌
pairs = [] # 对子池
total_money = -num_tiles # 初始花费(每张牌花费1元)
current_draw = num_tiles # 当前需要抽的牌数
while current_draw > 0 and tiles:
# 抽一张牌
tile = tiles.pop()
drawn_tiles.append(tile)
print(f"抽到: {tile}")
current_draw -= 1
# 检查是否与许愿种类一致
if wish in tile:
current_draw += 1
print(f"许愿触发!多抽一张牌。")
# 检查是否有对子
for t in set(drawn_tiles): # 遍历去重后的手牌
if drawn_tiles.count(t) == 2:
pairs.append(t)
print(f"形成对子: {t}")
# 移除对子
drawn_tiles = [x for x in drawn_tiles if x != t]
current_draw += 1
# 计算获得的金额
total_money += len(pairs)
print("\n抽牌结束。")
print(f"最终手牌: {drawn_tiles}")
print(f"对子池: {pairs}")
print(f"获得金额: {total_money}元")
# 输入初始翻牌数量和许愿选择
initial_draw = int(input("请输入初始翻牌数量: "))
# initial_draw = 10
wish_choice = input("请选择许愿种类(万、条、筒): ")
# wish_choice = "万"
# 开始游戏
draw_tiles(initial_draw, wish_choice)
运行程序,输入初始翻牌数量和许愿种类,程序会随机抽取指定数量的牌,以10和万子为例:
盈亏分析
从规则来看,许愿机制在重复游戏下对结果没什么影响,但是会对结果产生一些偶然影响。而初始翻牌数量显然会影响游戏的收益,这是因为初始翻牌数量越多,玩家的手牌越多,手牌越多,就越有可能形成对子,从而获得更多的收益,但初始投入的成本也会越多,我对初始翻牌与最终收益的关系感兴趣,通过一个1000次的模拟来分析一下,因为对子的个数最多是68个,所以初始翻牌的范围,设置在了1-68之间。
import random
# 初始抽牌价格
price = 1
def draw_tiles(num_tiles, wish):
# 定义标准麻将牌堆(136张)
tiles = (
['万' + str(i) for i in range(1, 10)] * 4 + # 万子
['条' + str(i) for i in range(1, 10)] * 4 + # 条子
['筒' + str(i) for i in range(1, 10)] * 4 + # 筒子
['东', '南', '西', '北'] * 4 + # 风牌
['中', '发', '白'] * 4 # 箭牌
)
# 打乱牌堆
random.shuffle(tiles)
drawn_tiles = [] # 当前手牌
pairs = [] # 对子池
total_money = -num_tiles*price # 初始花费(每张牌花费1元)
current_draw = num_tiles # 当前需要抽的牌数
while current_draw > 0 and tiles:
# 抽一张牌
tile = tiles.pop()
drawn_tiles.append(tile)
# print(f"抽到: {tile}")
current_draw -= 1
# 检查是否与许愿种类一致
if wish in tile:
current_draw += 1
# print(f"许愿触发!多抽一张牌。")
# 检查是否有对子
for t in set(drawn_tiles): # 遍历去重后的手牌
if drawn_tiles.count(t) == 2:
pairs.append(t)
# print(f"形成对子: {t}")
# 移除对子
drawn_tiles = [x for x in drawn_tiles if x != t]
current_draw += 1
# 计算获得的金额
total_money += len(pairs)
return total_money
wish_price = 0
average_money = [] # 平均花费
simluation_times = 1000 # 模拟次数
# 输入初始翻牌数量和许愿选择
wish_choice = "条"
for initial_draw in range(1, 68):
total_money = 0
for _ in range(simluation_times):
# 开始游戏
total_money += draw_tiles(initial_draw, wish_choice)
average_money.append(total_money / simluation_times - wish_price)
print(average_money)
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.figure(figsize=(10, 6))
plt.plot(range(1, 60), average_money)
plt.xlabel('初始翻牌数量')
plt.ylabel('平均花费')
plt.title('麻将牌抽牌模拟')
plt.show()
从结果中可以看出,在1-40张牌的范围内,初始翻牌数量越多,平均收益上升,在40张牌的时候达到了最高,平均有25左右的收益,也就是说初始翻40张牌,配合许愿机制和对子奖励,几乎可以将整副麻将牌抽完。
进一步地,我想探究一下,如果将许愿机制单独拆分,这个许愿机制价值多少,这个许愿机制显然会影响到游戏的收益,但是这个许愿机制的价值似乎在初始抽牌不同的情况下价值不同,我们可以尝试将许愿机制的价值与初始抽牌数量的关系进行分析(就像期权价值的分析一样)。
先来构造一局无许愿的模拟:
# 不许愿模式
average_money1 = [] # 平均花费
simluation_times = 1000 # 模拟次数
# 输入初始翻牌数量和许愿选择
wish_choice = "无"
for initial_draw in range(1, 68):
total_money = 0
for _ in range(simluation_times):
# 开始游戏
total_money += draw_tiles(initial_draw, wish_choice)
average_money1.append(total_money / simluation_times)
print(average_money)
plt.figure(figsize=(10, 6))
plt.plot(range(1, 68), average_money1)
plt.xlabel('初始翻牌数量')
plt.ylabel('无许愿平均花费')
plt.title('麻将牌抽牌模拟')
plt.show()
可以看出没有许愿机制的游戏,平均花费随着初始翻牌数量的增加,当达到30张时,平均花费达到了最高,随后几乎保持不变,在60张左右时,平均收益才有所上升。
然后通过有许愿的模拟减去无许愿的模拟,来分析许愿机制的价值:
wish_value = [i-j for i,j in zip(average_money, average_money1)]
plt.figure(figsize=(10, 6))
plt.plot(range(1, 68), wish_value)
plt.xlabel('初始翻牌数量')
plt.ylabel('许愿机制价值')
plt.title('麻将牌抽牌模拟')
plt.show()
从结果中可以看出,随着初始翻牌数量的增加,许愿机制的价值越来越高,还是在大约40张牌时,许愿机制的价值最高(达到了45左右),随后下降。
机制设计
有了这些分析,我们可以设计一些游戏机制来提高游戏的收益,作为游戏的制定方:
- 初始翻牌价格:因为在初始翻牌数量为40时,平均收益最高,我们可以通过调增初始翻牌价格来提高游戏的收益(1+25/40=1.75),当初始翻牌价格设定为1.75时,作为游戏的制定方,几乎稳赚不赔(平均来看)
import random
# 初始抽牌价格
price = 1.75
def draw_tiles(num_tiles, wish):
# 定义标准麻将牌堆(136张)
tiles = (
['万' + str(i) for i in range(1, 10)] * 4 + # 万子
['条' + str(i) for i in range(1, 10)] * 4 + # 条子
['筒' + str(i) for i in range(1, 10)] * 4 + # 筒子
['东', '南', '西', '北'] * 4 + # 风牌
['中', '发', '白'] * 4 # 箭牌
)
# 打乱牌堆
random.shuffle(tiles)
drawn_tiles = [] # 当前手牌
pairs = [] # 对子池
total_money = -num_tiles*price # 初始花费(每张牌花费1元)
current_draw = num_tiles # 当前需要抽的牌数
while current_draw > 0 and tiles:
# 抽一张牌
tile = tiles.pop()
drawn_tiles.append(tile)
# print(f"抽到: {tile}")
current_draw -= 1
# 检查是否与许愿种类一致
if wish in tile:
current_draw += 1
# print(f"许愿触发!多抽一张牌。")
# 检查是否有对子
for t in set(drawn_tiles): # 遍历去重后的手牌
if drawn_tiles.count(t) == 2:
pairs.append(t)
# print(f"形成对子: {t}")
# 移除对子
drawn_tiles = [x for x in drawn_tiles if x != t]
current_draw += 1
# 计算获得的金额
total_money += len(pairs)
return total_money
# 许愿模式要加10元
wish_price = 0
# 许愿模式
average_money = [] # 平均花费
simluation_times = 1000 # 模拟次数
# 输入初始翻牌数量和许愿选择
wish_choice = "条"
for initial_draw in range(1, 68):
total_money = 0
for _ in range(simluation_times):
# 开始游戏
total_money += draw_tiles(initial_draw, wish_choice)
average_money.append(total_money / simluation_times - wish_price)
print(average_money)
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.figure(figsize=(10, 6))
plt.plot(range(1, 68), average_money)
plt.xlabel('初始翻牌数量')
plt.ylabel('平均花费')
plt.title('麻将牌抽牌模拟')
plt.show()
- 将许愿机制单独拆分:经过刚才的分析,许愿机制最多值45元,我们可以通过降低初始翻牌价格,但是需要额外的45元来购买许愿机制。分析此时的最低初始翻牌价格为多少。通过多次的模拟,得出最低初始翻牌价格约为0.56,此时平均最高收益也小于等于0。
import random
# 初始抽牌价格
price = 0.56
def draw_tiles(num_tiles, wish):
# 定义标准麻将牌堆(136张)
tiles = (
['万' + str(i) for i in range(1, 10)] * 4 + # 万子
['条' + str(i) for i in range(1, 10)] * 4 + # 条子
['筒' + str(i) for i in range(1, 10)] * 4 + # 筒子
['东', '南', '西', '北'] * 4 + # 风牌
['中', '发', '白'] * 4 # 箭牌
)
# 打乱牌堆
random.shuffle(tiles)
drawn_tiles = [] # 当前手牌
pairs = [] # 对子池
total_money = -num_tiles*price # 初始花费(每张牌花费1元)
current_draw = num_tiles # 当前需要抽的牌数
while current_draw > 0 and tiles:
# 抽一张牌
tile = tiles.pop()
drawn_tiles.append(tile)
# print(f"抽到: {tile}")
current_draw -= 1
# 检查是否与许愿种类一致
if wish in tile:
current_draw += 1
# print(f"许愿触发!多抽一张牌。")
# 检查是否有对子
for t in set(drawn_tiles): # 遍历去重后的手牌
if drawn_tiles.count(t) == 2:
pairs.append(t)
# print(f"形成对子: {t}")
# 移除对子
drawn_tiles = [x for x in drawn_tiles if x != t]
current_draw += 1
# 计算获得的金额
total_money += len(pairs)
return total_money
# 许愿模式要加10元
wish_price = 45
# 许愿模式
average_money = [] # 平均花费
simluation_times = 1000 # 模拟次数
# 输入初始翻牌数量和许愿选择
wish_choice = "条"
for initial_draw in range(1, 68):
total_money = 0
for _ in range(simluation_times):
# 开始游戏
total_money += draw_tiles(initial_draw, wish_choice)
average_money.append(total_money / simluation_times - wish_price)
print(average_money)
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.figure(figsize=(10, 6))
plt.plot(range(1, 68), average_money)
plt.xlabel('初始翻牌数量')
plt.ylabel('平均花费')
plt.title('麻将牌抽牌模拟')
plt.show()
当然了,除了许愿机制,还可以更改游戏规则,比如许愿某一张牌,然后抽中了就可以多抽10张这样,当然可以像我这样进行分析。对每一个规则进行定价,虽然这是一个很简单的小游戏,但是可以看到资产定价的影子哈哈哈哈~ 分享一下我的分析过程,希望对你有所帮助。