0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

麻雀の手牌を一雀頭四面子に分解する簡単な方法

Last updated at Posted at 2024-09-21

はじめに

同じような記事はありますが、結局、実例と実際に動くプログラムで説明した方がわかりやすいと思うので記事にしてみます。

手書きで計算するとどうなるのか

実際の例として次の並びを考えてみます。

123456712赤56712

(1) 赤牌を普通の牌に変える

12345671256712

(2) 昇順にソートする

11122234556677

(3-1) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

☆左端からというのは元の並びの一番左の数字を必ず移動しなければならないということ
☆この時、対子を左へ2個移動するのは禁止
☆この時、順子を左へ移動した直後に、同じ数字で始まる刻子や対子を左へ移動するのは禁止

111                ← ***22234556677

(3-2) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

111 222            ← ******34556677

(3-3) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

111 222 345        ← *********56677

(3-4) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

111 222 345 567    ← ***********6*7

(4-4) 移動できなくなったとき一雀頭四面子できていれば完成してるので記録する

完成していないので何もしない

(5-4) まだ試していないパターンがあれば一つ前の状態に戻して(3-4)の続きからやる

左側の最後が567なので順子まですべて試したので何もしない

(6-3) まだ試していないパターンがなければ一つ前の状態に戻して(5-3)へ行く

111 222 345        ← *********56677

(5-3) まだ試していないパターンがあれば一つ前の状態に戻して(3-3)の続きからやる

左側の最後が345なので順子まですべて試したので何もしない

(6-2) まだ試していないパターンがなければ一つ前の状態に戻して(5-2)へ行く

111 222            ← ******34556677

(5-2) まだ試していないパターンがあれば一つ前の状態に戻して(3-2)の続きからやる

左側の最後が222なので刻子まで試して対子の22と順子の234は試していないので一つ前の状態に戻して(3-2)の続きからやる

111                ← ***22234556677

(3-2) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

111 22             ← *****234556677

(3-3) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

111 22 234         ← ********556677

(3-4) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

☆この時、対子を左へ2個移動するのは禁止なので55の移動は試さない

111 22 234 567     ← *********5*6*7

(3-5) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

111 22 234 567 567 ← **************

(4-5) 移動できなくなったとき一雀頭四面子できていれば完成してるので記録する

完成したので「111 22 234 567 567」を記録する

(5-5) まだ試していないパターンがあれば一つ前の状態に戻して(3-5)の続きからやる

左側の最後が567なので順子まですべて試したので何もしない

(6-4) まだ試していないパターンがなければ一つ前の状態に戻して(5-4)へ行く

111 22 234 567     ← *********5*6*7

(5-4) まだ試していないパターンがあれば一つ前の状態に戻して(3-4)の続きからやる

左側の最後が567なので順子まですべて試したので何もしない

(6-3) まだ試していないパターンがなければ一つ前の状態に戻して(5-3)へ行く

111 22 234         ← ********556677

(5-3) まだ試していないパターンがあれば一つ前の状態に戻して(3-3)の続きからやる

左側の最後が234なので順子まですべて試したので何もしない

(6-2) まだ試していないパターンがなければ一つ前の状態に戻して(5-2)へ行く

111 22             ← *****234556677

(5-2) まだ試していないパターンがあれば一つ前の状態に戻して(3-2)の続きからやる

左側の最後が22なので対子まで試して順子の234は試していないので一つ前の状態に戻して(3-2)の続きからやる

111                ← ***22234556677

(3-2) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

111 234            ← ****22**556677

☆この時、順子を左へ移動した直後に、同じ数字で始まる刻子や対子を左へ移動するのは禁止、左側の最後が234なので2で始まる右側の22は移動できないのでこれ以上移動できるものがない

従って(3-3)ではなく(4-2)へ進む

(4-2) 移動できなくなったとき一雀頭四面子できていれば完成してるので記録する

完成していないので何もしない

(5-2) まだ試していないパターンがあれば一つ前の状態に戻して(3-2)の続きからやる

左側の最後が234なので順子まですべて試したので何もしない

(6-1) まだ試していないパターンがなければ一つ前の状態に戻して(5-1)へ行く

