mgmg 1.5.6 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ae7f8d4f1a701c2f96a4fddbcabdda81fad3355276a368cf7b987ee47e6bfa3
4
- data.tar.gz: 26ee4b16fa9e8a97db0245ddc1019f94b8bbb166dac3fb1812a573cfc9920538
3
+ metadata.gz: beb7f465bd84969abfb7ce5897d95f46171e84c2a0e3de9a14c42ccef5c23dfc
4
+ data.tar.gz: '09a7eda9204cb28a7a9fc24857353fb5cdcfd21e44564d4a25997658c92fdfd3'
5
5
  SHA512:
6
- metadata.gz: e973d0aed431aa65d45b479a3017a78c04e1d7403d8cebacc3337065c4111b382294e51d472a240a80bfea1ed9281860aaf3597225fb5da0088d50758632842c
7
- data.tar.gz: d0ea591eaa7fe9418191289468592d1b207a434fc367a0e0a8811550a7104a7b2dfc9b75df60a929c83ceeda0c4e41915451718a3a25662dd93abe8b7b500986
6
+ metadata.gz: 5642dc8af3ac998c76650047c97747a32f2b631e9f6438c6a95e6068a9534dcd726d34dd8466fb1cba0e787f11847552aab4812e1b32d4320ff23a5ec8e15e18
7
+ data.tar.gz: a327745f26724f3f6bbeabf2ed9fcb687ca4e462ac608c219165845b6ccfeafd5a30c32fdda4ee182dd268db8e0b4528e0acc1bc819eaaae0698d9400c764797
data/CHANGELOG.md CHANGED
@@ -158,6 +158,14 @@
158
158
  - `String#min_level`において,正しい答えを返さなくなったバグを修正.
159
159
 
160
160
  ## 1.5.6 2022/07/17
161
- - `String#find_lowerbound`,`String#find_upperbound`における探索刻み幅を1から1/8に変更し,コーナーケースでの精度を上昇させた.
161
+ - `Mgmg.#find_lowerbound`,`Mgmg.#find_upperbound`における探索刻み幅を1から1/8に変更し,コーナーケースでの精度を上昇させた.
162
162
  - `Enumerable#min_level`が,原料☆によるレベル制限を無視した値を返すことがあったバグを修正.
163
- - `String#find_lowerbound`や`Mgmg::IR#atk_sd` などにおいて,返り値が,分母が1の`Rational`である場合,代わりに`Integer`を返すように修正.
163
+ - `Mgmg.#find_lowerbound`や`Mgmg::IR#atk_sd` などにおいて,返り値が,分母が1の`Rational`である場合,代わりに`Integer`を返すように修正.
164
+
165
+ ## 1.5.7 2022/10/15
166
+ - 経験値上限を指定して,目標パラメータを最大化する製作Lvを返す`Mgmg::Recipe#find_max`を追加.
167
+ - `Mgmg::Recipe#search`において,解の経験値が`cut_exp`ちょうどの場合に例外となっていたバグを修正.
168
+
169
+ ## 1.6.0 2022/10/18
170
+ - `Mgmg.#find_upperbound`のアルゴリズムを改善し,探索下限目標値の引数を削除.
171
+ - `Enumerable#search`,`Enumerable#find_max`が正しい解を返さない場合があったバグを修正.
data/lib/mgmg/option.rb CHANGED
@@ -2,7 +2,7 @@ module Mgmg
2
2
  class Option
3
3
  Defaults = {
4
4
  left_associative: true, include_system_equips: true,
5
- smith_max: 10000, armor_max: 10000, comp_max: 10000
5
+ smith_max: 10_000, armor_max: 10_000, comp_max: 10_000
6
6
  }
