♣︎. 简介
我用 PHP 制作了一个在控制台屏幕上运行的二十一点游戏。我会写一篇关于个人发展过程的文章。
操作时的控制台画面是这样的。
(这是文本的样子)
root@9444efa94780:/var/www/html# php Main.php
ブラックジャックの設定をします。
プレイヤーの人数を入力してください。(1〜3)
? 1
プレイヤー1名でゲームを開始します。
あなたの持っているチップは100ドルです。
ベットする額を入力してください。(1〜1000ドル)
? 10
10ドルをベットしました。
ブラックジャックを開始します。
あなたの引いたカードはハートの6です。
あなたの引いたカードはスペードのQです。
ディーラーの引いたカードはスペードの10です。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は16です。
カードを引きますか?(Y/N / DD/SP/SR)
※ 特殊ルール(DD: ダブルダウン, SP: スプリット, SR: サレンダー)は、最初に手札が配られたときのみ有効
? SR
サレンダーを宣言しました。
ベットしたチップ10ドルの半分5ドルを戻してゲームを降ります。
ディーラーの引いた2枚目のカードはダイヤのAでした。
あなたは負けたため、チップ 5 ドルは没収されます。
あなたのチップ残高は 95 ドルです。
ブラックジャックゲームを続けますか?(Y/N)
? Y
あなたの持っているチップは95ドルです。
ベットする額を入力してください。(1〜1000ドル)
? 10
10ドルをベットしました。
ブラックジャックを開始します。
あなたの引いたカードはクラブのAです。
あなたの引いたカードはクラブの3です。
ディーラーの引いたカードはスペードのJです。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は14です。
カードを引きますか?(Y/N / DD/SP/SR)
※ 特殊ルール(DD: ダブルダウン, SP: スプリット, SR: サレンダー)は、最初に手札が配られたときのみ有効
? Y
あなたの引いたカードはハートの6です。
あなたの現在の得点は20です。
カードを引きますか?(Y/N / DD/SP/SR)
※ 特殊ルール(DD: ダブルダウン, SP: スプリット, SR: サレンダー)は、最初に手札が配られたときのみ有効
? N
カードを引きません。
ディーラーの引いた2枚目のカードはスペードの7でした。
ディーラーの現在の得点は17です。
カードを引きません。
ディーラーの得点は17です。
あなたの勝ちです!?
あなたは勝ったため、チップ 10 ドルと同額の配当を得られます。
あなたのチップ残高は 105 ドルです。
ブラックジャックゲームを続けますか?(Y/N)
? Y
あなたの持っているチップは105ドルです。
ベットする額を入力してください。(1〜1000ドル)
? 105
105ドルをベットしました。
ブラックジャックを開始します。
あなたの引いたカードはスペードのKです。
あなたの引いたカードはクラブの5です。
ディーラーの引いたカードはスペードの9です。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は15です。
カードを引きますか?(Y/N / DD/SP/SR)
※ 特殊ルール(DD: ダブルダウン, SP: スプリット, SR: サレンダー)は、最初に手札が配られたときのみ有効
? Y
あなたの引いたカードはハートの2です。
あなたの現在の得点は17です。
カードを引きますか?(Y/N / DD/SP/SR)
※ 特殊ルール(DD: ダブルダウン, SP: スプリット, SR: サレンダー)は、最初に手札が配られたときのみ有効
? N
カードを引きません。
ディーラーの引いた2枚目のカードはクラブの7でした。
ディーラーの現在の得点は16です。
ディーラーの引いたカードはハートの9です。
ディーラーの得点は25です。
合計値が21を超えたので、ディーラーはバーストしました。
あなたの勝ちです!?
あなたは勝ったため、チップ 105 ドルと同額の配当を得られます。
あなたのチップ残高は 210 ドルです。
ブラックジャックゲームを続けますか?(Y/N)
? Y
あなたの持っているチップは210ドルです。
ベットする額を入力してください。(1〜1000ドル)
? 210
210ドルをベットしました。
ブラックジャックを開始します。
あなたの引いたカードはクラブの3です。
あなたの引いたカードはダイヤの4です。
ディーラーの引いたカードはダイヤの8です。
ディーラーの引いた2枚目のカードはわかりません。
あなたの現在の得点は7です。
カードを引きますか?(Y/N / DD/SP/SR)
※ 特殊ルール(DD: ダブルダウン, SP: スプリット, SR: サレンダー)は、最初に手札が配られたときのみ有効
? Y
あなたの引いたカードはスペードの4です。
あなたの現在の得点は11です。
カードを引きますか?(Y/N / DD/SP/SR)
※ 特殊ルール(DD: ダブルダウン, SP: スプリット, SR: サレンダー)は、最初に手札が配られたときのみ有効
? Y
あなたの引いたカードはクラブのKです。
あなたの現在の得点は21です。
カードを引きますか?(Y/N / DD/SP/SR)
※ 特殊ルール(DD: ダブルダウン, SP: スプリット, SR: サレンダー)は、最初に手札が配られたときのみ有効
? N
カードを引きません。
ディーラーの引いた2枚目のカードはハートの7でした。
ディーラーの現在の得点は15です。
ディーラーの引いたカードはハートの4です。
ディーラーの現在の得点は19です。
カードを引きません。
ディーラーの得点は19です。
あなたの勝ちです!?
あなたは勝ったため、チップ 210 ドルと同額の配当を得られます。
あなたのチップ残高は 420 ドルです。
ブラックジャックゲームを続けますか?(Y/N)
? N
ブラックジャックを終了します。
这是游戏的大纲。
- 您可以按照二十一点游戏的基本规则玩纸牌
- 您可以投注筹码(虚构投注),赢得比赛并增加您的筹码来玩
- 可以声明影响筹码的特殊规则(双下注、拆分、投降)
♣︎。
我之所以要创建它,是因为它是Web开发视频学习资料“自学工程师”的作业之一。
它是作为“PHP 和面向对象”部分的高潮而准备的。
顺便说一句,教材中没有提供答案示例,以便学生自行解决问题。
♣︎.目的
制作它的目的是获得编写 PHP 和面向对象代码的经验。
我从另一个行业转行已经九个月了,但我还没有机会在工作中编写代码。因此,为了准备将来有机会为实际工作编写代码,我将其作为持续面对代码的主题进行了研究。
顺便说一句,我花了大约 100 个小时来创建一个二十一点游戏。我觉得在二十一点下注时间作为奖励获得的经验值非常大。
♣︎.环境
- macOS Big Sur 版本:11.6
- Visual Studio 代码版本:1.70.2
- Docker 版本:20.10.14
- PHP 版本:8.1.7
* 没有使用语言框架
♣︎。
前半部分会回顾开发过程,后半部分会写下我在开发过程中试图保护的东西。
♠︎. 回顾发展历程
♠︎. 如何进行开发
♠︎. 准备开发环境
♠︎A. 基本规则的执行(第一步)
♠︎2.为卡片A添加规则(步骤2)
♠︎3. 添加玩家数量设置(步骤3)
♠︎4.添加筹码功能和特殊规则(第四步)
♦︎。
♦︎A. 编写UML
♦︎2. 先写测试再继续
♦︎3. 边调试边编码
♦︎4. 让所有静态分析工具通过
♦︎5. 多次重构
♠︎. 回顾发展历程
♠︎. 如何进行开发
由于它是个人开发,所以我是唯一实现它的人,但我在创建分支、更改代码、拉取请求和合并的同时继续开发。
由于它应该作为 GitHub 上的公共存储库发布并且这次是一篇文章,所以我尝试在削减分支的同时继续进行,以便尽可能容易地看到开发历史。
本文将根据 pull request 历史回顾开发过程。
♠︎. 准备开发环境
首先,我们搭建一个开发环境。
即使我说准备,我也不是从头开始,而是在山浦先生为讲座创建的 Docker 环境(php 8 系列,apache)中添加了静态分析工具等库。
♠︎A. 基本规则的执行(第一步)
第一个任务“步骤 1”包括以下内容:
◯ 步骤 1
创建一个两人控制台游戏,庄家和玩家。
按照以下规则在控制台(终端)上操作。
- 玩家是执行者,庄家由CPU自动执行
- 在执行开始时,闲家和庄家各抽两张牌。抽出的卡片显示在屏幕上。但是,庄家的第二张牌不应该知道
- 然后玩家先抓一张牌。如果玩家牌的总价值超过 21,则玩家输掉
- 玩家每次抽牌时可以选择抽下一张牌
- 当玩家抽完牌后,庄家继续抽牌,直到他的牌总点数达到 17 或更多
- 当玩家和庄家完成抽牌时,游戏获胜。总点数接近 21 的人获胜。
- 将 A 视为一个点
控制台屏幕的图像。
ブラックジャックを開始します。 あなたの引いたカードはハートの7です。 あなたの引いたカードはクラブの8です。 ディーラーの引いたカードはダイヤのQです。 ディーラーの引いた2枚目のカードはわかりません。 あなたの現在の得点は15です。カードを引きますか?(Y/N) Y あなたの引いたカードはスペードの5です。 あなたの現在の得点は20です。カードを引きますか?(Y/N) N ディーラーの引いた2枚目のカードはダイヤの2でした。 ディーラーの現在の得点は12です。 ディーラーの引いたカードはハートのKです。 あなたの得点は20です。 ディーラーの得点は22です。 あなたの勝ちです! ブラックジャックを終了します。步骤 1 的实施按以下顺序进行:
- 创建 UML
- 游戏开始部分的实现
- 基本规则的完整实施
- 静态分析工具中的重构
- 添加测试代码
创建 UML
在实现之前,我在 PlantUML 中编写了用例图、类图和序列图。
游戏开始部分的实现
实施从这里开始。
我根据类图创建了一个文件,并开始编写基于时序图启动游戏的流程。
在这个阶段我想到的是通过将任务分成小块来继续工作而已。
通过分解任务,我降低了开始写代码的门槛,为了防止手卡在中间,时间流逝,我决定在卡住的时候拆解任务。基本规则的完整执行
有了这个分支,基本实现就完成了庄家和玩家之间的两人控制台游戏。
不要急于写好代码,首先,即使代码很脏,我们还是让它工作吧我继续这个姿势。此时,我觉得我写的代码很难阅读。
静态分析工具中的重构
当它满足第 1 步的规范时,我使用静态分析工具(PHP_CodeSniffer、PHPMD、PHPStan)对其进行了重构。
添加测试代码
我添加了没有写的测试代码。
♠︎2.为卡片A添加规则(步骤2)
作业的“步骤 2”如下。
◯ 第 2 步
让我们修改程序以将 A 视为 1 或 11 个点。
A 会统计 21 以内总点数最大的那张牌。
完成卡A规则添加
作为附加功能,我们已将 A (Ace) 计为 1 点或 11 点,以方便为准。
/** * A の点数については、デフォルト 11 でカウントされており、 * 得点が21点を超えている場合は、 1 でカウントする * * @return void */ private function calcAceScore(): void { for ($i = 0; $i < $this->countAce; $i++) { if ($this->scoreTotal > 21) { $this->scoreTotal -= 10; } } }
calcAceScore方法的示例计算是: (对于“A 和 5”→“A”→“7”)
- 第一手 → “A 和 5”
scoreTotal是16(11+5)- 抽 1 张牌 → “A”
scoreTotal是27(11+5+11)
- A (
countAce) 的数量为2- A 在 1 和 11 之间切换,最大不超过 21
- for 语句两次
- 27-10=17 因为第一次超过 21
- 第二次不超过21,所以停留在17
scoreTotal是17- 再抽一张牌 → “7”
scoreTotal是34(11+5+11+7)- A (
countAce) 的数量是2
- for 语句两次
- 34-10=24 因为第一次超过 21
- 24-10 = 14 因为第二次超过 21
scoreTotal是14
♠︎3.添加玩家数量设置(步骤3)
作业的“第 3 步”如下。
◯ 步骤 3
最多允许 3 名玩家玩(庄家共 4 名)。增加的玩家数量由 CPU 自动控制。
完成添加设置玩家数量的功能
添加了设置玩家数量的功能。
简单的加了个“改成最多3人玩”的功能,5天(10小时左右)就完成了操作,但是感觉改代码有点难……做了。
静态分析工具中的重构
在静态分析工具(PHP_CodeSniffer、PHPMD、PHPStan)中实现重构。
关于第二个静态分析工具的重构,在用PHPMD的分析中指出代码比较复杂,所以我重写了很多。
具体来说,处理集中在
Game类的start方法中,所以我们随着游戏的进行分发处理。分散后,将每个玩家的动作和庄家的动作转移到每个班级。此外,我创建了一个
Message类来组织游戏中的消息。修复 UML
修改 UML(类图、序列图)以匹配当前代码。
根据 SOLID 原则进行的修改
整理类图的时候,觉得光看图很难理解,想通了,修改了代码。
Dealer职业有发牌、扮演玩家、决定输赢等多重角色。Dealer类、Player类和NonPlayerCharacter类有相同的抽卡行为,目标是在21分内获得更高的分数,但没有标准化,所以我们修复了它。。- 修复是把
Player类做成抽象类(模板方法模式),总结播放器的常用属性。Dealer方法仍然具有发牌和判断结果的作用,因此我们在下一步的第 4 步实现中对其进行了修复。- 参考:此时的类图
♠︎4.添加筹码功能和特殊规则(第四步)
作业的“第4步”是以下内容。
◯ 步骤 4(可选)
添加双下注、拆分和投降规则。请自行查看规则。
提示功能,添加特殊规则完成
我对二十一点游戏一无所知,所以我开始研究每条规则。
作为研究的结果,我发现有必要添加允许您下注筹码(虚拟投注)并赢得比赛的元素以增加筹码并进行游戏。大致整理并实施了以下补充。
- 让玩家拿着筹码(赌注)。
- 下注筹码。
- 根据输赢计算筹码余额。
- 添加特殊规则(双倍下注、拆分、投降)。
Double Down 和 Surrender 很容易实现,但 Split 很难实现。
顺便说一句,分牌意味着如果第一张牌的牌面相同,则可以将牌分成两张,每张牌都是单独的手牌(需要与初始赌注相等的额外金额)。这是一个规则。
关于第 4 步,虽然功能有效,但我们尚未确认重构或测试代码泄漏,因此我们将继续努力。
这就是对开发过程的审查。
♦︎。
在创建二十一点游戏的过程中,我们努力坚持以下几点:
- UML写
- 测试先写的时候
- 调试代码同时
- 静态分析工具将通过所有
- 很多次重构做
♦︎A. 编写UML
为了顺利实现,我在设计的时候就写了UML,一边修改UML一边实现。
使用 PlantUML
以下三个图表是使用 PlantUML 创建的。
- 用例图
- 类图
- 序列图
您可以在浏览器的上述页面中编辑 UML,但我安装并使用它作为 VSCode 的扩展。
UML 的目的是明确要创建什么,所以我注意了以下三点。
- 不要追求完美
- 不是图排列整齐,而是图通俗易懂,提高了观者的理解力,开发顺利。
- 不要花太多时间清理你的图表
- 在开始编写代码之前编写 UML 时,很难清楚地绘制规范图,所以我会在编写代码时更新并更好地理解需求。
- 考虑花费时间的成本效益。
- 确保 UML 图之间的一致性
- 确保类图和序列图之间没有不一致。
关于“类图”,甚至在我开始实现它之后,我在多次回顾图的同时也思考了实现。同样,我多次回头看“序列图”,以了解整体流程以及应该实现哪个类方法。通过创建和审查 UML,我感觉不那么拘泥于要做什么了。我感受到了创建 UML 的价值,它明确了要创建的内容。
创建的UML(编辑中)如下。
用例图
类图
序列图
♦︎2. 先写测试再继续
在实施之前编写测试时,我尝试继续。
使用 PHPUnit 进行自动化测试
为了测试,我使用了 PHPUnit。
为您要实现的功能编写一个测试,然后实现该功能我打算继续处理表格,但我根本无法保护它......
因为我不了解测试的核心,所以我倾向于推迟它,然后当我注意到时再写。我觉得我还没有到通过写作学习的阶段。我认为我们需要系统的投入。
♦︎3. 边调试边编码
使用调试工具 Xdebug
调试时编写代码,根据需要使用 PHP 调试工具 Xdebug 进行步进。
作为使用 Xdebug 的一种方式,我使用作为 Visual Studio Code 的扩展引入的 Remote Development 来设置 Docker 容器中的调试功能。
至于我在开发过程中试图保护的东西列表,调试工具太方便了,不能放手。
♦︎4。静态分析工具设置所有通行证
引入了以下三个静态分析工具以通过检查(无错误)。
- PHP_代码嗅探器
- PHPMD
- PHP 斯坦
PHP_代码嗅探器
PHP_CodeSniffer 检查实现的代码是否符合编码约定。
例如,它在运行时检查这样的事情。
root@9444efa94780:/var/www/html# composer phpcs > ./vendor/bin/phpcs --standard=phpcs.xml FILE: /var/www/html/lib/Deck.php -------------------------------------------------------------------------------------------------------------------------------- FOUND 1 ERROR AFFECTING 1 LINE -------------------------------------------------------------------------------------------------------------------------------- 41 | ERROR | [x] Parentheses must be used when instantiating a new class | | (PSR12.Classes.ClassInstantiation.MissingParentheses) -------------------------------------------------------------------------------------------------------------------------------- PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY --------------------------------------------------------------------------------------------------------------------------------新しいクラスをインスタンス化する際には、括弧を使用する必要があります它会像这样告诉你不符合编码约定(PSR12)的地方。
- $card = new Card; + $card = new Card();PHPMD
PHPMD 是一个工具,它通过检测和指出可能的错误、次优代码、复杂代码、未使用的参数、方法、属性和其他有问题的代码,帮助您更接近好的代码。
例如,它在运行时检查这样的事情。
root@9444efa94780:/var/www/html# composer phpmd > ./vendor/bin/phpmd . text rulesets.xml --suffixes php --exclude node_modules,resources,storage,vendor /var/www/html/lib/Game.php:13 ExcessiveClassComplexity The class Game has an overall complexity of 55 which is very high. The configured complexity threshold is 50.クラスGameの全体的な複雑度は55で、非常に高いです。設定されている複雑さの閾値は50です。“复杂度”似乎是通过将
if、while、for等相加来计算的,如果超过复杂度阈值,就会被查到。PHP 斯坦
PHPStan 是另一个发现潜在错误的工具。
例如,它在运行时检查这样的事情。
------ -------------------------------------------------------------------------------------------------------------------- Line lib/Player.php ------ -------------------------------------------------------------------------------------------------------------------- 96 Variable $message might not be defined. ------ --------------------------------------------------------------------------------------------------------------------変数 $message が定義されていない可能性があります。有迹象表明通过添加以下内容并在使用之前对其进行定义来解决它。
$message = '';
♦︎5. 多次重构
关于重构,我实施了反复试验,使难以更改或难以阅读的代码变得易于阅读,并且我会毫不犹豫地一遍又一遍地修改它。
另外,重构的检查点如下。
- 坚实的原则依据
- 设计模式有什么可以应用的
- 静态分析工具中是否清除了复杂性检查?(→♦︎4)
坚实的原则
SOLID 原则由以下五项原则组成,每一项均已确认为检查项。
- 单一职责原则
- 开闭原理
- Liskov 替换原则
- 接口分离原理
- 依赖倒置原则
我主要重写了我认为不符合“单一职责原则”的部分。
使用设计模式
到目前为止我学到的三种设计模式是:
- 适配器模式
- 抽象工厂模式
- 复合图案
至少在这三个中,我查看了是否有我可以使用的模式,并检查了是否有我可以介绍的地方。我还检查了是否有其他可以使用的模式。
♡. 结论
我没有第三方来审核,所以我自己通过反复试验写的,所以这是一个我在担心过程中写的破烂的代码。
但是,为了使完成的代码尽可能好,让我们在回顾到目前为止所学的内容并学习我们还没有理解的新事物的同时继续前进!我带着精神来了。
我把它作为一个函数完成了,我有一种我做到了的感觉。但是,有很多事情我没有意识到我可以做得更好,所以我想有机会让第三方审查代码。 (欢迎在 GitHub 上评论和评论
)
即使有很多需要指出的地方,我也会将其视为成长的机会。
我觉得在二十一点上投注时间作为奖励获得的经验非常丰富。
非常感谢。
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308626066.html