Skip to content

Latest commit

 

History

History
910 lines (701 loc) · 44.8 KB

tut_uml_modeling_bs04.adoc

File metadata and controls

910 lines (701 loc) · 44.8 KB

スコアやフレヌムの状態を調べる

蚭蚈で䜜成するモデルず実装に䜿うプログラムの察応づけができたたした。 この察応づけを前提に、ボりリングスコアのモデルの䜜成を進めたしょう。

フレヌムの状態に぀いお怜蚎する

[scoresheet01] を芋るず、フレヌムの衚瀺は、ゲヌムの進行状況によっお倉わっおいたす。

ゲヌムの進行状況によっおフレヌムの衚瀺は異なる
  • ただプレヌしおいないフレヌム

  • 1投目の投球を埅っおいるフレヌム珟圚のプレヌダヌの珟圚のフレヌムの1投目の前

  • 2投目の投球を埅っおいるフレヌム珟圚のプレヌダヌの珟圚のフレヌムの2投目の前

  • 2投目が投球されお獲埗ピン数が確定したフレヌム1,2投目のピン数ずトヌタルスコアが衚瀺されおいる

  • ストラむクのボヌナスが確定しないフレヌムストラむクの蚘録だけでトヌタルスコアは衚瀺されおいない

  • スペアのボヌナスが確定しないフレヌム1投目ずスペアの蚘録だけでトヌタルスコアは衚瀺されおいない

  • スペアたたはストラむクのボヌナスが確定したフレヌム1,2投目のピン数ずトヌタルスコアが衚瀺されおいる

それぞれに぀いお、もう少し詳しく芋おみたしょう。

ただプレヌしおいないフレヌム

ただプレヌしおないフレヌムは、フレヌムの最初の状態です ただプレヌしおいないフレヌム 。 このフレヌムは、ピン数の入力を埅っおいたせん。

{three-quarters-width}
Figure 1. ただプレヌしおいないフレヌム

1投目の投球を埅っおいるフレヌム

珟圚プレヌ䞭のフレヌムで、ただ1投目が投球されおいない状態のフレヌムです 1投目の投球を埅っおいるフレヌム 。 次にピン数を受け取るず、このフレヌムの1投目に蚘録されたす。

{three-quarters-width}
Figure 2. 1投目の投球を埅っおいるフレヌム

2投目の投球を埅っおいるフレヌム

珟圚プレヌ䞭のフレヌムで、1投目がストラむクでなかったずきに2投目を埅っおいる状態のフレヌムです 2投目の投球を埅っおいるフレヌム 。 次にピン数を受け取るず、このフレヌムの2投目に蚘録されたす。

{three-quarters-width}
Figure 3. 2投目の投球を埅っおいるフレヌム

スペアのボヌナスの確定埅ちのフレヌム

珟圚プレヌ䞭のフレヌムの前のフレヌムがスペアで、珟圚のフレヌムが1投目の投球を埅っおいるずき、前のフレヌムはスペアボヌナスの確定埅ちの状態です スペアのボヌナスの確定埅ちのフレヌム 。 次にピン数を受け取るず、珟圚のフレヌムの1投目に蚘録されるずずもに、前のフレヌムのスペアボヌナスが確定したす。

{three-quarters-width}
Figure 4. スペアのボヌナスの確定埅ちのフレヌム

ストラむクのボヌナスの確定埅ちのフレヌム

ストラむクボヌナスの確定埅ちには2通りの堎合がありたす。

1぀目は、珟圚プレヌ䞭のフレヌムの前のフレヌムがストラむクで、珟圚のフレヌムが2投目の投球を埅っおいるずきです。 このずき、前のフレヌムはストラむクボヌナスの確定埅ちの状態です ストラむクのボヌナスの確定埅ちのフレヌム次がストラむクでない 。 次にピン数を受け取るず、珟圚のフレヌムの2投目に蚘録されるずずもに、前のフレヌムのストラむクボヌナスが確定したす。

{three-quarters-width}
Figure 5. ストラむクのボヌナスの確定埅ちのフレヌム次がストラむクでない

2぀目は、珟圚プレヌ䞭のフレヌムが1投目の投球を埅っおいお、前のフレヌムず前の前のフレヌムがずもにストラむクであったずきダブルのずきずきです。 このずき、前の前のフレヌムはストラむクボヌナスの確定埅ちの状態です ストラむクのボヌナスの確定埅ちのフレヌム次がストラむク 。 次にピン数を受け取るず、珟圚のフレヌムの1投目に蚘録されるずずもに、前の前のフレヌムのストラむクボヌナスが確定したす。

{three-quarters-width}
Figure 6. ストラむクのボヌナスの確定埅ちのフレヌム次がストラむク

獲埗ピン数ずボヌナスが確定したフレヌム

珟圚のフレヌムがスペアやストラむクにならなかったずき、そのフレヌムはボヌナスの確定埅ちにならず、この段階でそのフレヌムのトヌタルボヌナスなしの獲埗ピン数が確定したす。

スペアボヌナスの確定埅ち、たたはストラむクボヌナスの確定埅ちのフレヌムは、埌のフレヌムの投球によっおボヌナスが確定するず、フレヌムのトヌタルが確定したす。 このずき、確定埅ちのフレヌムでは、そのフレヌムの獲埗ピン数ず確定したボヌナスの合蚈がそのフレヌムのトヌタルです。

フレヌムのトヌタルが求められるず、それ以前のフレヌムたでの「のべのトヌタル」にそのフレヌムのトヌタルを加算しお、のべのトヌタルを曎新したす。そしお、曎新したのべのトヌタルがフレヌムの䞋郚に蚘録されたす 2投目を投球しお、ピン数が確定したフレヌムのべのトヌタルが求められおいる 。 このずき、確定埅ちになっおいたフレヌムものべのトヌタルが曎新され、その段階のゲヌムのトヌタルも曎新されたす。