7
7
  def initialize(
8
8
  left_associative: Defaults[:left_associative],
data/lib/mgmg/recipe.rb CHANGED
@@ -93,6 +93,10 @@ module Mgmg
93
93
  opt = temp_opt(**kw)
94
94
  @recipe.search(para, target, opt:)
95
95
  end
96
+ def find_max(max_exp, para: @para, **kw)
97
+ opt = temp_opt(**kw)
98
+ @recipe.find_max(para, max_exp, opt:)
99
+ end
96
100
  private def correct_level(s, ac, x, opt)
97
101
  if s.nil?
98
102
  if x.equal?(false)
data/lib/mgmg/search.rb CHANGED
@@ -1,3 +1,4 @@
1
+ using Mgmg::Refiner
1
2
  class String
2
3
  def smith_search(para, target, comp, opt: Mgmg::Option.new)
3
4
  opt = opt.dup.set_default(self)
@@ -55,9 +56,9 @@ class String
55
56
  opt.comp_max = comp_search(para, target, opt.smith_min, opt:)
56
57
  ret = nil
57
58
  exp = Mgmg.exp(opt.smith_min, opt.comp_max)
58
- opt.cut_exp, ret = exp, [opt.smith_min, opt.comp_max] if exp < opt.cut_exp
59
+ opt.cut_exp, ret = exp, [opt.smith_min, opt.comp_max] if exp <= opt.cut_exp
59
60
  exp = Mgmg.exp(opt.smith_max, opt.comp_min)
60
- opt.cut_exp, ret = exp, [opt.smith_max, opt.comp_min] if exp < opt.cut_exp
61
+ opt.cut_exp, ret = exp, [opt.smith_max, opt.comp_min] if ( exp < opt.cut_exp || (ret.nil? && exp==opt.cut_exp) )
61
62
  (opt.comp_min+opt.step).step(opt.comp_max-1, opt.step) do |comp|
62
63
  break if opt.cut_exp < Mgmg.exp(opt.smith_min, comp)
63
64
  smith = smith_search(para, target, comp, opt:)
@@ -71,7 +72,36 @@ class String
71
72
  end
72
73
  rescue Mgmg::SearchCutException
73
74
  end
74
- raise Mgmg::SearchCutException, "the result exceeds given cut_exp=#{opt.cut_exp}" if ret.nil?
75
+ if ret.nil?
76
+ max = opt.irep.para_call(para, *find_max(para, opt.cut_exp, opt:))
77
+ 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}"
78
+ end
79
+ ret
80
+ end
81
+
82
+ private def minimize_smith(para, smith, comp, cur, opt)
83
+ (smith-1).downto(opt.smith_min) do |s|
84
+ foo = opt.irep.para_call(para, s, comp)
85
+ if cur == foo
86
+ smith = s
87
+ else
88
+ break
89
+ end
90
+ end
91
+ smith
92
+ end
93
+ def find_max(para, max_exp, opt: Mgmg::Option.new)
94
+ opt = opt.dup.set_default(self)
95
+ exp = Mgmg.exp(opt.smith_min, opt.comp_min)
96
+ raise Mgmg::SearchCutException, "the recipe requires #{exp.comma3} experiment points, which exceeds given max_exp=#{max_exp.comma3}" if max_exp < exp
97
+ ret = [Mgmg.invexp2(max_exp, opt.comp_min), opt.comp_min]
98
+ max = opt.irep.para_call(para, *ret)
99
+ (opt.comp_min+1).upto(Mgmg.invexp2c(max_exp, opt.smith_min)) do |comp|
100
+ smith = Mgmg.invexp2(max_exp, comp)
101
+ cur = opt.irep.para_call(para, smith, comp)
102
+ smith = minimize_smith(para, smith, comp, cur, opt) if max <= cur
103
+ ret, max = [smith, comp], cur if ( max < cur || ( max == cur && Mgmg.exp(smith, comp) < Mgmg.exp(*ret) ) )
104
+ end
75
105
  ret
76
106
  end
77
107
  end
@@ -138,10 +168,10 @@ module Enumerable
138
168
  opt.armor_max = armor_search(para, target, opt.smith_min, comp, opt: opt_nocut)
139
169
  ret = nil
140
170
  exp = Mgmg.exp(opt.smith_min, opt.armor_max, comp)
141
- opt.cut_exp, ret = exp, [opt.smith_min, opt.armor_max] if exp < opt.cut_exp
171
+ opt.cut_exp, ret = exp, [opt.smith_min, opt.armor_max] if exp <= opt.cut_exp
142
172
  exp2 = Mgmg.exp(opt.smith_max, opt.armor_min, comp)
143
173
  if exp2 < exp
144
- opt.cut_exp, ret = exp2, [opt.smith_max, opt.armor_min] if exp2 < opt.cut_exp
174
+ opt.cut_exp, ret = exp2, [opt.smith_max, opt.armor_min] if exp2 <= opt.cut_exp
145
175
  (opt.armor_min+1).upto(opt.armor_max-1) do |armor|
146
176
  break if opt.cut_exp < Mgmg.exp(opt.smith_min, armor, comp)
147
177
  smith = smith_search(para, target, armor, comp, opt:)
@@ -193,19 +223,79 @@ module Enumerable
193
223
  end
194
224
  opt.comp_max
195
225
  end
