転職したらスマレジだった件

スマレジのエンジニアやまてのテックブログです。マジレス大歓迎です。

CLIで操作するブラックジャックゲームをPHPで作ってみる - ③UML作成

こんにちは!

スマレジ・テックファームのWebエンジニアやまてと申します。

はじめに

CLIで操作するブラックジャックゲームをPHPで作ってみる」の 3 記事目として、「UML作成」について投稿します。


この課題は、『独学エンジニア』というWeb開発(主にサーバーサイド)の動画学習教材の中の課題の一つです。

dokugaku-engineer.com


前回までの投稿

ryamate.hatenablog.com

ryamate.hatenablog.com


環境


INDEX


1. PlantUML の利用

PlantUMLは、以下のようなダイアグラムを素早く作成するためのコンポーネントです。

plantuml.com

テキストで UML が書ける PlantUML を利用します。

PlantUML で書いたテキストは Git で履歴を管理します。


1-1. PlantUMLの設定方法

ブラウザ上でも上記ページ内UMLを編集できますが、VSCode拡張機能としてもインストールできるため、そちらを使用しています。

  • VSCode拡張機能「PlantUML」を検索した後、「インストール」ボタンをクリックして、インストール成功した後、Macの場合、下記のインストールをする必要があります。
brew install --cask temurin
brew install graphviz

※ 参考記事:

qiita.com



2. 作成した UML の種類

今回作成したUMLの種類は以下のとおりです。


2-1. ユースケース

ユースケース図とは、 ユースケースとアクターの関係を可視化して、システムが実現すべきことを明確にするためのもの です。

開発プロセスの使用フェーズは「要求分析」です。

ユースケースとアクターの関係を可視化」とは、例えば、以下のイメージです。


2-2. クラス図

クラス図とは、 クラスの情報とクラス間の関係から、システムの静的な構成を表現したもの です。

クラス図の作成手順は、以下のイメージです。

① クラスを抽出

② 関連を作成

③ 属性(プロパティ)や操作(メソッド)を追記


2-3. シーケンス図

シーケンス図とは、 クラスやオブジェクト間のやりとりを時間軸に沿って表現したもの です。

例えば、ブラックジャックのゲーム開始の部分だと、以下のようなものを作成しました。



3. 作成するときに心がけたこと

作るものを明確にするのが、UMLの目的 ということを作成するときに心がけました。

  • 完璧を求めない
    • 図をきれいに整えることに価値があるわけではなくて、図がわかりやすいことで、見た人の理解が捗り開発がスムーズにいくことに価値がある。
  • 図を整えるために時間をかけすぎない
    • 最初にコードを書き始める前に UML を作成するときは、仕様を明確に図にするのは難しいので、コードを書いていて要件についての理解が深まったときに更新する。
    • 時間をかける費用対効果を考える。
  • UML図の間で整合性が取れているようにする
    • クラス図とシーケンス図で整合性が取れていない、というようなことがないようにはする。



4. 作成した UML の履歴

実際に作成した UML について、設計段階、実装段階の履歴を載せます。


4-1. 設計段階

設計段階(実装前)に最初に作成した UML です。


4-1-1. ユースケース

ユースケース図(sequence.plantuml)の PlantUML のテキスト

@startuml Blackjack
left to right direction
actor Player <<MAN>> as p
actor Dealer <<CPU>> as d

rectangle Blackjack {
  usecase "ゲームスタートする" as UC1
  usecase "デッキからカードを引く" as UC2
  usecase "勝敗を判定する" as UC3
  usecase "ゲームを終了する" as UC4
}

p --> UC1
p --> UC2
p --> UC3
p --> UC4

UC1 <-- d
UC2 <-- d
UC3 <-- d
UC4 <-- d

@enduml


4-1-2. クラス図

クラス図(class.plantuml)の PlantUML のテキスト

@startuml Blackjack

class Main {
}

class Game {
  -NUM_OF_CARDS_IN_HAND
  -Deck deck
  -Player player
  -Dealer dealer
  +__construct()
  +getDeck()
  +getPlayer()
  +getDealer()
  +start()
  +showStartMesssage()

}

class Player {
  -array hand
  +__construct()
  +getHand()
  +drawHand()
  +calcScoreTotal()
}

class Dealer {
}

class Deck {
  -Card card
  -array deck
  +__construct()
  +getDeck()
  +initDeck()
  +takeCard()
}

class Card {
  -CARD_SCORE
  -array suits
  +createNewDeck()
}

