0%

小测试

前言

发现了游戏里一个有意思的机制:

血量低于80%时,减伤(80%-当前血量%)

无聊就打算建模一下,看看残血减伤有多可怕。

数学分析

假设对方每次伤害都打你 $x\%$ 的血量,且 $x\ge20$ (因为如果打没有20%,绝对是刮痧,不用分析了)

那么:

第一回合

当前血量 $(1-x\%)$

第二回合

当前血量 $(1-x\%-(80\%-(1-x\%))\times x\%) = 1-0.8x\%-(x\%)^2$

​ 即 上回合剩的血 - 减伤比例 × 伤害


看起来有点难看,设 $y = x\%$ ,则 第二回合当前血量 = $1-0.8y-y^2, (0.2\le y <1 )$
要想活下来,上式结果大于零,解得:

也就是说,第二下不死,且对面伤害不太刮痧,第一下打的血在以上区间内。

第 $n$ 回合

归纳可得每回合的血量百分比:


好像……不是很好算,那就,跑个代码算吧。

暴力求解

function current_hp(n, y) {
var cur_hp = [1];
for (var i = 1; i<=n;i++) {
if (cur_hp[i-1] >= 0.8) {
cur_hp.push(cur_hp[i-1] - y);
}
else {
cur_hp.push((1+y) * cur_hp[i-1] - 0.8 * y);
}
}
return cur_hp[n];
}
/* 二分法暴力解方程区间 */
function solve_y(n) {
var l, r, y;
l = 0.2, r = 1;
var pre = 0.01;
while (r - l > pre) {
y = (l+r)/2;
if (current_hp(n, y) > 0) {
l = y;
}
else {
r = y;
}
}
return y;
}
/* 算5个回合的好了 */
for (var i = 1; i < 6; i++) {
console.log("[round "+i+"] :" + "0.2<=y<"+solve_y(i));
}

结果如下:

[round 1] :0.2<=y<0.99375
[round 2] :0.2<=y<0.6812499999999999
[round 3] :0.2<=y<0.5437500000000001
[round 4] :0.2<=y<0.45625000000000004
[round 5] :0.2<=y<0.40625

所以,如果,对面第一下打你 40%,你可以撑到第五回合且不回血(不考虑伤害浮动和暴击)

更极端一点,比如第 8 回合?

[round 1] :0.2<=y<0.99375
[round 2] :0.2<=y<0.6812499999999999
[round 3] :0.2<=y<0.5437500000000001
[round 4] :0.2<=y<0.45625000000000004
[round 5] :0.2<=y<0.40625
[round 6] :0.2<=y<0.36875
[round 7] :0.2<=y<0.34375
[round 8] :0.2<=y<0.3187500000000001

31% ! 生命力非常顽强啊,第一下被打1/3,不回血可以撑 7、8 个回合

假设第一下打 50%,也就是不回血能撑 3 回合,设总血量 22000 ,看看每回合的血量?

for (var i = 1; i < 100000; i++) {
var res = current_hp(i,0.5);
if (res >= 0) {
console.log("[round"+i+"]: "+Math.floor(current_hp(i,0.5)*22000)+"/22000");
}
else {
console.log("[round"+i+"]: "+"0"+"/22000");
break;
}
}

结果如下:

[round1]: 11000/22000
[round2]: 7699/22000
[round3]: 2749/22000
[round4]: 0/22000

好像不够明显,看看 40%?

[round1]: 13200/22000
[round2]: 11439/22000
[round3]: 8975/22000
[round4]: 5526/22000
[round5]: 696/22000
[round6]: 0/22000

图像

