ぬるーむ

Unity初心者が誰もが知っているゲームの模倣をしています。個人的な備忘録ですが、入門書を読み終えたばかりの初心者の方は「こんなへなちょこでもいいのか!」「俺の方がうまく作れる」と作成意欲がわいたりするかもしれません。

Unityによる大富豪の作り方 2 ~ビット演算(応用)~


スポンサードリンク

ビット演算(応用)

前回は基本的なビット演算の紹介でしたが、今回はそれらを使って具体的に階段やグループの判定・作成方法を説明します。

階段かグループか判定する

public readonly static ulong M0001 = 0x1111111111111111;
private readonly static ulong M0011 = 0x3333333333333333;
private readonly static ulong M0100 = 0x4444444444444444;
private readonly static ulong M0101 = 0x5555555555555555;
private readonly static ulong M1000 = 0x8888888888888888;

//階段かどうか判定する。
public bool IsSequence(ulong bitCard, int cnt)
{
    if (bitCard == 0 || cnt < 3) return false;

    var head = bitCard;

    for (int i = 0; i < cnt - 1; i++)
    {
        head &= head >> 4;
    }

    return CountBit(head) == 1;
}

//グループかどうか判定する
public bool IsGroup(ulong bitCard, int cnt)
{
    if (cnt > 4) return false;

    var n = (1ul << cnt - 1) * M0001;
    var qpCard = ToQPCard(bitCard);

    return (qpCard & n) > 0;
}

//枚数位置型(QuantityPosition)に変換
public ulong ToQPCard(ulong bitCard)
{
    //2ビットごとの1の数を2進数で表現
    var a = ((bitCard & M0101) + ((bitCard >> 1) & M0101));
    
    //4枚あるランク
    var n4 = a & (a << 2) & M1000;
    
    //3枚あるランク
    var n3 = (a << 2) & (a >> 1) & M0100;
    n3 |= a & (a << 1) & M0100;

    //4ビットごとの1の数を2進数で表現(4は除く)
    var n12 = ((a & M0011) + ((a >> 2) & M0011)) & M0011;

    if (n3 != 0)
    {
        n4 |= n3;
        //3枚である場合を除く。
        n4 |= n12 & ~(n3 >> 1 | n3 >> 2);
    }
    else
    {
        n4 |= n12;
    }

    return n4;
}

枚数位置型への変換はわかりづらいので少し解説します。

var a = ((bitCard & M0101) + ((bitCard >> 1) & M0101));

aはbitCardを2ビットごとの1の数を2進数で表現しています。
例えば、bitCardが
1111 1011 0101 1100 0100 のとき、aは
1010 0110 0101 1000 0100 となります。

次に、このaを2ビットごとに足すと4ビットごとの1の数となります(この辺りは1の数を数えるCountBitと同じです)。 そして、4ビットごとの1の数とは各ランクごとのカードの枚数を表しています。
例えば、aが1010 0110 0101 1000 0100のとき、カードの枚数は4枚、3枚、2枚、2枚、1枚となります。

var n4 = a & (a << 2) & M1000;

あるランクが1010であるとき、そのランクのカードが4枚あるということなので4ビット目を1とします。

var n3 = (a << 2) & (a >> 1) & M0100;
n3 |= a & (a << 1) & M0100;

あるランクが1001または0110であるとき、そのランクのカードが3枚あるということなので3ビット目を1とします。

var n12 = ((a & M0011) + ((a >> 2) & M0011)) & M0011;

4ビットごとの1の数を2進数で表現し、M0011をANDすることで4を除いています。つまりn12はそのランクの枚数(0~3)を2進数で表しています。

if (n3 != 0)
{
    n4 |= n3;
    n4 |= n12 & ~(n3 >> 1 | n3 >> 2);
}
else
{
    n4 |= n12;
}

1、2枚の場合はn12から3枚の場合を除いたものを使うことで表現します。

階段を取得

長さが最大になる組み合わせですべての階段を取得
//長さが最大になる組み合わせですべての階段をList<ulong>で取得
public List<ulong> GetSequenceList(ulong bitCard)
{
    var sequences = new List<ulong>();
    ulong seq;

    do
    {
        seq = GetLongestSequence(bitCard);

        if (seq != 0)
        {
            sequences.Add(seq);
            bitCard ^= seq;
        }

    } while (seq != 0);

    return sequences;
}

//一番長い階段を取得
private ulong GetLongestSequence(ulong bitCard)
{
    var head = bitCard;
    var max = 0ul;
    int i = 1;

    while (head != 0)
    {
        if (i >= 3) max = head;
        head &= bitCard >> (i * 4);
        i++;
    }

    if (max == 0) return 0;
    else
    {
        var b = GetLeastSignificantBit(max);

        return CreateSequence(b, i - 1);
    }
}

//階段を生成
private ulong CreateSequence(ulong head, int cnt = 3)
{
    var seq = head;

    for (int i = 1; i < cnt; i++)
    {
        head <<= 4;
        seq |= head;
    }

    return seq;
}