111                ← ***22234556677

(5-1) まだ試していないパターンがあれば一つ前の状態に戻して(3-1)の続きからやる

左側の最後が111なので刻子まで試して対子の11と順子の123は試していないので一つ前の状態に戻して(3-1)の続きからやる

                   ← 11122234556677

(3-1) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

11                 ← **122234556677

(3-2) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

11 123             ← ****22*4556677

(4-2) 移動できなくなったとき一雀頭四面子できていれば完成してるので記録する

完成していないので何もしない

(5-2) まだ試していないパターンがあれば一つ前の状態に戻して(3-2)の続きからやる

左側の最後が123なので順子まですべて試したので何もしない

(6-1) まだ試していないパターンがなければ一つ前の状態に戻して(5-1)へ行く

11                 ← **122234556677

(5-1) まだ試していないパターンがあれば一つ前の状態に戻して(3-1)の続きからやる

左側の最後が11なので対子まで試して順子の123は試していないので一つ前の状態に戻して(3-2)の続きからやる

                   ← 11122234556677

(3-1) 刻子、対子、順子の順に元の並びの左端から左への移動を試していく

123                ← *11*22*4556677

(4-1) 移動できなくなったとき一雀頭四面子できていれば完成してるので記録する

完成していないので何もしない

(5-1) まだ試していないパターンがあれば一つ前の状態に戻して(3-1)の続きからやる

左側の最後が123なので順子まですべて試したので何もしない

(6-0) 終了

結果:「111 22 234 567 567」

右側の(-?)は探索の深さを表す
(3-?)から(3-?)に行くときに(3-?)の右側の?が増える
(5-?)から(6-?)に行くときに(6-?)の右側の?が減る
?が0になったら終了

上記のアルゴリズムのポイント

☆赤牌を普通の牌に変える
☆昇順にソートする

この二つはその方が簡単だからやっているだけです。一番小さい数字から始まる刻子や対子や順子を左に移動するという手順を守っていれば必須ではありません。

☆刻子、対子、順子の順に調べる

順番はどうでもいいですが、順番が決まっていることが重要です。さらに順に調べるということは状態を必要に応じて戻さなければなりません。
刻子を移動したときは、対子や順子を調べるより先に刻子を移動する前の状態に戻さないといけません。対子を移動したときは、順子を調べるより先に対子を移動する前の状態に戻さないといけません。

☆対子を左へ2個移動するのは禁止

雀頭は2つ必要ないので当然です。

☆元の並びの一番左の数字を必ず移動しなけれなならないということ

正確にいうと一番小さい数字です。ランダムな順で移動していると検索漏れが発生するので何らかの基準が必要です。

☆順子を左へ移動した直後に、同じ数字で始まる刻子や対子を左へ移動するのは禁止

重複して同じパターンを発見しないためです。

これがないと上記の例ですと

「111 22 234 567 567」
「111 234 22 567 567」

のように「22」と「234」が入れ替わった2パターンができてしまいます。

ただし

☆元の並びの一番左の数字を必ず移動しなければならない

というルールなので

「234」と「567」が入れ替わったりする心配はありません。「567」を左に移動したときには右側には5以上の数字しかないので「567」の後に「234」がででくることは絶対にありません。あくまで先頭の数字が同じ部分の並びが入れ替わった複数のパターンがでてくる心配だけがあります。

全く同じ並びである「567」と「567」が入れ替わる心配も同じ理由でありません。一番左の「5」のからできた「567」と左から二番目の「5」のからできた「567」なら
前者が必ず左側にあるからです。

「222」と「22」が入れ替わったりする心配もありません。それですと同じ牌が5つあることになって麻雀のルールに違反します。

したがって例えば2が先頭の部分に対しては、

  • 「222 234」と「234 222」
  • 「22 234」と「234 22」
  • 「22 234 234」と「234 22 234」と「234 234 22」

の3系統の重複だけ気にすればいいわけで「234」を左に移動した直後に「222」や「22」を左に移動しないと決めておけば解決します。これを一般化すると

☆順子を左へ移動した直後に、同じ数字で始まる刻子や対子を左へ移動するのは禁止

となるわけです。

サンプルコード

