Coverage Report

Created: 2025-09-03 03:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/nix/store/i1aar97n7b4yf8rk94p66if25brfvvdx-gqvzl8a5pvrg3xj44q0msrndcripi8dk-source/src/analysis/util.cc
Line
Count
Source
1
#include "analysis/util.h"
2
3
#include <algorithm>
4
#include <array>
5
#include <cstdint>
6
#include <vector>
7
#include "analysis/analysis.h"
8
#include "types/gamestate.h"
9
#include "types/pieces.h"
10
#include "types/piecetype.h"
11
#include "types/sets.h"
12
13
namespace mahjong {
14
namespace {
15
const std::vector<Piece> kPieceSet{
16
    kOneBamboo,    kTwoBamboo,      kThreeBamboo,    kFourBamboo,
17
    kFiveBamboo,   kSixBamboo,      kSevenBamboo,    kEightBamboo,
18
    kNineBamboo,   kOnePin,         kTwoPin,         kThreePin,
19
    kFourPin,      kFivePin,        kSixPin,         kSevenPin,
20
    kEightPin,     kNinePin,        kOneCharacter,   kNineCharacter,
21
    kTwoCharacter, kThreeCharacter, kFourCharacter,  kFiveCharacter,
22
    kSixCharacter, kSevenCharacter, kEightCharacter, kWhiteDragon,
23
    kGreenDragon,  kRedDragon,      kEastWind,       kSouthWind,
24
    kNorthWind,    kWestWind,
25
};
26
}  // namespace
27
28
0
std::vector<Piece> getRiichiDiscard(std::vector<Piece> hand) {
29
0
  if (hand.empty()) {
30
0
    return {};
31
0
  }
32
0
  std::array<int8_t, Piece::kPiecesize> counts = {};
33
0
  std::array<bool, Piece::kPiecesize> removedbefore = {};
34
0
  std::vector<Piece> remove_me;
35
0
  for (const auto& p : hand) {
36
0
    counts.at(p.toUint8_t())++;
37
0
  }
38
0
  for (int i = 0; i < 9; i++) {
39
0
    const Piece removed = hand.front();
40
0
    hand.erase(hand.begin());
41
0
    if (removedbefore.at(removed.toUint8_t())) {
42
0
      hand.push_back(removed);
43
0
      continue;
44
0
    }
45
0
    removedbefore.at(removed.toUint8_t()) = true;
46
0
    for (const auto& p : kPieceSet) {
47
0
      if (counts.at(p.toUint8_t()) == 4 || p == removed) {
48
0
        continue;
49
0
      }
50
0
      hand.push_back(p);
51
0
      auto root = breakdownHand(hand);
52
0
      if (root->IsComplete()) {
53
0
        remove_me.push_back(removed);
54
0
      }
55
56
0
      hand.pop_back();
57
0
    }
58
0
    hand.push_back(removed);
59
0
  }
60
0
  return remove_me;
61
0
}
62
63
// TODO(#20): This is an extremely inefficient algorithm but it's probably good enough for
64
// the frequency it needs to be ran
65
// will revisit if necessary
66
// assumption is 14 piece hand
67
12
std::vector<Piece> isInTenpai13Pieces(std::vector<Piece> hand, bool allWaits) {
68
12
  const int min_singles = countSingles(hand);
69
  // These numbers were found by looking at a lot of handtrees and their single count
70
12
  if (min_singles > 5 || min_singles == 3 || min_singles == 0) {
71
4
    return {};
72
4
  }
73
8
  std::array<int8_t, Piece::kPiecesize> counts = {};
74
8
  std::vector<Piece> waits;
75
99
  for (const auto& p : hand) {
76
99
    counts.at(p.toUint8_t())++;
77
99
  }
78
272
  for (const auto& p : kPieceSet) {
79
272
    if (counts.at(p.toUint8_t()) == 4) {
80
0
      continue;
81
0
    }
82
272
    hand.push_back(p);
83
272
    auto root = breakdownHand(hand);
84
272
    if (root->IsComplete()) {
85
5
      waits.push_back(p);
86
5
      if (!allWaits) {
87
0
        return waits;
88
0
      }
89
5
    }
90
91
272
    hand.pop_back();
92
272
  }
93
8
  return waits;
94
8
}
95
96
const int kPiecesinahand = 14;
97
98
10
std::vector<Piece> isInTenpai(std::vector<Piece> hand, bool allWaits) {
99
10
  if (hand.empty()) {
100
0
    return {};
101
0
  }
102
10
  const int min_singles = countSingles(hand);
103
  // These numbers are the same as above except one more piece means 1 higher on the single count
104
10
  if (min_singles > 6 || min_singles == 1 || min_singles == 4 ||
105
10
      min_singles == 0) {
106
9
    return {};
107
9
  }
108
1
  std::array<bool, Piece::kPiecesize> removedbefore = {};
109
1
  std::vector<Piece> waits;
110
15
  for (int i = 0; i < kPiecesinahand; i++) {
111
14
    const Piece removed = hand.front();
112
14
    hand.erase(hand.begin());
113
14
    if (removedbefore.at(removed.toUint8_t())) {
114
5
      hand.push_back(removed);
115
5
      continue;
116
5
    }
117
9
    removedbefore.at(removed.toUint8_t()) = true;
118
9
    std::vector<Piece> tempwaits = isInTenpai13Pieces(hand, allWaits);
119
9
    if (!tempwaits.empty()) {
120
0
      if (!allWaits) {
121
0
        return tempwaits;
122
0
      }
123
0
      waits.insert(waits.begin(), tempwaits.begin(), tempwaits.end());
124
0
    }
125
9
    hand.push_back(removed);
126
9
  }
127
1
  return waits;
128
1
}
129
130
22
int countSingles(const std::vector<Piece>& hand) {
131
22
  auto root = breakdownHand(hand);
132
22
  int min_singles = 15;
133
24
  for (const auto& branch : Node::AsBranchVectors(root.get())) {
134
24
    int singles = 0;
135
229
    for (const auto* node : branch) {
136
229
      if (node->type() == SetType::kSingle) {
137
156
        singles++;
138
156
      }
139
229
    }
140
24
    min_singles = std::min(singles, min_singles);
141
24
  }
142
22
  return min_singles;
143
22
}
144
145
0
int countPiece(const GameState& state, int player, Piece p) {
146
0
  int count = 0;
147
0
  for (const auto& piece : state.hands.at(player).live) {
148
0
    if (piece == p) {
149
0
      count++;
150
0
    }
151
0
  }
152
0
  return count;
153
0
}
154
155
}  // namespace mahjong