{three-quarters-width}
Figure 7. 2投目を投球しお、ピン数が確定したフレヌムのべのトヌタルが求められおいる

フレヌムの状態をステヌトマシン図で衚す

フレヌムの状態を怜蚎した結果、フレヌムはゲヌムの状況によっお倉わる耇数の状態を持぀こずがわかりたした。 たた、フレヌムが衚瀺できる情報も、状態によっお異なるこずがわかりたした。 このこずを、モデル図を䜿っお衚しおみたしょう。 状態ずその掚移状態遷移ず呌びたすを衚すには、ステヌトマシン図を䜿いたす。

「Frame」クラスにステヌトマシン図を远加する

「Frame」クラスの状態を衚す図を描きたいので、「Frame」クラスに図を远加したしょう。

「Frame」クラスにステヌトマシン図を远加する
  1. 「Frame」クラスにステヌトマシン図を远加する

{three-quarters-width}
Figure 8. 「Frame]クラスにステヌトマシン図を远加する
  1. ステヌトマシン図に名前を぀ける

    • 远加したステヌトマシン図のプロパティヌの「ベヌス」を開く。

    • 名前を線集しお「Frameクラスのステヌトマシン図」ずする。

    • ダむアグラム゚ディタのタむトルやタブにも反映される。

フレヌムのステヌトマシン図を䜜成する

ステヌトマシン図に、フレヌムの状態ず状態遷移を远加したしょう。

ステヌトマシン図に「Frame」クラスの状態遷移を䜜成する
  1. 「Frame」クラスの最初の状態を䜜成する 「Frame」クラスのステヌトマシン図に最初の状態を远加する 。

    • パレットから「開始疑䌌状態」を遞択し、ステヌトマシン図に远加する。

    • パレットから「状態」を遞択し、ステヌトマシン図に远加する。

    • 「ただプレヌしおいない」状態を「RESERVED」ずいう名前にする。

    • パレットから「遷移」を遞択しお、「開始疑䌌状態」から「RESERVED」ぞ遷移を匕く。

{full-width}
Figure 9. 「Frame」クラスのステヌトマシン図に最初の状態を远加する
  1. 最初の状態遷移ずむベントを远加する 「Frame」クラスのステヌトマシン図に1投目の前の状態遷移ずむベントを远加する 。

    • 「1投目の投球を埅っおいる」状態「BEFORE_1ST」を远加する。

    • 「RESERVED」から「BEFORE_1ST」ぞ状態遷移を匕き、むベント「SETUP」を割り圓おる。

{full-width}
Figure 10. 「Frame」クラスのステヌトマシン図に1投目の前の状態遷移ずむベントを远加する
  1. 1投目の埌の状態遷移ずむベントを远加する 「Frame」クラスのステヌトマシン図に1投目の埌の状態遷移ずむベントを远加する 。1投目の埌は、受け取ったピン数によっお遷移先は2通りあるストラむクか吊かので「遞択疑䌌状態」を䜿っお遷移先を分ける。

    • 「2投目の投球を埅っおいる」状態を「BEFORE_2ND」ずしお远加する。

    • 「スペアのボヌナスの確定埅ち」ず「ストラむクのボヌナスの確定埅ち」を「PENDING」ずしお远加する。

    • 「遞択疑䌌状態」を远加する。

    • ピン数を受け取るむベント「PINS」パラメヌタヌずしおピン数 pins を持぀を「BEFORE_1ST」から「遞択疑䌌状態」ぞの遷移に割り圓おる。アクションずしお、むベントで受け取ったピン数を1投目のピン数に保存する。

  2. 遞択疑䌌状態からの遷移を远加する。

    • 1投目のピン数がストラむクであれば「PENDING」ぞ遷移する。このずき2投目のピン数は「0」にする。

    • 1投目のピン数がストラクでなければ「BEFORE_2ND」ぞ遷移する。

{full-width}
Figure 11. 「Frame」クラスのステヌトマシン図に1投目の埌の状態遷移ずむベントを远加する
  1. 2投目の埌の状態遷移ずむベントを远加する 「Frame」クラスのステヌトマシン図に2投目の埌の状態遷移ずむベントを远加する 。2投目の埌は、受け取ったピン数によっお遷移先は2通りあるスペアか吊かので「遞択疑䌌状態」を䜿っお遷移先を分ける。

    • 「獲埗ピン数ずボヌナスが確定した」状態を衚す「FIXED」ずしお远加する。

    • 2投目の埌の凊理の遞択のために「遞択疑䌌状態」を远加する。

    • 「BEFORE_2ND」でむベント「PINS」を受け取るず、遞択疑䌌状態ぞ遷移する。アクションずしお、受け取ったピン数を2投目のピン数に保存する。

  2. 遞択疑䌌状態からの遷移を远加する。

    • 2投目のピン数がスペアであれば「PENDING」ぞ遷移する。

    • 2投目のピン数がスペアでなければ「FIXED」ぞ遷移する。

  3. 「PENDING」からの遷移を远加する。

    • 「PENDING」䞭のフレヌムで、むベント「DETERMINE」を受け取ったらトヌタルが確定したずし、「FIXED」ぞ遷移する。

{full-width}
Figure 12. 「Frame」クラスのステヌトマシン図に2投目の埌の状態遷移ずむベントを远加する

これで、フレヌムのステヌトマシン図が䜜成できたした。

フレヌムのクラス図を曎新する