専門用語が多すぎて英語にすると読みにくくてやってられないので日本語識別子で書いています。

見本.h
#pragma once

#include <iostream>
#include <array>
#include <bitset>
#include <vector>
#include <algorithm>
#include <string>

struct 
{
    int {0};

    static constexpr int 赤牌 = 1 << 6;
    static constexpr int 番号部分 = (1 << 4) - 1;
    static constexpr int 萬子 = 0 << 4;
    static constexpr int 筒子 = 1 << 4;
    static constexpr int 索子 = 2 << 4;
    static constexpr int 字牌 = 3 << 4;
    static constexpr int 次の種類 = 1 << 4;

    constexpr int 種類を得る() const noexcept { return  & 字牌; }
    constexpr char 種類を文字で得る() const noexcept
    {
        switch (種類を得る()) {
        case 索子: return 's';
        case 筒子: return 'p';
        case 字牌: return 'z';
        default: return 'm';
        }
    }
    constexpr int 番号を得る() const noexcept { return  & 番号部分; }
    constexpr bool 赤牌である() const noexcept { return  & 赤牌; }
    constexpr bool 萬子である() const noexcept { return 種類を得る() == 萬子; }
    constexpr bool 筒子である() const noexcept { return 種類を得る() == 筒子; }
    constexpr bool 索子である() const noexcept { return 種類を得る() == 索子; }
    constexpr bool 字牌である() const noexcept { return 種類を得る() == 字牌; }
    constexpr bool 数牌である() const noexcept { return 種類を得る() != 字牌; }
    constexpr bool 風牌である() const noexcept { return 字牌である() && 番号を得る() <= 4; }
    constexpr bool 三元牌である() const noexcept { return 字牌である() && 番号を得る() >= 5; }
    constexpr bool operator ==( 2) const noexcept { return  == 2.; }
    constexpr bool operator !=( 2) const noexcept { return  != 2.; }
    constexpr bool operator <( 2) const noexcept
    {
        if ((~赤牌 & ) < (~赤牌 & 2.)) return true;
        if ((~赤牌 & ) == (~赤牌 & 2.) &&  > 2.) return true;
        return false;;
    }
    constexpr bool operator >( 2) const noexcept { return 2 < *this; }
    constexpr  operator +(int 差分) const noexcept { return { + 差分}; }
    constexpr  operator -(int 差分) const noexcept { return { - 差分}; }
};

constexpr  萬子の(int 番号) { return {::萬子 | 番号}; }
constexpr  筒子の(int 番号) { return {::筒子 | 番号}; }
constexpr  索子の(int 番号) { return {::索子 | 番号}; }
constexpr  字牌の(int 番号) { return {::字牌 | 番号}; }
constexpr  赤牌の( ある牌) { return {::赤牌 | ある牌.}; }
constexpr  非赤牌の( ある牌) { return {~::赤牌 & ある牌.}; }

constexpr  無牌{0};
constexpr  {字牌の(1)}, {字牌の(2)}, 西{字牌の(3)}, {字牌の(4)};
constexpr  {字牌の(5)}, {字牌の(6)}, {字牌の(7)};

using 四牌 = std::array<, 4>;
using 十四牌 = std::array<, 14>;
using 十四ビット = std::bitset<14>;

constexpr size_t 未発見 = 14;

inline size_t 未使用牌を探す(const 十四牌& 手牌, const size_t 位置, const 十四ビット 使用中)
{
    size_t 未使用牌の位置 = 位置;
    while (未使用牌の位置 < 未発見 && 使用中.test(未使用牌の位置)) ++未使用牌の位置;
    return 未使用牌の位置;
}

inline size_t 牌を探す(const 十四牌& 手牌, const size_t 位置, const 十四ビット 使用中, const  対象の牌)
{
    size_t 対象の位置 = 未使用牌を探す(手牌, 位置, 使用中);
    if (対象の位置 < 未発見) {
        if (手牌[対象の位置] == 対象の牌) return 対象の位置;
        if (手牌[対象の位置] > 対象の牌) return 未発見;
    }
    if (対象の位置 + 1 >= 未発見) return 未発見;
    return 牌を探す(手牌, 対象の位置 + 1, 使用中, 対象の牌);
}

