OpenSiv3Dのトランプ描画機能(PlayingCard)

こんにちは。OpenSiv3Dのトランプ描画機能について紹介します。

```C++:PlayingCardSample.hpp
#include <Siv3D.hpp>
#include <HamFramework.hpp>
void Main()
{
    const PlayingCard::Pack pack(75, Palette::Red);
    Array<PlayingCard::Card> cards = PlayingCard::CreateDeck(2);
    Window::Resize(1280, 720);

    Graphics::SetBackground(Palette::Lightgreen);

    while (System::Update())
    {
        for (auto i : step(13 * 4 + 2))
        {
            const Vec2 center(10 + i % 13 * (pack.width() + 10), 10 + (i / 13) * (pack.height() + 10));

            if (pack.region(center).leftClicked())
            {
                cards[i].flip();
            }
            pack(cards[i]).draw(center);
        }
    }
}
```


![20171211-195201-530.png](https://qiita-image-store.s3.amazonaws.com/0/212031/b57dd33e-0f20-51e6-219e-d5e8073caee7.png)

この記事では全ての機能について詳細には説明しません(カード判定系の関数などはスルーします)ので、すべての機能が知りたければソースコードを読むなりしてみてください。


## 説明に入る前に
ヘッダファイル自体にもサンプルコードが最後にコメントアウトで含まれていますので、そちらもどうぞ。
[ヘッダファイルはこちら](https://github.com/Siv3D/OpenSiv3D/blob/master/Siv3D/include/HamFramework/PlayingCard.hpp)

# 準備

トランプ機能はHamFrameworkに組み込まれています。まずはHamFramework.hppをインクルードする必要があります。

```C++
#include <HamFramework.hpp>
```

全体的な使い方はFont型などに似たものになっています。すべての機能はPlayingCard::名前空間に入っています。トランプは英語でPlayingCardという(諸説あり)らしいので、この名前になりました。グローバルですね。

## トランプデータの作り方

Font型などと同様に、まずはカードの大きさと背面の色を指定したPlayingCard::Pack型を生成します。途中でカードのサイズは変えられませんが、中身は軽いのでたくさん作成しても問題はありません。
トランプの情報はPlayingCard::Card型で扱います。これをPackに渡すことで、それに応じた描画を行います。

```C++
//const Font font(20);
const PlayingCard::Pack pack(75, Palette::Red);
PlayingCard::Card card;

//font(L"Hello World").draw(100,100);
pack(card).draw(100, 100);
```
基本的な流れはこんな感じです。Font型と並べてみましたが、なんとなく似ているのがわかるのではないでしょうか。Fontに文字列を渡す要領で、PackにCardを渡しています。
ここで使われているPlayingCard::Card型ですが、カードの数字、マーク、裏表を保存したものです。ヘッダのコメントに全ての説明が書いてあるので一部抜粋してここに書いておきます。

```C++:トランプ情報クラス(Card)のメンバ変数の意味
/// <summary>
/// 番号
/// </summary>
int32 rank;

/// <summary>
/// スート(注釈：トランプにあるスペードなどのマークのことです。)
/// </summary>
Suit suit;

/// <summary>
/// 表向きであるか
/// </summary>
bool isFaceSide;
```

```C++:Suit型の定義
/// <summary>
/// カードのスート（絵柄のマーク）
/// </summary>
enum Suit : uint16
{
  /// <summary>
  /// ♠ スペード
  /// </summary>
  Spade,
 
  /// <summary>
  /// ♥ ハート
  /// </summary>
  Heart,

  /// <summary>
  /// ♣ クローバー
  /// </summary>
  Club,

  /// <summary>
  /// ♣ クローバー
  /// </summary>
  Clover = Club,

  /// <summary>
  /// ♦ ダイヤモンド
  /// </summary>
  Diamond,

  /// <summary>
  /// ジョーカー
  /// </summary>
  Joker,
};
```

直接トランプの数値を編集したいときはこれらのメンバ変数のデータを書き変えてやればいいです。例えばハートの9だったら、rank = 9、suit = Suit::Heartです。裏表は描画するときにのみ使われるので、手札なのか、相手が持っているカードなのか、などによって随時変更していけばいいですね。トランプのマークを示すSuit型の値についてですが、PlayingCard::Suit::まで入力すればVisual Studioの候補に出てくると思うのでそれを利用するといいです。

実際にトランプ機能を使うときは、トランプゲームを作ろうとしているときがほとんどでしょう。その場合はCreateDeckを使うことで、スムーズにデッキを作ることができます。

```C++
const PlayingCard::Pack pack(75, Palette::Red);

//引数はジョーカーの枚数
//型推論の結果は Array<PlayingCard::Card>型
auto cards = PlayingCard::CreateDeck(2);
```

52枚＋ジョーカーの枚数のトランプを格納したArray型を簡単に作れます。ここからOpenSiv3DのArrayで実装された強力な機能を組み合わせると、とても快適に作業をすることができます。

```C++
auto cards = PlayingCard::CreateDeck(2);

//カード(配列)をシャッフルする
cards.suffle();

//カードを4人に配る(配列を分割した配列を作る)
//handsはArray<Array<PlayingCard::Card>>型
auto hands = cards.in_groups(4);
```
これだけで4人にトランプを配る処理を実現できます。びっくりですね。
話がトランプ機能とは少し外れますが、この他にもOpenSiv3DのArrayは大幅に使いやすくなった機能がたくさんあるので、ヘッダを読むか、Visual Studioなどで"."を打つと出てくる候補から色々調べて使いこなしてみると幸せになれるかもしれません。

## 描画方法
トランプを画面に描画する方法には、簡易表示と完全版の表示が存在します。最初のスクリーンショットは完全版、次のスクリーンショットは簡易版の様子です。

![20171211-195354-858.png](https://qiita-image-store.s3.amazonaws.com/0/212031/2f96331e-c9eb-0972-4419-a8dd290bd285.png)

描画はpack(Card).draw(座標、角度)のような形で行います。もちろん、角度等は省略することが可能です。

```C++
PlayingCard::Card card;

pack(card).draw({100, 100}, 45_deg);
//drawAtで中心座標の指定も可能
pack(card).drawAt({100, 100}, 45_deg);
```

簡易表示はマークと数字を表示するだけの簡単なものです。小さく表示をしたときでも見やすいので手札をアイコン的な表示にしたいときに使えるかもしれません。draw関数にSimpleという名前がついたものがそれです。

```C++
pack(card).drawSimple({100,100});

//もちろん中心座標を指定できる
pack(card).drawSimpleAt({100,100});
```
カードの裏表の状態に関係なく裏面を描画したいときは、drawBackを使います。

```C++
pack(card).drawBack({100,100});
```

他にもジョーカーを判定するisJoker()や赤色か判定するisRed()など細かい機能が色々あるので、気になる方はIDEのインテリセンスで探すかヘッダを読んでみてください。

# 実装した感想など

　draw関数のコード読んでみれば分かりますが、トランプ描画は事前に用意した画像などではなく絵文字を使っているため、座標計算をとにかく気合いでねじ伏せています。バグが見つけづらい冗長なコードを書いてしまったのは反省点です。他に方法が思いつかなかったのであのようなことになってしまったのですが...

　Pack(card).draw() のような、クラスに引数を渡して関数のように扱う書き方は operator()を定義することで実現できるんですね。実装するときに教えて頂きとても勉強になりました。これを定義したクラスは関数のような書き方ができるので関数オブジェクトと呼ばれるらしいです。Siv3Dではよく見かけますね。

　回転描画などでTransformer2Dを毎回作っているのが少し気になるところなのですが、開発者であるRyoさんに聞いたところ、数百回呼んだ程度ではパフォーマンスにほとんど影響がないとのことです。ソースコードの可読性を多少なりとも確保するために内部で使用しています。座標変換、とても便利ですね。

　実は製作者自身がこの機能を全然使い込んでいないので、ここが使いづらいな、とかこれがあったら便利だな、という意見はgithubにissueを立てたりしてどんどん送っていくとよいです。「Ryoさんに送るのは気が引ける！」というシャイな方は、使いづらくなった元凶である私に[Twitter](https://twitter.com/co_para7_)のリプライなどで送りつけるなりして頂ければと思います。

### おわり

OpenSiv3Dのトランプ機能、ぜひ使ってみてください。

----
 [for Siv3D Advent Calendar 2017](https://qiita.com/advent-calendar/2017/siv3d)