ステヌトマシン図で状態遷移のために远加したアクションやガヌド条件甚の凊理を「Frame」クラスのメ゜ッドに远加しおおきたしょう ステヌトマシン図に合わせお「Frame」クラスを曎新する 。

{quarter-width}
Figure 13. ステヌトマシン図に合わせお「Frame」クラスを曎新する

フレヌムの凊理をプログラムに倉換する

フレヌムのクラスずステヌトマシン図が䜜成できたので、Rubyのプログラムに倉換しおみたしょう。 「[_model_to_code_design]」で決めたルヌルにしたがっお、モデルからコヌドぞ倉換したす。

たず、プログラムの初期化ずアクションの郚分は、 【Ruby】score.rb(1) のようになるでしょう。

【Ruby】score.rb(1)
# frozen_string_literal: true

require 'securerandom'

# Frameは1フレヌム分のピン数やボヌナスを蚘録する
class Frame
  attr_reader :frame_no
  attr_accessor :first, :second, :spare_bonus, :strike_bonus, :total, :state

  def initialize(frame_no)
    @frame_no = frame_no
    @first = 0
    @second = 0
    @spare_bonus = 0
    @strike_bonus = 0
    @total = 0
    @state = :RESERVED # (1)
  end

  def action(event, pins=0)
    case @state # (2)
    when :RESERVED
      case event
      when :SETUP # (3)
        @state = :BEFORE_1ST
      else
        puts "invalid event: #{event} is ignored."
      end
    when :BEFORE_1ST
      before_1st_porc(event, pins) # (4)
    when :BEFORE_2ND
      before_2nd_proc(event, pins) # (5)
    when :PENDING
      case event
      when :DETERMINE # (6)
        @state = :FIXED
      end
    when :FIXED
      puts 'fixed.'
    end
  end
end
  1. フレヌムの初期状態は「RESERVED」ずする。状態はRubyのシンボルを䜿っお衚珟する。

  2. 状態に応じお凊理を分ける。

  3. 「RESERVED」状態では「SETUP」むベントを受け取り、「BEFORE_1ST」状態ぞ遷移する。他のむベントが来たら無芖する。

  4. 「BEFORE_1ST」状態では「PINS」むベントを埅぀。詳现な凊理は「before_1st_porc」メ゜ッドに蚘茉する。

  5. 「BEFORE_2ND」状態では「PINS」むベントを埅぀。詳现な凊理は「before_2nd_porc」メ゜ッドに蚘茉する。

  6. 「PENDING」状態では「DETERMINE」むベントを受け取り、「FIXED」状態ぞ遷移する。

ガヌド条件やアクションのメ゜ッドは、 【Ruby】score.rb(2) のようになるでしょう。

【Ruby】score.rb(2)
# frozen_string_literal: true

require 'securerandom'

# Frameは1フレヌム分のピン数やボヌナスを蚘録する
class Frame
  # initialize、actionの定矩がここにある

  def frame_score # (1)
    @first + @second + @spare_bonus + @strike_bonus
  end

  def strike? # (2)
    @first == 10
  end

  def spare?
    @first < 10 && (@first + @second) == 10
  end

  def miss?
    @first < 10 && @second.zero && @state == :FIXED
  end

  def gutter?
    @first.zero
  end

  def fixed? # (3)
    @state == :FIXED
  end

  def to_s # (4)
    total = if @state == :FIXED
              @total
            else
              '   .'
            end
    format '|%2d|%3s|%3s|%5s|%11d|%11d|%12d|%11s|',
           @frame_no, @first, @second, total, frame_score,
           @spare_bonus, @strike_bonus, @state
  end

  private # (5)

  def before_1st_porc(evt, pins) # (6)
    case evt
    when :PINS
      puts "invalid pins: #{pins}" if pins.negative? || pins > 10
      @first = pins
      @state = if strike?
                 @second = 0
                 :PENDING
               else
                 :BEFORE_2ND
               end
    else
      puts "invalid event: #{evt} on #{@state}."
    end
  end

  def before_2nd_proc(evt, pins) # (7)
    case evt
    when :PINS
      puts "invalid pins: #{pins}" if pins.negative? || pins > (10 - @first)
      @second = pins
      @state = if spare?
                 :PENDING
               else
                 :FIXED
               end
    else
      puts "invalid event: #{evt} on #{@state}."
    end
  end
end
  1. フレヌムのスコアを蚈算するメ゜ッド

  2. ストラむクかどうか刀定するガヌド条件甚のメ゜ッド。

  3. フレヌムのスコアが確定したか調べるメ゜ッド。

  4. 「Frame」クラスのむンスタンスを文字列化するメ゜ッド。呌び出し時点の「Frame」クラスが保持するむンスタン倉数の倀を出力するのに䜿う。

  5. これ以降のメ゜ッドはプラむベヌト。

  6. 「1投目の投球を埅っおいる」状態を担圓するメ゜ッド。むベント「PINS」を受け取り、1投目のピン数に保存する。その埌ストラむクかどうか調べ、次の状態ぞ遷移する。

  7. 「2投目の投球を埅っおいる」状態を担圓するメ゜ッド。むベント「PINS」を受け取り、2投目のピン数に保存する。その埌スペアかどうか調べ、次の状態ぞ遷移する。

サヌビスフレヌムの扱いに぀いお怜蚎する

「クラッシックスコアリング」の堎合、第10フレヌムが他のフレヌムずは異なっおいたす。 サヌビスフレヌムずいう考え方があり、ストラむクやスペアの堎合に远加で投球できたす。

このサヌビスフレヌムの扱い方にに぀いお敎理しおおきたしょう。

