对对碰游戏概率计算

77 Lv2

🎮游戏规则

  1. 牌堆包含 1-9 每个数字 5 张,以及一张 luck 运气牌,总共 46 张牌;
  2. 游戏开始,选择一个许愿数字,初始化游戏区从牌堆随机抽取 9 张牌,抽走后,牌堆需要去除这9张牌;
  3. 游戏区每次匹配2张相同数字的牌,得 1 分,移除匹配牌并从牌堆抽取补充 1 张新牌,随机补充的牌是可能和游戏区的数字相同的;
  4. 如果游戏区出现luck运气牌则从牌堆抽取补充 3 张新牌,补充的3张牌之间可能相同,也可能不同,补充的3张牌是可能和游戏区的数字相同的;
  5. 如果游戏区出现许愿数字,匹配2张该许愿数字的牌后,不仅补充 1 张牌,还要从牌堆抽取补充 2 张额外的牌,总共从牌堆抽取补充 3 张;
  6. 如果最终得分大于等于5,那么再从牌堆抽取1张,如果出现匹配2张相同数字的牌,则重复上述操作,然后再计算最终得分;
  7. 记录每次模拟得到最高得分,并记录得到最高得分时的匹配和补充牌的详情;
  8. 每次模拟次数1w次;
  9. 最后统计每个分值出现次数,并生成正态分布图。

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import random
from collections import Counter
import matplotlib.pyplot as plt

# 初始化牌堆:包含1-9每个数字5张,以及1张luck运气牌
deck = [i for i in range(1, 10) for _ in range(5)] + ['luck']
print(deck)

# 规则参数
initial_game_zone_size = 9 # 初始游戏区的牌数
max_card_count = 5 # 每个数字出现的最大次数
max_luck_count = 1 # 运气牌最大出现次数
max_total_cards = 46 # 总牌数上限




# 游戏区的初始化
def draw_initial_game_zone():
print(random.sample(deck, initial_game_zone_size))
return random.sample(deck, initial_game_zone_size)


# 从牌堆中随机抽取牌
def draw_from_deck(deck, total_counter):
if not deck: # 检查牌堆是否为空
raise ValueError("牌堆已空,无法继续抽牌!")

# 避免违反规则:检查每个数字和运气牌的数量
while True:
card = random.choice(deck) # 选择一个元素不做删除
total_counter.update([card])
# 统计游戏区中每个卡牌的数量
if isinstance(card, int) and total_counter[card] > max_card_count:
continue # 如果该数字的出现次数已达到上限,继续选择
elif card == 'luck' and total_counter['luck'] > max_luck_count:
continue # 如果运气牌已达到上限,继续选择
elif len(deck) > max_total_cards:
continue # 如果总牌数超出上限,继续选择

# 一旦选中符合条件的卡牌,就可以返回
deck.remove(card) # 从排堆拿走抽走的牌
return card


# 执行游戏
def play_game():
score = 0
deck_copy = deck.copy() # 副本用来模拟游戏
# 用于统计所有曾经出现过的卡片的总次数
game_zone = draw_initial_game_zone() # 随机抽取9张牌
game_zone_counter = Counter(game_zone)
# 用于统计所有曾经出现过的卡片的总次数
total_counter = Counter(game_zone) # 计算每个数字在游戏区中的数量

# 随机选择许愿数字,范围从1到9
wish_number = random.choice(range(1, 10))
print(f"本轮游戏的许愿数字是: {wish_number}")

print(f"初始游戏区: {game_zone}")

# 记录抽牌顺序
draw_sequence = []
game_zone_history = [] # 用于记录游戏区每次的变化
card_additions = [] # 用于记录每一步补充了哪些牌以及补充的原因
current_total_counter = []
additional_card_step = None # 用于标记哪一步因为得分大于等于5而增加了一张牌

while any(count >= 2 for count in game_zone_counter.values()): # 游戏继续直到没有可匹配的牌
print(f"当前游戏区: {game_zone}")
print(f"当前得分: {score}")

# 记录当前游戏区的状态
game_zone_history.append(game_zone.copy())
current_total_counter.append(total_counter.copy())
# 查找是否有可以匹配的数字(数量大于等于2)
matched = False
for number, count in game_zone_counter.items():
if count >= 2:
print(f"匹配数字 {number},得分 +1")
# 记录需要删除的索引
to_remove_indices = []
# 确保只移除前两个
removed_count = 0
for i, card in enumerate(game_zone):
if card == number and removed_count < 2:
to_remove_indices.append(i)
removed_count += 1
if removed_count == 2:
break # 已经移除两个,跳出循环
# 删除记录的卡牌
for index in sorted(to_remove_indices, reverse=True): # 反向删除避免索引变化
game_zone.pop(index)

