shomisha / cards

本包包含一组类,用于表示可洗牌的牌组。

1.2.0 2020-04-19 22:27 UTC

This package is auto-updated.

Last update: 2024-09-08 08:30:42 UTC


README

Latest Stable Version Software License

本包提供了一个使用面向对象PHP实现牌组的简单实现。除了DeckCard类外,它还包含一个用于洗牌的Shuffler类和一个用于创建牌组的DeckBuilder类。

安装

您可以通过Composer安装此包。为此,只需从项目的根目录运行以下命令

composer require shomisha/cards

用法

如前所述,有四个主要类

  1. Shomisha\Cards\DeckBuilders\DeckBuilder
  2. Shomisha\Cards\Decks\Deck
  3. Shomisha\Cards\Cards\Card
  4. Shomisha\Cards\Shufflers\Shuffler

除了这些类之外,还有一个用于表示牌套的枚举Shomisha\Cards\Suites\Suite类和一个用于表示牌组中小丑的Shomisha\Cards\Cards\Joker类。

DeckBuilder

Shomisha\Cards\DeckBuilders\DeckBuilder类基本上是一个牌组的工厂。它公开了两个用于创建Deck类实例的方法

  • build()
  • buildMultiple(int $count)

build()方法返回一个包含54张牌的Deck类实例,即标准的52张牌和两张小丑。您可以创建没有小丑的牌组,您可以在本章后面了解有关内容。

buildMultiple(int $count)仍然创建一个Deck实例,但它填充了$count * 54张牌,即$count参数指定了这个牌组可以放入的一个牌组的数量。同样,您可以使用此方法构建没有小丑的牌组,在这种情况下创建的牌组将包含$count * 52张牌。

withJokers(bool $withJokers = true)

为了创建带或不带小丑的牌组,您可以使用接受单个参数truefalsewithJokers(bool $withJokers = true)方法。自然地,true将创建一个包含小丑的牌组,而false将不会在创建的牌组中包含小丑。

请注意,此命令是不可变的,这意味着它不会修改它被调用的实例,而是创建一个新的实例并在其上设置所需值。以下是一个示例

$builder = new \Shomisha\Cards\DeckBuilders\DeckBuilder();

$deck = $builder->build(); // Returns a deck with jokers
count($deck->cards()); // 54

$deck = $builder->withJokers(false)->build(); // Returns a deck without jokers
count($deck->cards()); // 52

$deck = $builder->buildMultiple(2);
count($deck->cards()); // 108

$deck = $builder->withJokers(false)->buildMultiple(2);
count($deck->cards()); // 104

发生的事情是,对withJokers()的调用创建了一个新的构建器实例,该实例没有保存到任何变量中。在原始构建器上调用build()方法仍然创建了一个包含小丑的牌组,因为该实例没有被withJokers()调用修改。同样适用于buildMultiple()调用。

Deck

Shomisha\Cards\Decks\Deck是本包中的主要对象。这是一个旨在充当真实牌组的类,并为用户提供他们应该拥有的实际牌组的功能。

它包含以下方法

  • cards(): array
  • draw(): ?Shomisha\Cards\Cards\Card
  • take(int $position): ?Shomisha\Cards\Cards\Card
  • place(Card $card): Shomisha\Cards\Decks\Deck
  • put(Card $card, int $position): Shomisha\Cards\Decks\Deck
  • split(int $position = -1): array
  • join(Deck $deck): Deck

cards()方法

此方法返回一个数组,包含调用它的那一刻牌组上的所有牌。此数组的元素都是卡片类的实例(普通卡片或小丑)。

请注意,此方法按值返回卡片数组,这意味着您对此数组所做的任何更改都不会反映在牌组的状态上。

此方法主要为了测试而实现,建议您不要使用它,而是依赖其他方法。

《draw()` 方法

《draw()` 方法的名字相当直观,它从牌堆顶部抽取一张牌。

它返回一个卡片类的实例。需要注意的是,抽取一张牌实际上会从牌堆中移除这张返回的牌。如果牌堆为空,此方法将返回 null。

考虑以下示例

use Shomisha\Cards\DeckBuilders\DeckBuilder;

$deck = (new DeckBuilder())->withJokers(false)->build();

count($deck->cards()); // Returns 52

$drawnCard = $deck->draw(); // Returns a Card instance

count($deck->cards()); // Returns 51

$deck->draw(); // Returns a Card instance
$deck->draw(); // Returns a Card instance

count($deck->cards()); // Returns 49

新创建的牌堆有 52 张牌,因为我们没有加入小丑牌。第一次抽取后,剩余 51 张牌,接着连续抽取两张后,剩余 49 张牌。

《take(int $position)》方法

此方法与《take()` 方法类似,但有一个重要的区别:它不返回牌堆顶部的牌,而是返回使用《$position》参数指定的位置的牌。如果请求的位置没有牌,该方法将返回 null。

与《draw()` 方法类似,从牌堆中取出的牌在牌堆中将不再可用。

