【Laravel】スタンプカード機能の設計:1日に何度も獲得できてしまう問題をどう防ぐか(仕様編)

スタンプを一日に何度も獲得できてしまう問題をどう防ぐかについての記事アイキャッチ プログラミング
スポンサーリンク

学習の備忘録です。タスク管理アプリを作っています。

今回は、スタンプ獲得機能を実装する際の仕様について、その考え方を思考の整理がてら書いていきます。

「実際にこういうコードで実装する」みたいなものではなく、考え方の記事なので具体的なコードはありません。(そういう実装手順についてはまた後日記事にするつもりです。)

現状でのスタンプ獲得の仕様については以下です。

  • 達成したタスク数/その日の全体のタスク数*100で達成率を割り出す
  • 達成率100%ならスタンプ獲得
  • スタンプが10個溜まったら「ごほうびチケット」を獲得

本記事は個人の学習記録です。内容の正確性を保証するものではありませんので、あらかじめご了承ください。

1日に何度も獲得できてしまう問題

一日のタスクの達成率は、現状以下のように計算しています。

    //達成率計算
    public static function calAchievementRate($tasks) {
        $total = $tasks->count();

        if ($total === 0) {
            return 0;
        }

        $completed = $tasks->where('is_completed', 1)->count();

        return (int)floor(($completed / $total) * 100);
    }

要するに、その日一日のタスクの合計を出して、そのうち達成しているタスク(is_completed)がいくつあるかで割合を出しています。

そして達成率が100%になったらスタンプを獲得できる仕組みです。

ただ、スタンプの獲得については制御をかけないといけません。

達成率100%になった時点でスタンプ獲得させると、以下のような問題が起きてしまいます。

①最初に決めていたタスクを全部完了させてスタンプ獲得

まずは一番最初に入っているタスクを完了させます。

以下の画像では、毎日自動で登録される「日課タスク」を完了させた状態です。タスクが1つしかないので、それを完了させた時点で達成率は100%になります。

達成率100%時点でスタンプ獲得の設定にしてしまうと、この時点でスタンプを1つ獲得することになります。

②追加分のタスクを完了させることでスタンプ獲得

現状では、当日のタスク変更は「編集チケット」の消費が必要です。

ただ、「当日の日付で、タスクが日課タスクしかない場合は、変更チケットの消費なしでタスクを追加できる」という仕様になっています。

よって、もしも前日にその日のタスクを登録していない場合は、「タスクを登録する」ボタンから、編集チケットの消費なしでタスクを登録できます。

すると全体のタスク数は3つになるので、達成率は33%となります。

そうやって増やしたタスクを全部完了させると、再び達成率が100%になり、またスタンプが獲得できます。

③変更チケットが続く限りスタンプ獲得ができる

その後も、編集チケットが続く限り達成率の操作ができるので、一日に何度もスタンプを獲得することができます。

スタンプを10個獲得したら「ごほうびチケット」を獲得できますが、1日に何回もスタンプを獲得できるなら、ごほうびチケットも簡単に手に入ってしまいます。

タスク管理アプリは「毎日継続して頑張っていく」という感じにしたいので、こういうスタンプの獲得方法は趣旨に合いません。

ということで、「1日に何度もスタンプを獲得できないようにする」という仕様で設計していく必要があります。

そこで、とりあえず以下の3つの方法を考えました。

1.深夜のバッチ処理で達成率を確認する

たとえば日付が変わった瞬間(00:00)に達成率を確認して、もしも100%だったら、次にアプリが開かれた時にスタンプを獲得します。

「〇日のタスク完了! おめでとう!」などの演出と共に、スタンプカードにスタンプが押されるアニメーションが入るイメージです。

メリット

  • システム的にデータの矛盾が起きない
  • アプリを開いた瞬間に褒められることで、継続のモチベーションに繋がる

デメリット

  • 全タスク完了時点(リアルタイム)でスタンプ獲得できないので、もどかしい部分も

2.1日1個以上スタンプ獲得できないようロックをかける

最初に達成率が100%になってスタンプを獲得したら、それ以降に達成率が変動して再び100%になっても、スタンプが獲得できないようにします。

メリット

  • リアルタイムの達成感を維持できる

デメリット

  • 仕様の矛盾(矛盾したデータ)が生まれやすい
    「今日の分のスタンプは獲得したのに、その日の達成率は33%しかない」みたいな事態になる。見た目ちょっと気持ち悪い。

3.「タスク完了」ボタンを付ける

画面下部などに「タスク完了」ボタンを設置し、タスクが100%になったらユーザー自身がそのボタンを押下し、その日のタスクの完了を確定します。自己申告方式です。

  1. 達成率が100%になるまで、ボタンはグレーアウトさせる(クリックさせない)
  2. 達成率が100%になったら押下でき、「完了しますか?」モーダルが開く
  3. 「はい」を押したらスタンプ獲得
  4. 一度完了させたら「タスク完了」ボタンは非表示にする。また、編集ボタンもグレーアウトさせる(完了後に達成率を操作できないようにする)

こんな感じで制御します。

メリット

  • 自分の手で行うので達成感を得やすい
  • タスク追加との相性がいい
    ※ユーザー自身が、「まだ後でタスクを増やすかもしれないから、完了ボタンは夜に押そう」とコントロールできる

デメリット

  • 達成率100%でもタスク完了の申告を忘れる場合があり、やる気低減に繋がる

まとめ:深夜のバッチ処理方式を採用

3つの方法の中で、私はバッチ処理方式での実装を選択しました。

理由としては、以下が一番大きいです。

  • 画面制御(ボタンの表示非表示など)の分岐が少ない
  • バグが一番起きにくい

処理の流れとしては、以下のようになります。

  1. 深夜00:00(バックエンド処理)
    昨日100%を達成したユーザーの「達成スタンプ数」を+1し、「スタンプ獲得のアニメーション演出をしたかどうか」の判定でfalseを入れる
  2. ユーザーの次回ログイン
    ユーザーがアプリを開いた際、コントローラーで「スタンプ獲得のアニメーション演出をしたかどうか」の判定を確認。
  3. フロントエンドでの演出
    もしも「スタンプ獲得のアニメーション演出をしたかどうか」の判定がfalseなら、画面が開いた瞬間に「昨日は達成率100%おめでとう!」などのモーダルを自動表示して、スタンプがポンッと押されるアニメーションを実行。
    その後「スタンプ獲得のアニメーション演出をしたかどうか」の判定をtrueに変更。

こんな感じで実装すれば、「1日1回だけスタンプを獲得する」という仕様を自然に実現できそうです。

また、これらの実現のためにはスタンプ獲得の履歴テーブルが欲しいので「stamp_histories」などのテーブルを新規で追加するつもりです。

実際に機能付けたら、それはそれで記事にしようと思います。

コメント

タイトルとURLをコピーしました