226
+ private def search_aonly(para, target, opt: Mgmg::Option.new)
227
+ opt_nocut = opt.dup; opt_nocut.cut_exp = Float::INFINITY
228
+ opt.armor_max = armor_search(para, target, -1, opt.comp_min, opt: opt_nocut)
229
+ opt.armor_min = armor_search(para, target, -1, opt.comp_max, opt: opt_nocut)
230
+ raise Mgmg::SearchCutException if opt.cut_exp < Mgmg.exp(opt.armor_min, opt.comp_min)
231
+ opt.comp_max = comp_search(para, target, -1, opt.armor_min, opt:)
232
+ ret = nil
233
+ exp = Mgmg.exp(opt.armor_min, opt.comp_max)
234
+ opt.cut_exp, ret = exp, [-1, opt.armor_min, opt.comp_max] if exp <= opt.cut_exp
235
+ exp = Mgmg.exp(opt.armor_max, opt.comp_min)
236
+ opt.cut_exp, ret = exp, [-1, opt.armor_max, opt.comp_min] if ( exp < opt.cut_exp || (ret.nil? && exp==opt.cut_exp) )
237
+ (opt.comp_min+opt.step).step(opt.comp_max-1, opt.step) do |comp|
238
+ break if opt.cut_exp < Mgmg.exp(opt.armor_min, comp)
239
+ armor = armor_search(para, target, -1, comp, opt:)
240
+ exp = Mgmg.exp(armor, comp)
241
+ if exp < opt.cut_exp
242
+ opt.cut_exp, ret = exp, [-1, armor, comp]
243
+ elsif exp == opt.cut_exp
244
+ if ret.nil? or opt.irep.para_call(para, *ret) < opt.irep.para_call(para, -1, armor, comp) then
245
+ ret = [-1, armor, comp]
246
+ end
247
+ end
248
+ rescue Mgmg::SearchCutException
249
+ end
250
+ if ret.nil?
251
+ max = opt.irep.para_call(para, *find_max(para, opt.cut_exp, opt:))
252
+ 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}"
253
+ end
254
+ ret
255
+ end
256
+ private def search_sonly(para, target, opt: Mgmg::Option.new)
257
+ opt_nocut = opt.dup; opt_nocut.cut_exp = Float::INFINITY
258
+ opt.smith_max = smith_search(para, target, -1, opt.comp_min, opt: opt_nocut)
259
+ opt.smith_min = smith_search(para, target, -1, opt.comp_max, opt: opt_nocut)
260
+ raise Mgmg::SearchCutException if opt.cut_exp < Mgmg.exp(opt.smith_min, opt.comp_min)
261
+ opt.comp_max = comp_search(para, target, opt.smith_min, -1, opt:)
262
+ ret = nil
263
+ exp = Mgmg.exp(opt.smith_min, opt.comp_max)
264
+ opt.cut_exp, ret = exp, [opt.smith_min, -1, opt.comp_max] if exp <= opt.cut_exp
265
+ exp = Mgmg.exp(opt.smith_max, opt.comp_min)
266
+ opt.cut_exp, ret = exp, [opt.smith_max, -1, opt.comp_min] if ( exp < opt.cut_exp || (ret.nil? && exp==opt.cut_exp) )
267
+ (opt.comp_min+opt.step).step(opt.comp_max-1, opt.step) do |comp|
268
+ break if opt.cut_exp < Mgmg.exp(opt.smith_min, comp)
269
+ smith = smith_search(para, target, -1, comp, opt:)
270
+ exp = Mgmg.exp(smith, comp)
271
+ if exp < opt.cut_exp
272
+ opt.cut_exp, ret = exp, [smith, -1, comp]
273
+ elsif exp == opt.cut_exp
274
+ if ret.nil? or opt.irep.para_call(para, *ret) < opt.irep.para_call(para, smith, -1, comp) then
275
+ ret = [smith, -1, comp]
276
+ end
277
+ end
278
+ rescue Mgmg::SearchCutException
279
+ end
280
+ if ret.nil?
281
+ max = opt.irep.para_call(para, *find_max(para, opt.cut_exp, opt:))
282
+ 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}"
283
+ end
284
+ ret
285
+ end
196
286
  def search(para, target, opt: Mgmg::Option.new)
197
287
  opt = opt.dup.set_default(self)
198
288
  opt.comp_min = comp_search(para, target, opt.smith_max, opt.armor_max, opt:)
199
- opt.smith_max, opt.armor_max = sa_search(para, target, opt.comp_min, opt:)
200
- opt.smith_min, opt.armor_min = sa_search(para, target, opt.comp_max, opt:)
289
+ opt_nocut = opt.dup; opt_nocut.cut_exp = Float::INFINITY
290
+ opt.smith_max = smith_search(para, target, opt.armor_min, opt.comp_min, opt: opt_nocut) rescue ( return search_aonly(para, target, opt:) )
291
+ opt.armor_max = armor_search(para, target, opt.smith_min, opt.comp_min, opt: opt_nocut) rescue ( return search_sonly(para, target, opt:) )
292
+ opt.smith_min = smith_search(para, target, opt.armor_max, opt.comp_max, opt: opt_nocut)
293
+ opt.armor_min = armor_search(para, target, opt.smith_max, opt.comp_max, opt: opt_nocut)
201
294
  raise Mgmg::SearchCutException if opt.cut_exp < Mgmg.exp(opt.smith_min, opt.armor_min, opt.comp_min)
