Skip to content

Instantly share code, notes, and snippets.

@na74
Last active January 3, 2016 03:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save na74/8401564 to your computer and use it in GitHub Desktop.
Save na74/8401564 to your computer and use it in GitHub Desktop.
クシナダCTW考察

クシナダダメージ計算

このコンテンツは古く、一部誤りがあります
最新の結果はWebアプリ版で確認してね

計算方法

  • 理想的なコンボができた場合のダメージを計算する
    • 理想的、とは、例えば盤面のドロップ数が木15, 光9, 闇6であれば、必ず10コンが組めるということ
    • ある色が18色以上あった場合は、エルモア積みをすることにする
  • 落ちコンは考えない
  • 陣(メイメイ、ソニアetc)を50000回使った際のダメージの平均最大最小標準偏差を比較する
  • 拡張できるように書いたつもりなので各自勝手に拡張しる
  • バグを見つけたらクシナダスレで教えてね
    • エルモア積み周りが非常にあやしい

結果一覧

サブメンバー 平均 最大 最低 標準偏差 要エルモア
イザナギ,メイメイ,光ヨミ,緑おでん 340万 670万 80万 80万 0.68%
モリリン,緑ソニア,闇ヨミ,緑おでん 490万 830万 9万 150万 36.18%
イザナギ,アルテミス,光ヨミ,パール 370万 700万 40万 70万 48.74%
イザナギ,アルテミス,光ヴィーナス,ヴァーチェ 380万 780万 60万 90万 16.79%

考察

  • 緑ソニア・バランスPTがダメージ最大
    • 標準偏差も大きいが、平均の差はそれ以上に大きい
    • ただし、まれに(闇: 26, 木: 4)とかを引くととことんダメージが下がる
  • イザナギ・メイメイPTは安定
    • 周回にはこちらの方が向くか
    • 大抵の場合ミルフィーユ積みでOKなのも楽
  • アルテミスPTも上2つに十分匹敵する
    • 状況に応じて適切にスキルを使う必要あり
    • エルモア積みの習得も要るか

イザナギ・メイメイPT

  • LF: クシナダ+297
  • S: イザナギ、メイメイ、光ヨミ、緑おでん
平均ダメージ: 3444038.01003
最小ダメージ: 805923 7コンボ (闇: 22, 光: 6, 木: 2)
最大ダメージ: 6742417 10コンボ (木: 15, 光: 12, 闇: 3)
標準偏差    : 842484
エルモア積み: 0.68%

緑ソニア・バランスPT

  • LF: クシナダ+297
  • S: モリリン、緑ソニア、闇ヨミ、緑おでん
平均ダメージ: 4911728.9232
最小ダメージ: 87243 3コンボ (闇: 26, 木: 4)
最大ダメージ: 8304481 10コンボ (闇: 15, 木: 15)
標準偏差    : 1485296
エルモア積み: 36.18%

アルテミス・神PT、その1

  • LF: クシナダ+297
  • S: イザナギ、アルテミス、光ヨミ、パール
平均ダメージ: 3727923.81642
最小ダメージ: 423562 7コンボ (火: 8, 回: 8, 水: 5, 木: 5, 闇: 2, 光: 2) => (回: 13, 火: 8, 木: 5, 闇: 2, 光: 2) ["防御態勢・木"]
最大ダメージ: 6999882 10コンボ (光: 12, 水: 4, 回: 4, 火: 4, 闇: 3, 木: 3) => (木: 15, 光: 12, 闇: 3) ["防御態勢・木", "ダブル攻撃態勢・木"]
標準偏差    : 725992
エルモア積み: 48.74%

アルテミス・神PT、その2

  • LF: クシナダ+297
  • S: イザナギ、アルテミス、光ヴィーナス、ヴァーチェ