<html>
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.js"></script>
<div id="input_group">
<div id = "maxhp">
<div>血量上限:<a id = "maxhp_text">22000</a></div>
<input id = "maxhp_slider" type="range" min="12000" max="25000" value="22000" onchange="onChange()">
</div>
<div id = "bhp">
<div>初始血量:<a id = "bhp_text">22000</a></div>
<input id = "bhp_slider" type="range" min="1" max="25000" value="22000" onchange="onChange()">
</div>
<div id = "damage">
<div>对手伤害:<a id = "damage_text">11000</a></div>
<input id = "damage_slider" type="range" min="1000" max="25000" value="11000" onchange="onChange()">
</div>
<div id = "ro">
<div>预览回合:<a id = "ro_text">5</a></div>
<input id = "ro_slider" type="range" min="1" max="15" value="5" onchange="onChange()">
</div>
</div>
<div id="bar_hp"></div>
<script>
var t_r = document.getElementById("ro_text");
var t_m = document.getElementById("maxhp_text");
var t_b = document.getElementById("bhp_text");
var t_d = document.getElementById("damage_text");
var round = +document.getElementById("ro_slider").value;
var max_hp = +document.getElementById("maxhp_slider").value;
var damage0 = +document.getElementById("damage_slider").value;
if (damage0 > max_hp) {
damage0 = max_hp;
}
var damage = damage0/max_hp;
var begin_hp = +document.getElementById("bhp_slider").value;
t_r.innerText = round;
t_m.innerText = max_hp;
t_b.innerText = begin_hp;
t_d.innerText = damage0;

function onChange(){
round = +document.getElementById("ro_slider").value;
max_hp = +document.getElementById("maxhp_slider").value;
damage0 = +document.getElementById("damage_slider").value;
if (damage0 > max_hp) {
damage0 = max_hp;
document.getElementById("damage_slider").value = max_hp;
}
damage = damage0/max_hp;
begin_hp = +document.getElementById("bhp_slider").value;
t_r.innerText = round;
t_m.innerText = max_hp;
t_b.innerText = begin_hp;
t_d.innerText = damage0;
d3.select("svg").remove();
applyAll();
}

var margin = {top: 20, right: 30, bottom: 40, left: 90},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;

function applyAll() {
function current_hp(n, y) {
var cur_hp = [begin_hp/max_hp];
for (var i = 1; i<=n;i++) {
if (cur_hp[i-1] >= 0.8) {
cur_hp.push(cur_hp[i-1] - y);
}
else {
cur_hp.push((1+y) * cur_hp[i-1] - 0.8 * y);
}
}
return cur_hp;
}
/* if you want to survive to round n, y max is */
function survive_y(n) {
var l, r, y;
l = 0.2, r = 1;
var pre = 0.01;
while (r - l > pre) {
y = (l+r)/2;
if (current_hp(n, y)[n] > 0) {
l = y;
}
else {
r = y;
}
}
return y;
}
/* the survive of maximum hp data */
var data1 = [];
for (var i = 1; i <= round; i++){
var temp = {"round": i, "hp": survive_y(i)};
data1.push(temp);
}
/* for damage, survive to when? */
var data2 = [];
for (var i = 1; i < 10000000; i++) {
var res = current_hp(i, damage)[i];
if (res >= 0) {
data2.push({"round": i, "hp": res});
}
else {
data2.push({"round": i, "hp": 0});
break;
}
}

var svg = d3.select("#bar_hp")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Add X axis
var x = d3.scaleLinear()
.domain([0, max_hp])
.range([ 0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end");

// Y axis
var y = d3.scaleBand()
.range([ 0, height])
.domain(data1.map(function(d) { return d.round; }))
.padding(.1);
svg.append("g")
.call(d3.axisLeft(y))

//Bars
function update() {
var u = svg.selectAll("myRect")
.data(data1);
u.enter()
.append("rect")
.attr("x", x(0) )
.attr("y", function(d) { return y(d.round); })
.attr("width", function(d) { return x(d.hp)*max_hp; })
.attr("height", y.bandwidth() )
.attr("fill", "#69b3a2")
}
update();
}

applyAll();

</script>
</html>