struct 雀頭
{
     代表牌{無牌};
};

inline std::ostream& operator <<(std::ostream& 出力, const 雀頭& )
{
    const  代表牌 = .代表牌;
    const int 番号 = 代表牌.番号を得る();
    出力 << 番号 << 番号 << 代表牌.種類を文字で得る();

    return 出力;
}

struct 面子
{
    int 種類{順子};
     代表牌{無牌};

    static constexpr int 順子 = 0;
    static constexpr int 刻子 = 1;
    static constexpr int 槓子 = 2;
};

constexpr 面子 順子の( 代表牌) { return {面子::順子, 代表牌}; }
constexpr 面子 刻子の( 代表牌) { return {面子::刻子, 代表牌}; }
constexpr 面子 槓子の( 代表牌) { return {面子::槓子, 代表牌}; }

inline std::ostream& operator <<(std::ostream& 出力, const 面子& )
{
    const int 種類 = .種類;
    const  代表牌 = .代表牌;
    const int 番号 = 代表牌.番号を得る();
    if (種類 == 面子::順子) {
        出力 << 番号 << 番号 + 1 << 番号 + 2;
    } else {
        出力 << 番号 << 番号 << 番号;
        if (種類 == 面子::槓子) 出力 << 番号;
    }
    出力 << 代表牌.種類を文字で得る();

    return 出力;
}

struct 一雀頭四面子
{
    雀頭 一雀頭{無牌};
    std::array<面子, 4> 四面子{};
    size_t 面子の数{0};
    size_t 副露の数{0};
    size_t 暗槓の数{0};

    bool 雀頭が有る() const noexcept
    {
        return 一雀頭.代表牌 != 無牌;
    }

    bool 完成した() const noexcept
    {
        return 雀頭が有る() && 面子の数 >= 4;
    }

    bool 雀頭を設定する(const 十四牌& 手牌, const size_t 対象1, 十四ビット& 使用中) noexcept
    {
        if (雀頭が有る()) return false;

        const size_t 対象2 = 牌を探す(手牌, 対象1 + 1, 使用中, 手牌[対象1]);
        if (対象2 >= 未発見) return false;

        使用中.set(対象1);
        使用中.set(対象2);
        一雀頭 = {手牌[対象1]};

        return true;
    }

    bool 刻子を設定する(const 十四牌& 手牌, const size_t 対象1, 十四ビット& 使用中) noexcept
    {
        return 面子を設定する(手牌, 対象1, 使用中, 0);
    }

    bool 順子を設定する(const 十四牌& 手牌, const size_t 対象1, 十四ビット& 使用中) noexcept
    {
        return 面子を設定する(手牌, 対象1, 使用中, 1);
    }

private:
    bool 面子を設定する(const 十四牌& 手牌, const size_t 対象1, 十四ビット& 使用中, int 差分) noexcept
    {
        if (面子の数 >= 4) return false;

        const size_t 対象2 = 牌を探す(手牌, 対象1 + 1, 使用中, 手牌[対象1] + 差分);
        if (対象2 + 1 >= 未発見) return false;

        const size_t 対象3 = 牌を探す(手牌, 対象2 + 1, 使用中, 手牌[対象1] + 差分 * 2);
        if (対象3 >= 未発見) return false;

        auto& 新規の面子 = 四面子[面子の数];
        使用中.set(対象1);
        使用中.set(対象2);
        使用中.set(対象3);
        新規の面子 = {(差分 == 0) ? 面子::刻子 : 面子::順子, 手牌[対象1]};
        ++面子の数;

        return true;
    }
};

inline std::ostream& operator <<(std::ostream& 出力, const 一雀頭四面子& )
{
    if (.雀頭が有る()) {
        出力 << .一雀頭;
    }
    for (size_t n = 0; n < .面子の数; ++n) {
        出力 << .四面子[n];
    }
    出力 << std::endl;

    return 出力;
}