score += 1

# 处理补充牌的部分,确保每次只补充3张牌
if number == wish_number:
print(f"许愿数字 {number} 匹配成功,补充3张牌")
added_cards = [] # 用来记录补充的卡牌
for _ in range(3):
new_card = draw_from_deck(deck_copy, total_counter)
game_zone.append(new_card) # 将新卡加入游戏区
added_cards.append(new_card) # 将新卡加入记录的补充卡牌列表
draw_sequence.append(new_card) # 记录抽牌顺序
card_additions.append((added_cards, f"许愿数字 {number} 匹配成功,补充3张牌:{added_cards}"))
else:
print(f"补充1张牌到游戏区")
added_cards = [draw_from_deck(deck_copy, total_counter)]
game_zone.extend(added_cards) # 扩展游戏区,添加1张牌
card_additions.append((added_cards, f"匹配数字 {number},补充1张牌{added_cards}"))
draw_sequence.extend(added_cards) # 记录抽牌顺序充1张牌"))

matched = True
break # 在一轮中匹配一对即可

# 如果抽到运气牌,补充3张新牌
if 'luck' in game_zone:
print("运气牌触发,补充3张牌")
game_zone.remove('luck')
added_cards = []
for _ in range(3):
new_card = draw_from_deck(deck_copy, total_counter)
game_zone.append(new_card)
added_cards.append(new_card)
draw_sequence.append(new_card) # 记录抽牌顺序
card_additions.append((added_cards, "运气牌触发,补充3张牌"))

# 更新game_zone_counter
game_zone_counter = Counter(game_zone)
print(game_zone_counter)
# 如果没有匹配的牌并且没有运气牌,结束游戏
if not matched and 'luck' not in game_zone:
break

print(f"正常游戏结束!最终得分:{score}")
# 记录正常游戏结束后游戏区
game_zone_history.append(game_zone.copy())
current_total_counter.append(total_counter.copy())
# 额外规则:如果得分大于等于5,并且牌堆中还有牌,继续抽一张牌
if score >= 5 and deck_copy:
print("得分大于等于5,奶一口继续从牌堆中抽一张牌并计算新的得分。")
# new_card = draw_from_deck(deck_copy, total_counter)
# game_zone.append(new_card)
# draw_sequence.append(new_card) # 记录抽牌顺序
# additional_card_step = len(game_zone_history) + 1 # 标记在哪一步增加了一张牌
# print(f"奶一口新抽取的牌: {new_card}")
added_cards = [draw_from_deck(deck_copy, total_counter)]
game_zone.extend(added_cards) # 扩展游戏区,添加1张牌
game_zone_history.append(game_zone.copy())
current_total_counter.append(total_counter.copy())
card_additions.append((added_cards, f"满足奶一口规则,补充1张牌{added_cards}"))
draw_sequence.extend(added_cards) # 记录抽牌顺序充1张牌"))
print(f"奶一口新抽取的牌: {added_cards}")

# 重新计算得分
game_zone_counter = Counter(game_zone)
new_score = score

# 查找奶一口后是否有可以匹配的数字(数量大于等于2)
milk_matched = False
# 查找是否有可以匹配的数字(数量大于等于2)
for number, count in game_zone_counter.items():
if count >= 2:
print(f"奶一口后匹配数字 {number},得分 +1")
# 记录需要删除的索引
to_remove_indices = []
# 确保只移除前两个
removed_count = 0
for i, card in enumerate(game_zone):
if card == number and removed_count < 2:
to_remove_indices.append(i)
removed_count += 1
if removed_count == 2:
break # 已经移除两个,跳出循环
# 删除记录的卡牌
for index in sorted(to_remove_indices, reverse=True): # 反向删除避免索引变化
game_zone.pop(index)

new_score += 1

# 处理补充牌的部分,确保每次只补充3张牌
if number == wish_number:
print(f"奶一口后许愿数字 {number} 匹配成功,补充3张牌")
added_cards = [] # 用来记录补充的卡牌
for _ in range(3):
new_card = draw_from_deck(deck_copy, total_counter)
game_zone.append(new_card) # 将新卡加入游戏区
added_cards.append(new_card) # 将新卡加入记录的补充卡牌列表
draw_sequence.append(new_card) # 记录抽牌顺序
game_zone_history.append(game_zone.copy())
current_total_counter.append(total_counter.copy())
card_additions.append((added_cards, f"奶一口后许愿数字 {number} 匹配成功,补充3张牌:{added_cards}"))
else:
print(f"奶一口后补充1张牌到游戏区")
added_cards = [draw_from_deck(deck_copy, total_counter)]
game_zone.extend(added_cards) # 扩展游戏区,添加1张牌
game_zone_history.append(game_zone.copy())
current_total_counter.append(total_counter.copy())
card_additions.append((added_cards, f"奶一口后匹配数字 {number},补充1张牌{added_cards}"))
draw_sequence.extend(added_cards) # 记录抽牌顺序充1张牌"))