202
295
  opt.comp_max = comp_search(para, target, opt.smith_min, opt.armor_min, opt:)
203
- ret = nil
204
296
  exp = Mgmg.exp(opt.smith_min, opt.armor_min, opt.comp_max)
205
- opt.cut_exp, ret = exp, [opt.smith_min, opt.armor_min,opt. comp_max] if exp < opt.cut_exp
206
- exp = Mgmg.exp(opt.smith_max, opt.armor_max, opt.comp_min)
207
- opt.cut_exp, ret = exp, [opt.smith_max, opt.armor_max, opt.comp_min] if exp < opt.cut_exp
208
- (opt.comp_min+1).upto(opt.comp_max-1) do |comp|
297
+ opt.cut_exp, ret = exp, [opt.smith_min, opt.armor_min,opt. comp_max] if exp <= opt.cut_exp
298
+ (opt.comp_min).upto(opt.comp_max-1) do |comp|
209
299
  break if opt.cut_exp < Mgmg.exp(opt.smith_min, opt.armor_min, comp)
210
300
  smith, armor = sa_search(para, target, comp, opt:)
211
301
  exp = Mgmg.exp(smith, armor, comp)
@@ -218,14 +308,46 @@ module Enumerable
218
308
  end
219
309
  rescue Mgmg::SearchCutException
220
310
  end
221
- raise Mgmg::SearchCutException, "the result exceeds given cut_exp=#{opt.cut_exp}" if ret.nil?
311
+ if ret.nil?
312
+ max = opt.irep.para_call(para, *find_max(para, opt.cut_exp, opt:))
313
+ 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}"
314
+ end
315
+ ret
316
+ end
317
+
318
+ private def minimize_smith(para, smith, armor, comp, cur, opt)
319
+ return -1 if opt.smith_min < 0
320
+ (smith-1).downto(opt.smith_min) do |s|
321
+ foo = opt.irep.para_call(para, s, armor, comp)
322
+ if cur == foo
323
+ smith = s
324
+ else
325
+ break
326
+ end
327
+ end
328
+ smith
329
+ end
330
+ def find_max(para, max_exp, opt: Mgmg::Option.new)
331
+ opt = opt.dup.set_default(self)
332
+ exp = Mgmg.exp(opt.smith_min, opt.armor_min, opt.comp_min)
333
+ raise Mgmg::SearchCutException, "the recipe requires #{exp.comma3} experiment points, which exceeds given max_exp=#{max_exp.comma3}" if max_exp < exp
334
+ ret = [Mgmg.invexp3(max_exp, opt.armor_min, opt.comp_min), opt.armor_min, opt.comp_min]
335
+ max = opt.irep.para_call(para, *ret)
336
+ (opt.comp_min).step(Mgmg.invexp3c(max_exp, opt.smith_min, opt.armor_min), opt.step) do |comp|
337
+ opt.armor_min.upto(Mgmg.invexp3(max_exp, opt.smith_min, comp)) do |armor|
338
+ smith = Mgmg.invexp3(max_exp, armor, comp)
339
+ cur = opt.irep.para_call(para, smith, armor, comp)
340
+ smith = minimize_smith(para, smith, armor, comp, cur, opt) if max <= cur
341
+ ret, max = [smith, armor, comp], cur if ( max < cur || ( max == cur && Mgmg.exp(smith, armor, comp) < Mgmg.exp(*ret) ) )
342
+ break if armor < 0
343
+ end
344
+ end
222
345
  ret
223
346
  end
224
347
  end
225
348
 
226
349
  module Mgmg
227
350
  Eighth = 1.quo(8)
228
- using Refiner
229
351
  module_function def find_lowerbound(a, b, para, start, term, opt_a: Option.new, opt_b: Option.new)
230
352
  if term <= start
231
353
  raise ArgumentError, "start < term is needed, (start, term) = (#{start}, #{term}) are given"
@@ -244,38 +366,42 @@ module Mgmg
244
366
  end
245
367
  sca, scb = a.search(para, start, opt: opt_a), b.search(para, start, opt: opt_b)
246
368
  ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
247
- if eb < ea || ( ea == eb && opt_a.irep.para_call(para, *sca) < opt_b.irep.para_call(para, *scb) )
369
+ pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
370
+ if eb < ea || ( ea == eb && pa < pb )
248
371
  a, b, opt_a, opt_b, sca, scb, ea, eb = b, a, opt_b, opt_a, scb, sca, eb, ea
249
372
  end