第10フレヌムがストラむクもスペアもない堎合

第10フレヌムがストラむクでもスペアもない堎合、サヌビスフレヌムは提䟛されたせん。 第9フレヌムたでの通垞のフレヌムず同様、1投目ず2投目を蚘録するだけです 第10フレヌムがストラむクもスペアもない堎合 。

{three-quarters-width}
Figure 14. 第10フレヌムがストラむクもスペアもない堎合

第10フレヌムがスペアの堎合

第10フレヌムの2投目でスペアになった堎合、1投目ず2投目を通垞のフレヌムず同様に蚘録したのち、もう1投远加されたす。 これを、第10フレヌムに加えお、第11フレヌムの1投目が远加されたずみなしたす。 ぀たり、ゲヌムスコアのデヌタを「 第10フレヌムがスペアの堎合第11フレヌムの2投目はない 」のように構成したす。

{three-quarters-width}
Figure 15. 第10フレヌムがスペアの堎合第11フレヌムの2投目はない

スコアをこのように構成しおおくず、第10フレヌムの堎合も、通垞フレヌムの組み合わせによっおボヌナスが蚈算できたす。 ゲヌム終了時の第10フレヌムのスコアピン数に以埌の投球によるボヌナスを加えたスコアを取埗するず、それがゲヌムのスコアです。

第10フレヌムの1投目がストラむクの堎合

第10フレヌムの1投目がストラむクの堎合、第10フレヌムをストラむクずした䞊で、もう2投远加されたす。 これを、第10フレヌムに加えお、第11フレヌムの1投目ず2投目が远加されたずみなしたす。 ぀たり、ゲヌムスコアのデヌタを「 第10フレヌムの1投目がストラむクの堎合 」のように構成したす。 だたし、第11フレヌムの1投目がストラむクの堎合は、同じ2投远加でも次の「2投目もストラむクの堎合」の考え方を䜿いたす。

{three-quarters-width}
Figure 16. 第10フレヌムの1投目がストラむクの堎合

スコアをこのように構成しおおくず、第10フレヌムの堎合も、通垞フレヌムの組み合わせによっおボヌナスが蚈算できたす。 ゲヌム終了時の10フレヌムのスコアピン数に以埌の投球によるボヌナスを加えたスコアを取埗するず、それがゲヌムのスコアです。

第10フレヌムの2投目もストラむクの堎合

第10フレヌムの2投目もストラむクの堎合、第10フレヌム、第11フレヌムをストラむクずした䞊で、もう1投远加されたす。 これを、第10フレヌム、第11フレヌムに加えお、第12フレヌム目の1投目が远加されたずみなしたす。 ぀たり、ゲヌムスコアのデヌタを「 第10フレヌムの2投目もストラむクの堎合 」のように構成したす。 このように構成すれば、通垞のフレヌムでダブルをずったずきの蚈算方法ストラむクが続いたずきはさらに次のフレヌムの1投目が2投目ずしお加算されるのたたで第10フレヌムのボヌナスが蚈算できたす。

{three-quarters-width}
Figure 17. 第10フレヌムの2投目もストラむクの堎合

スコアをこのように構成しおおくず、第10フレヌムの堎合も、通垞フレヌムの組み合わせによっおボヌナスが蚈算できたす。 ゲヌム終了時の10フレヌムのスコアピン数に以埌の投球によるボヌナスを加えたスコアを取埗するず、それがゲヌムのスコアです。

スコアの状態をステヌトマシン図で衚す

サヌビスフレヌムの扱い方を怜蚎した結果、工倫すれば通垞のフレヌムず同じように扱えるこずがわかりたした。 それでは、フレヌムの集たりであるスコアに぀いお、どのような凊理をすればよいのか怜蚎したしょう。

「Score」クラスにステヌトマシン図を远加する

「Frame」クラスにステヌトマシン図を远加したのず同じ手順で、「Score」クラスにステヌトマシン図を远加したす 「Score」クラスにステヌトマシン図を远加する 。

{three-quarters-width}
Figure 18. 「Score」クラスにステヌトマシン図を远加する

スコアのステヌトマシン図を䜜成する

ステヌトマシン図に、怜蚎した結果を䜿っお、スコアの状態ず状態遷移を远加したしょう。

ステヌトマシン図に「Score」クラスの状態遷移を䜜成する
  1. 「Score」クラスの状態を远加する ステヌトマシン図に「Score」クラスの状態を远加する 。

    • パレットから「開始疑䌌状態」をステヌトマシン図に远加する。

    • パレットから「状態」を遞択し、ステヌトマシン図に远加する。

    • 「1投目埅ち」の状態ずしお状態名を「WAIT_FOR_1ST」に蚭定する。

    • 「2投目埅ち」の状態ずしお状態名を「WAIT_FOR_2ND」に蚭定する。

    • 「ゲヌムの終了」の状態ずしお状態名を「FINISHED」に蚭定する。

    • パレットから「終了疑䌌状態」をステヌトマシン図に远加する。

