mgmg 1.7.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c14a55ea3bffa94e0b9aa9e65a7a5013d34d8bec62179baae302dabdd150f08
4
- data.tar.gz: db35e0738dc57da24e132bacea2cd30a1040d6dc563449ce3b3b8b19a62fa0ae
3
+ metadata.gz: 3698312de546d3f9a7eac1aadd6024ae8132a8c58f97c041fa07b788828a3bcc
4
+ data.tar.gz: 19d8263ccfd11a476472868a8420959cccc356bbd7f69fcf619f831fde589794
5
5
  SHA512:
6
- metadata.gz: 475431bc7166ad3c18fb60aec3fb921e43a2987086caf357ab20de75b085da125df70941c4810e06094f3b859c8480258dae41a89b29a46562cb3a4510016aa5
7
- data.tar.gz: a4466773b3f3f77e9636b4ab5fa46934ec3781f8d088e889c39dbb72f45e79c5a7217dbb15b79767e067283bf30425449f28804a1794b591bbbef07669ed8749
6
+ metadata.gz: c0edb6042eb1fccbe9d6b4761afd1a2ad0814910850b2664325d20f109fe9d6805cf28aab5d25b363842cde62dce3e1739319668446e912b6a15d94e2043654f
7
+ data.tar.gz: 70ed1f01ca7bfd7ef4be9fc886f74f2049d466cc73e2fb79276dd9768d40bed0232119cde2a177def7721f677271ff602b71d3231d207a94a5850fcd82844b8a
data/CHANGELOG.md CHANGED
@@ -184,3 +184,13 @@
184
184
  - `Mgmg.#find_upperbound`のアルゴリズムに誤りがあり,大きく間違えた解を返していた問題を修正.
185
185
  - `Mgmg::IR#attack`等において,引数が`nil`(製作Lvを指定せず,多項式を返す)の場合に,例外となっていたバグを修正.
186
186
  - オブション`smith/armor/comp_max`のデフォルト値を10^9に変更.
187
+
188
+ ## 1.8.0 2022/11/14
189
+ - オプション`comp_ext`を`fib_ext`に変更し,追加探索の範囲を修正.
190
+ - オプション`smith/armor/comp_max`の扱い方を修正し,デフォルト値を10^5に変更.
191
+ - `Enumerable#find_max`,`Mgmg.#find_lowerbound`のバグを修正.
192
+
193
+ ## 1.8.1 2022/11/19
194
+ - `Mgmg::Recipe`に`name`属性を追加.`String/Enumerable#to_recipe`の際,キーワード引数`name`を追加することで設定できる.
195
+ - `Mgmg.#efficient_list` を追加.
196
+ - `Enumerable#build`,`Enumerable#search`で意図せぬ例外が発生していた問題を修正.
data/README.md CHANGED
@@ -132,6 +132,25 @@ puts r.build(*sc, buff: []) # 一時的に強化を解除して素の性能を
132
132
  #=> 重鎧8☆20(綿宝)[物防:32,538, 魔防:4]