Main -- Game
Game -- Deck
Game -- Player
Deck -- Card

Player <|-- Dealer

@enduml


4-1-3. シーケンス図

シーケンス図(sequence.plantuml)の PlantUML のテキスト

@startuml Blackjack
participant Main order 10
participant Game order 20
participant Dealer order 30
participant Player order 40
participant Deck order 50
participant Card order 60

skinparam responseMessageBelowArrow true
Main -> Game : ブラックジャックを\n開始する
activate Game
Game -> Deck : デッキ(カード52枚)を初期化する
activate Deck
Deck -> Card : カード52枚を取得する
activate Card
Card --> Deck : デッキ(カード52枚)を\nシャッフルして返却する
deactivate Card
Deck --> Game : デッキ(カード52枚)を返却する

Game -> Player : プレイヤーを取得する
activate Player
Player -> Deck : 手札2枚を取得する
Deck --> Player : 手札2枚を返却する
Player --> Game : プレイヤーを返却する

Game ->  Dealer : ディーラーを取得する
activate  Dealer
Dealer -> Deck : 手札2枚を取得する
Deck --> Dealer : 手札2枚を返却する
Dealer --> Game : ディーラーを返却する

Game ->  Game : ブラックジャックの開始時の\nメッセージを表示する

note over Game
ブラックジャックを開始します。
あなたの引いたカードはハートの7です。
あなたの引いたカードはクラブの8です。
ディーラーの引いたカードはダイヤのQです。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は15です。カードを引きますか?(Y/N)
end note

deactivate Deck
deactivate Game
deactivate Dealer
deactivate Player

@enduml


4-2. 実装段階

ステップ3の実装時に更新した UML です。

◯ステップ3

最大3人までのプレイヤーでプレイできるようにしましょう(ディーラーと合わせて合計4人)。増えたプレイヤーはCPUが自動的に操作します。


4-2-1. ユースケース

ユースケース図(sequence.plantuml)の PlantUML のテキスト

@startuml Blackjack
skinparam actorStyle awesome
left to right direction
actor Dealer as d
actor Player <<Manual>> as mp #AliceBlue
actor Player1 <<Auto>> as ap1 #LavenderBlush
actor Player2 <<Auto>> as ap2 #LavenderBlush

rectangle Blackjack {
  usecase "ゲームスタートする" as UC1
  usecase "デッキからカードを引く" as UC2
  usecase "勝敗を判定する" as UC3
  usecase "ゲームを終了する" as UC4
  UC1 --> UC2
  UC2 --> UC3
  UC3 --> UC4
}
  mp --> UC2
  ap1 -> UC2
  d -> UC2
  UC2 <-- ap2
  d --> UC3

@enduml


4-2-2. クラス図

クラス図(class.plantuml)の PlantUML のテキスト

@startuml Blackjack

class Game {
  -Deck deck
  -Dealer dealer
  -array<int,Player> players

  +__construct()
  +play()
  -set()
  -start()
  -action()
  -result()
  -end()
}

class Deck {
  -array<int,array<string,int|string>> deck

  +__construct()
  +getDeck()
  +initDeck()
  +takeACard()
}

class Card {
  -CARD_SCORE

  -array suits
  +createNewDeck()
}

class Dealer {
  -NUM_OF_FIRST_HAND
  -DealerPlayer dealerPlayer

  +__construct()
  +getDealerPlayer()
  +dealOutFirstHand()
  +dealOneCard()
  +checkBurst()
  +judgeWinOrLose()
  -hasStand()
  -compareScoreTotal()
}

abstract Player {
  -string name
  -array<int,array<string,int|string>> hand
  -int scoreTotal
  -int countAce
  -string status

  +{abstract} action()
  +{abstract} selectHitOrStand()

  +__construct()
  +getName()
  +getHand()
  +getScoreTotal()
  +getStatus()
  +addACardToHand()
  +calcScoreTotal()
  -calcAceScore()
  +changeStatus()
}

class DealerPlayer {
  +action()
  +selectHitOrStand()
}

class ManualPlayer {
  +action()
  +selectHitOrStand()
}

class AutoPlayer {
  +action()
  +selectHitOrStand()
}

class Message {
  +getSettingMessage()
  +getInputNumOfPlayerMessage()
  +getSettingInputErrorMessage()

  +getStartMessage()
  +getFirstHandMessage()
  +getDealerFirstHandMessage()
  +getLoseByBurstMessage()

  +getProgressMessage()
  +getProgressQuestionMessage()
  +getCardDrawnMessage()
  +getInputErrorMessage()