以下是一个示例

use Shomisha\Cards\DeckBuilders\DeckBuilder;

$deck = (new DeckBuilder())->withJokers(false)->build();

count($deck->cards()); // Returns 52

$drawnCard = $deck->take(12); // Returns a Card instance

count($deck->cards()); // Returns 51

$deck->take(33); // Returns a Card instance
$deck->take(29); // Returns a Card instance

count($deck->cards()); // Returns 49

示例与上一个示例相似,但取出的卡片实例不是连续的,也不是从牌堆顶部开始,而是从目标位置开始。

在从牌堆中取走一张牌后,所有剩余的牌都将重新键入,以保持一个从《0》到《n - 1》的有序数组,其中《n》是牌堆中的卡片数。

《place(Card $card)》方法

《place(Card $card)》方法在某种程度上是《draw()` 方法的对立面,因为它将一张牌放在牌堆顶部。此方法会修改被调用的牌堆。

看看这个示例

use Shomisha\Cards\Cards\Joker;
use Shomisha\Cards\DeckBuilders\DeckBuilder;

$deck = (new DeckBuilder())->withJokers(false)->build();
count($deck->cards()); // Returns 52

$deck->place(new Joker());
count($deck->cards()); // Returns 53

$deck->draw(); // Returns the previously created instance of Joker
count($deck->cards()); // Returns 52

《put(Card $card, int $position)》方法

此方法的行为与《place()` 方法类似,不同之处在于将牌放在指定的位置,而不是牌堆的顶部。与兄弟方法一样,它也会修改被调用的牌堆。

use Shomisha\Cards\Cards\Joker;
use Shomisha\Cards\DeckBuilders\DeckBuilder;

$deck = (new DeckBuilder())->withJokers(false)->build();

$deck->put(new Joker(), 42);
$deck->take(42); // Returns the previously created Joker.

如果指定的位置已经存在一张牌,新牌不会覆盖现有牌,而是将所有后续牌的位置向上移动,为自身腾出空间。

《split(int $position = -1)》方法

《split(int $position = -1)》方法用于将牌堆分成两个独立的牌堆。它可以有两种行为:受控分割,其中您控制牌堆在什么牌上分割,和随机分割,其中分割牌是随机选择的。

此方法与其他大多数《Deck》方法一样工作,即它会修改被调用的牌堆。换句话说,它将从现有牌堆中移除进入新分割牌堆的牌。

以下是如何执行受控分割的示例

use \Shomisha\Cards\DeckBuilders\DeckBuilder;

$deck = (new DeckBuilder())->build();

$splitDecks = $deck->split(20);

count($splitDecks[0]->cards()); // 20
count($splitDecks[1]->cards()); // 34
$deck === $splitDecks[1];       // true