250
- tag = opt_a.irep.para_call(para, *sca) + Eighth
251
- sca, scb = a.search(para, term, opt: opt_a), b.search(para, term, opt: opt_b)
252
- ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
253
- if ea < eb || ( ea == eb && opt_b.irep.para_call(para, *scb) < opt_a.irep.para_call(para, *sca) )
254
- raise Mgmg::SearchCutException
255
- end
256
- while tag < term
257
- sca, scb = a.search(para, tag, opt: opt_a), b.search(para, tag, opt: opt_b)
258
- ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
259
- pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
260
- if eb < ea
261
- return [(tag-Eighth).to_ii, pb]
262
- elsif ea == eb
263
- if pa < pb
264
- return [(tag-Eighth).to_ii, pa]
265
- else
266
- tag = pb + Eighth
267
- end
268
- else
373
+
374
+ loop do
375
+ loop do
376
+ foo = a.find_max(para, eb, opt: opt_a)
377
+ break if sca == foo
378
+ sca, pa = foo, opt_a.irep.para_call(para, *foo)
379
+ scb = b.search(para, pa, opt: opt_b)
380
+ foo = Mgmg.exp(*scb)
381
+ break if eb == foo
382
+ eb = foo
383
+ end
384
+ ea = Mgmg.exp(*sca)
385
+ while ea<=eb
269
386
  tag = pa + Eighth
387
+ raise Mgmg::SearchCutException, "given recipes are never reversed from start target=#{start.comma3} until term target=#{term.comma3}" if term < tag
388
+ sca, scb = a.search(para, tag, opt: opt_a), b.search(para, tag, opt: opt_b)
389
+ ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
390
+ pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
391
+ break if ea == eb && pa < pb
392
+ end
393
+ if eb < ea || ( ea == eb && pa < pb )
394
+ until ea < eb || ( ea == eb && pb < pa )
395
+ sca = a.find_max(para, ea-1, opt: opt_a)
396
+ ea, pa = Mgmg.exp(*sca), opt_a.irep.para_call(para, *sca)
397
+ end
398
+ return [pa, pb]
270
399
  end
271
400
  end
272
401
  raise UnexpectedError
273
402
  end
274
403
 
275
- module_function def find_upperbound(a, b, para, start, term, opt_a: Option.new, opt_b: Option.new)
276
- if start <= term
277
- raise ArgumentError, "term < start is needed, (start, term) = (#{start}, #{term}) are given"
278
- end
404
+ module_function def find_upperbound(a, b, para, start, opt_a: Option.new, opt_b: Option.new)
279
405
  if a.kind_of?(Recipe)
280
406
  opt_a = a.option.dup
281
407
  a = a.recipe
@@ -290,58 +416,53 @@ module Mgmg
290
416
  end
291
417
  sca, scb = a.search(para, start, opt: opt_a), b.search(para, start, opt: opt_b)
292
418
  ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
293
- if ea < eb || ( ea == eb && opt_b.irep.para_call(para, *scb) < opt_a.irep.para_call(para, *sca) )
419
+ pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
420
+ if ea < eb || ( ea == eb && pb < pa )
294
421
  a, b, opt_a, opt_b, sca, scb, ea, eb = b, a, opt_b, opt_a, scb, sca, eb, ea
295
422
  end
296
- tagu = opt_a.irep.para_call(para, *sca)
297
- sca[-1] -= 2
298
- tagl = opt_a.irep.para_call(para, *sca)
299
- sca, scb = a.search(para, term, opt: opt_a), b.search(para, term, opt: opt_b)
300
- ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
301
- if eb < ea || ( ea == eb && opt_a.irep.para_call(para, *sca) < opt_b.irep.para_call(para, *scb) )
302
- raise Mgmg::SearchCutException
303
- end
304
- while term < tagu
305
- ret = nil
306
- sca = a.search(para, tagl, opt: opt_a)
307
- next_tagu, next_sca = tagl, sca
308
- scb = b.search(para, tagl, opt: opt_b)
309
- while tagl < tagu
423
+
424
+ loop do
425
+ loop do
426
+ foo = a.find_max(para, eb, opt: opt_a)
427
+ break if sca == foo
428
+ sca, pa = foo, opt_a.irep.para_call(para, *foo)
429
+ scb = b.search(para, pa, opt: opt_b)
430
+ foo = Mgmg.exp(*scb)
431
+ break if eb == foo
432
+ eb = foo
433
+ end
434
+ ea = Mgmg.exp(*sca)
435
+ while ea==eb do
436
+ res = 0
437
+ begin
438
+ sca = a.find_max(para, ea-1, opt: opt_a)
439
+ rescue Mgmg::SearchCutException
440
+ res += 1
441
+ end
442
+ begin
443
+ scb = b.find_max(para, eb-1, opt: opt_b)
444
+ rescue Mgmg::SearchCutException
445
+ res += 1
446
+ end
310
447
  ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
