mgmg 1.5.6 → 1.6.0

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: 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.