Skip to content

Instantly share code, notes, and snippets.

@tomohxx
Last active July 13, 2022 16:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomohxx/036d814e5099ce972ba9683826b9d41e to your computer and use it in GitHub Desktop.
Save tomohxx/036d814e5099ce972ba9683826b9d41e to your computer and use it in GitHub Desktop.
配牌時向聴数

配牌時向聴数(完全版)

概要

配牌時向聴数を求めるプログラムに01-BFSによる距離テーブルを作成する機能を追加し, 1つのファイルで完結するようにした完全版.

ソースコード

C++17で記述.

#include <algorithm>
#include <chrono>
#include <deque>
#include <functional>
#include <iostream>
#include <numeric>
#include <unordered_map>
#include <unordered_set>
#include <valarray>
#include <vector>
#include <boost/container_hash/hash.hpp>
constexpr int INF = 1 << 10;
using Vec = std::vector<int>;
using Hands = std::vector<Vec>;
struct HashHand;
using Dist = std::unordered_map<Vec, int, HashHand>;
using Table = std::unordered_map<Vec, Vec, HashHand>;
struct Key;
struct HashKey;
using Keys = std::unordered_map<Key, int, HashKey>;

struct HashHand {
  std::size_t operator()(const Vec& hand) const
  {
    return boost::hash_range(hand.begin(), hand.end());
  }
};

struct Key {
  // 距離テーブルへのポインタ
  Vec* dist;
  // 手牌のパターン数
  long long dens;
  // 七対子を構成する牌の種類数
  int kind_sp;
  // 七対子を構成する対子の種類数
  int pair_sp;
  // 国士無双を構成する牌の種類数
  int kind_to;
  // 国士無双を構成する対子の種類数
  int pair_to;
};

struct HashKey {
  std::size_t operator()(const Key& key) const
  {
    using boost::hash_combine;
    using boost::hash_range;

    std::size_t seed = 0;

    hash_combine(seed, hash_range(key.dist->begin(), key.dist->end()));
    hash_combine(seed, key.kind_sp);
    hash_combine(seed, key.pair_sp);
    hash_combine(seed, key.kind_to);
    hash_combine(seed, key.kind_sp);

    return seed;
  }
};

bool operator==(const Key& lhs, const Key& rhs)
{
  return *lhs.dist == *rhs.dist &&
         lhs.dens == rhs.dens &&
         lhs.kind_sp == rhs.kind_sp &&
         lhs.pair_sp == rhs.pair_sp &&
         lhs.kind_to == rhs.kind_to &&
         lhs.pair_to == rhs.pair_to;
}

// 手牌生成(全幅)
template <int N>
void generate(int n, int m, std::function<void(std::vector<int>&)> func)
{
  static Vec h(N);

  if (n == N) {
    func(h);
  }
  else {
    for (int i = std::max(0, m - 4 * (N - 1 - n)); i <= std::min(4, m); ++i) {
      h[n] = i;
      generate<N>(n + 1, m - i, func);
    }
  }
}

// 手牌生成(全幅)
template <bool IsHonor>
void generate(int n, int m, Table& table, Keys& keys)
{
  // 組み合わせ(4Cr)
  static const Vec c = {1, 4, 6, 4, 1};
  static constexpr int N = IsHonor ? 7 : 9;
  static Vec hand(N);
  static Vec dens(N + 1, 1);
  static Vec kind_sp(N + 1);
  static Vec pair_sp(N + 1);
  static Vec kind_to(N + 1);
  static Vec pair_to(N + 1);

  if (n == N) {
    const Key key{
        &table[hand],
        dens[N],
        kind_sp[N],
        pair_sp[N],
        kind_to[N],
        pair_to[N]};

    if (auto itr = keys.find(key); itr == keys.end()) {
      keys.emplace(key, 1);
    }
    else {
      ++itr->second;
    }
  }
  else {
    for (int i = std::max(0, m - 4 * (N - 1 - n)); i <= std::min(4, m); ++i) {
      hand[n] = i;
      dens[n + 1] = dens[n] * c[i];
      kind_sp[n + 1] = kind_sp[n] + (i > 0 ? 1 : 0);
      pair_sp[n + 1] = pair_sp[n] + (i >= 2 ? 1 : 0);
      kind_to[n + 1] = kind_to[n] + ((IsHonor || n == 0 || n == N - 1) && i > 0 ? 1 : 0);
      pair_to[n + 1] = pair_to[n] + ((IsHonor || n == 0 || n == N - 1) && i >= 2 ? 1 : 0);

      generate<IsHonor>(n + 1, m - i, table, keys);
    }
  }
}