  +getScoreTotalResultMessage()
  +getStandMessage()
  +getDealerBurstMessage()
  +getWinByBurstMessage()
  +getResultMessage()

  +getEndMessage()
}

Game -- Deck
Deck *-- Card
Game -- Dealer
Dealer *-- DealerPlayer
Game -- Player
Game --- ManualPlayer
Game --- AutoPlayer
Player <|-- DealerPlayer
Player <|-- ManualPlayer
Player <|-- AutoPlayer
Game -- Message

@enduml


4-2-3. シーケンス図

※ 図の文字が小さくしか表示されないかもしれないため、PlantUMLのWeb Server上で上記のシーケンス図を表示するリンクを貼ります

www.plantuml.com

シーケンス図(sequence.plantuml)の PlantUML のテキスト

@startuml Blackjack
participant Main order 10
participant Game order 20
participant Message order 30
participant Dealer order 40
participant Player order 50 #AliceBlue
participant NPC order 60 #LavenderBlush
participant Deck order 70
participant Card order 80

skinparam responseMessageBelowArrow true

group #LightGoldenRodYellow Game を new する
  Main -> Game : Game を new する
  activate Game
  Game ->  Dealer : Dealer を new する
  activate  Dealer
  Dealer -> Deck : Deck を new する
  activate Deck
  Deck -> Card : カード52枚を取得する
  activate Card
  Card --> Deck : カード52枚を返却する
  deactivate Card

  Game -> Player : Player を new する
  activate Player #AliceBlue
end

group #LightGoldenRodYellow ゲームの設定をする
  Main -> Game : ブラックジャックの\n設定をする
  Game -> Message : プレイヤー数を決める(メッセージ表示)
  note over Message
  ブラックジャックの設定をします。
  プレイヤーの人数を選んでください。(1, 2, 3)
  end note

  Game -> NPC : 決めた人数の NPC を new する
  activate NPC #LavenderBlush
end

group #LightGoldenRodYellow ゲームを開始する
  Main -> Game : ブラックジャックを\n開始する

  Game -> Dealer : デッキをシャッフルする
  Dealer -> Deck : デッキをシャッフルする

  Dealer -> Deck : デッキからプレイヤーに手札2枚を配る
  Deck --> Player

  Dealer -> Deck : デッキから NPC に手札2枚を配る
  Deck --> NPC

  Dealer -> Deck : デッキから ディーラー に手札2枚を配る
  Deck --> Dealer

  Game ->  Message : ブラックジャックの開始時の\nメッセージを表示する

  note over Message
    ブラックジャックを開始します。
    あなたの引いたカードはハートの7です。
    あなたの引いたカードはクラブの8です。
    ディーラーの引いたカードはダイヤのQです。
    ディーラーの引いた2枚目のカードはわかりません。
    あなたの現在の得点は15です。カードを引きますか?(Y/N)
  end note

end