133
133
  ```
134
134
 
135
+ 連続して探索し,最適レシピの推移をcsvファイルに出力する.また,探索範囲内で少なくとも一度は最適に選ばれたレシピのリストを確認する.
136
+
137
+ ```ruby
138
+ str = <<EOS
139
+ 斧(火玉5綿1)+[杖(水1綿1)+[斧(玉5水1)+[杖(綿1綿1)+[斧(玉5綿1)+[剣(金3牙1)+[斧(木2牙1)+[剣(木2牙1)+双短剣(鉄10皮1)]]]]]]] 鍛冶Lv66
140
+ 斧(火玉5綿1)+[杖(鉄2綿1)+[斧(木1金3)+[杖(綿1綿1)+[斧(玉5綿1)+[剣(金3牙1)+[双短剣(金3牙1)+[斧(木2牙1)+剣(鉄10木1)]]]]]]] 鍛冶Lv94
141
+ 斧(木2皮1)+[杖(鉄2綿1)+[斧(木1金3)+[杖(綿1綿1)+[斧(玉5玉5)+[剣(金3牙1)+[双短剣(金3牙1)+[斧(木2牙1)+剣(鉄10木1)]]]]]]] 鍛冶Lv106
142
+ 斧(木2皮1)+[杖(鉄2綿1)+[斧(玉5金3)+[杖(綿1綿1)+[斧(玉5金3)+[剣(金3牙1)+[斧(木2牙1)+[剣(木2牙1)+双短剣(鉄10木1)]]]]]]] 鍛冶Lv116
143
+ 斧(金3水1)+[杖(水1綿1)+[斧(金3水1)+[杖(綿1綿1)+[斧(玉5金3)+[剣(金3牙1)+[双短剣(金3牙1)+[斧(木2牙1)+剣(鉄10木1)]]]]]]] 鍛冶Lv152
144
+ EOS
145
+ rs = str.split(/\n/).map do |line|
146
+ ary = line.split(/ /)
147
+ ary[0].to_recipe(:atkstr, target_weight: 9, name: ary[1])
148
+ end
149
+ res = Mgmg.efficient_list(rs, 20_000, 50_000, './csv.csv', external_encoding: 'Windows-31J')
150
+ puts res.map(&:name).join(', ')
151
+ #=> 鍛冶Lv106, 鍛冶Lv94, 鍛冶Lv116
152
+ ```
153
+
135
154
  各メソッドの詳しい説明等は [リファレンス](./reference.md) を参照されたい.
136
155
 
137
156
  ### 表記ゆれについて
data/lib/mgmg/option.rb CHANGED
@@ -2,13 +2,13 @@ module Mgmg
2
2
  class Option
3
3
  Defaults = {
4
4
  left_associative: true, include_system_equips: true,
5
- smith_max: 1_000_000_000, armor_max: 1_000_000_000, comp_max: 1_000_000_000,
6
- comp_ext: [0.1, 100, 10_000, 0.1]
5
+ smith_max: 100_000, armor_max: 100_000, comp_max: 100_000,
6
+ fib_ext: [4, 10]
7
7
  }
8
8
  def initialize(
9
9
  left_associative: Defaults[:left_associative],
10
10
  smith_min: nil, armor_min:nil, comp_min: nil, smith_max: Defaults[:smith_max], armor_max: Defaults[:armor_max], comp_max: Defaults[:comp_max],
11
- comp_ext: Defaults[:comp_ext],
11
+ fib_ext: Defaults[:fib_ext],
12
12
  magdef_maximize: true,
13
13
  target_weight: 0, reinforcement: [], buff: nil,
14
14
  irep: nil, cut_exp: Float::INFINITY,
@@ -21,7 +21,7 @@ module Mgmg
21
21
  @smith_max = smith_max
22
22
  @armor_max = armor_max
23
23
  @comp_max = comp_max
24
- @comp_ext = comp_ext
24
+ @fib_ext = fib_ext
25
25
  @magdef_maximize = magdef_maximize
26
26
  @target_weight = target_weight
27
27
  @reinforcement = reinforcement
@@ -37,7 +37,7 @@ module Mgmg
37
37
  @include_system_equips = include_system_equips
38
38
  @system_equips_checked = false
39
39
  end
40
- attr_accessor :left_associative, :smith_min, :armor_min, :comp_min, :smith_max, :armor_max, :comp_max, :comp_ext
40
+ attr_accessor :left_associative, :smith_min, :armor_min, :comp_min, :smith_max, :armor_max, :comp_max, :fib_ext
41
41
  attr_accessor :magdef_maximize, :target_weight, :reinforcement, :irep, :cut_exp, :include_system_equips, :system_equips_checked
42
42
  def initialize_copy(other)
43
43
  @left_associative = other.left_associative
@@ -47,7 +47,7 @@ module Mgmg
47
47
  @smith_max = other.smith_max
48
48
  @armor_max = other.armor_max
49
49
  @comp_max = other.comp_max
50
- @comp_ext = other.comp_ext
50
+ @fib_ext = other.fib_ext.dup
51
51
  @magdef_maximize = other.magdef_maximize
52
52
  @target_weight = other.target_weight
53
53
  @reinforcement = other.reinforcement.dup
data/lib/mgmg/recipe.rb CHANGED
@@ -1,14 +1,23 @@
1
1
  module Mgmg
2
2
  class Recipe
3
- def initialize(recipe, para=:power, **kw)
3
+ def initialize(recipe, para=:power, name: nil, **kw)
4
4
  @recipe = recipe
5
5
  @recipe.each(&:freeze) if @recipe.kind_of?(Enumerable)
6
6
  @recipe.freeze
7
7
  @para = para
8
+ if name.nil?
9
+ if recipe.kind_of?(String)
10
+ @name = recipe
11
+ else
12
+ @name = recipe.join(' ')
13
+ end
14
+ else
15
+ @name = name
16
+ end
8
17
  @option = Option.new(**kw).set_default(@recipe)
9
18
  end
10
19
  attr_reader :recipe
11
- attr_accessor :para
20
+ attr_accessor :para, :name
12
21
  def initialize_copy(other)
13
22
  @recipe = other.recipe.dup
14
23
  @option = other.option.dup
@@ -82,9 +91,9 @@ module Mgmg
82
91
  smith, armor, comp = opt.smith_min, opt.armor_min, opt.comp_min if smith.nil?
83
92
  case @recipe
84
93
  when String
85
- recipe.show(smith, comp, para:, opt:)
94
+ @recipe.show(smith, comp, para:, name: @name, opt:)
86
95
  when Enumerable
87
- recipe.show(smith, armor, comp, para:, opt:)
96
+ @recipe.show(smith, armor, comp, para:, name: @name, opt:)
88
97
  else
89
98
  raise BrokenRecipeError
90
99
  end
data/lib/mgmg/search.rb CHANGED
@@ -34,7 +34,7 @@ class String
34
34
  if target <= opt.irep.para_call(para, smith, opt.comp_min)
35
35
  return opt.comp_min
36
36
  elsif opt.irep.para_call(para, smith, opt.comp_max) < target
37
- raise Mgmg::SearchCutException
37
+ raise Mgmg::SearchCutException, "given comp_max=#{opt.comp_max} does not satisfies the target"
38
38
  end
39
39
  while 1 < opt.comp_max - opt.comp_min do
40
40
  comp = (opt.comp_max - opt.comp_min).div(2) + opt.comp_min
@@ -71,7 +71,12 @@ class String
71
71
  def search(para, target, opt: Mgmg::Option.new)
72
72
  opt = opt.dup.set_default(self)
73
73
  opt_nocut = opt.dup; opt_nocut.cut_exp = Float::INFINITY
74
- opt.comp_min = comp_search(para, target, opt.smith_max, opt:)
74
+ begin
75
+ opt.comp_min = comp_search(para, target, opt.smith_max, opt:)
76
+ rescue Mgmg::SearchCutException
77
+ foo = opt.irep.para_call(para, opt.smith_max, opt.comp_max)
78
+ raise Mgmg::SearchCutException, "#{self} could not reach target=#{target} until (smith_max, comp_max)=(#{opt.smith_max}, #{opt.comp_max}), which yields #{foo.comma3}"
79
+ end
75
80
  opt.smith_max = smith_search(para, target, opt.comp_min, opt: opt_nocut)
76
81
  opt.smith_min = smith_search(para, target, opt.comp_max, opt: opt_nocut)
77
82
  raise Mgmg::SearchCutException if opt.cut_exp < Mgmg.exp(opt.smith_min, opt.comp_min)
@@ -104,16 +109,19 @@ class String
104
109
  end
105
110
  end
106
111
  exp_best = opt.cut_exp
107
- opt.cut_exp = exp_best + (exp_best*opt.comp_ext[0]).to_i.clamp(opt.comp_ext[1], opt.comp_ext[2])
108
- (comps[0]-1).downto(opt.comp_min) do |comp|
109
- exp_best, ret = fine(exp_best, ret, para, target, comp, opt, eo)
110
- rescue Mgmg::SearchCutException
111
- break
112
- end
113
- (comps[3]+1).upto(opt.comp_max) do |comp|
114
- exp_best, ret = fine(exp_best, ret, para, target, comp, opt, eo)
115
- rescue Mgmg::SearchCutException
116
- break
112
+ diff = values.max-values.min
113
+ if 0 < diff
114
+ opt.cut_exp = exp_best + diff*opt.fib_ext[0]
115
+ (comps[0]-1).downto(opt.comp_min) do |comp|
116
+ exp_best, ret = fine(exp_best, ret, para, target, comp, opt, eo)
117
+ rescue Mgmg::SearchCutException
118
+ break
119
+ end
120
+ (comps[3]+1).upto(opt.comp_max) do |comp|
121
+ exp_best, ret = fine(exp_best, ret, para, target, comp, opt, eo)
122
+ rescue Mgmg::SearchCutException
123
+ break
124
+ end
117
125
  end
118
126
  if ret.nil?
119
127
  max = opt.irep.para_call(para, *find_max(para, opt.cut_exp, opt:))
@@ -170,18 +178,21 @@ class String
170
178
  values = [values[1], values[2], cur, values[3]]
171
179
  end
172
180
  end
173
- th = max - (max*opt.comp_ext[3]).ceil
174
- (comps[0]-1).downto(opt.comp_min) do |comp|
175
- next if ( eo & (2**(comp&1)) == 0 )
176
- cur, smith = eval_comp_fm(para, comp, eo, opt, max, max_exp)
177
- ret, max = [smith, comp], cur if ( max < cur || ( max == cur && Mgmg.exp(smith, comp) < Mgmg.exp(*ret) ) )
178
- break if cur < th
179
- end
180
- (comps[3]+1).upto(opt.comp_max) do |comp|
181
- next if ( eo & (2**(comp&1)) == 0 )
182
- cur, smith = eval_comp_fm(para, comp, eo, opt, max, max_exp)
183
- ret, max = [smith, comp], cur if ( max < cur || ( max == cur && Mgmg.exp(smith, comp) < Mgmg.exp(*ret) ) )
184
- break if cur < th
181
+ diff = values.max-values.min
182
+ if 0 < diff
183
+ th = max - diff*opt.fib_ext[1]
184
+ (comps[0]-1).downto(opt.comp_min) do |comp|
185
+ next if ( eo & (2**(comp&1)) == 0 )
186
+ cur, smith = eval_comp_fm(para, comp, eo, opt, max, max_exp)
187
+ ret, max = [smith, comp], cur if ( max < cur || ( max == cur && Mgmg.exp(smith, comp) < Mgmg.exp(*ret) ) )
188
+ break if cur < th
189
+ end
190
+ (comps[3]+1).upto(opt.comp_max) do |comp|
191
+ next if ( eo & (2**(comp&1)) == 0 )
192
+ cur, smith = eval_comp_fm(para, comp, eo, opt, max, max_exp)
193
+ ret, max = [smith, comp], cur if ( max < cur || ( max == cur && Mgmg.exp(smith, comp) < Mgmg.exp(*ret) ) )
194
+ break if cur < th
195
+ end
185
196
  end
186
197
  ret
187
198
  end
@@ -292,7 +303,7 @@ module Enumerable
292
303
  if target <= opt.irep.para_call(para, smith, armor, opt.comp_min)
293
304
  return opt.comp_min
294
305
  elsif opt.irep.para_call(para, smith, armor, opt.comp_max) < target
295
- raise ArgumentError, "given comp_max=#{opt.comp_max} does not satisfies the target"
306
+ raise Mgmg::SearchCutException, "given comp_max=#{opt.comp_max} does not satisfies the target"
296
307
  end
297
308
  while 1 < opt.comp_max - opt.comp_min do
298
309
  comp = (opt.comp_max - opt.comp_min).div(2) + opt.comp_min
@@ -304,152 +315,6 @@ module Enumerable
304
315
  end
305
316
  opt.comp_max
306
317
  end
307
- private def eval_comp_a(para, target, comp, opt, eo)
308
- return [nil, Float::INFINITY] if (comp < opt.comp_min or opt.comp_max < comp)
309
- comp -= 1 if ( opt.comp_min<comp and eo & (2**(comp&1)) == 0 )
310
- armor = armor_search(para, target, -1, comp, opt:)
311
- exp = Mgmg.exp(armor, comp)
312
- [[-1, armor, comp], exp]
313
- rescue Mgmg::SearchCutException
314
- [nil, Float::INFINITY]
315
- end
316
- private def fine_a(exp_best, ret, para, target, comp, opt, eo)
317
- return [exp_best, ret] if eo & (2**(comp&1)) == 0
318
- armor = armor_search(para, target, -1, comp, opt:)
319
- exp = Mgmg.exp(armor, comp)
320
- if exp < exp_best
321
- exp_best, ret = exp, [-1, armor, comp]
322
- elsif exp == exp_best
323
- if ret.nil? or opt.irep.para_call(para, *ret) < opt.irep.para_call(para, -1, armor, comp) then
324
- ret = [-1, armor, comp]
325
- end
326
- end
327
- [exp_best, ret]
328
- end
329
- private def search_aonly(para, target, opt: Mgmg::Option.new)
330
- opt_nocut = opt.dup; opt_nocut.cut_exp = Float::INFINITY
331
- opt.armor_max = armor_search(para, target, -1, opt.comp_min, opt: opt_nocut)
332
- opt.armor_min = armor_search(para, target, -1, opt.comp_max, opt: opt_nocut)
333
- raise Mgmg::SearchCutException if opt.cut_exp < Mgmg.exp(opt.armor_min, opt.comp_min)
334
- opt.comp_max = comp_search(para, target, -1, opt.armor_min, opt:)
335
- ret = nil
336
- exp = Mgmg.exp(opt.armor_min, opt.comp_max)
337
- opt.cut_exp, ret = exp, [-1, opt.armor_min, opt.comp_max] if exp <= opt.cut_exp
338
- exp = Mgmg.exp(opt.armor_max, opt.comp_min)
339
- opt.cut_exp, ret = exp, [-1, opt.armor_max, opt.comp_min] if ( exp < opt.cut_exp || (ret.nil? && exp==opt.cut_exp) )
340
- eo = opt.irep.eo_para(para)
341
- comps = Mgmg.fib_init(opt.comp_min, opt.comp_max)
342
- values = comps.map do |comp|
343
- r, e = eval_comp_a(para, target, comp, opt_nocut, eo)
344
- opt.cut_exp, ret = e, r if e < opt.cut_exp
345
- e
346
- end
347
- while 3 < comps[3]-comps[0]
348
- if values[1] <= values[2]
349
- comp = comps[0] + comps[2]-comps[1]
350
- comps = [comps[0], comp, comps[1], comps[2]]
351
- r, e = eval_comp_a(para, target, comp, opt_nocut, eo)
352
- opt.cut_exp, ret = e, r if e < opt.cut_exp
353
- values = [values[0], e, values[1], values[2]]
354
- else
355
- comp = comps[1] + comps[3]-comps[2]
356
- comps = [comps[1], comps[2], comp, comps[3]]
357
- r, e = eval_comp_a(para, target, comp, opt_nocut, eo)
358
- opt.cut_exp, ret = e, r if e < opt.cut_exp
359
- values = [values[1], values[2], e, values[3]]
360
- end
361
- end
362
- exp_best = opt.cut_exp
363
- opt.cut_exp = exp_best + (exp_best*opt.comp_ext[0]).to_i.clamp(opt.comp_ext[1], opt.comp_ext[2])
364
- (comps[0]-1).downto(opt.comp_min) do |comp|
365
- exp_best, ret = fine_a(exp_best, ret, para, target, comp, opt, eo)
366
- rescue Mgmg::SearchCutException
367
- break
368
- end
369
- (comps[3]+1).upto(opt.comp_max) do |comp|
370
- exp_best, ret = fine_a(exp_best, ret, para, target, comp, opt, eo)
371
- rescue Mgmg::SearchCutException
372
- break
373
- end
374
- if ret.nil?
375
- max = opt.irep.para_call(para, *find_max(para, opt.cut_exp, opt:))
376
- raise Mgmg::SearchCutException, "the maximum output with given cut_exp=#{opt.cut_exp.comma3} is #{max.comma3}, which does not reach given target=#{target.comma3}"
377
- end
378
- ret
379
- end
380
- private def eval_comp_s(para, target, comp, opt, eo)
381
- return [nil, Float::INFINITY] if (comp < opt.comp_min or opt.comp_max < comp)
382
- comp -= 1 if ( opt.comp_min<comp and eo & (2**(comp&1)) == 0 )
383
- smith = smith_search(para, target, -1, comp, opt:)
384
- exp = Mgmg.exp(smith, comp)
385
- [[smith, -1, comp], exp]
386
- rescue Mgmg::SearchCutException
387
- [nil, Float::INFINITY]
388
- end
389
- private def fine_s(exp_best, ret, para, target, comp, opt, eo)
390
- return [exp_best, ret] if eo & (2**(comp&1)) == 0
391
- smith = smith_search(para, target, -1, comp, opt:)
392
- exp = Mgmg.exp(smith, comp)
393
- if exp < exp_best
394
- exp_best, ret = exp, [smith, -1, comp]
395
- elsif exp == exp_best
396
- if ret.nil? or opt.irep.para_call(para, *ret) < opt.irep.para_call(para, smith, -1, comp) then
397
- ret = [smith, -1, comp]
398
- end
399
- end
400
- [exp_best, ret]
401
- end
402
- private def search_sonly(para, target, opt: Mgmg::Option.new)
403
- opt_nocut = opt.dup; opt_nocut.cut_exp = Float::INFINITY
404
- opt.smith_max = smith_search(para, target, -1, opt.comp_min, opt: opt_nocut)
405
- opt.smith_min = smith_search(para, target, -1, opt.comp_max, opt: opt_nocut)
406
- raise Mgmg::SearchCutException if opt.cut_exp < Mgmg.exp(opt.smith_min, opt.comp_min)
407
- opt.comp_max = comp_search(para, target, opt.smith_min, -1, opt:)
408
- ret = nil
409
- exp = Mgmg.exp(opt.smith_min, opt.comp_max)
410
- opt.cut_exp, ret = exp, [opt.smith_min, -1, opt.comp_max] if exp <= opt.cut_exp
411
- exp = Mgmg.exp(opt.smith_max, opt.comp_min)
412
- opt.cut_exp, ret = exp, [opt.smith_max, -1, opt.comp_min] if ( exp < opt.cut_exp || (ret.nil? && exp==opt.cut_exp) )
413
- eo = opt.irep.eo_para(para)
414
- comps = Mgmg.fib_init(opt.comp_min, opt.comp_max)
415
- values = comps.map do |comp|
416
- r, e = eval_comp_s(para, target, comp, opt_nocut, eo)
417
- opt.cut_exp, ret = e, r if e < opt.cut_exp
418
- e
419
- end
420
- while 3 < comps[3]-comps[0]
421
- if values[1] <= values[2]
422
- comp = comps[0] + comps[2]-comps[1]
423
- comps = [comps[0], comp, comps[1], comps[2]]
424
- r, e = eval_comp_s(para, target, comp, opt_nocut, eo)
425
- opt.cut_exp, ret = e, r if e < opt.cut_exp
426
- values = [values[0], e, values[1], values[2]]
427
- else
428
- comp = comps[1] + comps[3]-comps[2]
429
- comps = [comps[1], comps[2], comp, comps[3]]
430
- r, e = eval_comp_s(para, target, comp, opt_nocut, eo)
431
- opt.cut_exp, ret = e, r if e < opt.cut_exp
432
- values = [values[1], values[2], e, values[3]]
433
- end
434
- end
435
- exp_best = opt.cut_exp
436
- opt.cut_exp = exp_best + (exp_best*opt.comp_ext[0]).to_i.clamp(opt.comp_ext[1], opt.comp_ext[2])
437
- (comps[0]-1).downto(opt.comp_min) do |comp|
438
- exp_best, ret = fine_s(exp_best, ret, para, target, comp, opt, eo)
439
- rescue Mgmg::SearchCutException
440
- break
441
- end
442
- (comps[3]+1).upto(opt.comp_max) do |comp|
443
- exp_best, ret = fine_s(exp_best, ret, para, target, comp, opt, eo)
444
- rescue Mgmg::SearchCutException
445
- break
446
- end
447
- if ret.nil?
448
- max = opt.irep.para_call(para, *find_max(para, opt.cut_exp, opt:))
449
- raise Mgmg::SearchCutException, "the maximum output with given cut_exp=#{opt.cut_exp.comma3} is #{max.comma3}, which does not reach given target=#{target.comma3}"
450
- end
451
- ret
452
- end
453
318
  private def eval_comp_sa(para, target, comp, opt, eo)
454
319
  return [nil, Float::INFINITY] if (comp < opt.comp_min or opt.comp_max < comp)
455
320
  comp -= 1 if ( opt.comp_min<comp and eo & (2**(comp&1)) == 0 )
@@ -474,14 +339,19 @@ module Enumerable
474
339
  end
475
340
  def search(para, target, opt: Mgmg::Option.new)
476
341
  opt = opt.dup.set_default(self)
477
- opt.comp_min = comp_search(para, target, opt.smith_max, opt.armor_max, opt:)
342
+ begin
343
+ opt.comp_min = comp_search(para, target, opt.smith_max, opt.armor_max, opt:)
344
+ rescue Mgmg::SearchCutException
345
+ foo = opt.irep.para_call(para, opt.smith_max, opt.armor_max, opt.comp_max)
346
+ raise Mgmg::SearchCutException, "#{self} could not reach target=#{target} until (smith_max, armor_max, comp_max)=(#{opt.smith_max}, #{opt.armor_max}, #{opt.comp_max}), which yields #{foo.comma3}"
347
+ end
478
348
  opt_nocut = opt.dup; opt_nocut.cut_exp = Float::INFINITY
479
- opt.smith_max = smith_search(para, target, opt.armor_min, opt.comp_min, opt: opt_nocut) rescue ( return search_aonly(para, target, opt:) )
480
- opt.armor_max = armor_search(para, target, opt.smith_min, opt.comp_min, opt: opt_nocut) rescue ( return search_sonly(para, target, opt:) )
349
+ opt.smith_max = ( smith_search(para, target, opt.armor_min, opt.comp_min, opt: opt_nocut) rescue opt.smith_max )
350
+ opt.armor_max = ( armor_search(para, target, opt.smith_min, opt.comp_min, opt: opt_nocut) rescue opt.armor_max )
481
351
  opt.smith_min = smith_search(para, target, opt.armor_max, opt.comp_max, opt: opt_nocut)
482
352
  opt.armor_min = armor_search(para, target, opt.smith_max, opt.comp_max, opt: opt_nocut)
483
353
  raise Mgmg::SearchCutException if opt.cut_exp < Mgmg.exp(opt.smith_min, opt.armor_min, opt.comp_min)
484
- opt.comp_max = comp_search(para, target, opt.smith_min, opt.armor_min, opt:)
354
+ opt.comp_max = ( comp_search(para, target, opt.smith_min, opt.armor_min, opt:) rescue opt.comp_max )
485
355
  exp = Mgmg.exp(opt.smith_min, opt.armor_min, opt.comp_max)
486
356
  opt.cut_exp, ret = exp, [opt.smith_min, opt.armor_min,opt. comp_max] if exp <= opt.cut_exp
487
357
  eo = opt.irep.eo_para(para)
@@ -507,16 +377,19 @@ module Enumerable
507
377
  end
508
378
  end
509
379
  exp_best = opt.cut_exp
510
- opt.cut_exp = exp_best + (exp_best*opt.comp_ext[0]).to_i.clamp(opt.comp_ext[1], opt.comp_ext[2])
511
- (comps[0]-1).downto(opt.comp_min) do |comp|
512
- exp_best, ret = fine_sa(exp_best, ret, para, target, comp, opt, eo)
513
- rescue Mgmg::SearchCutException
514
- break
515
- end
516
- (comps[3]+1).upto(opt.comp_max) do |comp|
517
- exp_best, ret = fine_sa(exp_best, ret, para, target, comp, opt, eo)
518
- rescue Mgmg::SearchCutException
519
- break
380
+ diff = values.max-values.min
381
+ if 0 < diff
382
+ opt.cut_exp = exp_best + diff*opt.fib_ext[0]
383
+ (comps[0]-1).downto(opt.comp_min) do |comp|
384
+ exp_best, ret = fine_sa(exp_best, ret, para, target, comp, opt, eo)
385
+ rescue Mgmg::SearchCutException
386
+ break
387
+ end
388
+ (comps[3]+1).upto(opt.comp_max) do |comp|
389
+ exp_best, ret = fine_sa(exp_best, ret, para, target, comp, opt, eo)
390
+ rescue Mgmg::SearchCutException
391
+ break
392
+ end
520
393
  end
521
394
  if ret.nil?
522
395
  max = opt.irep.para_call(para, *find_max(para, opt.cut_exp, opt:))
@@ -539,13 +412,15 @@ module Enumerable
539
412
  end
540
413
  private def eval_arm(para, armor, comp, eo, opt, ret, max, max_exp)
541
414
  smith = Mgmg.invexp3(max_exp, armor, comp)
415
+ exp = Mgmg.exp(smith, armor, comp)
416
+ return [-Float::INFINITY, ret, max] if max_exp < exp
542
417
  cur = opt.irep.para_call(para, smith, armor, comp)
543
418
  smith = minimize_smith(para, smith, armor, comp, cur, opt) if max <= cur
544
- ret, max = [smith, armor, comp], cur if ( max < cur || ( max == cur && Mgmg.exp(smith, armor, comp) < Mgmg.exp(*ret) ) )
419
+ ret, max = [smith, armor, comp], cur if ( max < cur || ( max == cur && exp < Mgmg.exp(*ret) ) )
545
420
  [cur, ret, max]
546
421
  end
547
422
  private def eval_comp_fm(para, comp, eo, opt, ret, max, max_exp)
548
- return [-Float::INFINITY, Float::INFINITY, max] if (comp < opt.comp_min or opt.comp_max < comp)
423
+ return [-Float::INFINITY, ret, max] if (comp < opt.comp_min or opt.comp_max < comp)
549
424
  comp -= 1 if ( opt.comp_min<comp and ( eo & (2**(comp&1)) == 0 ) )
550
425
  cur = -Float::INFINITY
551
426
  a_max = [opt.armor_max, Mgmg.invexp3(max_exp, opt.smith_min, comp)].min
@@ -569,16 +444,19 @@ module Enumerable
569
444
  cur = cur_i if cur < cur_i
570
445
  end
571
446
  end
572
- th = max - (max*opt.comp_ext[3]).ceil
573
- (arms[0]-1).downto(opt.armor_min) do |armor|
574
- cur_i, ret, max = eval_arm(para, armor, comp, eo, opt, ret, max, max_exp)
575
- break if cur_i < th
576
- cur = cur_i if cur < cur_i
577
- end
578
- (arms[3]+1).upto(a_max) do |armor|
579
- cur_i, ret, max = eval_arm(para, armor, comp, eo, opt, ret, max, max_exp)
580
- break if cur_i < th
581
- cur = cur_i if cur < cur_i
447
+ diff = a_vals.max-a_vals.min
448
+ if 0 < diff
449
+ th = max - diff*opt.fib_ext[1]
450
+ (arms[0]-1).downto(opt.armor_min) do |armor|
451
+ cur_i, ret, max = eval_arm(para, armor, comp, eo, opt, ret, max, max_exp)
452
+ break if cur_i < th
453
+ cur = cur_i if cur < cur_i
454
+ end
455
+ (arms[3]+1).upto(a_max) do |armor|
456
+ cur_i, ret, max = eval_arm(para, armor, comp, eo, opt, ret, max, max_exp)
457
+ break if cur_i < th
458
+ cur = cur_i if cur < cur_i
459
+ end
582
460
  end
583
461
  [cur, ret, max]
584
462
  end
@@ -608,16 +486,19 @@ module Enumerable
608
486
  values = [values[1], values[2], cur, values[3]]
609
487
  end
610
488
  end
611
- th = max - (max*opt.comp_ext[3]).ceil
612
- (comps[0]-1).downto(opt.comp_min) do |comp|
613
- next if ( eo & (2**(comp&1)) == 0 )
614
- cur, ret, max = eval_comp_fm(para, comp, eo, opt, ret, max, max_exp)
615
- break if cur < th
616
- end
617
- (comps[3]+1).upto(opt.comp_max) do |comp|
618
- next if ( eo & (2**(comp&1)) == 0 )
619
- cur, ret, max = eval_comp_fm(para, comp, eo, opt, ret, max, max_exp)
620
- break if cur < th
489
+ diff = values.max-values.min
490
+ if 0 < diff
491
+ th = max - diff*opt.fib_ext[1]
492
+ (comps[0]-1).downto(opt.comp_min) do |comp|
493
+ next if ( eo & (2**(comp&1)) == 0 )
494
+ cur, ret, max = eval_comp_fm(para, comp, eo, opt, ret, max, max_exp)
495
+ break if cur < th
496
+ end
497
+ (comps[3]+1).upto(opt.comp_max) do |comp|
498
+ next if ( eo & (2**(comp&1)) == 0 )
499
+ cur, ret, max = eval_comp_fm(para, comp, eo, opt, ret, max, max_exp)
500
+ break if cur < th
501
+ end
621
502
  end
622
503
  ret
623
504
  end
@@ -670,28 +551,27 @@ module Mgmg
670
551
  loop do
671
552
  foo = a.find_max(para, eb, opt: opt_a)
672
553
  break if sca == foo
673
- sca, pa = foo, opt_a.irep.para_call(para, *foo)
554
+ bar = opt_a.irep.para_call(para, *foo)
555
+ break if bar < pa
556
+ sca, pa = foo, bar
674
557
  scb = b.search(para, pa, opt: opt_b)
675
558
  foo = Mgmg.exp(*scb)
676
559
  break if eb == foo
677
560
  eb = foo
678
561
  end
679
562
  ea = Mgmg.exp(*sca)
680
- while ea<=eb
681
- tag = pa + Eighth
682
- raise Mgmg::SearchCutException, "given recipes are never reversed from start target=#{start.comma3} until term target=#{term.comma3}" if term < tag
683
- sca, scb = a.search(para, tag, opt: opt_a), b.search(para, tag, opt: opt_b)
684
- ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
685
- pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
686
- break if ea == eb && pa < pb
687
- end
688
- if eb < ea || ( ea == eb && pa < pb )
563
+ if (eb <= ea and pa <= pb and (eb+pa)!=(ea+pb)) or (eb < ea and sca == a.search(para, pb, opt: opt_a)) then
689
564
  until ea < eb || ( ea == eb && pb < pa )
690
565
  sca = a.find_max(para, ea-1, opt: opt_a)
691
566
  ea, pa = Mgmg.exp(*sca), opt_a.irep.para_call(para, *sca)
692
567
  end
693
568
  return [pa, pb]
694
569
  end
570
+ tag = pa + Eighth
571
+ raise Mgmg::SearchCutException, "given recipes are never reversed from start target=#{start.comma3} until term target=#{term.comma3}" if term < tag
572
+ sca, scb = a.search(para, tag, opt: opt_a), b.search(para, tag, opt: opt_b)
573
+ ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
574
+ pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
695
575
  end
696
576
  raise UnexpectedError
697
577
  end
@@ -799,4 +679,81 @@ module Mgmg
799
679
  pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
800
680
  [sca, ea, pa, scb, eb, pb]
801
681
  end
682
+
683
+ class ELItem
684
+ def initialize(recipe=nil, sc=nil)
685
+ if recipe.nil?
686
+ @para = -Float::INFINITY
687
+ @exp = Float::INFINITY
688
+ else
689
+ @recipe = recipe
690
+ if sc.size == 3
691
+ @smith, @armor, @comp = *sc
692
+ else
693
+ if recipe.option.irep.kind < 8
694
+ @smith, @comp = *sc
695
+ @armor = -1
696
+ else
697
+ @armor, @comp = *sc
698
+ @smith = -1
699
+ end
700
+ end
701
+ @para = recipe.para_call(*sc)
702
+ @exp = Mgmg.exp(*sc)
703
+ @name = recipe.name
704
+ end
705
+ end
706
+ attr_reader :recipe, :smith, :armor, :comp, :para, :exp, :name
707
+ %i|attack phydef magdef hp mp str dex speed magic atkstr atk_sd dex_as mag_das magic2 magmag pmdef hs|.each do |sym|
708
+ define_method(sym) do
709
+ @recipe.para_call(@smith, @armor, @comp, para: sym)
710
+ end
711
+ end
712
+ def weight
713
+ @recipe.build(@smith, @armor, @comp).weight
714
+ end
715
+ end
716
+ private_module_function def _el_sub(f, recipes, start, term, params, header, separator)
717
+ tag, ret = start, []
718
+ f.puts params.join(separator) if header && !f.nil?
719
+ while tag < term
720
+ best = ELItem.new()
721
+ recipes.each do |r|
722
+ cur = ELItem.new(r, r.search(tag))
723
+ if cur.exp < best.exp
724
+ best = cur
725
+ elsif cur.exp == best.exp
726
+ if best.para < cur.para
727
+ best = cur
728
+ elsif best.para == cur.para
729
+ if block_given?
730
+ best = cur if yield(best, cur)
731
+ end
732
+ end
733
+ end
734
+ end
735
+ f.puts( params.map do |sym|
736
+ best.method(sym).call
737
+ end.join(separator) ) unless f.nil?
738
+ ret << best.recipe unless ret.include?(best.recipe)
739
+ tag = best.para+Eighth
740
+ end
741
+ ret
742
+ end
743
+ module_function def efficient_list(recipes, start, term, out=nil, params=[:defaults], separator: ',', header: true, **kw)
744
+ i = params.index(:defaults)
745
+ if i
746
+ params[i] = [:smith, :armor, :comp, :exp, :para, :name]
747
+ params.flatten!
748
+ end
749
+ ret = nil
750
+ if out.kind_of?(String)
751
+ File.open(out, 'w', **kw) do |f|
752
+ ret = _el_sub(f, recipes, start, term, params, header, separator)
753
+ end
754
+ else
755
+ ret = _el_sub(nil, recipes, start, term, params, header, separator)
756
+ end
757
+ ret
758
+ end
802
759
  end
data/lib/mgmg/utils.rb CHANGED
@@ -1,5 +1,13 @@
1
1
  module Mgmg
2
2
  module Refiner
3
+ refine Module do
4
+ private def private_module_function(sym)
5
+ module_function(sym)
6
+ singleton_class.instance_eval do
7
+ private(sym)
8
+ end
9
+ end
10
+ end
3
11
  refine Integer do
4
12
  def comma3
5
13
  self.to_s.gsub(/(\d)(?=(\d{3})+(?!\d))/, '\1,')
data/lib/mgmg/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mgmg
2
- VERSION = "1.7.0"
2
+ VERSION = "1.8.1"
3
3
  end
data/lib/mgmg.rb CHANGED
@@ -117,18 +117,19 @@ class String
117
117
  def peff(para, smith, comp=smith, opt: Mgmg::Option.new)
118
118
  poly(para, opt:).eff(smith, comp)
119
119
  end
120
- def show(smith=-1, comp=smith, para: :power, opt: Mgmg::Option.new)
120
+ def show(smith=-1, comp=smith, para: :power, name: nil, opt: Mgmg::Option.new)
121
121
  rein = case opt.reinforcement
122
122
  when Array
123
123
  opt.reinforcement.map{|r| Mgmg::Reinforcement.compile(r)}
124
124
  else
125
125
  [Mgmg::Reinforcement.compile(opt.reinforcement)]
126
126
  end
127
+ name = self if name.nil?
127
128
  built = build(smith, comp, opt:)
128
129
  pstr = '%.3f' % built.para_call(para)
129
130
  pstr.sub!(/\.?0+\Z/, '')
130
131
  puts "With levels (#{smith}, #{comp}: #{Mgmg.exp(smith, comp).comma3}), building"
131
- puts " #{self}"
132
+ puts " #{name}"
132
133
  rein = rein.empty? ? '' : "reinforced by {#{rein.join(',')}} "
133
134
  puts "#{rein}yields (#{pstr}, #{built.total_cost})"
134
135
  puts " #{built}"
@@ -155,31 +156,36 @@ module Enumerable
155
156
  opt = opt.dup
156
157
  rein = opt.reinforcement
157
158
  opt.reinforcement = []
158
- self.sum(Mgmg::Equip::Zero) do |str|
159
- if Mgmg::EquipPosition[str.build(opt:).kind] == 0
160
- str.build(smith, comp, opt:)
161
- else
162
- str.build(armor, comp, opt:)
163
- end
164
- end.reinforce(*rein)
159
+ if self.empty?
160
+ Mgmg::Equip::Zero
161
+ else
162
+ self.sum(Mgmg::Equip::Zero) do |str|
163
+ if Mgmg::EquipPosition[str.build(opt:).kind] == 0
164
+ str.build(smith, comp, opt:)
165
+ else
166
+ str.build(armor, comp, opt:)
167
+ end
168
+ end.reinforce(*rein)
169
+ end
165
170
  end
166
171
  def ir(opt: Mgmg::Option.new)
167
172
  self.sum(Mgmg::IR::Zero) do |str|
168
173
  str.ir(opt:)
169
174
  end.add_reinforcement(opt.reinforcement)
170
175
  end
171
- def show(smith=-1, armor=smith, comp=armor.tap{armor=smith}, para: :power, opt: Mgmg::Option.new)
176
+ def show(smith=-1, armor=smith, comp=armor.tap{armor=smith}, para: :power, name: nil, opt: Mgmg::Option.new)
172
177
  rein = case opt.reinforcement
173
178
  when Array
174
179
  opt.reinforcement.map{|r| Mgmg::Reinforcement.compile(r)}
175
180
  else
176
181
  [Mgmg::Reinforcement.compile(opt.reinforcement)]
177
182
  end
183
+ name = self.join(' ') if name.nil?
178
184
  built = self.build(smith, armor, comp, opt:)
179
185
  pstr = '%.3f' % built.para_call(para)
180
186
  pstr.sub!(/\.?0+\Z/, '')
181
187
  puts "With levels (#{smith}, #{armor}, #{comp}: #{Mgmg.exp(smith, armor, comp).comma3}), building"
182
- puts " #{self.join(', ')}"
188
+ puts " #{name}"
183
189
  rein = rein.empty? ? '' : "reinforced by {#{rein.join(',')}} "
184
190
  puts "#{rein}yields (#{pstr}, #{built.total_cost})"
185
191
  puts " #{built}"
data/reference.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # リファレンス
2
2
  本ライブラリで定義される主要なメソッドを以下に解説します.
3
3
 
4
- ## `String#to_recipe(para=:power, allow_over20: false, **kw)`,`Enumerable#to_recipe(para=:power, allow_over20: false, **kw)`
4
+ ## `String#to_recipe(para=:power, allow_over20: false, name: nil, **kw)`,`Enumerable#to_recipe(para=:power, allow_over20: false, name: nil, **kw)`
5
5
  レシピ文字列である`self`と,注目パラメータ`para`,オプション`Mgmg.option(**kw)`をセットにした[後述](#mgmgrecipe)の`Mgmg::Recipe`オブジェクトを生成して返します.デフォルト値としてセットされたパラメータを使用する以外は,レシピ文字列と同様に扱えます.
6
6
 
7
7
  `allow_over20`が偽の場合,レシピの☆を確認し,20を超える場合は,例外`Mgmg::Over20Error`を発生します.このチェックを抑制したい場合は,真にしてください.
8
8
 
9
+ `name`は,一部のメソッドでレシピ文字列の代わりに使われる文字列を指定します.デフォルト値の`nil`の場合,`String`なら`self`,`Enumerable`なら`self.join(' ')`になります.
10
+
9
11
  ## `String#build(smith=-1, comp=smith, opt: Mgmg.option())`
10
12
  レシピ文字列である`self`を解釈し,鍛冶・防具製作Lvを`smith`,道具製作Lvを`comp`として鍛冶・防具製作及び武器・防具合成を行った結果を[後述](#mgmgequip)の`Mgmg::Equip`クラスのインスタンスとして生成し,返します.例えば,
11
13
  ```ruby
@@ -108,7 +110,7 @@
108
110
 
109
111
  `opt`は,`left_associative`と`include_system_equips`,`reinforcement`を使用します.
110
112
 
111
- ## `String#smith_seach(para, target, comp, opt: Mgmg.option())`
113
+ ## `String#smith_search(para, target, comp, opt: Mgmg.option())`
112
114
  `para`の値が`target`以上となるのに必要な最小の鍛冶・防具製作Lvを二分探索で探索して返します.
113
115
  道具製作Lvは`comp`で固定,鍛冶・防具製作Lvを`opt.smith_min`と`opt.smith_max`で挟み込んで探索します.
114
116
 
@@ -123,16 +125,16 @@
123
125
  `opt`は,上記の他に`left_associative`,`reinforcement`,`irep`を使用します.
124
126
 
125
127
  ## `String#comp_search(para, target, smith, opt: Mgmg.option())`
126
- `String#smith_seach`とは逆に,鍛冶・防具製作Lvを固定して最小の道具製作Lvを探索します.
128
+ `String#smith_search`とは逆に,鍛冶・防具製作Lvを固定して最小の道具製作Lvを探索します.
127
129
  探索の起点である`opt.comp_min`のデフォルト値は,製作に必要な最小の道具製作Lv (`self.min_comp`)です.
128
- その他は`String#smith_seach`と同様です.
130
+ その他は`String#smith_search`と同様です.
129
131
 
130
132
  `opt`は,`comp_min`,`comp_max`,`left_associative`,`reinforcement`,`irep`を使用します.
131
133
 
132
134
  ## `String#search(para, target, opt: Mgmg.option())`
133
- 合計経験値が最小となる鍛冶・防具製作Lvと道具製作Lvの組を探索します.道具製作Lvを決定変数として,`smith_search`の返り値との合計経験値を最小化する道具製作Lvをフィボナッチ探索で探索した後,得られた解 c の前後を探索し,最適化します.道具製作Lv c における合計経験値`exp`に対して,`exp+(exp*opt.comp_ext[0]).to_i.clamp(opt.comp_ext[1], opt.comp_ext[2])`を超えない範囲を探索します.この目的関数は単峰ではないため,フィボナッチ探索のみでは最適解が得られないことがあります.
135
+ 合計経験値が最小となる鍛冶・防具製作Lvと道具製作Lvの組を探索します.道具製作Lvを決定変数として,`smith_search`の返り値との合計経験値を最小化する道具製作Lvをフィボナッチ探索で探索した後,得られた解 c の前後を探索し,最適化します.このとき,道具製作Lv c における合計経験値`exp`,及びフィボナッチ探索で得られた最後の4点の経験値の最大値`exp2`に対して,`exp+(exp2-exp)*opt.fib_ext[0]`を超えない範囲を探索します.この目的関数は単峰ではないため,フィボナッチ探索のみでは最適解が得られないことがあります.
134
136
 
135
- その他は`String#smith_seach`と同様で,`opt`は,`String#smith_search`または`String#comp_search`で使われるすべてのパラメータと,`comp_ext`を使用します.
137
+ その他は`String#smith_search`と同様で,`opt`は,`String#smith_search`または`String#comp_search`で使われるすべてのパラメータと,`fib_ext`を使用します.
136
138
 
137
139
  ## `Enumerable#search(para, target, opt: Mgmg.option())`
138
140
  複数装備の組について,`para`の値が`target`以上となる最小経験値の`[鍛冶Lv,防具製作Lv,道具製作Lv]`を返します.
@@ -140,12 +142,12 @@
140
142
 
141
143
  `opt.smith_min`および`opt.armor_min`のデフォルト値は,武器・防具の各総重量を`opt.target_weight`で製作するのに必要な鍛冶・防具製作Lv (`self.min_level(*opt.target_weight)`)です.`opt.target_weight`には2要素からなる配列を指定しますが,整数が与えられた場合,それを2つ並べた配列と同等のものとして扱います.合計重量を指定することにはならないので注意してください.`opt.target_weight`のデフォルト値は`0`です.
142
144
 
143
- その他は,`String#seach`と同様です.
145
+ その他は,`String#search`と同様です.
144
146
 
145
147
  ## `String#find_max(para, max_exp, opt: Mgmg.option())`
146
148
  経験値の合計が`max_exp`以下の範囲で,`para`の値が最大となる鍛冶・防具製作Lvと道具製作Lvからなる配列を返します.`para`の値が最大となる組が複数存在する場合,経験値が小さい方が優先されます.
147
149
 
148
- `String#search`と同様に,道具製作Lvをフィボナッチ探索で探索した後,得られた解 c の前後を探索し,最適化します.道具製作Lv c における最大の`para`値`p_c`に対して,`p_c-(p_c*opt.comp_ext[3]).ceil`を下回らない範囲を探索します.
150
+ `String#search`と同様に,道具製作Lvをフィボナッチ探索で探索した後,得られた解 c の前後を探索し,最適化します.道具製作Lv c における最大の`para`値`p_c`,及びフィボナッチ探索で得られた最後の4点の`para`値の最小値`p_min`に対して,`p_c-(p_c-p_min)*opt.fib_ext[1]`を下回らない範囲を探索します.
149
151
 
150
152
  ## `Enumerable#find_max(para, max_exp, opt: Mgmg.option())`
151
153
  複数装備の組について,経験値の合計が`max_exp`以下の範囲で,`para`の値が最大となる鍛冶Lv,防具製作Lv,道具製作Lvからなる配列を返します.`para`の値が最大となる組が複数存在する場合,経験値が小さい方が優先されます.
@@ -165,6 +167,46 @@
165
167
 
166
168
  `opt_a`,`opt_b`は,`Mgmg.#find_lowerbound`と同様です.
167
169
 
170
+ ## `Mgmg.#efficient_list(recipes, start, term, out=nil, params=[:defaults], separator: ',', header: true, **kw){|former, latter| ...}`
171
+ 目標値`start`から`term`までにおいて,最も効率の良いレシピとそのときのpara値を求め,一覧にしたcsvファイルを出力します.
172
+ `recipes`のうち,一度でも最適レシピに選ばれたものを抽出した配列を返します.
173
+
174
+ 具体的には,以下のように動作します.
175
+ `Mgmg::Recipe`の配列`recipes`の各要素`r`に対し,`r.search(start)`を実行し,総経験値が最も少なくなる`r_best`を求めます.
176
+ `r_best`の行を出力した後,目標値を`r_best.para_call(*r_best.search(start))+1.quo(8)`に変更し,目標値が`term`を超えるまで繰り返します.
177
+
178
+ 結果は`out`に出力します.`out`が`String`の場合,ファイルパスとみなし,`File.open(out, 'w', **kw)`に対して出力します.
179
+ `out`が`nil`の場合,csvファイルは出力しません.その他の場合,`out.puts`により,`out`そのものに書き込みます.
180
+
181
+ `params`には,出力したい項目名の`Symbol`を並べた配列を指定します.`:defaults`は,自動的に`:smith, :armor, :comp, :exp, :para, :name`に展開され,
182
+ それぞれ鍛冶Lv,防具製作Lv,道具製作Lv,総経験値,para値,レシピ名です.その他,
183
+ `:weight`, `:attack`, `:phydef`, `:magdef`, `:hp`, `:mp`, `:str`, `:dex`, `:speed`, `:magic`, `:atkstr`, `:atk_sd`, `:dex_as`, `:mag_das`, `:magic2`, `:magmag`, `:pmdef`, `:hs`
184
+ が指定でき,各製作Lvでの個別のパラメータを出力できます.
185
+
186
+ `separator`は,項目間の区切り文字を指定します.`header`が真の場合,1行目に,項目名を並べたヘッダを出力します.不要な場合は偽を指定してください.
187
+ 残りの`kw`は,`File.open(out, 'w', **kw)`にそのまま渡されます.出力結果をExcelで開く場合には`external_encoding: "Windows-31J"`などを指定します.
188
+ `out`が`String`でない場合は,`kw`は無視されます.
189
+
190
+ ブロック付きで呼び出した場合,ブロックの評価でタイブレイクを行います.必要な経験値が同じだった場合,para値が大きい方を優先しますが,para値も同じ場合,
191
+ `recipes`の並びで前の方を`former`,後ろの方を`latter`としてブロックを実行します.`yield(former, latter)`が真なら`latter`を,偽なら`former`を優先します.
192
+ `fomer`,`latter`は,`Mgmg::ELItem`インスタンスであり,`params`に指定できる項目がそのままメソッド名として定義されている他,
193
+ `Mgmg::ELItem#recipe`により,`recipes`の要素として渡したレシピオブジェクトを取り出せます.
194
+ 例えば,双短剣のレシピを比較していて,para値として指定した威力が同じ場合は器用さの高い方を,それも同じ場合は素早さが高い方を優先したい場合,以下のようにします.
195
+
196
+ ```ruby
197
+ Mgmg.efficient_list(recipes, start, term) do |former, latter|
198
+ if former.dex < latter.dex
199
+ true
200
+ elsif former.dex == latter.dex
201
+ former.speed < latter.speed
202
+ else
203
+ false
204
+ end
205
+ end
206
+ ```
207
+
208
+ ブロックを指定していない場合,常に`former`が優先されます.
209
+
168
210
  ## `String#eff(para, smith, comp=smith, opt: Mgmg.option())`
169
211
  [`smith`を1上げたときの`para`値/(`smith`を1上げるのに必要な経験値), `comp`を1上げたときの`para`値/(`comp`を2上げるのに必要な経験値)]を返します.
170
212
  `para`は,`Mgmg::Equip`のメソッド名をシンボルで指定(`:power, :fpower`も可)します.
@@ -438,7 +480,7 @@ alias として`*`があるほか`scalar(1.quo(value))`として`quo`,`/`,`s
438
480
  ### 引数4つ
439
481
  調理法,主食材,副食材,料理Lvを指定し,対応する料理の効果の**概算値**を計算します.計算式は [Wikiの記述](https://wikiwiki.jp/guruguru/%E3%82%A2%E3%82%A4%E3%83%86%E3%83%A0/%E9%A3%9F%E7%B3%A7%E5%93%81#y1576f2d) に基づきますが,正確ではないことがわかっています.例えば,`('蒸す', 'アースドラン', '氷河酒', 27)`の物防は87ですが,この計算式では88になります.調理法,主食材,副食材は文字列で,料理Lvは整数で指定します.
440
482
 
441
- 調理法は「焼き」か「蒸す」,主食材は「獣肉」「ウッチ」「ゴッチ」「ガガッチ」「ドランギョ」「ドラバーン」「フレドラン」「アースドラン」「アクアドラン」「ダークドン」,副食材は「氷酒」「氷水酒」「氷河酒」「カエン酒」「爆炎酒」「煉獄酒」のみ対応しています.攻撃,物防,魔防の強化を考える場合,これらで十分と判断しての選択となっています.なお,副食材の数値は,Wikiの表で順番が間違っていると思われる部分を修正しています.
483
+ 調理法は「焼き」か「蒸す」,主食材は「獣肉」「ウッチ」「ゴッチ」「ガガッチ」「ドランギョ」「ドラバーン」「フレドラン」「アースドラン」「アクアドラン」「ダークドン」,副食材は「氷酒」「氷水酒」「氷河酒」「カエン酒」「爆炎酒」「煉獄酒」のみ対応しています.攻撃,物防,魔防の強化を考える場合,これらで十分と判断しての選択となっています.
442
484
 
443
485
  ## `Mgmg::Option`
444
486
  多くのメソッドのオプション引数をまとめるクラスです.このクラスのインスタンスを使ってそれぞれのメソッドに引き渡します.
@@ -446,7 +488,7 @@ alias として`*`があるほか`scalar(1.quo(value))`として`quo`,`/`,`s
446
488
  ## `Mgmg.#option(recipe=nil, **kw)`
447
489
  `kw`はキーワード引数本体です.定義されているキーワードと意味,使用される主なメソッドは下表の通りです.デフォルト値は簡易的な表示であり,細かい点では不正確です.
448
490
  `recipe`にレシピ`String`または`Enumerable`を渡すと,そのレシピを使ってデフォルト値を具体化しますが,各メソッドで自動的に具体化されるため,通常は必要ありません.
449
- `Defaults`対応が「対応」となっているキーワード引数については,`Mgmg::Option::Defaults[:include_system_equips]=false`などとすることで,デフォルト値をグローバルに変更することができます.デフォルト値にかかわらず,メソッド呼び出し時に個別に指定すればその値が優先されます.
491
+ `Defaults`対応が「対応」となっているキーワード引数については,`Mgmg::Option::Defaults[:include_system_equips]=false`などとすることで,デフォルト値をグローバルに変更することができます.デフォルト値にかかわらず,メソッド呼び出し時に個別に指定すればその値が優先されます.また,デフォルト値を変更しても,すでに生成された`Option`オブジェクトの値は変更されません.
450
492
 
451
493
  |キーワード|デフォルト値|`Defaults`対応|意味|主なメソッド,備考|
452
494
  |:-|:-|:-|:-|:-|
@@ -454,8 +496,8 @@ alias として`*`があるほか`scalar(1.quo(value))`として`quo`,`/`,`s
454
496
  |smith_min|`recipe.min_level(target_weight)`|非対応|鍛冶Lvに関する探索範囲の最小値|`String#search`など|
455
497
  |armor_min|`recipe.min_level(*target_weight)[1]`|非対応|防具製作Lvに関する探索範囲の最小値|`Enumerable#search`など.`String`系では代わりに`smith_min`を使う|
456
498
  |comp_min|`recipe.min_comp`|非対応|道具製作Lvに関する探索範囲の最小値|`String#search`など|
457
- |smith_max, armor_max, comp_max|`1_000_000_000`|対応|各製作Lvの探索範囲の最大値|`String#search`など|
458
- |comp_ext|`[0.1, 100, 10_000, 0.1]`|対応|フィボナッチ探索後に追加探索を行う範囲|`String#search`など|
499
+ |smith_max, armor_max, comp_max|`100_000`|対応|各製作Lvの探索範囲の最大値|`String#search`など|
500
+ |fib_ext|`[4, 10]`|対応|フィボナッチ探索後に追加探索を行う範囲|`String#search`など|
459
501
  |target_weight|`0`|非対応|`smith_min`のデフォルト値計算に使う目標重量|`String#search`など|
460
502
  |magdef_maximize|`true`|非対応|目標を魔防最大(真)かコスト最小(偽)にするためのスイッチ|`String#phydef_optimize`|
461
503
  |reinforcement|`[]`|非対応|[前述](#mgmgequipreinforcearg)の`Mgmg::Equip#reinforce`による強化リスト|一部を除くすべてのメソッド|
@@ -491,7 +533,7 @@ alias として`*`があるほか`scalar(1.quo(value))`として`quo`,`/`,`s
491
533
  `self.recipe.find_max`を呼び出して返します.注目パラメータはセットされたものを自動的に渡しますが,別のパラメータを使いたい場合,キーワード引数で指定します.その他のキーワード引数を与えた場合,オプションのうち,与えられたパラメータのみ一時的に上書きします.
492
534
 
493
535
  ## `Mgmg::Recipe#min_level(w=self.option.target_weight, include_outsourcing=false)`
494
- `self.recipe.min_level(w, include_outsourcing)`を返します.`w`のデフォルトパラメータが`target_weight`になっている以外は`String#min_level`または`Enumerable#min_level`と同じです.
536
+ `self.recipe.min_level(w, include_outsourcing)`を返します.`w`のデフォルト値が`target_weight`になっている以外は`String#min_level`または`Enumerable#min_level`と同じです.
495
537
 
496
538
  ## `Mgmg::Recipe#min_weight, max_weight, min_levels, min_levels_max, min_smith, min_comp`
497
539
  `self.recipe`の同名メソッドをそのまま呼び出して返します.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mgmg
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - KAZOON
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-12 00:00:00.000000000 Z
11
+ date: 2022-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
104
  - !ruby/object:Gem::Version
105
105
  version: '0'
106
106
  requirements: []
107
- rubygems_version: 3.3.25
107
+ rubygems_version: 3.3.26
108
108
  signing_key:
109
109
  specification_version: 4
110
110
  summary: Calculate specs of equipments of Megurimeguru, a game produced by Kou.