こんにちは、スタッフのやまさきです!

めっちゃ暑い日が続いたかと思っていたら、今度は急に涼しい日が続いたり…
体調を崩さないように気をつけるのも簡単じゃないですねー
これまでの記事では、Ruby Silver試験対策Webアプリの開発や本番環境への移行、AIを活用した品質改善、さらにログイン機能のダッシュボードへの統合やSEO対策について紹介してきました。
今回は、そのWebアプリに新たに経験値(EXP)とランキング機能を追加しました。
Ruby Silver試験の学習を続けていくためには、「問題を解いて正解する」という学習そのものに加えて、もう少し続けてみようと思える仕組みも重要ですよね。
そこで今回は、ゲームなどでおなじみの「経験値」という仕組みを学習に取り入れました。
単に正解数を競うのではなく、問題への挑戦そのものも評価することで、学習を継続するきっかけになることを狙っています。
一方で、今回の開発では機能そのもの以上に、既存ユーザーの学習履歴や本番データを壊さずに新機能を追加することにも重点を置きました。
学習すると経験値(EXP)が貯まる
今回追加したのが「経験値(EXP)」です。
問題に取り組むことでEXPを獲得し、学習を続けるほど累計EXPが増えていきます。
獲得できるEXPは、問題の難易度によって変わります。
| 結果 | 獲得EXP |
|---|---|
| 初級・正解 | +2 EXP |
| 中級・正解 | +4 EXP |
| 上級・正解 | +6 EXP |
| 不正解 | +1 EXP |
| 未回答 | 0 EXP |
ここで少し特徴的なのが、不正解でも+1 EXPになることです。
当初は不正解をマイナス評価する案もありました。
しかし、実際に学習サービスとして考えた場合、
「間違えたからポイントが減る」
という仕組みよりも、
「難しい問題にも挑戦した」
ことを評価する方が、学習の継続という目的には合っていると判断しました。
そのため、最終的には不正解によるペナルティを廃止し、挑戦したこと自体を+1 EXPとして評価する仕様に変更しました。
「正解数」ではなく「学習への挑戦」を評価
今回のEXP設計では、単純に正解数だけを競う仕組みにはしていません。
例えば、上級問題に挑戦して正解すれば+6 EXP。
一方、難しい問題に挑戦して不正解だったとしても+1 EXPです。
つまり、
「正解できた」ことだけではなく、「学習に取り組んだ」ことも評価する
という考え方です。
これは今回のゲーミフィケーション機能を設計するうえで、かなり重要なポイントになりました。
学習サービスの場合、ランキングを競うこと自体が目的になってしまうと、本来の「学習する」という目的から外れてしまう可能性があります。
そのため、競争を促しつつも、まずは継続して問題に取り組むことを肯定する仕組みを目指しました。
1問1答だけでなく、模擬試験もEXPの対象に
EXPの獲得対象は、通常の「1問1答モード」だけではありません。
実際の試験を想定した「模擬試験モード」も対象にしました。
これによって、
- 普段の学習
- 試験形式での実践
- 苦手分野の復習
など、さまざまな学習方法をEXPとして一つに集約できます。
また、複数選択問題についても注意が必要でした。
選択肢を複数選ぶ問題では、選択肢ごとに処理すると、1問の回答に対してEXPが複数回加算されてしまう可能性があります。
そこで、複数選択問題についても1問につき1回だけEXPを加算する仕様にしました。
こうした細かな仕様を事前に整理しておくことで、問題形式によってEXPの獲得量が不公平にならないようにしています。
ダッシュボードに現在のEXPと順位を表示
獲得したEXPはダッシュボードから確認できます。
表示するのは、
- 累計経験値
- 全体順位
です。
例えば、
1,250 EXP 全体 8位 / 42人中
というように、自分が現在どの位置にいるのかを確認できます。
学習の成果を数値として確認できるため、
「前回よりEXPが増えた」
「あと少しで順位が上がる」
といった、学習を続けるための小さな目標を作ることができます。
チーム内ランキングも追加
チームに所属しているユーザーについては、全体ランキングだけでなくチーム内ランキングも表示するようにしました。
チーム内ランキングは上位5名までを表示し、同じEXPの場合は同順位としています。
さらに、ゲームらしさを出すため、上位3位にはメダル表示も追加しました。
ランキング画面を見たときに、
- 1位
- 2位
- 3位
が直感的に分かるようにしています。
ただし、ランキングを大量に表示すると「自分がどこにいるのか」が分かりにくくなります。
そのため、チームランキングについては上位5名に絞ることにしました。
既存ユーザーの学習履歴も無駄にしない
今回の実装で特に重要だったのが、既存ユーザーへの対応です。
新機能を追加した時点からEXPをカウントするだけであれば、比較的簡単に実装できます。
しかし、このサイトにはすでに学習しているユーザーがいます。
そのユーザーたちは、これまでにも多くの問題を解いています。
そこで、
「ランキング機能を追加した日からの学習だけをEXPとして計算する」
という方法ではなく、これまでの学習履歴からEXPを遡って計算することにしました。
これをバックフィル処理として実装しています。
過去の回答データをもとに、
- 問題の難易度
- 正解・不正解
- 回答履歴
などを確認し、これまでの学習実績をEXPに変換します。
これによって、既存ユーザーもこれまで積み重ねてきた学習をランキングに反映できます。
新機能を追加したことで、
「今まで頑張って勉強してきた分が全部リセットされる」
ということがないようにしました。
実装前に、あえて仕様を細かく確認
今回の開発では、コードを書き始める前の仕様確認にも時間をかけました。
例えば、
- 1問1答だけをEXP対象にするのか
- 模擬試験も対象にするのか
- 不正解は減点するのか
- 不正解でもEXPを付与するのか
- 複数選択問題はどう扱うのか
- 未回答はどう扱うのか
- 既存ユーザーの過去データをどうするのか
- チームランキングは何人まで表示するのか
- 同点の場合はどうするのか
などです。
こうした仕様は、最初からすべて明確になっているとは限りません。
今回も実装を進めながら仕様を整理し、最終的なルールを決めていきました。
特に、
「不正解=マイナス」から「不正解でも挑戦したことを評価して+1 EXP」へ変更したこと
は、機能の目的を考え直した結果です。
技術的に実装できるかだけではなく、その機能によってユーザーにどのような行動をしてもらいたいのかを考えることが重要だと改めて感じました。
本番DBを確認すると、思わぬ違いが見つかった
今回、もう一つ重要だったのが本番環境への反映です。
ローカル開発環境では問題なく動作していても、本番環境にそのまま反映できるとは限りません。
実際に本番DBのスキーマを確認したところ、ローカル開発環境と本番環境でカラム構成に違いがあることが判明しました。
これは非常に重要な発見でした。
もしローカル環境のDB構造を前提に修正を行い、そのまま本番へ適用していたら、既存機能に影響する可能性があります。
特にこのサイトには、すでに利用されている模擬試験機能があります。
新しいランキング機能を追加したことで、既存の模擬試験機能が動かなくなってしまっては意味がありません。
そこで、本番環境の実際のスキーマを確認し、本番DBの構造に合わせて実装を修正しました。
「動けばOK」ではなく、既存機能を守る
新機能を追加するときにありがちな問題が、
「新機能は動くけれど、既存機能が壊れている」
という状態です。
今回のEXPランキング機能でも、単純に新しいカラムを追加して処理を書くだけなら、それほど難しい実装ではありません。
しかし実際には、
既存のデータ構造や既存機能との関係を確認したうえで追加する
必要があります。
そのため、本番環境へ反映する前に、
- 本番DBのスキーマを確認
- ローカル環境との差異を確認
- 既存機能への影響を確認
- SQLやPHPコードを調整
- ローカル環境でテスト
- 本番環境へ反映
という手順で進めました。
地味な作業ですが、実際のWebサービスを運用するうえでは非常に重要な部分です。
DB変更用SQLは「何度実行しても安全」に
今回、データベースの変更や過去データの移行に使用するSQLについても工夫しました。
ポイントは冪等性(べきとうせい)です。
簡単に言えば、
同じSQLを何回実行しても、最終的な結果が同じになる
ように設計しました。
例えば、
「一度実行したから、もう二度と実行してはいけない」
というSQLでは、作業手順を間違えたときのリスクが高くなります。
一方で、何度実行しても同じ状態になるようにしておけば、
- 手順を間違えて再実行した
- 本番環境で再度実行する必要が出た
- デプロイ手順をやり直した
といった場合でも、データが二重加算されるなどの事故を防ぎやすくなります。
特に今回は過去の学習履歴からEXPを計算するバックフィル処理があるため、データ移行処理を安全に繰り返せることを重視しました。
技術的にはシンプルなPHPで実装
今回の機能も、Laravelなどのフレームワークは使用していません。
既存のWebアプリと同じく、
- PHP
- PDO
- MySQL
を中心としたシンプルな構成です。
データベースにはEXPを管理するためのカラムを追加し、既存のコードベースの設計や命名規則に合わせてロジックを追加しました。
今回追加した主なDB項目は、
users.pointsexam_results.points_earned
です。
できるだけ既存機能への変更を少なくすることで、現在動いている機能への影響を抑えています。
本番反映前にブラウザで自動テスト
本番環境へ反映する前には、ローカル環境で自動ブラウザテストを実施しました。
実際のブラウザ操作を想定して、
- 問題を解く
- 正解・不正解を確認する
- EXPが加算される
- ダッシュボードに累計EXPが表示される
- ランキングが表示される
- チームランキングが表示される
といった一連の動作を確認しました。
コードだけを見て「大丈夫そう」と判断するのではなく、実際のユーザー操作に近い形で確認することを重視しています。
リリース後も仕様は改善していく
今回の機能は、一度リリースして終わりではありません。
実際に使ってみた結果やフィードバックをもとに、いくつかの仕様を調整しました。
例えば、当初は「ポイント」と呼んでいたものを、サイト全体のゲーム的な世界観に合わせて「経験値(EXP)」に統一しました。
また、不正解時の-1点というペナルティを廃止し、+1 EXPへ変更しました。
さらに、ランキングの表示についても、情報量が多くなりすぎないように整理しています。
このように、
実装 → 利用 → フィードバック → 仕様調整 → 再実装
というサイクルを回しながら、サービスを改善しています。
ゲーミフィケーションは「ゲームにすること」が目的ではない
今回の機能を実装していて改めて感じたのは、ゲーミフィケーションの目的は、単純にWebサイトをゲームっぽくすることではないということです。
今回のRuby Silver試験対策サイトで目指しているのは、
「もう1問やってみよう」 「昨日よりEXPを増やしてみよう」 「次はこの順位を目指そう」
という小さな動機を作ることです。
試験勉強は、短期間で一気に取り組むよりも、毎日少しずつ継続することが重要です。
そのため、EXPやランキングを使って学習行動を可視化し、継続するきっかけを作ることには意味があると考えています。
もちろん、ランキングが苦手な人もいるため、競争だけを強調するのではなく、自分自身の学習量や成長を確認できる仕組みとして活用できることも意識しています。
今回の開発で大切にしたこと
今回のEXPランキング機能では、表面的には「ランキングを追加した」というシンプルな機能です。
しかし、実際の開発では、
- どの行動を評価するのか
- 不正解をどう扱うのか
- 複数選択問題をどう扱うのか
- 過去の学習履歴をどうするのか
- 本番DBとローカルDBの違いはないか
- 既存機能に影響しないか
- データ移行を安全に行えるか
- リリース後のフィードバックをどう反映するか
といった多くの検討が必要でした。
特に、新機能を作ることよりも、既存のサービスを壊さずに新機能を追加することの方が難しい場面があります。
今回、本番環境のDBスキーマにローカル環境との差異があることを事前に発見できたのは、その典型的な例でした。
まとめ
今回、Ruby Silver試験対策Webアプリに経験値(EXP)とランキング機能を追加しました。
今回のポイントをまとめると、
- 問題への挑戦でEXPを獲得
- 難易度に応じて獲得EXPを変更
- 不正解でも+1 EXP
- 1問1答・模擬試験の両方に対応
- 全体ランキングを表示
- チームランキングにも対応
- 既存ユーザーの過去の学習履歴をEXPへ反映
- 本番DBのスキーマ差異を事前に発見
- データ移行SQLを冪等な設計に
- ブラウザ自動テストを行ってから本番へ反映
という形で実装しました。
今回の開発では、ゲーミフィケーションという「ユーザー体験」の改善と、DBやデータ移行という「システムの安全性」の両方を考える必要がありました。
AIを活用することでコードを書くスピードは大きく向上しています。
しかし、「何を作るのか」という仕様の整理や、「本番環境で本当に安全なのか」という確認までAIに任せて終わり、というわけにはいきません。
実際のDB構造を確認し、既存機能への影響を考え、テストを行い、必要であれば仕様そのものを見直す。
こうした地道な確認を積み重ねながら、Ruby Silver試験対策Webアプリを少しずつ改善しています。
今後も、単に問題を解くだけではなく、「続けたくなる学習サービス」を目指して、機能改善を続けていきます。