311
448
  pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
312
- if ea < eb
313
- ret = tagl
314
- sca = a.search(para, pa + Eighth, opt: opt_a)
315
- tagl = opt_a.irep.para_call(para, *sca)
316
- scb = b.search(para, tagl, opt: opt_b)
317
- elsif ea == eb
318
- if pb < pa
319
- ret = tagl
320
- sca = a.search(para, pa + Eighth, opt: opt_a)
321
- tagl = opt_a.irep.para_call(para, *sca)
322
- scb = b.search(para, tagl, opt: opt_b)
323
- else
324
- scb = b.search(para, pb + Eighth, opt: opt_b)
325
- tagl = opt_b.irep.para_call(para, *scb)
326
- sca = a.search(para, tagl, opt: opt_a)
449
+ if ea < eb || ( ea == eb && pb < pa )
450
+ until pa < pb
451
+ scb = b.search(para, pb+Eighth, opt: opt_b)
452
+ pb = opt_b.irep.para_call(para, *scb)
327
453
  end
328
- else
329
- sca = a.search(para, pa + Eighth, opt: opt_a)
330
- tagl = opt_a.irep.para_call(para, *sca)
331
- scb = b.search(para, tagl, opt: opt_b)
454
+ return [pa, pb]
455
+ elsif res == 2
456
+ raise Mgmg::SearchCutException, "given recipes are never reversed from the start target=#{start.comma3} until #{pa.comma3}"
332
457
  end
333
458
  end
334
- if ret.nil?
335
- tagu = next_tagu
336
- next_sca[-1] -= 2
337
- tagl = opt_a.irep.para_call(para, *next_sca)
338
- if tagl == tagu
339
- tagl = term
459
+ if ea < eb
460
+ pb = opt_b.irep.para_call(para, *scb)
461
+ until pa < pb
462
+ scb = b.search(para, pb+Eighth, opt: opt_b)
463
+ pb = opt_b.irep.para_call(para, *scb)
340
464
  end
341
- else
342
- pa = opt_a.irep.para_call(para, *a.search(para, ret+Eighth, opt: opt_a))
343
- pb = opt_b.irep.para_call(para, *b.search(para, ret+Eighth, opt: opt_b))
344
- return [ret.to_ii, [pa, pb].min]
465
+ return [pa, pb]
345
466
  end
346
467
  end
347
468
  raise UnexpectedError
@@ -349,7 +470,32 @@ module Mgmg
349
470
 
350
471
  module_function def find_lubounds(a, b, para, lower, upper, opt_a: Mgmg::Option.new, opt_b: Mgmg::Option.new)
351
472
  xl, yl = find_lowerbound(a, b, para, lower, upper, opt_a:, opt_b:)
352
- xu, yu = find_upperbound(a, b, para, upper, lower, opt_a:, opt_b:)
473
+ xu, yu = find_upperbound(a, b, para, upper, opt_a:, opt_b:)
353
474
  [xl, yl, xu, yu]
354
475
  end
476
+ module_function def find_lubounds2(a, b, para, lower, upper, opt_a: Mgmg::Option.new, opt_b: Mgmg::Option.new)
477
+ xl, yl, xu, yu = find_lubounds(a, b, para, lower, upper, opt_a: Mgmg::Option.new, opt_b: Mgmg::Option.new)
478
+ if a.kind_of?(Recipe)
479
+ opt_a = a.option.dup
480
+ a = a.recipe
481
+ else
482
+ opt_a = opt_a.dup.set_default(a)
483
+ end
484
+ if b.kind_of?(Recipe)
485
+ opt_b = b.option.dup
486
+ b = b.recipe
487
+ else
488
+ opt_b = opt_b.dup.set_default(b)
489
+ end
490
+ sca, scb = a.search(para, lower, opt: opt_a), b.search(para, lower, opt: opt_b)
491
+ ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
492
+ pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
493
+ if eb < ea || ( ea == eb && pa < pb )
494
+ a, b, opt_a, opt_b, sca, scb, ea, eb = b, a, opt_b, opt_a, scb, sca, eb, ea
495
+ end
496
+ sca, scb = a.search(para, xl, opt: opt_a), b.search(para, yu, opt: opt_b)
497
+ ea, eb = Mgmg.exp(*sca), Mgmg.exp(*scb)
498
+ pa, pb = opt_a.irep.para_call(para, *sca), opt_b.irep.para_call(para, *scb)
499
+ [sca, ea, pa, scb, eb, pb]
500
+ end
355
501
  end
data/lib/mgmg/utils.rb CHANGED
@@ -158,10 +158,58 @@ module Mgmg
158
158
  end
159
159
  end
160
160
  module_function def invexp2(exp, comp)