如你所见,《split()` 方法返回一个数组。这个数组包含两个分割后的牌堆:第一个是新牌堆,第二个是被分割的旧牌堆。第一个返回的牌堆将包含旧牌堆中顶部 N 张牌,N 是分割牌的位置。第二个牌堆将是执行分割的牌堆实例。现在这个牌堆包含从顶部到底部 M - N 张剩余的牌,M 是原始牌堆中的牌数,N 是分割牌的位置。这种行为以及所有其他行为都模仿了当你将真实的牌堆分成两半时实际发生的情况。

以下是牌堆随机分割时的情况

use \Shomisha\Cards\DeckBuilders\DeckBuilder;

$deck = (new DeckBuilder())->build();

$splitDecks = $deck->split();

count($splitDecks[0]->cards()); // 12
count($splitDecks[1]->cards()); // 42
$deck === $splitDecks[1];       // true

随机分割一副牌的行为与控制分割牌的行为完全相同,区别在于分割的牌是随机选择的,而不是用户选择的。返回值相同,是新牌和原牌,原牌以相同的方式修改。

方法 join(Deck $deck)

此方法与 split() 方法相反。它用于将两副牌合并在一起,或者说将一副牌的牌添加到另一副牌中。此方法会将作为参数提供的牌组的所有牌放在调用此方法的那副牌的顶部。

以下是一个示例

use \Shomisha\Cards\DeckBuilders\DeckBuilder;

$builder = new DeckBuilder();
$deck1 = $builder->build();
$deck2 = $builder->build();

$deck1->join($deck2);

count($deck1->cards()); // 108
count($deck2->cards()); // 0

上面的代码片段显示了如何将一副牌合并到另一副牌中,以及所有来自被合并牌组的牌都转移到了合并的牌组中。被合并牌组的牌被放在了合并牌组的牌的顶部,因此成为了它的一部分。

Shomisha\Cards\Cards\Card 类本质上是用于表示牌的数据对象。

此类公开了四个方法

  • identifier(): string
  • rank(): string
  • value(): int
  • suite(): Shomisha\Cards\Suites\Suite

方法 identifier()

此方法用于唯一地定义牌的值和花色。它返回一个组合字符串,其中包含牌的花色和其数值。

以下是一个示例

use Shomisha\Cards\Cards\Card;
use Shomisha\Cards\Suites\Suite;

$card = new Card(Suite::SPADES(), 10);

$card->identifier(); // Returns "spades-10"

建议您使用 value()suite() 方法来建立牌的身份,然而也可以使用 identifier() 方法来实现此目的。

方法 rank()

rank() 方法用于获取牌的“名称”。对于没有特殊意义的牌,它简单地返回该牌的英文单词。对于人头牌,它返回该牌的英文名称。

以下是每个牌的 rank() 方法将返回的值

 1 => "Ace"
 2 => "Two"
 3 => "Three"
 4 => "Four"
 5 => "Five"
 6 => "Six"
 7 => "Seven"
 8 => "Eight"
 9 => "Nine"
10 => "Ten"
12 => "Jack"
13 => "Queen"
14 => "King"

以下是一个示例

use Shomisha\Cards\Cards\Card;
use Shomisha\Cards\Suites\Suite;

$cards = [
    new Card(Suite::SPADES(), 1),
    new Card(Suite::SPADES(), 2),
    new Card(Suite::SPADES(), 12),
    new Card(Suite::SPADES(), 14),
];

$cards[0]->rank(); // returns "Ace"
$cards[1]->rank(); // returns "Two"
$cards[2]->rank(); // returns "Jack"
$cards[3]->rank(); // returns "King"

方法 value()

value() 方法相当简单,它返回被调用牌的数值。

以下是一个示例

use Shomisha\Cards\Cards\Card;
use Shomisha\Cards\Suites\Suite;

$cards = [
    new Card(Suite::SPADES(), 1),
    new Card(Suite::SPADES(), 2),
    new Card(Suite::SPADES(), 12),
    new Card(Suite::SPADES(), 14),
];

$cards[0]->value(); // returns 1
$cards[1]->value(); // returns 2
$cards[2]->value(); // returns 12
$cards[3]->value(); // returns 14

方法 suite()

suite() 方法可用于访问牌的花色。此方法返回一个 Shomisha\Cards\Suites\Suite 实例,它是一个枚举类。

Suite 类的实例有一个方法:name()。它返回一个字符串,即所询问花色的英文单词。

除了此方法之外,Suite 类还有一个 __toString() 方法,用于将对象自动转换为字符串。换句话说,您可以自由使用 $card->suite() 方法的返回值作为字符串。

以下是一个示例

use Shomisha\Cards\Cards\Card;
use Shomisha\Cards\Suites\Suite;


$card = new Card(Suite::HEARTS(), 10);

$suite = $card->suite(); // Returns an instance of Shomisha\Cards\Suites\Suite;

$suite->name() == 'hearts';  // true
$suite->name() === 'hearts'; // true

$suite == 'hearts';  // true
$suite === 'hearts'; // false

$suite->name() == Suite::HEARTS();  // true
$suite->name() === Suite::HEARTS(); // false

$suite == Suite::HEARTS();  // true
$suite === Suite::HEARTS(); // false

Shomisha\Cards\Cards\Joker

Shomisha\Cards\Cards\Joker 类用于表示牌组中的鬼牌。它不扩展 Shomisha\Cards\Cards\Card 类,但它实现了 Shomisha\Cards\Contracts\Card 接口,因此也是同一树的一部分。

与基础牌类相比,它的唯一区别是它的方法始终返回相同的值

use Shomisha\Cards\Cards\Joker;

$joker = new Joker();

$joker->rank(); // Returns "joker"
$joker->value(); // Returns 15
$joker->identifier(); // Returns "joker"

(string) $joker->suite(); // Returns "joker"

洗牌器

Shomisha\Cards\Shufflers\Shuffler 用于洗牌组,正如其名所示。

它公开了一个名为 shuffle(Deck $deck, int $rounds = 1) 的单个方法。此方法的第一个参数是要洗的牌组。重要的是要记住,此方法将修改传入的牌组,而不会创建新的牌组。

第二个参数是应该洗牌的次数。传入的数字越高,牌组中牌的顺序的随机性就越高。

use Shomisha\Cards\DeckBuilders\DeckBuilder;
use Shomisha\Cards\Shufflers\Shuffler;

$deck = (new DeckBuilder())->build();
$deck->cards()[0]->identifier(); // Returns "clubs-1"

(new Shuffler())->shuffle($deck, 3);

$deck->cards()[0]->identifier(); // Returns "spades-7"

请注意,上面的示例是随机的,洗牌后 spades-7 不会总是位于牌组的顶部。

洗牌器可以与任何大小的牌组一起工作,不论卡片之前是否已从牌组中取出,或者牌组由多个牌组组成,这都是无关紧要的。