// 3N和了判定
bool iswh0(const int* h)
{
  int a = h[0], b = h[1];

  for (int i = 0; i < 7; ++i) {
    if (int r = a % 3; b >= r && h[i + 2] >= r) {
      a = b - r;
      b = h[i + 2] - r;
    }
    else return false;
  }
  return a % 3 == 0 && b % 3 == 0;
}

// 3N+2和了判定
bool iswh2(int* h)
{
  int s = 0;

  for (int i = 0; i < 9; ++i) {
    s += i * h[i];
  }

  for (int p = s * 2 % 3; p < 9; p += 3) {
    if (h[p] >= 2) {
      h[p] -= 2;

      if (iswh0(h)) {
        h[p] += 2;
        return true;
      }
      else h[p] += 2;
    }
  }
  return false;
}

// 01-BFS
Dist bfs(const Hands& hands)
{
  Dist dist;
  std::deque<Vec> deq;

  for (const auto& hand : hands) {
    dist[hand] = 0;
    deq.push_back(hand);
  }

  while (deq.size()) {
    const Vec hand = deq.front();

    deq.pop_front();

    const auto sum = std::accumulate(hand.begin(), hand.end(), 0);

    for (int i = 0; i < 9; ++i) {
      if (hand[i] < 4 && sum < 14) {
        auto before = INF;
        auto added = hand;

        ++added[i];

        if (const auto itr = dist.find(added); itr != dist.end()) {
          before = itr->second;
        }

        if (int after = dist[hand]; after < before) {
          dist[added] = after;
          deq.push_front(added);
        }
      }

      if (hand[i] > 0) {
        auto before = INF;
        auto removed = hand;

        --removed[i];

        if (const auto itr = dist.find(removed); itr != dist.end()) {
          before = itr->second;
        }

        if (int after = dist[hand] + 1; after < before) {
          dist[removed] = after;
          deq.push_back(removed);
        }
      }
    }
  }

  return dist;
}

void add1(Vec& lhs, const Vec& rhs, const int m)
{
  for (int j = m + 5; j >= 5; --j) {
    int sht = std::min(lhs[j] + rhs[0], lhs[0] + rhs[j]);

    for (int k = 5; k < j; ++k) {
      sht = std::min({sht, lhs[k] + rhs[j - k], lhs[j - k] + rhs[k]});
    }
    lhs[j] = sht;
  }
  for (int j = m; j >= 0; --j) {
    int sht = lhs[j] + rhs[0];

    for (int k = 0; k < j; ++k) {
      sht = std::min(sht, lhs[k] + rhs[j - k]);
    }
    lhs[j] = sht;
  }
}

void add2(Vec& lhs, const Vec& rhs, const int m)
{
  int j = m + 5;
  int sht = std::min(lhs[j] + rhs[0], lhs[0] + rhs[j]);

  for (int k = 5; k < j; ++k) {
    sht = std::min({sht, lhs[k] + rhs[j - k], lhs[j - k] + rhs[k]});
  }
  lhs[j] = sht;
}

inline int coef(const int i, const int j, const int k)
{
  if (i == j && j == k) {
    return 1;
  }
  else if (i == j || j == k || k == i) {
    return 3;
  }
  else {
    return 6;
  }
}