手札から最も長い階段を取得し、そのカードを除いた手札から再度、最大の長さの階段を取得する、ということを繰り返しています。すべての階段(BitCard型)をList<ulong>で取得します。自分が親で、枚数制限がない場合に使います。

例えば手札が♠️4 ♠️5 ♠️6 ♦️6 ♠️7 ♦️7 ♦️8 ♠️9 ♠️10の場合

[ ♠️4 ♠️5 ♠️6 ♠️7 ] [ ♦️6 ♦️7 ♦️8 ]を取得します。

指定した枚数の階段をすべて取得
//指定した枚数の階段をすべてList<ulong>で取得
public List<ulong> GetSequenceList(ulong bitCard, int cnt)
{
    var sequences = new List<ulong>();
    var head = bitCard;

    for (int i = 1; i < cnt; i++)
    {
        head &= bitCard >> (i * 4);
    }

    while (head != 0)
    {
        var b = GetLeastSignificantBit(head);
        sequences.Add(CreateSequence(b, cnt));
        head ^= b;
    }

    return sequences;
}

指定した枚数より多い階段が存在した場合、その枚数で組み合わせ可能な階段をすべて取得します。すべての階段(BitCard型)をList<ulong>で取得します。自分が子で、出せる枚数に制限がある場合に使います。

例えば手札が♠️4 ♠️5 ♠️6 ♠️7 ♠️8で場の枚数が3の場合

[ ♠️4 ♠️5 ♠️6 ] [ ♠️5 ♠️6 ♠️7 ] [ ♠️6 ♠️7 ♠️8 ]を取得します。

グループを取得

指定した枚数以上のグループをすべて取得
//指定した枚数以上のグループをすべてList<ulong>で取得
public List<ulong> GetGroupList(ulong bitCard, int cnt)
{
    var groups = new List<ulong>();
    var qpGroup = GetGroupByQPCard(bitCard, cnt);

    ToBitList(qpGroup).ForEach(q=>
    {
        groups.Add(ToBitCard(q, bitCard));
    });
}

//指定した枚数以上のグループを全て枚数位置型で取得
private ulong GetGroupByQPCard(ulong bitCard, int cnt)
{
    var qpCard = ToQPCard(bitCard);

    int n = cnt - 1;
    var noless = 16 - (1ul << n);
    var cntMask = M0001 * noless;

    return qpCard & cntMask;
}

//枚数位置型をBitCard型に変換
public ulong ToBitCard(ulong qpCard, ulong bitCard)
{
    var result = 0ul;

    while (qpCard != 0)
    {
        int i = BitScanForward(qpCard);
        int r = i / 4;

        result |= bitCard & (15ul << (r * 4));
        qpCard &= qpCard - 1;
    }

    return result;
}

//ビットが1の値をそれぞれ分けてList<ulong>で取得
public List<ulong> ToBitList(ulong bit)
{
    var list = new List<ulong>();

    while (bit != 0)
    {
        var b = GetMostSignificantBit(bit);
        list.Add(b);
        bit ^= b;
    }

    return list;
}

すべてのグループ(BitCard型)をList<ulong>で取得します。自分が子で、出せる枚数に制限がある場合に使います。

例えば手札が♠️3 ♠️5 ♦️5 ♠️6 ♦️6 ♣️6 ♠️8 ♦️8 ♣️8 ❤️8で場の枚数が3の場合

[ ♠️6 ♦️6 ♣️6 ] [ ♠️8 ♦️8 ♣️8 ❤️8 ]を取得します。

すべてのグループを取得
//すべてのグループをList<ulong>で取得
public List<ulong> GetGroupList(ulong bitCard)
{
    return GetGroupList(bitCard, 2);
}

すべてのグループ(BitCard型)をList<ulong>で取得します。自分が親で、枚数制限がない場合に使います。

例えば手札が♠️3 ♠️5 ♦️5 ♠️6 ♦️6 ♣️6 ♠️8 ♦️8 ♣️8 ❤️8の場合

[ ♠️5 ♦️5 ] [ ♠️6 ♦️6 ♣️6 ] [ ♠️8 ♦️8 ♣️8 ❤️8 ]を取得します。

その他

基準よりランクの高いカードを取得
//基準よりランクの高いカードを取得
private readonly static ulong M1111 = 0xffffffffffffffff;

public ulong GetHigherBitCard(ulong bitCard, ulong basisCard,
    bool isRevolutionzlizing)
{
    if (!isRevolutionzlizing)
    {
        int n = BitScanReverse(basisCard);
        int r = n < 0 ? 0 : n / 4 + 1;
        var noless = M1111 - ((1ul << r * 4) - 1ul);

        return bitCard & noless;
    }
    else
    {
        int n = BitScanForward(basisCard);
        int r = n < 0 ? 0 : n / 4;
        var nomore = (1ul << r * 4) - 1ul;

        return bitCard & nomore;
    }
}

基準のカードよりランクが高い(革命時は低い)カードを取得します。場に出せるカードを抽出するときや、相手が自分より強いカードをどれだけ持っているか計算するときに使います。