Pt很低即将掉段时,应该故意掉段吗(2)

《Pt很低即将掉段时,应该故意掉段吗》

之前的文章讨论了雀魂三麻圣3低pt时故意掉段是否有利。这篇文章用相同的方式,计算天凤三麻八段10pt时故意掉段是否有利。本文以一直打下去直到升到九段为止的对局数的期望值来衡量是否有利。

背景:

在天凤,pt达到升段所需pt(含)即可升段;降到0以下(不含)就会降段。从其他段位升入或降入某一段位时,会持有一定量的pt,该pt称为该段位的原点

各段位pt原点/升段所需pt分别为:七段1400/2800,八段1600/3200,九段1800/3600,十段2000/4000。

例如,八段pt>=3200时,就会变成九段1800pt;八段pt<0时,就会变成七段1400pt。

天凤三麻凤桌半庄战(即三凤南),无论段位多少,吃1增加135pt;吃2pt不变;吃3根据玩家段位扣减pt,七段减少135pt,八段减少150pt,九段减少165pt,十段减少180pt。

接下来进入正题。

八段时,吃1增加135pt,吃3减少150pt,135和150的最大公因数为15,因此,pt取值只能为1600+15m,其中m为整数;1600+15m的非负最小值为1600+15*(-106) = 10,即八段pt最小值为10。笔者刚好到过这个状态,当时想知道是否应该故意掉段,就有了以下的计算。

前提:>=七段时,只打三凤南。

抽象的、一般的推导请看Pt很低即将掉段时,应该故意掉段吗,这次用一个具体的例子说明一下。

玩家的状态用段位、pt这两个参数即可描述。每一个状态,即图中段位pt数轴上的每一个点,都会对应一个“升段所需对局数期望”。令\(n(g,pt)\) 等于从 \(g\)\(pt\) pt开始直到升到 \((g+1)\) 段时的对局数期望。假设我们一开始站在数轴上八段1600pt的地方,升段所需对局数期望为\(n(8,1600)\)。打一把三凤南,有\(p_1\)概率吃1,我们的位置转移到八段1735pt,升段所需对局数期望变成了\(n(8,1735)\);有\(p_2\)概率吃2,pt不变,升段所需对局数期望仍为\(n(8,1600)\);有\(p_3\)概率吃3,我们的位置转移到1450pt,升段所需对局数期望变成了\(n(8,1450)\)。另外,无论这场对局顺位如何,对局数都增加了1。所以我们有:

\[n(8,1600)= p_1 \cdot n(8,1735)+ p_2 \cdot n(8,1600)+ p_3 \cdot n(8,1450) + 1\]

再考虑边界情况,若 \(pt \geq 3200\),则已经升段,不需要再对局了,因此 \(n(8,3200以上) = 0\)

\(pt < 0\),需要从七段原点打回八段原点,再从八段原点打到九段。因此对局数期望等于\(n(7, 1400) + n(8,1600)\)。现在加上“如果当前pt小于某个阈值,则故意掉段”,令这个阈值为 t。若\(0≤pt<t\),则故意降段,不计故意降段的挂机对局数(挂机期间无需花费精力),则和 \(pt<0\) 的情况是一样的。

\[n(8,pt)=\begin{cases} 0 \quad &pt\geq 3200, \\ 1 + p_1\cdot n(8, pt+135) + p_2 \cdot n(8,pt)+ p_3 \cdot n(8,pt-150) & t \leq pt<3200,\\ n(7,1400) + n(8,1600) \quad &pt<t, \end{cases}\]

这是一个高阶差分方程。

这次我们目标是探究10pt故意掉段是否有利。我们可以比较当t取0和11时\(n(8, 10)\)的大小。(注:t取(10, 25]是等价的。我们也可以比较\(n(8, 任意pt)\),因为“哪个策略更优”和当前的pt是无关的,如果10pt时掉段有利,那么1600pt时采用“如果以后掉到10pt时,则掉段”的策略也会有利。)

玩家安8的情况

假设某玩家1、2、3位率分别20/57、19/57、18/57(刚好安8)。

解得:无故意掉段时,\(n(8, 10) = 419.785+1.94171\cdot n(7,1400)\)

10pt故意掉段时,\(n'(8, 10) = 406.700+2.00617\cdot n(7,1400)\) (注:\('\)非导数,只是个标记)

