ソシャゲ系にありがちな経験値計算を自作電卓に組み込んでみた


 こんなん書いてもなー、って思わなくもないが、どうでもいい狭いネタなんか過去に山のように書いてるしな。
 つーことで、自作電卓の話。

 元々、CUIな電卓の自作は好きで、DOS向けに小さいのをアセンブラで書いたりしたこともあった。
 小さいの、と言っても、実行ファイルのサイズが小さいって話で、アセンブラだからソースは無駄にでかい。内部はbigint/rationalな処理で、循環小数は循環節付きで出力するみたいな無駄に頑張った仕様だった。
 Wolfram Alphaとかがある今時なら、こんなもん作ろうとも思わなかっただろうけどさ。

 あの頃の俺はなー、ソースの可読性とか投げ捨てるもの以前で、「見易いソース」って感覚が皆無だった(←今でも怪しい)。そもそもアセンブラで何から何まで全部書いちまおうとか考えた時点で、悪い意味のバカであるな。だがそもそも当時の俺はC言語すら知らねえのだ(笑)。
 なお、TASMのIDEALモードとかいう、もう心底アレなソースだった。いや面白い仕様だったし好きだけど。でもなー。
 しかも、何も考えずに全ての箇所を最適化した。何でもないところでもcall+retをjmpに変えたり、sbbで分岐減らしたり、フラグレジスタ読んで論理演算でどーにかとか、再帰的なマクロでテーブル作って除算を高速にとかやってたような。しかもバカだから曲芸でもコメント書かねえ。
 このように典型的なクソコードであり、完成したからまだいいけど、本気で何やってるか分からんソースになったし、あの経験が特に役に立つことも無かった気はする。
 けど、ほら、アレだ、「昔オフゲで死ぬほどチートやって飽きたからもうオンラインゲームとかでわざわざチートしたいとか思わんし」って現象があるけど、同様に「昔アセンブラで死ぬほどパズルみたいなコード書いて飽きたからもう要らん最適化したいとか思わんし」みたいなのもあるかもしれん。どうだろう。

 何かどんどん狭い話をしてる気が。

 話を戻して。

 Pythonを触るようになって少し経った頃、evalでクソ簡単な電卓作れるよなー、と思った。
 いやまーPerlも入れてたから、ワンライナーで済む話ではあったのだが、そこはそれ。何か作りたくなったのだ。

from __future__ import division, print_function
import sys
expr = ' '.join(sys.argv[1:]) or 'None'
print(expr, '=', eval(expr))

こんな程度だし。

 で、普段から使うようになって、千年戦争アイギスの経験値の計算とかもやってたんだけど。
 ソシャゲ的な奴って、「育成目標は経験値2400、主要狩場で拾えるカードから得られる経験値は225と255、あと手持ちで素材にしたいカードは経験値40が2枚、70が1枚、140が3枚」みたいな状況が良くあるじゃん。
 それを、

>e game.xp(2400, 225, 255, (40,2), (70,1), (140,3))

とかで自動的に計算してくれる機能が欲しいな、と。
 面倒だからやらずにいたけど、アイギスのデイリー復刻実装でこういう計算の需要が俺的に多発したんで、試しに拡張してみた。

from __future__ import division, print_function
import sys
import collections


def clamp(v, min_v, max_v):
    return min(max_v, max(min_v, v))