group #LightGoldenRodYellow ゲームを進行する

  group #AliceBlue Player(あなた)は Hit か Stand かを選択する
    alt Y (Hit)
      Game -> Player : プレイヤーは1枚カードを引く
      Player -> Deck : 1枚カードを引く
      Deck --> Player : 1枚カードを返す
      Player -> Player : 得点を計算する

      Game -> Dealer : ディーラーはバーストか否かチェックする
      Dealer -> Player : バーストか否かチェックする

      alt カードの合計値が 21 を超えていない場合
        Dealer --> Game :

        Game ->  Message : メッセージを表示する
        note over Message
          あなたの引いたカードはスペードの5です。
          あなたの現在の得点は20です。カードを引きますか?(Y/N)
        end note
      else Burst (カードの合計値が 21 を超えていた場合)
        Dealer -> Player : ステータスを\n「burst」に変更する
        Dealer --> Game :

      end
    else N (Stand)

      Game ->  Message : メッセージを表示する
      note over Message
        あなたの引いた2枚目のカードはダイヤの2でした。
        あなたの現在の得点は12です。
      end note

      Game -> Player : ステータスを\n「stand」に変更する

    else 再入力
      Game ->  Message : メッセージを表示する
      note over Message
        Y/N で入力してください。カードを引きますか?(Y/N)
      end note
      Game -> Game : 再入力
    end

    alt Burst (プレイヤーのカードの合計値が 21 を超えていた場合)

      Game ->  Message : メッセージを表示する
      note over Message #Yellow
        あなたの引いたカードはダイヤのJです。
        あなたの現在の得点は25です。
        合計値が21を超えたので、あなたの負けです。
      end note
    end
  end

  group #LavenderBlush NPC(NPC1, NPC2)がいれば Hit か Stand かを選択する(自動)
    alt Y (Hit)
      Game -> NPC : NPC は1枚カードを引く
      NPC -> Deck : 1枚カードを引く
      Deck --> NPC : 1枚カードを返す
      NPC -> NPC : 得点を計算する

      Game -> Dealer : ディーラーはバーストか否かチェックする
      Dealer -> NPC : バーストか否かチェックする

      alt カードの合計値が 21 を超えていない場合
        Dealer --> Game :
        Game ->  Message : メッセージを表示する
        note over Message
          NPC の引いたカードはスペードの5です。
          NPC の現在の得点は20です。カードを引きますか?(Y/N)
        end note
      else Burst (カードの合計値が 21 を超えていた場合)
        Dealer -> NPC : ステータスを\n「burst」に変更する
        Dealer --> Game :

      end
    else N (Stand)

      Game ->  Message : メッセージを表示する
      note over Message
        NPC の引いた2枚目のカードはダイヤの2でした。
        NPC の現在の得点は12です。
      end note

      Game -> Player : ステータスを\n「stand」に変更する

    else 再入力
      Game ->  Message : メッセージを表示する
      note over Message
        Y/N で入力してください。カードを引きますか?(Y/N)
      end note
      Game -> Game : 再入力
    end

    alt Burst (プNPC カードの合計値が 21 を超えていた場合)
      Game ->  Message : メッセージを表示する
      note over Message #Yellow
        NPC の引いたカードはダイヤのJです。
        NPC の現在の得点は25です。
        合計値が21を超えたので、 NPC の負けです。
      end note
    end
  end

end

group #LightGoldenRodYellow 結果を判定する
  alt Stand (カードの合計値が 21 以下) のプレイヤーが残っていた場合

      Game -> Dealer : ディーラーは自分のカードの合計値が\n 17 以上になるまで引き続ける
    loop
      Dealer -> Deck : 1枚カードを引く
      Deck --> Dealer : 1枚カードを返す
      Dealer -> Dealer : 得点を計算する
      Dealer -> Dealer : ステータスを変更する
      Dealer ->  Message : メッセージを表示する
      note over Message
        ディーラーの引いたカードはハートのKです。
      end note
    end

    alt Burst (ディーラーのカードの合計値が 21 を超えていた場合)
      Dealer ->  Message : メッセージを表示する
      note over Message #Yellow
        ディーラーの得点は22です。
        合計値が21を超えたので、ディーラーはバーストしました。
        あなたの勝ちです!
        (NPCの勝ちです!)
      end note

    else ディーラーのカードの合計値が 21 を超えていない場合

      Dealer -> Dealer : 勝敗を判定する
      Dealer -> Dealer : スコアを取得する
      Dealer -> Player : スコアを取得する
      Dealer -> Player : ステータスを\nwin, lose, draw に変更する
      Dealer -> NPC : ステータスを\nwin, lose, draw に変更する


      note over Game #Yellow
        あなたの勝ちです!
        (NPCの勝ちです!)
        ブラックジャックを終了します。
      end note
    end
    Game --> Main : ゲーム終了
  deactivate Game
  deactivate Dealer
  deactivate Player
  deactivate NPC
  deactivate Deck
  end
end

@enduml



5. 自己レビュー

ユースケース図」については、ブラックジャックゲームは、既存のゲームをかたちにするだけなので、あんまり誰が何をするかという点について悩む余地がないため、ユースケース図の必要性をあまり感じるタイミングはありませんでした。(要求分析のための図なので、必要性を感じないのは当然かもしれません)

そのため、最初に作ってから図の更新はほぼしていません。最初のユースケース図もかなり微妙な仕上がりではありますが…


「クラス図」については、実装をし始めてからも、何度も図を見返しながら実装内容を考えました。「シーケンス図」も同様に、全体の流れの把握や、どのクラスのメソッドとして実装すれば良いかなど、何度も図を見返しました。 UML を作成したり見返したりすることで、どうすれば良いか行き詰まってしまうことが少なくなると感じます。作るものが明確になる UML 作成の価値を感じられました。


作成することにだいぶ慣れてきたので、今後もサクッと図を書けるようになって、設計や実装に役立てていきます。



おわりに

今回は「UML作成」についてでした。次回は「実装」の内容について投稿します。

今回の記事は以上です!ありがとうございました。



毎日の小さな積み重ねの力を信じてます。