为了比较\(n\)\(n'\)的大小,我们仍需求出\(n(7,1400)\)

七段时统一采用不故意掉段策略。解得 \(n(7,1400) = 215.121+0.31381\cdot n(6, 1200)\)

代入八段数据,可得

\(n(8, 10) = 837.488+0.60933\cdot n(6,1200)\)

\(n'(8, 10) = 838.269+0.62956\cdot n(6,1200)\)

\(n'\) 的常数项及一次项均分别大于 \(n\) 的常数项及一次项。因此,无论六段回七所需对局数如何,“八段10pt时故意掉段升九期望对局数”总是大于“八段10pt时不故意掉段升九期望对局数”,八段10pt不故意掉段有利。

玩家安7的情况

那么安7时又如何呢?假设某玩家1、2、3位率分别1/3、1/3、1/3(刚好安7)。

解得:

\(n(8, 10) = 812.162+4.37936\cdot n(7,1400)\)

\(n'(8, 10) = 797.895+4.42410\cdot n(7,1400)\)

\(n(7,1400) = 363.001+1.00000\cdot n(6, 1200)\)

代入得

\(n(8, 10) = 2401.874+4.37936\cdot n(6,1200)\)

\(n'(8, 10) = 2403.847+4.42410\cdot n(6,1200)\)

\(n'\) 的常数项及一次项均分别大于 \(n\) 的常数项及一次项。因此,无论六段回七所需对局数如何,“八段10pt时故意掉段升九期望对局数”总是大于“八段10pt时不故意掉段升九期望对局数”,八段10pt不故意掉段有利。

附录:数值方法解方程代码

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
origin = 106 # 原点
goal = 213 # 升段所需pt
pt_change = [9, 0, -10] # 吃123时的pt变动
rank_prob = [10/28.5, 1/3, 9/28.5] # 吃123概率
start_pt = 106 # 起始pt,不能小于等于故意掉段阈值
# 以上数值为安8,从八段原点开始打
# origin = 1400
# goal = 2800
# pt_change = [135, 0, -135]
# rank_prob = [1/3, 1/3, 1/3]
# start_pt = 1400

current_pt = start_pt
deliberate_downgrade = True # 是否故意掉段
downgrade_threshold = 0 # 当deliberate_downgrade为True时,当pt小于等于downgrade_threshold时故意掉段

n_back = 0 + 1j # 从更低一个段位原点开始,回到当前段位原点所需对局数期望
n_games_forward = [0] * goal # 从<下标>pt开始,升段所需对局数期望,均初始化为0
n_games_backward = [100000 + 100j] * goal # 从<下标>pt开始,升段所需对局数期望,初始化为很大的数

def get_n_games(pt, is_forward):
n_games = n_games_forward
if not is_forward:
n_games = n_games_backward

if pt >= goal:
return 0
elif deliberate_downgrade and pt <= downgrade_threshold:
return n_back + n_games[origin]
elif pt < 0:
return n_back + n_games[origin]
else:
return n_games[pt]

def iterate():
# 将n_games双向各进行一次迭代,并返回迭代后双向实部的差的绝对值的最大值、虚部的差的绝对值的最大值
new_n_games_forward = [0] * goal
new_n_games_backward = [0] * goal
delta_re_max = 0
delta_im_max = 0
range_stop = downgrade_threshold if deliberate_downgrade else -1
for pt in range(goal - 1, range_stop, -1):
new_n_games_forward[pt] = rank_prob[0] * (get_n_games(pt + pt_change[0], True) + 1) \
+ rank_prob[1] * (get_n_games(pt + pt_change[1], True) + 1) \
+ rank_prob[2] * (get_n_games(pt + pt_change[2], True) + 1)
new_n_games_backward[pt] = rank_prob[0] * (get_n_games(pt + pt_change[0], False) + 1) \
+ rank_prob[1] * (get_n_games(pt + pt_change[1], False) + 1) \
+ rank_prob[2] * (get_n_games(pt + pt_change[2], False) + 1)
delta = new_n_games_backward[pt] - new_n_games_forward[pt]
delta_re = abs(delta.real)
delta_im = abs(delta.imag)
if delta_re > delta_re_max:
delta_re_max = delta_re
if delta_im > delta_im_max:
delta_im_max = delta_im

global n_games_forward, n_games_backward
n_games_forward = new_n_games_forward
n_games_backward = new_n_games_backward
return delta_re_max, delta_im_max


def main():
delta_re_threshold = 0.001
delta_im_threshold = 0.000001
n_iterations_min = 1
n_iterations_max = 100000
n_iterations = 0
delta_re_max, delta_im_max = iterate()
n_iterations += 1
# 重复迭代,直到双向结果的差值小于阈值,或者达到迭代次数上限
while (delta_re_max >= delta_re_threshold or delta_im_max >= delta_im_threshold or n_iterations < n_iterations_min) and n_iterations < n_iterations_max:
delta_re_max, delta_im_max = iterate()
n_iterations += 1
if n_iterations % 1000 == 0:
print('n_iterations:', n_iterations, 'delta:', delta_re_max + delta_im_max * (0+1j))
if n_iterations == n_iterations_max:
print('warning: number of iterations reached maximum')
print('delta:', delta_re_max, delta_im_max)
print('deliberate downgrade:', deliberate_downgrade, ', start pt:', start_pt)
print(n_iterations, n_games_backward[start_pt])

if __name__ == '__main__':
main()