class game(object):
    @staticmethod
    def xp(target, *exp_and_limits):
        RESULT_HEAD_LEN_MIN = 15
        RESULT_HEAD_LEN_MAX = 50
        RESULT_TAIL_LEN_MIN = 5
        RESULT_TAIL_LEN_MAX = 30

        exp_lims = [
            x if isinstance(x, collections.Iterable)
                else (x, float('inf'))
            for x in exp_and_limits]

        # return [[num, ...], ...]
        def inner_func(target_rest, exp_lims_rest):
            exp_unit, num_limit = exp_lims_rest[0]
            max_num = -(-target_rest // exp_unit)
              # just round up
            max_num = clamp(max_num, 0, num_limit)
            if len(exp_lims_rest) == 1:
                return [[max_num]]

            result = []
            inner_exp_lims = exp_lims_rest[1:]
            for num in range(0, max_num + 1):
                exp_product = exp_unit * num
                inner_target = target_rest - exp_product
                inner_result = inner_func(
                    inner_target, inner_exp_lims)
                for row in inner_result:
                    result.append([num] + row)
            return result

        raw_result = inner_func(target, exp_lims)
        result = []
        for row in raw_result:
            exp_total = sum(
                exp * num
                for (exp, limit), num
                in zip(exp_lims, row))
            excess = exp_total - target
            if excess < 0:
                continue
            result.append((excess, row))
        result.sort(
            key=lambda x: [-x[0], x[1:]], reverse=True)

        head_len = clamp(
            len([
                1 for row in result
                if row[0] == result[0][0]]),
            RESULT_HEAD_LEN_MIN, RESULT_HEAD_LEN_MAX)
        tail_len = clamp(
            len([
                1 for row in result
                if row[0] == result[-1][0]]),
            RESULT_TAIL_LEN_MIN, RESULT_TAIL_LEN_MAX)
        if len(result) > head_len + tail_len + 1:
            result[head_len:-tail_len] = [
                '{} sets omitted'.format(
                    len(result) - head_len - tail_len)]
        return result


expr = ' '.join(sys.argv[1:]) or 'None'
print(expr, '=', eval(expr))

こんな感じで。
 元ソースから他の俺向け機能を削除しまくってから貼ったんで、ちゃんと動くかは知らないけど、試しにさっきのコマンドを走らせてみたら、

>e game.xp(2400, 225, 255, (40,2), (70,1), (140,3))
game.xp(2400, 225, 255, (40,2), (70,1), (140,3)) = [(0, [10, 0, 2, 1, 0]), (0, [7, 1, 2, 1, 3]), (0, [5, 5, 0, 0, 0]), (0, [2, 6, 0, 0, 3]), (0, [1, 7, 1, 1, 2]), (0, [0, 8, 2, 0, 2]), (5, [8, 1, 0, 1, 2]), (5, [7, 2, 1, 0, 2]), (5, [6, 3, 2, 1, 1]), (5, [1, 8, 0, 0, 1]), (5, [0, 9, 1, 1, 0]), (10, [7, 3, 0, 1, 0]), (10, [6, 4, 1, 0, 0]), (10, [4, 4, 0, 1, 3]), (10, [3, 5, 1, 0, 3]), '109 sets omitted', (135, [4, 5, 2, 0, 2]), (150, [0, 10, 0, 0, 0]), (165, [8, 3, 0, 0, 0]), (195, [7, 4, 0, 0, 0]), (225, [6, 5, 0, 0, 0])]

って感じになったので、多分動く気はする。怪しいが。
 なお、出力の意味は、最初の等号の右の

(0, [10, 0, 2, 1, 0])

だけ説明すりゃ分かるかなーと。この場合は「超過0、225*10 + 255*0 + 40*2 + 70*1 + 140*0」ってことで。左の項目が多い方から順にソートされてるから、優先的に使いたい素材を左に指定すると便利とかそんな感じ。
 あと、項目数が10個くらいになるとメモリ使い切る勢いになってくるし、何千通りも最適解が表示されてもまともに見る気にもならないんで、大人しく7個くらいまでにしておいた方が。

 相変わらずの説明する気ねーだろ的記事であるが、まー、分かるべ。行ける行ける。
 あと、Python2.7.x用のつもりだけど、Python3.xでも動いてるようには見える。知らんけど。
 ついでにWindowsの場合、cmd.exeへのショートカットをデスクトップに置いてプロパティ開いてショートカットキーを設定しておけば更に楽に。

 たまにこういう小さいソースコード貼っただけみたいな記事を書くけど、需要無さそうだよなー本気で。ハハハ。
 まーでも、ちょっとコード書ける人なら「そーかーevalで手軽に自作電卓とか便利そうだなー」と思うかもしれないなー、というのもある。ワンライナーと違って、自分用の関数とか定数とか作れるのはかなり嬉しい。
 俺は当時最適だと思ったPythonで書いたけど、Node.jsが登場して一荒れして落ち着いて風格が出てきた今となっては、そっちで書いた方が良い気がしなくもない。電卓としてはPythonの除算演算子(新仕様)は便利だけど。いちいちintとかfloatとか考えなくて済むからなー。

 なお、これで色々な経験値素材の組み合わせを計算させてみたけど、素材を何種類か持ってれば、無駄の無い組み合わせは大抵はゴロゴロ出てくるっぽい。
 だが、そこまでして無駄をなくす意味は多分無い。


千年戦争アイギスを後発組がお勧めしてみる


 確か2015/4/2辺りから、完全にアイギスがメインな感じなんで紹介しますよ。

 運営初期から時々気になってはいたんだが、一年以上掛けてじわじわ盛り上がってる感じなので、もしかして本格的に面白いんじゃねーの、って思ったのよな。
 んで、始めてみたら結構なハマりっぷりで。
 いや、実は最初に一度投げ出したのだが。確か運営開始数ヶ月後くらいに一度触ってみて、こんな仕様じゃ大量に有料アイテムを買わなきゃやってられんだろ、と思ってしまった気がする。まさか膨大な量の有料アイテムを毎週のようにバラ撒いてくるとは普通思わんし。

 つーか、前から何度も何度も記事を書きかけては、何か違うなーと思って保留、を繰り返して、ランク60の時に書き始めたのに今じゃランク158である。
 まー、うちみたいな場末のblogが、他のとこにいくらでも書かれてるような話を長々と書いてもしゃーないわな、ってことには気付いたので、

  • 凄く大まかな概要
  • 後発新規に良くありそうな誤解(俺自身がしてたのも含めて)
  • 昔は推奨されてたけど今の新規には当てはまらなそうなこと
  • 絶対に押さえておきたい知識

などに絞って、ざっと書いてみようと。

 でも最初に、アイギスにおける最大のネックについて触れておきたい。
 このゲームは、かなりブラウザを選ぶ。i7-2600Kマシンでメモリ8GBとかあっても、ブラウザ次第であっさり落ちる。
 推奨はChromeで、まーまー安定して遊べてはいる。糞重くなったことがあるけど、ハードウェアアクセラレーションを切ったら直った。怪しいですな。まー、ブラゲやるならどっちにしろ複数ブラウザをインストールしとくのはお勧めなんだけどさ。でもなー。
 Android版アプリもあって軽快だけど、公式には5.x非対応。タッチ操作だから、精密動作△、素早い操作◎、って感じで結構快適だし、頻繁に起動してるし、結局5.1.1にしちゃった俺のNexus7(2013)では一応大丈夫だったけど。

 んじゃ、そろそろ概要から書いてみる。

  • エロ版と一般版があって、データは共通。
    エロは、ユニットさえ取れば楽に見られる。この手のゲームにしては意外なくらい。
  • ソシャゲのフォーマットで、ほぼ完全なソロゲー。
  • 無料プレイでも、少し続ければ戦力的にはそれほどは困らない。筈。
    ヌルゲーという訳でもないけど、腕でカバー出来る範囲も広く、ひっきりなしに開催されるイベントで割と強いユニットもどんどん配布されるので、何とかなる感じ。
    その一方で、札束で叩くヌルゲー化の手段も豊富。
  • つーか、ゲーム性が相当ガチ寄り。一見それほどでもないのに。
  • 神聖結晶(以下結晶)が継続的に大量に配布される。
    これは有料サービスを買う為のアイテムで、一個67〜100円(まとめ買い割引あり)で購入可能。
    つまり、無料プレイでも有料サービスは全て受けられる。予算枠が狭いだけ。(ラノベ付録とかニコ生プレミアム会員向けの配布とかは例外)
    プレイ初期は普通に進めてるだけでドカドカ貰えるし、イベントやお詫びなどで毎月普通に数十個は貰える。
    運営が深刻な大失態をやらかしたりすると、それのお詫び分だけで月60個とかいう狂気の配布を始めるし、加えて普段の平常運転レベルの失態でも月5個くらい、さらにイベントやログインボーナスで数十個。大丈夫なのか。つーか失態多いな。
  • ダメージ1まで計算しまくれる攻略が熱いけど、世界観やキャラの扱いは雑、という、良くも悪くも艦これの真逆路線。
  • ドット絵はかなり見所。

 こんなもんかなー。
 なお、俺は結晶30個くらい買ったはいいけどなかなか使わなくて在庫が常時150個とかあるので、無料でも同じプレイは確実に可能な筈。むしろ使わなすぎて非効率的である。ラストの付かないエリクサーもほぼ全部残ってるタイプ。
 結晶の所有上限が299個、ってのは一応注意。

 んじゃ次、プレイし始めた人向けに、後発プレイヤー視点で色々と。
 異論も色々ありそうだけど。お決まりの育成コースがあまり無いゲームだし、一部のユニットを偏愛したりする方がむしろ楽しいゲームでもあるし。まーそこはそれ、俺式ってことで。

  • 好感度(女ユニット)はめちゃくちゃ効くんでさっさと上げる。
    信頼度(男ユニット)はそれほどでもないかも。
    クラスチェンジ(以下CC)前は50%が上限なので注意。CCの無いクラスなら平気。
  • ランク100まではどんどん上がるし、優先的にどんどん上げたい。経験値1.2倍もお勧め。
    カリスマ/スタミナも増えて、何より王子が強くなるので。
  • 低予算だとイベント報酬はそのまま主力になることも多い。ならないことも少なくはないけど。
    特に、コスト下限、スキル上限付近まで仕上がったユニットが当たり性能だと、非常に使いやすい。
    コスト/スキルが仕上がってなくてもそれなりに強いし寝室もあるんで、とりあえず取った方がもちろん良い。
    毎回完璧に仕上げるのはきついし、序盤にイベントに集中し過ぎて育成が進まないといつまでもイベントで苦労したりするので、結晶ぶっ込んで全力で仕上げるか最低性能で確保するかの選択は必要だけど、イベント報酬の性能は読みにくい。世間の評判で当たり外れを見極めるのは厳禁。マジで。
    まーでも、イベントは頻繁にあるので勝手に戦力は揃ってくるから、あまり悩み過ぎない程度に。絵を見たいだけなら強さなんてあんまり要らないのだから。
    なお、確実に強そうなの以外はイベントで結晶ぶっ込むのをやめて全部ガチャに回す、という路線も十分ありらしい。でも俺の知らない世界なので割愛。
    俺は逆に、イベントなるべく毎回最強に仕上げたい派。神級さえ行ければ、無料プレイでも復刻以外は全部ほぼ最強(ドロップ系のスキルは7〜9程度で我慢)で取れそうな気がする。常時ガチガチに節約してれば。
  • 収集イベントは、完璧な仕上がりを狙うと異常に結晶の消費が激しいのが特徴。頻繁に開催はされないので、普段節約していれば足りると思うし、報酬も強い傾向らしいので、相応の価値はあるっぽい。
    逆に、自然回復だけで我慢すれば一気に結晶に余裕が出るけど。
    ある程度の戦力が育てば(極級行けて最大スタミナ14以上くらい?)結晶40〜50個くらいで済むけど、極級に行けないくらいだと消費も結構増えるので悩みどころ。
    結晶の消費を計算するには、「千年戦争アイギスツール」で検索すると便利なツールがあるかも。多分「○○タイマー」って名前が付いてる。
  • 復刻イベントは、ドロップ系は行けるけど、刻結晶系は結晶の消費が厳しい。世間の評価がそこそこ参考にはなるけど。その場合も、叩く意見は全部無視していいかも。
  • ゴールドラッシュは、報酬次第ではかなり狙い目。
    うちの今の主力組でゴールドラッシュに登場しそうなのは、クレア、イーリス、ベルニス、フェドラ、キャリー、テミス、メーリス。あとドロップ上昇系のキュテリ、モニカ。
    ベルニスは特に、堅いヘビーアーマーが他にいなければお勧め。他は、役割の被る金以上がいればそれほどは。
  • 白金の聖霊は膨大に消費するので、序盤に多少余っても出来れば溜め込んでおきたい。
  • 「差し込み」「避雷針」は必須知識なので調べておく。
    これだけは知らないと、他人のプレイを参考にすら出来ないので。
  • アクションゲームが嫌いじゃなければ、いわゆる空蝉(敵に撃たせてから撤退)や、ヒーラーのモーションを見る系の技も非常に有効。動画を良く見る人ならすぐに目にすることになる筈。
    ヒーラーのモーションを見る系は、回復をずらす技(先置が待機モーションに戻った辺りで配置)や、設置直後に回復させる技(先置が手を上げる瞬間に配置)など。
  • 「いずれ同キャラ合成する予定だから、今はカンストさせない方がお得かな」というのは結構やるけど、銀の場合は素材としての経験値がストミ終盤の銅と大差無い程度なんで、銀なら気にせずカンストさせちゃう派。金以上は気分次第。
    序盤は動画を参考にして背伸びしたいけど、少しでもユニット能力が劣ると展開が崩れたりするので、というのもある。
  • 定番クラスを一通り揃えるまでは銀を先に育てた方がもちろんいいけど、銀のコスト/スキルを完璧に仕上げに行くより先に、金以上を素のままでいいから覚醒させに行く方が早いと思う。多分。
    覚醒の宝珠をぎりぎり狙える戦力が揃ったら速攻で覚醒狙い、くらいでもいいかも。特に弓とヒーラー。
  • 多分今はバシラを早めに育てるとかなり楽なので、イベントマップに低消費の銀弓ドロップがあるならCC素材として是非狙いたい。銀弓の需要はかなり多いので、無駄になることもあんまり無いと思う。

 ってなところで。

 やっぱり長くなったなー。
 でも最初は数倍あったのだ。書きたいことが本当に多くて。だいぶ削ったので、おかしな表現になってないといいけど。

 ということで、アイギスお勧めである。いやもう久々に来たんじゃねーかなこれ。
 育成途中は、ぼんやりとした不満の種はいくらかあったけど、戦力が揃ってきたら割とどーでも良くなってきた。
 それに、どんどん不満が減る方向に仕様が変わってくし。確定ドロップがどんどん増えたりなー。
 つーか、これだけ結晶が貰えると、不満なんかどこで持てばいいのよ、って感じなのよな。俺みたいなプレイスタイルだと。
 あと、あれだ、カリスマやスタミナを無駄にするとめちゃくちゃストレス溜まるタイプの人(俺とか)や、攻略を見て目一杯に下準備をするのが好きなタイプの人(俺とか)は、プレイ動画を参考にするのがお勧め。良動画が大量にあるので、低予算だとめちゃくちゃ助かる。背伸びしようとすればするほど、参考元と全然違う形になったりするのも楽しい。

 まー、お勧めです。かなり。

 最後にドバーンとバナー貼っとこうか。

千年戦争アイギス オンラインゲーム
↑一般版と、

千年戦争アイギスR オンラインゲーム
↑エロゲー版で。

 どっちもエロゲーのバナーにしか見えねえ。(2015/9/8現在)