こんにちは!
スマレジ・テックファームのWebエンジニアやまてと申します。
はじめに
「CLIで操作するブラックジャックゲームをPHPで作ってみる」の 3 記事目として、「UML作成」について投稿します。
この課題は、『独学エンジニア』というWeb開発(主にサーバーサイド)の動画学習教材の中の課題の一つです。
前回までの投稿
環境
- macOS Big Sur バージョン: 11.6
- Visual Studio Code バージョン: 1.70.2
INDEX
1. PlantUML の利用
PlantUMLは、以下のようなダイアグラムを素早く作成するためのコンポーネントです。
テキストで UML が書ける PlantUML を利用します。
PlantUML で書いたテキストは Git で履歴を管理します。
1-1. PlantUMLの設定方法
ブラウザ上でも上記ページ内でUMLを編集できますが、VSCodeの拡張機能としてもインストールできるため、そちらを使用しています。
brew install --cask temurin brew install graphviz
※ 参考記事:
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上で上記のシーケンス図を表示するリンクを貼ります
シーケンス図(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作成」についてでした。次回は「実装」の内容について投稿します。
今回の記事は以上です!ありがとうございました。
毎日続けるコツは、毎日続けること。と、割と本気で思ってる。エンジニアに関する学習についての毎日継続は丸2年(730日)を超えた。前日まで毎日続けてきたこと自体が、今日も続けることの理由になってる。ここまでくるともはや安易に途切れさせる気にもならない。
— やまて|Webエンジニア2年目 (@r_yamate) 2022年8月7日
毎日の小さな積み重ねの力を信じてます。