ランキング処理の汎用化をしてみました。

これは何のエントリ?

すごろくゲーム内のとある処理を汎用化するために、クラスの継承を利用したことについてのエントリです。
話が長くなったのでそれだけ先に書いておきます。
興味のある方は続きをどうぞ!
 

処理を汎用化するためにクラスを継承した話

すごろくゲームにはランキングの処理が実装されています。
ランキングメソッドの名前はこんな感じです。
 
  • make_ranking_of_finished_player():ゴールしたプレイヤーのランキング作成
  • make_ranking_of_unfinished_player():ゴールしていないプレイヤーのランキング作成
 
この名前の場合、ひとつ問題があります。名前にがっつりすごろく要素が入っているため、他のゲームを作るときに使い回しが効かないのです。ですがこれには事情がありました。
 

ランキングメソッドの名前が汎用的ではない事情

ランキングのアルゴリズムを調べたとき「なんて面白い計算なんだ!」と感動したので、いろんなゲームに使い回しできるようにしたかったのですが、名前をうまく決められませんでした。
 
汎用的な名前であれば、例えば「昇順ランキング作成」や「降順ランキング作成」となる気がします。実際最初はそういう名前にしていました。
ですがこの名前だと、すごろく的に何をやっているのかが直感的にわかりにくいのです。
 
すごろくは「進んだマス目が多いほど順位が高い」ですが、「ゴールした順が早いほど順位が高い」ものでもあります。
つまりゴールしていないプレイヤーは「進んだマス目の多さ」を比較し、ゴールしたプレイヤーは「かかったターンの少なさ」を比較する必要があります。降順と昇順のアルゴリズムを使い分けなければいけないのです。
 
そういう前提を踏まえた上で、すごろくゲームの中に「昇順ランキング作成」や「降順ランキング作成」といった名前のメソッドが出てきたとき、それぞれが何のための処理なのか、パッと見で理解できるでしょうか。自分は自信がありません。
 
実際のすごろくなら、誰でも盤面を見ただけで順位がわかります。昇順やら降順やらマス目の数やらターン数やらを意識している人はきっといないでしょう。
そんなときに予想もし得なかった名前のメソッドを突然出されて、混乱せずにソースコードが読めるのでしょうか。
 
自分は読めないと思ったので、素直に「ゴールしたプレイヤーのランキング作成」、「ゴールしていないプレイヤーのランキング作成」という名前のメソッドを作ったわけです。
 
ですがやっぱりランキングメソッドは汎用化しておきたい……! せっかく面白いアルゴリズムなのだから、いろんなところで使えるようにしておきたい……!
 
うんうんと悩んでいたのですが(というか問いが具体的になったのは答えが見えてからで、それまでは「なんとなく微妙だからどこかを変えたい」という感じだったのですが)、解決策を思いつきました。
 
汎用的なランキング処理のクラスを、すごろく用のランキング処理のクラスに継承させればよいのです。
 

実際にやったこと

変更前のプレイヤークラスとランキングクラスはこんな感じでした。

(今回の説明に関係ない部分は記載を省略しています)

変更前のランキングクラス

 
ランキング処理の内容を簡単に説明します。
 
ランキングの算出は、全プレイヤー同士を比較して、自分より成績のいい相手一人につき、自分の順位に1を足す(順位を一つ下げる)という処理になっています。
ゴールしたプレイヤーを比較するメソッドでは「finish_turn」というインスタンス変数を比較し、ゴールしていないプレイヤーを比較するメソッドでは、「position」というインスタンス変数を比較しています。
 
ゴールしていないプレイヤーのランキングを算出するメソッドで見ている「finished_players_size」という変数は、ゴールした人数を表す変数です。
「ゴールしたプレイヤーが二人いる場合、ゴールしていないプレイヤーの最高位は3位からになる」という感じで、ランキングの最高位を決めるために参照しています。
 
そして下記が変更したクラスになります。

変更後のランキングクラス

 
以下の点を変更し、改善しています。
 
  • ランキングメソッドの名前を、汎用的に使えるものに変更。
  • ランキングメソッドにはプレイヤーの配列とトップランクを渡すように変更。ランキングが何位からでも始められるようになった。
  • ランキングメソッド内で比較するものも「get_point」というメソッドにし、メソッドが返す値なら何でも比較できるように変更。
  • すごろくプレイヤークラスに「get_point」メソッドを追加し、ゴールしたプレイヤーは「finish_turn」、ゴールしていないプレイヤーは「position」を返すように変更。

 

これで今後、すごろくゲーム以外のゲームを作る際にも、ランキングの処理を使いまわすことが出来ます。やったー!!
いっそのことプレイヤーの親クラスも作って「get_point」メソッドを持たせても良かったかもしれませんが、今回はそこまでしていません。ブログ書きながら気付いたので、後日すると思います。
 
ランキングアルゴリズムは計算の仕組みに感動したので、いつでも使い回せるようになったのはけっこう嬉しいです。
 

所感

わかってしまえば簡単なもので、汎用化したクラスの継承は、別に特別な技術でも何でもありません。OPPの基本であるとさえいえます。ですが今までの自分には使えませんでした。
「教えられた知識を応用できるかどうか」は、深い理解を必要とする気がします。昨日までの自分は、知識として継承を知っていても、使えるレベルまで理解していなかったと言えるのかもしれません。逆に言えば、継承の知識は今回の件でしっかりと定着してくれたのでしょう。
 
このやり方は突然思いつきましたが、それは別に天才的なひらめきでも何でもなく、普段からOPPを用いたソースコードや、デザインパターンサンプルソースを読んでいたことが実を結んだのだと思っています。
アウトプットの方が大切だという考えはとりあえず変わっていませんが、インプットもやはり大切ですね。怠ることなく両輪を回していきたいと思います。
 

次にやってみたいこと

OPPといえば、privateやらprotectedやらpublicやらだとも思うのですが、すごろくゲームはまったく意識しないでコーディングしてしまいました。
今後はそのあたりもやってみようかな。
 
もっといいクラス分けの方法もまだまだありそうなので、そこももう少し考えていきたいです。