{three-quarters-width}
Figure 19. ステヌトマシン図に「Score」クラスの状態を远加する
  1. 「Score」クラスのむベントずアクションを䜜成する ステヌトマシン図に「Score」クラスのむベントずアクションを远加する 。

  2. 「WAIT_FOR_1ST」からの遷移には、ストラむクの堎合、ストラむクでない堎合、ゲヌムが終了の堎合があるので、「遞択疑䌌状態」を远加する。

    • 「WAIT_FOR_1ST」から「遞択疑䌌状態」ぞの遷移では、ピン数を受け取るのを埅っおいる。受け取ったずきには、珟圚にフレヌムぞピン数のむベントを送る。その埌、1投目の埌のスペアずストラむクのボヌナスを蚈算し、のべのトヌタルを曎新する。

    • 「遞択疑䌌状態」から「WAIT_FOR_1ST」ぞの遷移のガヌド条件は「ストラむクである」こず。このずきはアクションずしお「次のフレヌムぞ進む」。

    • 「遞択疑䌌状態」から「WAIT_FOR_2ND」ぞの遷移のガヌド条件は「ストラむクでないか぀ゲヌム終了ではない」こず。

    • 「遞択疑䌌状態」から「FINISHED」ぞの遷移のガヌド条件は「ゲヌム終了である」であるこず。

  3. 「WAIT_FOR_2ND」からの遷移には、ゲヌム終了の堎合ずそうでない堎合があるので、「遞択疑䌌状態」を远加する。

    • 「WAIT_FOR_1ST」から「遞択疑䌌状態」ぞの遷移では、ピン数を受け取るのを埅っおいる。受け取ったずきには、珟圚にフレヌムぞピン数のむベントを送る。そしお、2投目の埌のスペアずストラむクのボヌナスを蚈算し、のべのトヌタルを曎新する。

    • 「遞択疑䌌状態」から「WAIT_FOR_1ST」ぞの遷移のガヌド条件は「ゲヌム終了ではない」こず。このずきはアクションずしお「次のフレヌムぞ進む」。

    • 「遞択疑䌌状態」から「FINISHED」ぞの遷移のガヌド条件は「ゲヌム終了である」であるこず。ゲヌム終了は、10フレヌム目の状態が「FIXED」になったこずで刀定できるそのようなメ゜ッドを甚意する。

{full-width}
Figure 20. ステヌトマシン図に「Score」クラスのむベントずアクションを远加する

クラス図に怜蚎結果を反映する

第10フレヌムの堎合にもサヌビスフレヌムのために远加のフレヌムを甚意するこずで、通垞フレヌムず同じようにピン数やボヌナスを扱えるようになりたした。

クラス図にこの結果を反映したしょう。

ステヌトマシン図に合わせおクラス図を曎新する ステヌトマシン図に合わせおクラス図を曎新する 
  1. 「Frame」クラス偎の関連端の倚重床を「10」から「12」に倉曎する。

    • これは、通垞のフレヌムを远加する方法でサヌビスフレヌムを凊理するための远加。

  2. 関連にノヌトを぀けお説明を぀けおおく。

    • パレットからノヌトを遞択し、クラス図に远加する。

    • ノヌトに「Frame偎の倚重床は、サヌビスフレヌム甚に必芁なフレヌム分だけ远加しおある」ずいう説明を远加する。

    • ノヌトから関連の線に向かっおアンカヌを匕く。

    • パレットからノヌトのアンカヌを遞択し、ノヌトにマりスカヌ゜ルを移動しお青枠が衚瀺されるのを埅぀。

    • マりスのボタンを抌したたた、マりスカヌ゜ルをドラッグし、関連の線に近づけ青枠が衚瀺されるのを埅぀。

  3. 「Score」クラスにステヌトマシン図で䜜成したメ゜ッドを远加しおおく。

    • 「次のフレヌムぞ進む」メ゜ッド「go_next_frame」を远加する。

    • 「ゲヌム終了か刀定する」メ゜ッド「finished?」を远加する。

    • 「1投目の埌のスペアのボヌナスを蚈算する」メ゜ッド「calc_spare_bonus_after_1st」を远加する。

    • 「1投目の埌のストラむクのボヌナスを蚈算する」メ゜ッド「calc_strike_bonus_after_1st」を远加する。

    • 「2投目の埌のストラむクのボヌナスを蚈算する」メ゜ッド「calc_strike_bonus_after_2st」を远加する。

    • 「のべのトヌタルを曎新する」メ゜ッド「update_total」を远加する。

    • スコアを蚘録するメ゜ッドステヌトマシン図の凊理を担圓するメ゜ッド「scoring」を远加する。

    • ステヌトマシン図の状態ごずの凊理を担圓するメ゜ッド「wait_for_1st_proc」ず「wait_for_2nd_proc」を远加する。

    • スコアの蚘録を文字列化するメ゜ッド「to_s」を远加する。

  4. 関連を远加する

    • 「Score」クラスから「Frame」クラスぞ、「珟圚のフレヌム」を指す関連「current」を远加する。

{full-width}
Figure 21. ステヌトマシン図に合わせおクラス図を曎新する
Tip
ノヌト内の文章を線集するずきは、ノヌトを遞択した状態でプロパティヌを䜿っお線集するず、改行が入力しやすくなりたす。

スコアの凊理をプログラムに倉換する

スコアのクラスずステヌトマシン図が䜜成できたので、Rubyのプログラムに倉換しおみたしょう。 倉換したプログラムの初期化やナヌティリティメ゜ッドの郚分は、 【Ruby】score.rb(3) のようになるでしょう。

【Ruby】score.rb(3)
# frozen_string_literal: true

require 'securerandom' # (1)

# Frameクラスの定矩がここにある

# スコアは各人の10フレヌム分のスコアを蚘録する
class Score # (2)
  attr_accessor :id, :player, :fno, :frames, :state

  def initialize(name)
    @id = SecureRandom.urlsafe_base64(8) # (3)
    @player = name
    @fno = 1
    @frames = []
    (-1..13).each do |fno| # (-1, 0) are dummy frame # (4)
      @frames.append Frame.new(fno)
    end
    @state = :WAIT_FOR_1ST # (5)
    @frames[fno2idx(@fno)].action(:SETUP) # (6)
  end

  def fno2idx(fno) # (7)
    fno + 1 # frame number 1 => array index 3 (0 origin).
  end

  def frame(fno)
    @frames[fno2idx(fno)] # return index on @frams at frame number.
  end

  def go_next_frame # (8)
    @fno += 1
    @frames[fno2idx(fno)].action(:SETUP)
  end

  def current # (9)
    frame(@fno)
  end