milk_matched = True
break # 在一轮中匹配一对即可

# 如果抽到运气牌,补充3张新牌
if 'luck' in game_zone:
print("奶一口后运气牌触发,补充3张牌")
game_zone.remove('luck')
added_cards = []
for _ in range(3):
new_card = draw_from_deck(deck_copy, total_counter)
game_zone.append(new_card)
added_cards.append(new_card)
draw_sequence.append(new_card) # 记录抽牌顺序
game_zone_history.append(game_zone.copy())
current_total_counter.append(total_counter.copy())
card_additions.append((added_cards, "奶一口后运气牌触发,补充3张牌"))

# 更新game_zone_counter
game_zone_counter = Counter(game_zone)
print(game_zone_counter)
# 如果没有匹配的牌并且没有运气牌,结束游戏
if not milk_matched and 'luck' not in game_zone:
print("奶一口后也无法再加分")

print(f"奶一口之后的新得分:{new_score}")
score = new_score

return score, draw_sequence, wish_number, game_zone_history,current_total_counter, card_additions, additional_card_step # 返回得分、抽牌顺序、许愿数字和增加牌的步骤


# 模拟多次游戏,计算不同分数出现的频率
def monte_carlo_simulation(num_simulations=500000):
highest_score = 0
highest_score_sequence = [] # 记录最高得分时的抽牌顺序
highest_wish_number = None # 记录最高得分时的许愿数字
highest_game_zone_history = [] # 记录最高得分时的游戏区变化
highest_current_total_counter = []
highest_card_additions = [] # 记录最高得分时的每步牌补充详情
highest_additional_card_step = None # 记录最高得分时增加牌的步骤
scores = []

for _ in range(num_simulations):
score, draw_sequence, wish_number, game_zone_history, current_total_counter,card_additions, additional_card_step = play_game()
scores.append(score)

# 跟踪最高得分的模拟
if score > highest_score:
highest_score = score
highest_score_sequence = draw_sequence
highest_wish_number = wish_number
highest_game_zone_history = game_zone_history
highest_current_total_counter = current_total_counter
highest_card_additions = card_additions
highest_additional_card_step = additional_card_step

# 绘制得分分布
plt.hist(scores, bins=range(min(scores), max(scores) + 1), edgecolor='black')
plt.xlabel('得分')
plt.ylabel('出现次数')
plt.title(f'游戏得分分布(模拟 {num_simulations} 次)')
plt.show()

return highest_score, highest_score_sequence, highest_wish_number, highest_game_zone_history,highest_current_total_counter, highest_card_additions, highest_additional_card_step


# 进行多次模拟
highest_score, highest_score_sequence, highest_wish_number, highest_game_zone_history,highest_current_total_counter, highest_card_additions, highest_additional_card_step = monte_carlo_simulation()
print(f"最高得分: {highest_score}")
print(f"最高得分时的抽牌顺序: {highest_score_sequence}")
print(f"最高得分时的许愿数字: {highest_wish_number}")

# 打印最高得分时的游戏区变化
print(f"最高得分时的游戏区变化:")
# for idx, history in enumerate(highest_game_zone_history, start=1):
for idx, (history, used_count) in enumerate(zip(highest_game_zone_history, highest_current_total_counter), start=1):
print(f"步骤 {idx}: {history},已经用掉的卡牌次数:{used_count}")

# 打印每步牌补充的详细信息
print(f"最高得分时的每步牌补充详情:")
for idx, (cards, reason) in enumerate(highest_card_additions, start=1):
print(f"步骤 {idx}: {reason} -> 补充了: {cards}")

print(f"最高得分时增加牌的步骤: {highest_additional_card_step}")

游戏结果

正态分布图
2025-01-03-18-35-53
最高得分计算
2025-01-03-18-36-26.png

  • 标题: 对对碰游戏概率计算
  • 作者: 77
  • 创建于 : 2025-01-03 18:30:15
  • 更新于 : 2025-01-13 14:06:12
  • 链接: https://www.jiaheqi.cloud/2025/01/03/对对碰游戏概率计算/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
对对碰游戏概率计算