Posts for: #Game

なにも手につかない休日

1時までオフィスで作業して3時に寝てやや吐き気で起きて9時前に起きた。夕方から「翔んで埼玉」の1作目をみた。想像した雰囲気とはちょっと違っていた。私が関東圏に関心がないからそれほど親近感はなかった。

呪術廻戦のスマホゲーム

コロナ禍明けから広告系の会社の株価が落ちている。サイバーの23年9月期、純利益78%減「ウマ娘」失速 にあるように、サイバーエージェントはとくにウマ娘のヒットが落ち着きあるようで、さらに株価が落ちていた。直近の高値2,441円から 半値八掛け二割引 で算出すると781円になる。

2441 * 0.5 * 0.8 * 0.8 ≠ 781 円

1年ぐらい前からサイバーエージェントの株価もたまたま観測していた。この計算式はだいたい1/3ぐらいと覚えていたので800円台が底値かなと目安にみていて、いま計算し直したら781円だった。そろそろ底値付近かなと少しずつ買っていた。直近の約定日の時系列の推移が次になる。

日付始値高値安値終値出来高調整後終値
2023-07-27959970900.4923.833,373,800923.8
2023-09-08900901.9851.7854.826,553,700854.8
2023-09-22790.6812.1787.4802.78,628,700802.7
2023-10-20782785.3775777.93,993,600777.9
2023-10-27768.7774.7758.2773.25,453,700773.2
2023-11-10815.6815.6792.4810.76,552,700810.7
2023-11-16847850.3824.2831.96,180,300831.9
2023-11-17831.9833.9817.3819.65,098,200819.6

ここ2-3日で気付いたら評価益が増えているなと気付いて調べたら 呪術廻戦 ファントムパレード というサイバーエージェントのグループ企業がゲームをリリースしてランキングが1位になっているらしい。一応チェックするかと思って、午前中はこのゲームをダウンロードして2-3時間やってみた。現時点ではソーシャル要素はほとんどなく、原作に忠実なキャラクターゲームという構成になっている。ストーリーのムービーはアニメの台詞をそのままをゲームの映像にあわせて流しているようなもの。私は原作をみたことがあるからスキップするけど、知らない人たちはこれをみながらストーリーを進めていくのだろうか。

全体として原作ファンならそこそこ楽しめると思う。五条悟が最強で領域展開しない限り攻撃が当たらない、しかし最強なので7ターンしたら戦闘離脱 (戦闘不能) するという設定になっている。ある程度、強い敵と連続的に戦闘しないといけないときは途中離脱してしまうのでそれでゲームバランスを調整している。最初のガチャだけ引き直しが出来て、自分の好きなキャラが出るまでやり直せる。それで五条悟が出るまで粘った。20回ぐらい引き直しした。

戦闘シーンもよく出来ているし、ムービーや声優さんの台詞もあり、大きな欠点のない普通のゲームといった印象。原因はわからないが、タップの操作を受け付けなくなるときがちらほらある。通信でブロックしているのか、イベントを処理できていないのか。プレイヤーだけでなく、アイテムやスキルにレベルを上げる要素があって、完凸を目指すような人は2-3年はやり続けるのかもしれない。ガチャで取得した五条悟の必殺技は術式反転「赫」になる。半年後のガチャでは虚式「茈」も実装されるのだろうと推測がつく。たしかに茈の戦闘シーンをみてみたい。出足からランキング1位でアクティブユーザーが多いなら予算もついて今後もゲームのコンテンツがパワーアップしていく可能性は高い。大ヒットしないまでも、普通のヒット作になって2-3年は続くのではないかと思えた。一方で私のような、ゲームも呪術廻戦の漫画 (アニメ) もにわかファンには、レベルをずっと上げ続けないといけないゲームはしんどいので、課金しないならそのうちに離脱してしまうと思う。時間がないから継続できない。

たまたまなのか、トレンド転換としてもよいタイミングなので順張りしながら様子をみてみようと思う。