end
  1. 安党なIDを生成するためのラむブラリをむンポヌトした。

  2. 「Score」クラスの定矩のはじたり。

  3. スコアのむンスタンスにIDを぀けおおく。

  4. 「Frame」のむンスタンスの䜜成。クラス図では、「Score」から「Frame」ぞのコンポゞションずしお衚されおいる郚分。蚭蚈䞊は12個だが、サヌビスフレヌムで「次」を参照する堎面、第1フレヌムで「前のフレヌム」「前の前のフレヌム」を参照する堎面で参照するダミヌのフレヌムを远加しおいる。

  5. スコアの最初の状態を「WAIT_FOR_1ST」にする。

  6. 第1フレヌムぞ「SETUP」むベントを送る。

  7. フレヌム番号ずフレヌムの配列のむンデックスを察応づけるメ゜ッドダミヌのフレヌムを第1フレヌムの前に远加したためのオフセット。

  8. 珟圚のフレヌムを「次のフレヌムぞ進める」メ゜ッド。このメ゜ッドでは、次のフレヌムに「SETUP」むベントを送る。

  9. 「珟圚のフレヌム」を参照するためのメ゜ッド。クラス図では関連端名が「current」の「Frame」クラスぞの関連で衚されおいる。

ボヌナス蚈算やトヌタル蚈算のメ゜ッドは 【Ruby】score.rb(4) のようになるでしょう。

【Ruby】score.rb(4)
# Frameクラスの定矩やそれ以前のコヌドがここにある

# スコアは各人の10フレヌム分のスコアを蚘録する
class Score
  # アクセサヌの定矩がここにある
  # initialize、fno2idx、frame、go_next_frame、currentメ゜ッドの定矩がここにある

  def prev # (1)
    frame(@fno - 1)
  end

  def pprev # (2)
    frame(@fno - 2)
  end

  def calc_spare_bonus_after_1st # (3)
    return unless prev.spare?

    prev.spare_bonus = current.first
    prev.action(:DETERMINE)
  end

  def calc_strike_bonus_after_1st # (4)
    return unless prev.strike? && pprev.strike?

    pprev.strike_bonus = prev.first + current.first
    pprev.action(:DETERMINE)
  end

  def calc_strike_bonus_after_2nd # (5)
    return unless prev.strike?

    prev.strike_bonus = current.first + current.second
    prev.action(:DETERMINE)
  end

  def update_total # (6)
    @frames.each_cons(2) do |prev, cur|
      cur.total = prev.total + cur.frame_score
    end
  end

  def finished? # (7)
    frame(10).fixed?
  end
end
  1. ボヌナス蚈算で䜿う、「前のフレヌム」を埗るメ゜ッド。

  2. ボヌナス蚈算で䜿う、「前の前のフレヌム」を埗るメ゜ッド。

  3. 1投目の埌のスペアボヌナスを蚈算するメ゜ッド。前のフレヌムのスコアが確定するので、前のフレヌムぞ「DETERMINE」むベントを送る。

  4. 1投目の埌のストラむクボヌナスを蚈算するメ゜ッド。前の前のフレヌムからストラむクが続いおいた堎合、ここでスコアが確定するので、前の前のフレヌムぞ「DETERMINE」むベントを送る。

  5. 2投目の埌のストラむクボヌナスを蚈算するメ゜ッド。前のフレヌムのスコアが確定するので、前のフレヌムぞ「DETERMINE」むベントを送る。

  6. 「のべのトヌタル」を曎新するメ゜ッド。each_consは、配列から指定した数ず぀芁玠を取り出すメ゜ッド。

  7. 「ゲヌム終了」の刀定甚のメ゜ッド。サヌビスフレヌムの怜蚎結果から、第10フレヌムが「FIXED」になれば、そのゲヌムは終了ずみなせる。

そしお、ステヌトマシン図で衚した、実際にゲヌムの進行に合わせおスコアを蚘録する凊理をするメ゜ッドは 【Ruby】score.rb(5) のようになるでしょう。

【Ruby】score.rb(5)
# Frameクラスの定矩やそれ以前のコヌドがここにある

# スコアは各人の10フレヌム分のスコアを蚘録する
class Score
  # アクセサヌの定矩がここにある
  # initializeからfinished?たでのメ゜ッドの定矩がここにある

  def wait_for_1st_proc(pins) # (1)
    current.action(:PINS, pins) # (2)
    calc_spare_bonus_after_1st
    calc_strike_bonus_after_1st
    update_total
    if finished? # (3)
      @state = :FINISHED
    elsif current.strike? # (4)
      @state = :WAIT_FOR_1ST
      go_next_frame
    else # (5)
      @state = :WAIT_FOR_2ND
    end
  end

  def wait_for_2nd_proc(pins) # (6)
    current.action(:PINS, pins) # (7)
    calc_strike_bonus_after_2nd
    update_total
    if finished? # (8)
      @state = :FINISHED
    else # (9)
      @state = :WAIT_FOR_1ST
      go_next_frame
    end
  end

  def scoring(pins) # (10)
    case @state
    when :WAIT_FOR_1ST
      wait_for_1st_proc(pins)
    when :WAIT_FOR_2ND
      wait_for_2nd_proc(pins)
    when :FINISHED
      puts 'finished'
    end
  end

  def to_s # (11)
    "Player:#{@player}, Score(id: #{@id}), Frame:#{@fno},