161
- Math.sqrt(exp - (2*((comp-1)**2)) - 3).round + 1
161
+ begin
162
+ ret = Math.sqrt(exp - (2*((comp-1)**2)) - 3).floor + 2
163
+ rescue Math::DomainError
164
+ return -1
165
+ end
166
+ if Mgmg.exp(ret, comp) <= exp
167
+ ret
168
+ else
169
+ ret-1
170
+ end
171
+ end
172
+ module_function def invexp2c(exp, s)
173
+ begin
174
+ ret = Math.sqrt((exp - (((s-1)**2)) - 3).quo(2)).floor + 2
175
+ rescue Math::DomainError
176
+ return -1
177
+ end
178
+ if Mgmg.exp(s, ret) <= exp
179
+ ret
180
+ else
181
+ ret-1
182
+ end
162
183
  end
163
184
  module_function def invexp3(exp, sa, comp)
164
- Math.sqrt(exp - ((sa-1)**2) - (2*((comp-1)**2)) - 4).round + 1
185
+ return invexp2(exp, comp) if sa < 0
186
+ begin
187
+ ret = Math.sqrt(exp - ((sa-1)**2) - (2*((comp-1)**2)) - 4).floor + 2
188
+ rescue Math::DomainError
189
+ return -1
190
+ end
191
+ if Mgmg.exp(ret, sa, comp) <= exp
192
+ ret
193
+ else
194
+ ret-1
195
+ end
196
+ end
197
+ module_function def invexp3c(exp, smith, armor)
198
+ if smith < 0
199
+ return invexp2c(exp, armor)
200
+ elsif armor < 0
201
+ return invexp2c(exp, smith)
202
+ end
203
+ begin
204
+ ret = Math.sqrt((exp - ((smith-1)**2) - ((armor-1)**2) - 4).quo(2)).floor + 2
205
+ rescue Math::DomainError
206
+ return -1
207
+ end
208
+ if Mgmg.exp(smith, armor, ret) <= exp
209
+ ret
210
+ else
211
+ ret-1
212
+ end
165
213
  end
166
214
  module_function def clear_cache
167
215
  CacheMLS.clear; Equip::Cache.clear; Equip::CacheML.clear; TPolynomial::Cache.clear; IR::Cache.clear
data/lib/mgmg/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mgmg
2
- VERSION = "1.5.6"
2
+ VERSION = "1.6.0"
3
3
  end
data/reference.md CHANGED
@@ -130,7 +130,7 @@
130
130
  `opt`は,`comp_min`,`comp_max`,`left_associative`,`reinforcement`,`irep`を使用します.
131
131
 
132
132
  ## `String#search(para, target, opt: Mgmg.option())`
133
- `c_min=comp_search(para, target, opt.smith_max, opt: opt)` から `c_max=comp_search(para, target, opt.smith_min, opt: opt)` まで,`opt.step`ずつ動かして,
133
+ 道具製作Lvを `c_min=comp_search(para, target, opt.smith_max, opt: opt)` から `c_max=comp_search(para, target, opt.smith_min, opt: opt)` まで,`opt.step`ずつ動かして,
134
134
  `smith_search`を行い,その過程で得られた最小経験値の鍛冶・防具製作Lvと道具製作Lvからなる配列を返します.
135
135
  レシピ中の,対象パラメータの種別値がすべて奇数,または全て偶数であるなら,`opt.step`を`2`にしても探索すべき範囲を網羅できます.
136
136
  その他は`String#smith_seach`と同様です.
@@ -139,12 +139,18 @@
139
139
 
140
140
  ## `Enumerable#search(para, target, opt: Mgmg.option())`
141
141
  複数装備の組について,`para`の値が`target`以上となる最小経験値の`[鍛冶Lv,防具製作Lv,道具製作Lv]`を返します.
142
- 武器のみなら防具製作Lvは`0`,防具のみなら鍛冶Lvは`0`,合成なしなら道具製作Lvは`0`となります.
142
+ 武器のみなら防具製作Lvは`-1`,防具のみなら鍛冶Lvは`-1`,合成なしなら道具製作Lvは`0`となります.
143
143
 
144
144
  `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`です.
145
145
 
146
146
  その他は,`String#smith_seach`と同様です.
147
147
 
148
+ ## `String#find_max(para, max_exp, opt: Mgmg.option())`
149
+ 経験値の合計が`max_exp`以下の範囲で,`para`の値が最大となる鍛冶・防具製作Lvと道具製作Lvからなる配列を返します.`para`の値が最大となる組が複数存在する場合,経験値が小さい方が優先されます.
150
+
151
+ ## `Enumerable#find_max(para, max_exp, opt: Mgmg.option())`
152
+ 複数装備の組について,経験値の合計が`max_exp`以下の範囲で,`para`の値が最大となる鍛冶Lv,防具製作Lv,道具製作Lvからなる配列を返します.`para`の値が最大となる組が複数存在する場合,経験値が小さい方が優先されます.
153
+
148
154
  ## `Mgmg.#find_lowerbound(a, b, para, start, term, opt_a: Mgmg.option(), opt_b: Mgmg.option())`