inline void 一雀頭四面子に分解する(
    一雀頭四面子& 現状, std::vector<一雀頭四面子>& 分解後,
    const 十四牌& 手牌, const size_t 位置, 十四ビット 使用中, const  直前の順子の先頭の牌)
{
    if (現状.完成した()) {
        分解後.push_back(現状);
        return;
    }

    一雀頭四面子 元の状況 = 現状;
    十四ビット 元の使用中 = 使用中;

    const auto 次の位置 = 未使用牌を探す(手牌, 位置, 使用中);
    if (次の位置 >= 未発見) return;

    bool 順子を分離済み = 手牌[次の位置] == 直前の順子の先頭の牌;
    if (!順子を分離済み && 現状.雀頭を設定する(手牌, 次の位置, 使用中)) {
        一雀頭四面子に分解する(現状, 分解後, 手牌, 次の位置 + 1, 使用中, 無牌);
        現状 = 元の状況;
        使用中 = 元の使用中;
    }
    if (!順子を分離済み && 現状.刻子を設定する(手牌, 次の位置, 使用中)) {
        一雀頭四面子に分解する(現状, 分解後, 手牌, 次の位置 + 1, 使用中, 無牌);
        現状 = 元の状況;
        使用中 = 元の使用中;
    }
    if (手牌[次の位置].数牌である() && 現状.順子を設定する(手牌, 次の位置, 使用中)) {
        一雀頭四面子に分解する(現状, 分解後, 手牌, 次の位置 + 1, 使用中, 手牌[次の位置]);
    }
}

inline void 一雀頭四面子に分解する(
    一雀頭四面子& 現状, std::vector<一雀頭四面子>& 分解後,
    const 十四牌& 手牌, const size_t 位置, 十四ビット 使用中)
{
    // 無理だとすぐに判断できる場合は分解を試さない
    int ブロックの長さ = 1;
    bool 対子有り = false;
    for (size_t n = 0; n < 13; ++n) {
        if (手牌[n + 1] == 手牌[n]) {
            対子有り = true;
            ++ブロックの長さ;
        } else if (手牌[n + 1] == 手牌[n] + 1) {
            ++ブロックの長さ;
        } else {
            // ブロックの長さを3で割って1余るならば
            // 雀頭と面子の組み合わせに分けることはできない
            if (ブロックの長さ % 3 == 1) return;
            // 次の牌は次のブロックの始まり
            ブロックの長さ = 1;
        }
    }
    // 対子が一つもなければ絶対に雀頭を作ることができない
    if (!対子有り) return;

    一雀頭四面子に分解する(現状, 分解後, 手牌, 位置, 使用中, 無牌);
}

inline std::vector<一雀頭四面子> 一雀頭四面子のリストを作成する(
    const 十四牌& 理牌済み赤無しの手牌, const std::vector<面子>& 副露面子, const size_t 暗槓の数 = 0)
{
    一雀頭四面子 現状{};
    std::vector<一雀頭四面子> 分解後{};
    size_t 位置{0};
    十四ビット 使用中 = {};

    for (size_t n = 0; n < 副露面子.size() && n < 4; ++n) {
        現状.四面子[n] = 副露面子[n];
    }
    現状.面子の数 = 副露面子.size();

    // 副露の数を記録しておかないと一盃口などの判定がしづらくなる
    現状.副露の数 = 現状.面子の数;
    // 副露の数と暗槓の数を記録しておかないと三暗刻や四暗刻などの判定がしづらくなる
    現状.暗槓の数 = 暗槓の数;

    一雀頭四面子に分解する(現状, 分解後, 理牌済み赤無しの手牌, 位置, 使用中);
    return 分解後;
}