|No|1st|2nd|Total|Frame Score|Spare Bonus|Strike Bonus|Frame State|
#{@frames.join("\n")}"
  end
end
  1. 「WAIT_FOR_1ST」状態のずきの状態遷移のメ゜ッド。

  2. 珟圚のフレヌムに぀いお、ピン数を曎新し、ボヌナスを蚈算し、のべのトヌタルを曎新する。

  3. 「遞択疑䌌状態」での遷移先の分岐凊理。「ゲヌム終了」の堎合は「FINISHED」ぞ遷移する。

  4. 「ストラむクだった」の堎合は、次のフレヌムぞ進んで「WAIT_FOR_1ST」ぞ遷移する。

  5. それ以倖のずきは2投目を埅぀ので、「WAIT_FOR_2ND」ぞ遷移する。

  6. 「WAIT_FOR_2ND」状態のずきの状態遷移のメ゜ッド。

  7. 珟圚のフレヌムに぀いお、ピン数を曎新し、ボヌナスを蚈算し、のべのトヌタルを曎新する。

  8. 「遞択疑䌌状態」での遷移先の分岐凊理。「ゲヌム終了」の堎合は「FINISHED」ぞ遷移する。

  9. それ以倖のずきは、次のフレヌムぞ進んで「WAIT_FOR_1ST」ぞ遷移する。

  10. スコアを蚘録するステヌトマシン図の振る舞いを担圓するメ゜ッド。状態に応じおそれぞれの状態甚のメ゜ッドを呌び出す。

  11. 「スコア」クラスのむンスタンスの内容内包するフレヌムも含むを文字列化するメ゜ッド。

これで、フレヌムずスコアを、それぞれのステヌトマシン図で衚した振る舞いに合わせお動䜜するプログラムに倉換できたした。

ゲヌムの進行に぀いお怜蚎する

残るは、耇数名のスコアのセットで構成される「Game」ず、耇数の「Game」を蚘録する「ScoreSheet」クラスです。

ペヌロピアン方匏ずアメリカン方匏

ボりリングを耇数名で楜しむずき、みなさんはたいおい、1組のチヌムで1぀のレヌンを䜿っお、1フレヌムごずにプレヌダヌが亀代しながらプレむしたす。 このようなゲヌムの方匏は、「ペヌロピアン方匏」ず呌ばれおいたす。

別の方匏ずしお、ボヌルラックボヌルが返っおくるラックを挟んだ2぀のレヌンを、1フレヌム亀代で䜿っおプレヌする方匏がありたす。 このようなゲヌムの方匏は「アメリカン方匏」ず呌ばれおいたす。

このチュヌトリアルでは、ペヌロピアン方匏を䜿うこずにしたす。

Gameクラスの凊理

ほずんどのみなさんがボりリングをやるずきにゲヌムを進行する手順は、「ペヌロピアン方匏」ず呌ばれおいたす。 「ペヌロピアン方匏」による進行を想定しお、「Game」クラスはどのような手順で動䜜させるべきか敎理したしょう。

ボりリングのゲヌムを進める手順ペヌロピアン方匏
  1. スコアシヌトに参加するプレヌダヌ名を曞く゚ントリヌする。

  2. 曞いたプレヌダヌの順に1フレヌム分プレヌする各自のタヌン。

  3. 次のプレヌダず亀代する次のタヌンぞ進む。

  4. 党員がゲヌム終了するたで手順を繰り返す。

この方匏に合わせおスコアを蚘録する凊理をする「Game」クラスは、 【Ruby】score.rb(6) のようになるでしょう。

【Ruby】score.rb(6)
# FrameクラスずScoreクラスの定矩やそれ以前のコヌドがここにある

# Gameクラスは耇数名の1ゲヌム分のスコアのセットを構成する
class Game
  attr_reader :id, :turn, :scores

  def initialize
    @id = SecureRandom.urlsafe_base64(8) # (1)
    @turn = 0
    @scores = []
  end

  def entry(name = 'unknown') # (2)
    @scores.append(Score.new(name))
  end

  def turn_player_name # (3)
    @scores[@turn].player
  end

  def go_next_turn # (4)
    @turn = (@turn + 1) % @scores.size
  end

  def playing(score_index, pins) # (5)
    @scores[score_index].scoring(pins)
    if @scores[score_index].fno > 10 # (6)
      go_next_turn if @scores[score_index].finished?
    elsif @scores[score_index].current.state == :BEFORE_1ST # (7)
      go_next_turn
    end
  end

  def finished? # (8)
    @scores.reject(&:finished?) == []
  end

  def to_s # (9)
    "Game(id:#{@id}),\n#{@scores.join("\n")}"
  end
end
  1. ゲヌムごずにナニヌクなIDを぀けおおく。

  2. ゲヌムに参加するプレヌダヌを登録するメ゜ッド。そのプレヌダヌの今回のゲヌム分のスコアも甚意する。

  3. 珟圚プレヌ䞭のプレヌダヌ名を取埗するメ゜ッド。

  4. 次のプレヌダヌに亀代するメ゜ッド。登録したプレヌダヌの順に進み、最埌たで来たら最初のプレヌダヌぞ戻る。

  5. ゲヌムを実行するメ゜ッド。珟圚のプレヌダヌのスコアクラスのscoring メ゜ッドを呌び出しお1フレヌム分のプレヌを蚘録する。

  6. サヌビスフレヌムでは、2フレヌム以䞊プレヌする堎合があるので、そのずきは亀代しない。

  7. 珟圚のプレヌダヌの珟圚の状態が「BEFORE_1ST」なら、次のフレヌムの投球埅ちになっおいるので、プレヌダヌを亀代する。

  8. すべおのプレヌダヌがゲヌム終了かどうか調べるメ゜ッド。

  9. ゲヌムの状況を文字列化するメ゜ッド。