平均ダメージ: 3762093.03963
最小ダメージ: 593415 7コンボ (回: 11, 火: 8, 木: 5, 光: 3, 水: 2, 闇: 1) => (回: 11, 火: 8, 木: 5, 光: 4, 水: 2) ["ドロップ変化・光"]
最大ダメージ: 7772212 10コンボ (光: 8, 木: 7, 闇: 7, 火: 5, 回: 3) => (木: 15, 光: 15) ["ダブル攻撃態勢・木", "ドロップ変化・光"]
標準偏差    : 899790
エルモア積み: 16.79%
---
-
name: クシナダ
atk: 1232
element: [2]
type: [神]
-
name: メイメイ
atk: 1364
element: [2]
type: [神]
-
name: イザナギ
atk: 1366
element: [3]
type: [神]
-
name: 光ヨミ
atk: 2036
element: [4, 3]
type: [神,攻撃]
-
name: 闇ヨミ
atk: 1436
element: [4, 4]
type: [神,バランス]
-
name: 闇メタ
atk: 1881
element: [4, 3]
type: [神,攻撃]
-
name: 緑おでん
atk: 1203
element: [2, 3]
type: [神,バランス]
-
name: 緑ソニア
atk: 1720
element: [2, 4]
type: [ドラゴン,バランス]
-
name: モリリン
atk: 644
element: [2]
type: [バランス]
-
name: アルテミス
atk: 1465
element: [2, 3]
type: [神,バランス]
-
name: パール
atk: 1356
element: [2]
type: [神]
-
name: ヴァーチェ
atk: 1297
element: [3, 3]
type: [神,バランス]
-
name: 光ヴィーナス
atk: 1286
element: [3, 3]
type: [神,回復]
-
name: 闇ヴィーナス
atk: 1286
element: [3, 4]
type: [神,悪魔]
require "yaml"
module Puzzdra
Drop = Struct.new(:color, :reinforced)
Vanish = Struct.new(:color, :drops_count)
Attack = Struct.new(:color, :type, :damage)
DropString = %w/火 水 木 光 闇 回 * 毒/
class Field
attr_reader :drops
def initialize
@drops = 30.times.map { Puzzdra::Drop.new(rand(6), 0) }
end
def inspect
_color_counts.reject { |_, count| count == 0 }.map do |color, count|
"#{DropString[color]}: #{count}"
end.join(", ")
end
def skill(name)
case name
when "北方七星陣"
colors = [2, 3, 4]
@drops = 30.times.map { Puzzdra::Drop.new(colors.sample, 0) }
when "2色陣・木闇" || "継界召龍陣・木闇"
colors = [2, 4]
@drops = 30.times.map { Puzzdra::Drop.new(colors.sample, 0) }
when "ドロップ強化・光闇" || "神魔の息吹"
@drops.each do |d|
next unless [3, 4].member? d.color
d.reinforced = 1
end
when "ダブル攻撃態勢・木"
@drops.each do |d|
next unless [0, 5].member? d.color
d.color = 2
end
when "防御態勢・木"
@drops.each do |d|
next unless d.color == 1
d.color = 5
end
when "ドロップ変化・光"
@drops.each do |d|
next unless d.color == 4
d.color = 3
end
else
raise
end
end
# 盤面、落ちコンを考慮しない理論最大コンボ
# returns array of Puzzdra::Vanish
def max_combo
if elmore?
_max_combo_elmore _color_counts
else
_max_combo_normal _color_counts
end
end
def elmore?
# max_color_countが16, 17の場合はミルフィーユ積みを採用する
if _color_counts.values.max > 17
true
else
false
end
end
private
# 色数の大きい順に並んでいることを保証する
def _color_counts
colors = (0..7).map do |color|
{
color: color,
count: @drops.count { |d| d.color == color }
}
end
colors.sort_by { |c| c[:count] }.reverse.each_with_object({}) do |c, result|
result[c[:color]] = c[:count]
end
end
def _max_combo_normal(color_counts)
max_color_count = color_counts.values.max
color_counts.each_with_object([]) do |(color, count), combo|
loop do
if count >= 6
combo.push Vanish.new(color, 3)
elsif count >= 3
if max_color_count > 15
combo.push Vanish.new(color, 3)
else
combo.push Vanish.new(color, count)
end
break
else
break
end
count -= 3
end
end
end
# http://livedoor.blogimg.jp/myhrtks/imgs/1/2/128525a9.jpg
# てきとう
ELMORE_COMBO = {
1 => [],
2 => [],
3 => [3],
4 => [3],
5 => [5],
6 => [3,3],
7 => [5],
8 => [4,3],
9 => [3,3,3],
10 => [3,3,3],
11 => [3,3,3],
12 => [3,3,3],
13 => [3,3,3,3],
14 => [3,3,3,3],
15 => [3,3,3,3,3],
16 => [3,3,3,3,4],
17 => [3,3,3,3,4],
18 => [3,3,3,3,3,3],
19 => [3,3,3,3,3,3],
20 => [8,3,3,3,3],
21 => [8,3,3,3,3],
22 => [9,3,3,3,3],
23 => [14,3,3,3],
24 => [17,3,3],
25 => [18,3,3],
26 => [23,3],
27 => [27],
28 => [28],
29 => [29],
30 => [30],
}
def _max_combo_elmore(color_counts)
max_color_count = color_counts.values.max
remains = 30
color_counts.each_with_object([]) do |(color, count), combo|
next if count == 0
# XXX: [18, 9, 3], [18, 6, 6] のケースへの暫定対応
if max_color_count == 18
if count == 3 && remains == 3
next
elsif count == 6 && remains == 6
combo.push Vanish.new(color, 3)
next
end
end
ELMORE_COMBO[count].each do |c|
combo.push Vanish.new(color, c)
end
remains -= count
end
end
end
class Monster
Data = YAML.load File.read("monsters.yml")
attr_reader :data
def initialize(name, plus)
@data = Data.find { |d| d["name"] == name }
@plus = plus || [0, 0, 0]
end
def atk
@data["atk"] + @plus[1] * 5
end
# ダメージ計算
# 攻撃力(atk) * 属性倍率(color_power) * コンボ倍率(combo_power)
def attack(combo)
attacks = []
main_color = @data["element"][0]
sub_color = @data["element"][1]
attacks.push Attack.new(
main_color,
@data["type"],
atk * _color_power(main_color, combo) * _combo_power(combo)
)
if sub_color
sub_power = main_color == sub_color ? 0.1 : 0.3
attacks.push Attack.new(
sub_color,
@data["type"],
atk * _color_power(sub_color, combo) * _combo_power(combo) * sub_power
)
end
attacks
end
private
def _color_power(color, combo)
combo.select { |v| v.color == color }.inject(0) do |power, v|
power += 1 + 0.25 * (v.drops_count - 3)
end
end
def _combo_power(combo)
return 0 if combo.empty?
1 + 0.25 * (combo.size - 1)
end
end
class DamageCalc
def initialize(field, party)
@field = field
@party = party
@leader_skills = []
@skills = []
end
def total_damage
combo = @field.max_combo
attacks = @party.map { |monster| monster.attack combo }.flatten
# apply skill
@skills.each do |s|
s.call(attacks, combo)
end
# apply leader_skill
@leader_skills.each do |ls|
ls.call(attacks, combo)
end
attacks.inject(0) { |total, attack| total += attack.damage }.to_i
end
def leader_skill(name)
case name
when "クシナダ" || "撫子の想い"
@leader_skills.push(
lambda do |attacks, combo|
return attacks if combo.size < 3
power = [combo.size / 2, 10].min
attacks.each { |a| a.damage = a.damage * power }
end
)
else
raise
end
end
def skill(name)
case name
when "神エンハンス" || "創造の息吹"
@skills.push(
lambda do |attacks, combo|
attacks.each do |a|
next unless a.type.member?("神")
a.damage = a.damage * 2
end
end
)
when "バランスエンハンス"
@skills.push(
lambda do |attacks, combo|
attacks.each do |a|
next unless a.type.member?("バランス")
a.damage = a.damage * 3
end
end
)
else
raise
end
end
end
# 既に計算した結果があればキャッシュしてくれるやつ
class DamageCalcManager
attr_reader :stats
def initialize(party: [], leader: "", friend: "", skills: [], field_skills: [], field_skill_selectable: false, cache_by_original_field: false)
@party = party
@leader = leader
@friend = friend
@skills = skills
@field_skills = field_skills
@result_cache_by_field = {}
@result_cache_by_original_field = {}
@stats = {
total: 0,
normal: 0,
elmore: 0,
}
# field_skillを使う前の盤面で結果をキャッシュするか否か
@cache_by_original_field = cache_by_original_field
# field_skillを使用するか否かと、使用する順番を任意に選択可能か
@field_skill_selectable = field_skill_selectable
end
def calc(original_field)
original_field_str = original_field.inspect
# get cache by original field
if @cache_by_original_field && @result_cache_by_original_field[original_field_str]
return _update_stats @result_cache_by_original_field[original_field_str]
end
if @field_skill_selectable
result_candidates = _field_skill_permutation.map do |field_skills|
_calc_sub(original_field, field_skills)
end
result = result_candidates.sort_by { |rc| [rc[:damage], rc[:field_skills].size * -1 ] }.last
else
result = _calc_sub(original_field)
end
# set cache
@result_cache_by_original_field[original_field_str] = result if @cache_by_original_field
return _update_stats result
end
private
def _update_stats(result)
@stats[:total] += 1
if result[:field].elmore?
@stats[:elmore] += 1
else
@stats[:normal] += 1
end
result
end
# スキルの使用順を考慮した組み合わせ
# [:a, :b] => [[], [:a], [:b], [:a, :b], [:b, :a]]
def _field_skill_permutation
@field_skill_permutation ||= (0..@field_skills.size).map do |skill_count|
@field_skills.permutation(skill_count).to_a
end.flatten(1)
end
def _calc_sub(original_field, field_skills = @field_skills)
# deep copy
field = Marshal.load(Marshal.dump(original_field))
field_skills.each do |fs|
field.skill fs
end
field_str = field.inspect
# get cache by field
if @result_cache_by_field[field_str]
return @result_cache_by_field[field_str]
end
damage_calc = Puzzdra::DamageCalc.new(field, @party)
damage_calc.leader_skill(@leader)
damage_calc.leader_skill(@friend)
@skills.each do |s|
damage_calc.skill s
end
# set cache
@result_cache_by_field[field_str] = {
damage: damage_calc.total_damage,
field: field,
original_field: original_field,
combo: field.max_combo,
field_skills: field_skills,
}
end
end
end
def result_format(result, cache_by_original_field)
if cache_by_original_field
"#{result[:damage]} #{result[:combo].size}コンボ (#{result[:original_field].inspect}) => (#{result[:field].inspect}) #{result[:field_skills]}"
else
"#{result[:damage]} #{result[:combo].size}コンボ (#{result[:field].inspect})"
end
end
YAML.load(File.read("teams.yml")).each do |team|
p "--"
p team["members"]
party = team["members"].each_with_index.map do |name, i|
Puzzdra::Monster.new(name, team["pluses"][i])
end
result = []
damage_calc_manager = Puzzdra::DamageCalcManager.new(
party: party,
leader: team["members"].first,
friend: team["members"].last,
skills: team["skills"],
field_skills: team["field_skills"],
field_skill_selectable: team["field_skill_selectable"],
cache_by_original_field: team["cache_by_original_field"],
)
100000.times do |i|
#p i if (i % 10000 == 0 && i != 0)
field = Puzzdra::Field.new
result.push damage_calc_manager.calc(field)
end
damage_avg = result.inject(0) { |sum, r| sum += r[:damage] }.to_f / result.size
min_result = result.min_by { |r| r[:damage] }
max_result = result.max_by { |r| r[:damage] }
variance = result.inject(0) { |sum, r| sum += (r[:damage] - damage_avg) ** 2 } / result.size
puts "平均ダメージ: #{damage_avg}"
puts "最小ダメージ: #{result_format(min_result, team["cache_by_original_field"])}"
puts "最大ダメージ: #{result_format(max_result, team["cache_by_original_field"])}"
puts "標準偏差 : #{Math.sqrt(variance).to_i}"
puts "エルモア積み: #{sprintf("%.2f", damage_calc_manager.stats[:elmore].to_f * 100 / damage_calc_manager.stats[:total])}%"
end
---
-
members:
- クシナダ
- イザナギ
- メイメイ
- 光ヨミ
- 緑おでん
- クシナダ
pluses:
- [99, 99, 99]
- [0, 0, 0]
- [0, 0, 0]
- [0, 0, 0]
- [0, 0, 0]
- [99, 99, 99]
field_skills:
- 北方七星陣
skills:
- 神エンハンス
-
members:
- クシナダ
- モリリン
- 緑ソニア
- 闇ヨミ
- 緑おでん
- クシナダ
pluses:
- [99, 99, 99]
- [0, 0, 0]
- [0, 0, 0]
- [0, 0, 0]
- [0, 0, 0]
- [99, 99, 99]
field_skills:
- 2色陣・木闇
skills:
- バランスエンハンス
-
members:
- クシナダ
- イザナギ
- アルテミス
- 光ヨミ
- パール
- クシナダ
pluses:
- [99, 99, 99]
- [0, 0, 0]
- [0, 0, 0]
- [0, 0, 0]
- [0, 0, 0]
- [99, 99, 99]
field_skills:
- ダブル攻撃態勢・木
- 防御態勢・木
skills:
- 神エンハンス
field_skill_selectable: true
cache_by_original_field: true
-
members:
- クシナダ
- イザナギ
- アルテミス
- 光ヴィーナス
- ヴァーチェ
- クシナダ
pluses:
- [99, 99, 99]
- [0, 0, 0]
- [0, 0, 0]
- [0, 0, 0]
- [0, 0, 0]
- [99, 99, 99]
field_skills:
- ダブル攻撃態勢・木
- ドロップ変化・光
skills:
- 神エンハンス
field_skill_selectable: true
cache_by_original_field: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment