LoginSignup
13
3

More than 1 year has passed since last update.

【Ruby】麻雀の点棒を値オブジェクトにしてみた🀄️

Posted at

はじめに

この記事は、麻雀大好きな新米エンジニアが見よう見まねで点棒(麻雀における通貨みたいなもの)を値オブジェクトにしてみたという記事です。暇つぶし程度に読んでもらえたら嬉しいです。
まず簡単に「そもそも値オブジェクトって?」という話をしてから、実際に自分が書いてみた麻雀の点棒オブジェクトを紹介したいと思います。
(※ 麻雀への知識がある前提で書かれた記事です。何もわからないという方は、「値オブジェクトとは」の項目だけ読むことをお勧めします。)

値オブジェクトとは

値オブジェクトとは、値をクラスとして表現する設計パターンのことです。
より詳しく言えば、アプリケーションで頻繁に使われる金額や日付、電話番号といった値をクラスとして扱い、各値の正常性を初期化段階で担保しながら値そのものとその振る舞いをまとめて管理することです。

例えば金額を単なるint型の変数として扱った場合、金額用の変数に負の値が入力されてしまったり、「販売数」など別のint型を金額用の変数に加算してしまうといったミスが起こり得ますが、金額を値オブジェクト化することでこのようなミスを防ぐことができます。
また、税込計算メソッドなど値に持たせたい振る舞いを凝集度(*1)高く持つことができます。

上記のように何かと便利な値オブジェクトですが、実装する際には以下の特徴を実装しなくてはならない点に注意が必要です。

  • 値が不変である
  • 交換可能
  • 値の等価性によって比較される

※ 参考資料

点棒オブジェクト

サンプルコード

# frozen_string_literal: true

#
# 点棒
#
class Counter
  include Comparable

  attr_reader :point

  # ゲーム開始時の点棒
  DEFAULT_POINT = 25000
  # 順位点処理として、4位3位2位がそれぞれ1位に対して支払う点棒(オカ)
  OKA_POINT = 5000
  # 順位点処理として、4位が1位に支払う点棒(ウマ)
  UMA_POINT1 = 30000
  # 順位点処理として、3位が2位に支払う点棒(ウマ)
  UMA_POINT2 = 10000

  def initialize(point = DEFAULT_POINT)
    # 最小の点棒が100点なので、100で割り切れない値は不正とする
    raise ArgumentError, '不正な点棒です' if point % 100 != 0

    @point = point
    freeze # 不変にする
  end

  # 値オブジェクトは不変なので、オブジェクトを交換する形で足し算を実装する
  def add(other)
    Counter.new(point + other.point)
  end

  # 引き算も同様
  def subtract(other)
    Counter.new(point - other.point)
  end

  # 値の等価性で比較する
  def <=>(other)
    point <=> other.point
  end

  # 着順に応じて順位点処理を行う
  def add_rank_point(rank)
    case rank
    when 1
      rank_point =  UMA_POINT1 + OKA_POINT * 3
    when 2
      rank_point =  UMA_POINT2 - OKA_POINT
    when 3
      rank_point = -UMA_POINT2 - OKA_POINT
    when 4
      rank_point = -UMA_POINT1 - OKA_POINT
    else
      raise ArgumentError, '不正な着順です'
    end
    Counter.new(point + rank_point)
  end
end

サンプルコード詳細

attr_reader

値を不変としたかったので、setterのみを定義するattr_readerで実装しています。

initialize

麻雀の点棒は100の位までしか存在しないので100で割り切れない引数に対してエラーを吐くようにしました。金額オブジェクトなどを実装する際は、同様にinitialize内で負の値に対してバリデーションをかけるといいと思います。
また、不変にするためにfreezeをかけています。

<=>

値の等価性で比較するために記述しています。
この記述がないと同値のオブジェクトであってもfalseと判定されてしまいます。

Counter.new(100) == Counter.new(100)
#=> false

# <=>メソッド定義後
Counter.new(100) == Counter.new(100)
#=> true

add_rank_point

着順に応じた順位点処理を記述しています。このように値そのものと値の振る舞いをまとめて記述出来ることが値オブジェクトのいい点です。
具体的な内容については特に見なくても大丈夫です。

感想

実務で値オブジェクトを書く機会が今までなく、初めて書いたのですがなかなか面白かったです。もっとこうした方がいいという意見があれば是非コメントお願いします。
足し算と引き算をオブジェクトを受け取るシンプルな形で実装しましたが、麻雀は動く点数が翻数と符によって確定するので引数に翻数、符、供託を受け取って計算した方が点数の正当性は確保できたかもしれません。もし機会があればいつか書いてみます。
Mリーグも開幕です!麻雀の熱上げていきましょう🔥

13
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
3