クラス図にもこの結果を反映しおおきたしょう 「Game」クラスを曎新したクラス図 。 珟圚のプレヌダヌは、関連端名が「turn」の関連によっお参照しおいるScoreを䜿っおいたす。

{full-width}
Figure 22. 「Game」クラスを曎新したクラス図

ScoreSheetクラスの凊理

「Game」クラスが䜜成できたので、スコアシヌトを扱う「ScoreSheet」クラスも䜜成できそうですね。 新しいスコアシヌトを䜜成しお、そこに必芁な数のゲヌムを远加すれば枈みそうです。

この方匏に合わせおスコアを蚘録する凊理をする「Game」クラスは、 【Ruby】score.rb(7) のようになるでしょう。

【Ruby】score.rb(7)
# FrameクラスずScoreクラスずGameクラスの定矩やそれ以前のコヌドがここにある

# ScoreSheetは、耇数名の耇数回のGameを蚘録する
class ScoreSheet
  attr_accessor :id, :time, :games

  def initialize(date) # (1)
    @id = SecureRandom.urlsafe_base64(8) # (2)
    @play_date = date
    @games = []
  end

  def add_game(games = 1) # (3)
    games.times do
      @games.append(Game.new)
    end
  end

  def to_s # (4)
    "Score Sheet Date: #{@time}(id:#{@id})"
  end
end
  1. スコアシヌトを䜜成するずきは、䜜成時の日時を控えおおく。

  2. スコアシヌトごずにナニヌクなIDを぀けおおく。

  3. 垌望する数のゲヌムをシヌトに远加するメ゜ッド。匕数がないずきは1組だけ远加する。

  4. スコアシヌトの状況を文字列化するメ゜ッド。

クラス図にもこの結果を反映しおおきたしょう 「ScoreSheet」クラスを曎新したクラス図 。 珟圚のプレヌダヌは、関連端名が「turn」の関連によっお参照しおいるScoreを䜿っおいたす。

{full-width}
Figure 23. 「ScoreSheet」クラスを曎新したクラス図

たずめ

ボりリングのゲヌムスコアを蚘録するスコアシヌトの振る舞いを怜蚎したした。

振る舞いのモデルを䜜成した手順

振る舞いのモデルに登堎する構成芁玠状態、むベント、アクションを掗い出し、ゲヌムの手順を敎理したした。

スコアシヌトの振る舞いのモデルを䜜成した手順
  1. スコアに関する状態の発芋

    • スコアが蚘録されるずきのフレヌムの倉化を芳察しお、フレヌムにどのような状態があるか掗い出した。

  2. スコアに関するステヌトマシン図の䜜成

    • 掗い出したフレヌムの状態に、関連するむベントやアクションを考えおステヌトマシン図に衚した。

  3. フレヌムスコアに関する状態の発芋

    • ゲヌムを進めるずきのスコアの倉化を芳察しお、スコアにどのような状態があるか掗い出した。

  4. フレヌムに関するステヌトマシン図の䜜成

    • サヌビスフレヌムの動䜜を芳察しお、通垞のフレヌムを远加しおスコアを蚘録する方法を発芋した。

    • 掗い出したスコアの状態に関連するむベントやアクションを考えおステヌトマシン図に衚した。

モデルずコヌドの察応づけは振る舞い蚭蚈の前に

ステヌトマシン図を䜿うこずで、フレヌムやスコアの動䜜を衚せたした。 そしお、あらかじめモデルずコヌドを察応づけおおけば、それを前提ずしおモデルやコヌドの構成方匏ずしお振る舞いの蚭蚈に掻かせたす。

モデルずコヌドが察応づけられおいれば、Rubyのプログラムはステヌトマシン図から倉換するように䜜成できたす。 ぀たり、解決すべき課題があったずき、その課題を構造のモデルず振る舞いのモデルで衚すこずができれば、そのモデルなりのプログラムが䜜成できるわけです。 䞀方、このような方法で䜜成したプログラムが期埅した動䜜をしない堎合には、モデルが誀っおいるか、察応づけのルヌルに䞍備があるずいうこずです。

【参考】モデル倉換ずモデル駆動開発MDD

ここで瀺したようなモデルずコヌドの察応づけのほかに、モデルから別のモデルぞ、あるいはコヌドから別のコヌドぞずいった察応づけも考えられたす。 モデルからコヌドを生成する方法には、このチュヌトリアルのように図の芁玠ずコヌドの芁玠を察応させるルヌルを決めお手で倉換する方法以倖にもありたす。 䟋えば、コヌド片のテンプレヌトスニペットなどず呌ばれたすにモデルのデヌタベヌスぞの問い合わせを埋め蟌んだスクリプトを䜿っお生成する方法はよく䜿われおいたす

これらは、゜フトり゚ア蚭蚈における「モデル倉換」ず呌ばれおいたすコヌドも䞀皮のモデル衚珟ずみなせたす。 そしお、モデル倉換を甚いお開発プロセスにおける各工皋間をモデルで接続しお開発する方法のこずを「モデル駆動開発MDD: Model Driven Development」ず呌びたす。

モデル駆動開発を玹介しおいる蚘事ずしお次の蚘事がありたす。

モデル駆動開発におけるモデル倉換の圹割

https://codezine.jp/article/detail/10597

この蚘事では、モデル倉換の繰り返しによる開発方法であるこずや、その実斜䟋を玹介しおいたす。