戦略シミュレーションと特性

ストレッチ

今日の開脚幅は開始前158cmで、ストレッチ後161cmだった。先週よりやや数字が悪くなった。それでも先週はストレッチを受けていても体全体のだるさのようなものを感じていたのが、今回はちゃんと筋を伸ばしている感覚があって先週の体調の悪さはなくなったように思える。右股関節周りの詰まりに加えて、右前ももの張りがいつもより大きかった気がする。毎週ストレッチを受けていると物理的な体調の良し悪しもわかるのがよい。

わざと負ける理由

一昨日からリアルタイム対戦 にはまっている。数をこなしていてわかってきたことがたくさんある。戦略シミュレーションゲームはやればやるほど学びがある。

  • パーティーの特性によって相性のよい対戦相手とそうじゃないのがある
    • リアルタイム対戦のマッチングは選択できないのでマッチングしないと相手がわからない
    • 相性の悪い相手だとマッチング時点で辞退する (負けを認める) 相手もいる
  • こっちの戦略に呼び込むための布石がいる
    • 3つぐらい戦略を用意して最終的にどの戦略に呼び込むかを相手の動きをみながら考えないといけない
    • キャラを動かす制限時間が15秒なのでわりと忙しい
    • こちらの布石にはめて最終的に勝てると達成感がある
      • 中盤まで負けていて向こうに勝ったと思わせて逆転できるとなお嬉しい
      • ダークドレアム は奇数ターン (1, 3, 5, 7 …) ごとに攻撃力・守備力・すばやさ・かしこさが1段階上がるバフがかかる
        • ダークドレアムはすばやさが低いので徐々にすばやさが上がっていって先制できるようになると最後の1手違いで勝つ状況が出来上がる
        • ダークドレアムで5ターンまで戦闘を継続できれば勝率が高い
  • すばやさの高いパーティーには何もできないから勝てない
    • 高すばやさ (且つ、高火力または状態異常) パーティーには絶対に勝てない
      • リアルタイム対戦でダークドレアムがあまり使われていない理由だと思う
    • 初手前にパーティーの半分ぐらいがやられている
    • 相手の攻撃が届かない初期配置がないので絶対に防げない
  • 状態異常攻撃の主体パーティーは対戦していておもしろくない
    • 混乱・麻痺・眠り・魅了といった状態異常になると2ターン何もできない
    • 状態異常攻撃 + 高すばやさのキャラの攻撃を防ぐ方法はない
  • パーティー編成のウェイト制限がうまく調整されている
    • 高ランク (ステータス高い) のキャラばかりを編成できないようにウェイト制限がある
    • これにより、パーティー編成が4人か5人かにわかれる
    • 低ランク (ステータス低い) のキャラはウェイトも低いので5人目には入れやすい

本題のわざと負ける理由だが、わかってきた。リアルタイム対戦のマッチングは同じランク内で行われる。上位のランクになればなるほど、対戦相手も強くなる (強くなければ上位のランクに上がれない) 。一定量のポイントを獲得するとランクアップしていけるが、負けるとポイントが下がるペナルティがつく。一定のランクで勝ち負けを繰り返すとそのランクに留まり続けるということができる。私も自分の限界までランクアップしてみて気付いたのは周りも強いので限界に到達すると勝ったり負けたりを繰り返す。ここで別のルールで勝った回数に応じてコインが支払われるボーナスがある。強い人が低いランクで勝ち数を稼ぐのは限界に近いランクで勝ったり負けたりを繰り返すよりもはるかに効率がよい。それはリアルタイム対戦のマッチングに時間制限があるからなおさらそうなる。実力が均衡した相手と時間をたくさん使って2勝3敗を繰り返すよりも、わざと負けながら弱い相手に勝ち続ける5勝?敗を繰り返す方が絶対数としての勝ち数を稼ぐには効率がよい。おそらく負けたときのペナルティがあるランクからわざと負ける人が現れ始めていると思う。なるほどなぁ。

dto に対するリフレクションの是非

1時に寝て7時に起きた。

ドラクエタクトのリアルタイム対戦

もう2年間もずっとゲームし続けている。最近 リアルタイム対戦 モードがリリースされた。全然やる気なかったんやけど、リアルタイム対戦で得たコインでもらえるアイテムが魅力的なのでやることにした。そして、実際にアルタイム対戦をやってみるとはまる。運営の手の平でゲームさせられている。

  • 人間が相手で狡猾な戦略で負けると悔しい
  • 人間が相手の戦略の方が創意工夫があって学びになる
  • 実際にやり始めるとおもしろくなってきてずっとやってしまう

できる時間を制限しているというのもうまいやり方だなと思っている。朝・昼・晩の7-9時、12-14時、19-22時に制限している。そこまでしてリアルタイムに人間同士をマッチングしてゲームさせる必要があるのか?という素朴な疑問に私は辿りつくが、おそらくゲーム開発者からみたらそうじゃない大事なユーザー体験があるのだろうと推測する。あと不思議なことが1つ。マッチングしていると、たまにわざと負けてくれる人がマッチングされる。チームのメンバーが1人で向こうが先行なら戦いを辞退 (こちらの勝ちになる) するし、こちらが先行でもすぐにやっつけられる。ちゃんと統計をとってないけど、20回に1回ぐらいの頻度でわざと負けてくれる人とマッチングする。あの人たちは一体どういう理由でわざと負けているんだろう?

リフレクションのユーティリティを作った

いまお手伝いで開発している api サーバーは外界と内部のデータの境界を明確にわけていて、外向けのオブジェクト定義と内部向けのオブジェクト定義が異なる。ほとんど同じデータであっても dto を介して値を受け渡ししないといけない。そうすると、次のような dto と他のオブジェクトとの値渡しのための処理が型ごとにあちこちに実装されている。

private MyRecord toMyRecord(MyDataInput in) {
    var record = new MyRecord();
    record.id = in.id
    record.name = in.name;
    record.someId1 = in.someId1;
    record.someId2 = in.someId2;
    record.someId3 = in.someId3;
    record.sortOrder = in.sortOrder;
    record.createUser = in.createUser;
    record.updateUser = in.updateUser;
    ...
}

メンバー数が20-30ぐらいあると、たまに値のセット忘れがあったり、あとから追加したメンバーの保守ができてないとか、たまにトラブルが起きる。これ自体は間違っているわけじゃなくて境界を明確にわけるメリットもあるのでプログラミングの煩雑さとトレードオフと言える。

最近、私が管理系の web api のエンドポイントを作る機会が多いせいか、dto と外部向けのオブジェクトを明確にわける必要のない要件もあったりする。試しにリフレクションを使って同名のフィールド間の値の受け渡しは自動でやってみたらどんな感じかな?と思って作ってみた。

public class ReflectionUtil {

    private ReflectionUtil() {
        throw new AssertionError("ReflectionUtil is a utility class");
    }

    private static <T> Field getField(Class<T> klass, String fieldName) {
        try {
            return klass.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            return null;
        }
    }

    public static <T1, T2> T2 mapFieldValues(T1 fromInstance, T2 toInstance) {
        var fromClass = fromInstance.getClass();
        for (var toField : toInstance.getClass().getDeclaredFields()) {
            toField.setAccessible(true);
            var fromField = getField(fromClass, toField.getName());
            if (fromField != null) {
                fromField.setAccessible(true);
                try {
                    toField.set(toInstance, fromField.get(fromInstance));
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return toInstance;
    }
}

これを使うと、先のコードがこれだけで済む。煩わしい値の受け渡しだけのコードを削減できる。

var record = ReflectionUtil.mapFieldValues(in, new MyRecord());

こんなことやると、セキュリティ的によくないとか反論されるかな?と思いながら pr を出してみたら思いの外、好評だったのでちょっと使ってみようと思う。