// 読み込んだ牌の数を戻す
// mが萬子、pが筒子、sが筒子、zが字牌、0が赤牌、1~9は数牌ならそのまま、
// 字牌ならば1z/2z/3z/4z/5z/6z/7zがそれぞれ東/南/北/西/白/發/中を表現している
template <size_t N> inline size_t 読み込む(std::array<, N>& 牌のリスト, const std::string& 牌文字列)
{
    牌のリスト = {};
    size_t 位置{0};
    auto 追加する = [&牌のリスト, &位置](int 各牌) { 牌のリスト[位置] = {各牌}; ++位置; };

    int 種類 = ::萬子;
    auto 終端 = 牌文字列.crend();
    for (auto it = 牌文字列.crbegin(); it != 終端 && 位置 < N; ++it) {
        switch (*it) {
        case '0': if (種類 != ::字牌) 追加する(::赤牌 | 種類 | 5); break;
        case '1': 追加する(種類 | 1); break;
        case '2': 追加する(種類 | 2); break;
        case '3': 追加する(種類 | 3); break;
        case '4': 追加する(種類 | 4); break;
        case '5': 追加する(種類 | 5); break;
        case '6': 追加する(種類 | 6); break;
        case '7': 追加する(種類 | 7); break;
        case '8': if (種類 != ::字牌) 追加する(種類 | 8); break;
        case '9': if (種類 != ::字牌) 追加する(種類 | 9); break;
        case 'm': 種類 = ::萬子; break;
        case 'p': 種類 = ::筒子; break;
        case 's': 種類 = ::索子; break;
        case 'z': 種類 = ::字牌; break;
        default: break;
        }
    }

    std::reverse(牌のリスト.begin(), 牌のリスト.begin() + 位置);
    return 位置;
}

template <size_t N> inline size_t 赤を消して理牌する(
    std::array<, N>& 牌のリスト, const size_t 枚数 = N)
{
    size_t 赤ドラの数{0};
    for (size_t n = 0; n < 枚数; ++n) {
        auto& 各牌 = 牌のリスト[n];
        if (各牌.赤牌である()) {
            ++赤ドラの数;
            各牌 = 非赤牌の(各牌);
        }
    }
    std::sort(牌のリスト.begin(), 牌のリスト.begin() + 枚数, []( ,  ) { return . < .; });

    return 赤ドラの数;
}

// このファイルのライセンス
/*
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>
*/

以下に簡単なテストコードをあげておきます。

見本.cpp
#include "pch.h"

#include "見本.h"
#include <sstream>

static std::string 分割する(
    const std::string 牌姿, std::vector<std::string> 副露文字列 = {}, const size_t 暗槓の数 = 0)
{
    std::ostringstream 出力;
    出力 << "* " << 牌姿;
    for (const auto& 各文字列 : 副露文字列) {
        出力 << " / " << 各文字列;
    }

    十四牌 手牌{};

    const size_t 手牌の枚数 = 読み込む(手牌, 牌姿);
    size_t 赤ドラの数 = 赤を消して理牌する(手牌, 手牌の枚数);

    std::vector<面子> 副露面子;
    for (const auto& 各文字列 : 副露文字列) {
        四牌 各四牌{};
        const size_t 牌の枚数 = 読み込む(各四牌, 各文字列);
        赤ドラの数 += 赤を消して理牌する(各四牌, 牌の枚数);
        if (牌の枚数 == 3) {
            if (各四牌[0] != 各四牌[1]) {
                副露面子.push_back({面子::順子, 各四牌[0]});
            } else {
                副露面子.push_back({面子::刻子, 各四牌[0]});
            }
        } else if (牌の枚数 == 4) {
            副露面子.push_back({面子::槓子, 各四牌[0]});
        }
    }

    if (赤ドラの数 > 0) 出力 << " 赤" << 赤ドラの数;
    出力 << std::endl;

    std::vector<一雀頭四面子> リスト = 一雀頭四面子のリストを作成する(手牌, 副露面子, 暗槓の数);
    for (const auto& 各要素 : リスト) {
        出力 << 各要素;
    }
    出力 << std::endl;
    return 出力.str();
}

#if defined(_WIN32) && defined(__clang__)
#include "Windows.h"
inline void コンソールの文字コードを変更する() { SetConsoleOutputCP(CP_UTF8); }
#else
inline void コンソールの文字コードを変更する() {}
#endif

int main()
{
    コンソールの文字コードを変更する();

    std::cout
    << 分割する("12345671206712m")
    << 分割する("11123456678999s")
    << 分割する("11123455678999s")
    << 分割する("11123450999s", {"678m"})
    << 分割する("55554444776666m")
    << 分割する("33445566778899p")
    << 分割する("789s05566p", {"340s", "406m"})
    << 分割する("55544433322211m")
    << 分割する("55544433322211z");

    return 0;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?