149
155
  レシピ`a`とレシピ`b`について,`para`の値を目標値以上にする最小経験値の組において,目標値`start`における優劣が逆転する目標値の下限を探索し,返します.
150
156
  返り値は`[逆転しない最大目標値, 逆転時の最小para値]`です.前者は逆転目標値の下限,後者は,目標値が前者よりも少しだけ大きいときの`para`値です.
@@ -155,7 +161,7 @@
155
161
 
156
162
  `opt_a`,`opt_b`には,それぞれ`a`と`b`に関するオプションパラメータを指定します.`smith_min`,`armor_min`,`min_smith`,`left_associative`,`reinforcement`,`irep`を使用します.
157
163
 
158
- ## `Mgmg.#find_upperbound(a, b, para, start, term, opt_a: Mgmg.option(), opt_b: Mgmg.option())`
164
+ ## `Mgmg.#find_upperbound(a, b, para, start, opt_a: Mgmg.option(), opt_b: Mgmg.option())`
159
165
  `Mgmg.#find_lowerbound`とは逆に,目標値を下げながら,優劣が逆転する最大の目標値を探索し,返します.返り値は`[逆転する最大目標値, 逆転前の最小para値]`です.目標値が,前者よりも少しでも大きいと逆転が起こらず(逆転する目標値の上限),少しだけ大きい時の`para`値が後者になります.
160
166
 
161
167
  `opt_a`,`opt_b`は,`Mgmg.#find_lowerbound`と同様です.
@@ -449,7 +455,7 @@ alias として`*`があるほか`scalar(1.quo(value))`として`quo`,`/`,`s
449
455
  |smith_min|`recipe.min_level(target_weight)`|非対応|鍛冶Lvに関する探索範囲の最小値|`String#search`など|
450
456
  |armor_min|`recipe.min_level(*target_weight)[1]`|非対応|防具製作Lvに関する探索範囲の最小値|`Enumerable#search`など.`String`系では代わりに`smith_min`を使う|
451
457
  |comp_min|`recipe.min_comp`|非対応|道具製作Lvに関する探索範囲の最小値|`String#search`など|
452
- |smith_max, armor_max, comp_max|`10000`|対応|各製作Lvの探索範囲の最大値|`String#search`など|
458
+ |smith_max, armor_max, comp_max|`10,000`|対応|各製作Lvの探索範囲の最大値|`String#search`など|
453
459
  |target_weight|`0`|非対応|`smith_min`のデフォルト値計算に使う目標重量|`String#search`など|
454
460
  |step|`1`|非対応|探索時において道具製作Lvを動かす幅|`String#search`など|
455
461
  |magdef_maximize|`true`|非対応|目標を魔防最大(真)かコスト最小(偽)にするためのスイッチ|`String#phydef_optimize`|
@@ -482,6 +488,9 @@ alias として`*`があるほか`scalar(1.quo(value))`として`quo`,`/`,`s
482
488
  ## `Mgmg::Recipe#search(target, para: self.para, **kw)`
483
489
  `self.recipe.search`を呼び出して返します.注目パラメータはセットされたものを自動的に渡しますが,別のパラメータを使いたい場合,キーワード引数で指定します.その他のキーワード引数を与えた場合,オプションのうち,与えられたパラメータのみ一時的に上書きします.
484
490
 
491
+ ## `Mgmg::Recipe#find_max(max_exp, para: self.para, **kw)`
492
+ `self.recipe.find_max`を呼び出して返します.注目パラメータはセットされたものを自動的に渡しますが,別のパラメータを使いたい場合,キーワード引数で指定します.その他のキーワード引数を与えた場合,オプションのうち,与えられたパラメータのみ一時的に上書きします.
493
+
485
494
  ## `Mgmg::Recipe#min_level(w=self.option.target_weight, include_outsourcing=false)`
486
495
  `self.recipe.min_level(w, include_outsourcing)`を返します.`w`のデフォルトパラメータが`target_weight`になっている以外は`String#min_level`または`Enumerable#min_level`と同じです.
487
496
 
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.5.6
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - KAZOON
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-17 00:00:00.000000000 Z
11
+ date: 2022-10-18 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.18
107
+ rubygems_version: 3.3.24
108
108
  signing_key:
109
109
  specification_version: 4
110
110
  summary: Calculate specs of equipments of Megurimeguru, a game produced by Kou.