int main()
{
  constexpr int M = 14;
  const auto start = std::chrono::system_clock::now();

  std::vector<std::vector<Dist>> dists(2, std::vector<Dist>(10));

  for (int m = 0; m <= 4; ++m) {
    Hands hands10, hands12;
    Hands hands20, hands22;

    generate<9>(0, 3 * m, [&](std::vector<int>& h) {
      if (iswh0(h.data())) {
        hands10.push_back(h);
      }
    });

    generate<9>(0, 3 * m + 2, [&](std::vector<int>& h) {
      if (iswh2(h.data())) {
        hands12.push_back(h);
      }
    });

    generate<7>(0, 3 * m, [&](std::vector<int>& h) {
      std::vector<int> cnt(5, 0);

      for (int i = 0; i < 7; ++i) {
        ++cnt[h[i]];
      }

      if (cnt[1] == 0 && cnt[2] == 0 && cnt[4] == 0) {
        hands20.push_back(h);
      }
    });

    generate<7>(0, 3 * m + 2, [&](std::vector<int>& h) {
      std::vector<int> cnt(5, 0);

      for (int i = 0; i < 7; ++i) {
        ++cnt[h[i]];
      }

      if (cnt[1] == 0 && cnt[2] == 1 && cnt[4] == 0) {
        hands22.push_back(h);
      }
    });

    dists[0][m] = bfs(hands10);
    dists[0][m + 5] = bfs(hands12);
    dists[1][m] = bfs(hands20);
    dists[1][m + 5] = bfs(hands22);
  }

  std::vector<Table> tables(2);

  for (int i = 0; i < 2; ++i) {
    for (const auto& [hand, dist] : dists[i][0]) {
      Vec sht(10);

      sht[0] = dist;

      for (int j = 1; j < 10; ++j) {
        sht[j] = dists[i][j][hand];
      }
      tables[i][hand] = sht;
    }
  }

  std::vector<Keys> keys1(M + 1);
  std::vector<Keys> keys2(M + 1);

  for (int m = 0; m <= M; ++m) {
    generate<false>(0, m, tables[0], keys1[m]);
    generate<true>(0, m, tables[1], keys2[m]);
  }

  std::valarray<long long> cnt(8);

  for (int i = 0; i <= M; ++i) {
    for (int j = i; j <= M - i; ++j) {
      for (int k = j; k <= M - i - j; ++k) {
        std::valarray<long long> tmp(8);

        for (const auto& [key0, value0] : keys1[i]) {
          for (const auto& [key1, value1] : keys1[j]) {
            for (const auto& [key2, value2] : keys1[k]) {
              for (const auto& [key3, value3] : keys2[M - i - j - k]) {
                Vec lhs = *key0.dist;
                add1(lhs, *key1.dist, M / 3);
                add1(lhs, *key2.dist, M / 3);
                add2(lhs, *key3.dist, M / 3);

                const auto dens = key0.dens * value0 * key1.dens * value1 * key2.dens * value2 * key3.dens * value3;
                const auto kind_sp = key0.kind_sp + key1.kind_sp + key2.kind_sp + key3.kind_sp;
                const auto pair_sp = key0.pair_sp + key1.pair_sp + key2.pair_sp + key3.pair_sp;
                const auto kind_to = key0.kind_to + key1.kind_to + key2.kind_to + key3.kind_to;
                const auto pair_to = key0.pair_to + key1.pair_to + key2.pair_to + key3.pair_to;

                const int num_lh = lhs[5 + M / 3];
                const int num_sp = 7 - pair_sp + (kind_sp < 7 ? 7 - kind_sp : 0);
                const int num_to = 14 - kind_to - (pair_to > 0 ? 1 : 0);
                const int num_all = std::min({num_lh, num_sp, num_to});

                tmp[num_all] += dens;
              }
            }
          }
        }

        cnt += tmp * coef(i, j, k);
      }
    }
  }

  const auto end = std::chrono::system_clock::now();
  const auto total = cnt.sum();
  double ev = 0.0;

  std::cout << "Shanten\tCount\tProp" << std::endl;

  for (int i = 0; i < 8; ++i) {
    ev += (i - 1) * cnt[i];
    std::cout << i - 1 << "\t" << cnt[i] << "\t" << static_cast<double>(cnt[i]) / total << std::endl;
  }

  std::cout << "Number of Tiles: " << M << std::endl;
  std::cout << "Total: " << total << std::endl;
  std::cout << "Time (msec.): " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << std::endl;
  std::cout << "Avg Shanten: " << ev / total << std::endl;

  return 0;
}

出力

Shanten Count   Prop
-1      12859078207674  3.02545e-06
0       2966241795738948        0.000697889
1       99154452630748356       0.0233288
2       828714358375292670      0.194978
3       1867404976243926528     0.439358
4       1211948980271480832     0.285144
5       233501763289743360      0.0549376
6       6601397483077632        0.00155316
Number of Tiles: 14
Total: 4250305029168216000
Time (msec.): 20908
Avg Shanten: 3.15594

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment