見出し画像

万能!ドロップシミュレータで複数PJのQAコストを激減させた件について

こんにちは。エンジニアのふるやです🙃
今回は ドロップシミュレータで複数PJのQAコストを激減させた件について 綴らせていただきます。

経緯

私が所属しているプロジェクトでは、
「日々の売上向上を目的とした施策」に加えて、
「各セクションのコスト削減を考えていこう」とPMが舵を切りました。
そのコスト削減案の一つとして「QAコスト削減」が挙がりました。
後に品質管理部長を含めたMTGにて、ドロップシミュレータが最も効果的!
という結論になり、同ツールを開発することになりました。

開発の際に意識・工夫した点

・工数はできるだけ抑える
・他案件への移植を想定した汎用的な作り
・可読性、保守性

実際のシミュレート画面

「※」マークは入力必須項目です。

「実行」ボタンより下は「実行結果」です。

スクリーンショット 2021-07-20 6.58.01

画面の説明(1)「スキーマ名」

スキーマ名はプルダウンで選択できるようにしています。
デフォルトは最も使用頻度の高いスキーマを選択中にしています。

画面の説明(2)「テーブル名」

参照したいテーブル名を入力します。
例えば、ガチャの排出データをシミュレートしたい場合は排出マスタを、
イベントの発生パターンを確認したい場合は該当のイベントマスタを
入力します。

画面の説明(3)「イベントID」

テーブルによってはイベントIDを指定しているものがあります。
その場合は入力します。数値のみ入力可能にしています。

画面の説明(4)「ステップ数」

ガチャの場合はステップ数を入力します。
数値のみ入力可能にしています。

画面の説明(5)「実行回数」

実行したい抽選回数を指定します。
処理的に特に制限する必要もないのですが、
上限値は内部的に10,000回にしています。
それより大きい数値を入力した場合は10,000回に補正されます。

画面の説明(6)「確率カラム名」

抽選に使用するカラム名を指定します。
指定しない場合、サーバー側であらかじめ定数化した値を参照します。
DESCRIBEコマンドでカラムの存在を確認しています。

ポイント:
 案件やテーブルによって「アイテムID、確率、個数」のカラムが
 異なる場合があります。
 「検索するカラム一覧」にないカラムは随時追記し、
 汎用性を高めています。

/**
* シミュレート実行
*/
public function execSimulateAction()
{
  /* 省略 */

    // カラムの存在を確認し、確定させる
    $itemIdColumn = self::fixColumn($schemaName, $tableName, 'item_id');
    $rateColumn = self::fixColumn($schemaName, $tableName, 'rate', $rateColumnName);
    $numColumn = self::fixColumn($schemaName, $tableName, 'num');

  /* 省略 */    
}


/**
* カラムの存在を確認し、確定させる
*/
private static function fixColumn ($schemaName, $tableName, $type, $rateColumnName=null)
{
   // 検索するカラム一覧
   $columnList = array(
                   "item_id" => array('item_id', 'get_reward_id', 'prize_id'),
                   "rate" => array('rate', 'wait', 'weight'),
                   "num" => array('num', 'item_num', 'count', 'amount'),
               );
               
   // カラム名の指定がある場合はそれを返す
   if (!empty($rateColumnName)) {
       $sql    = "DESCRIBE " . $schemaName . "." . $tableName . " " . $rateColumnName;
       $array  = array();
       $retData = App::getDb()->fetchAll($sql, $array);
       if (empty($retData)) {
           return null;
       }
       return $rateColumnName;
   } else {
       foreach ($columnList[$type] as $column) {
           $sql    = "DESCRIBE " . $schemaName . "." . $tableName . " " . $column;
           $array  = array();
           $retData = App::getDb()->fetchAll($sql, $array);
           if (!empty($retData)) {
               return $column;
           }
       }
       return null;
   }
}

画面の説明(7)「カスタム1カラム名」

「タイプ1の排出を確認したい」など、
複数のタイプを内包するテーブルがあります。
この「タイプ」を指定する時に使用します。
「カラム名」を指定します(具体的にはWHERE句の左辺の値です)
二個目が必要になった時にもすぐ追加できるようシンプルな造りにし、
可読性を意識しています。

// 条件を整理、追加する
$whereList = array();
if (!empty($evId))                  $whereList[] = array("ev_id"                => (string)$evId);
if (!empty($stepId))                $whereList[] = array("step_id"              => (string)$stepId);
if (!empty($custom1ColumnName))     $whereList[] = array($custom1ColumnName     => (string)$custom1Value);
if (!empty($custom2ColumnName))     $whereList[] = array($custom2ColumnName     => (string)$custom2Value);
if (!empty($whereList))  $sql .= " WHERE";
$i = 0;
foreach ($whereList as $where) {
   foreach ($where as $key => $val) {
       if ($i > 0) $sql .= " AND";
       $sql .= " t." . $key . " = '{$val}'";
       ++$i;
   }
}

画面の説明(8)「カスタム1値」

上記(7)の「値」を入力します。
具体的にはWHERE句の右辺の値です。

画面の説明(9)「単価」

アイテムを獲得する為の「期待値」を計算したい場合は入力します。

画面の説明(10)「SQL」

実行時のSQLを表示させています。

画面の説明(11)「合計課金額」

実行回数 × 単価 の値を表示しています。
これだけ使って◯◯◯◯が出た!」等、体感できます。

画面の説明(12)「実行結果」

ページ下部に実行結果の表を表示しています。
獲得した アイテムIDアイテム名排出個数排出割合期待値
を表示させています。
「アイテム名」を押下すると「アイテム詳細」を開くようにしています。

所感

条件は「=」のみにしていますが不等号もありかなと思ったり、
まだまだ改善の余地はあると感じています。
(が、一旦は現状でQAさん的には十分とのことなのでこちらで終えました)

僅か4h程度QAコストを激減できたのは大きかったです。
(詳細は控えますが現場のインパクトは大きかったとのことです)
移植もコピペで一瞬で完了するのでやってよかったと思います。

デバッグツール開発に言えることですが、言語仕様を学びつつ
イベントや定常で使用されている主要なコードを大まかに整理できるので、
OJTの素材に良さそう!と思いました。
機会があれば検討しようと思います!

ツールを作っている時の ”楽しさ” ってなんでしょうね😊


以上、エンジニアふるや(@h_furuya_)が綴らせていただきました。