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/handtree.cc
Line
Count
Source
1
#include <algorithm>
2
#include <fstream>
3
#include <iostream>
4
#include <map>
5
#include <memory>
6
#include <utility>
7
#include <vector>
8
9
#include "analysis/analysis.h"
10
#include "analysis/handnode.h"
11
#include "types/pieces.h"
12
#include "types/piecetype.h"
13
#include "types/sets.h"
14
15
namespace mahjong {
16
17
struct Breakdown {
18
  std::unique_ptr<Node> rootNode;
19
  Node* currentNode{};
20
  int id = 0;
21
  std::map<Piece, int> counts;
22
  std::map<Piece, int> possibilities;
23
  std::vector<Piece> pieces;
24
};
25
26
namespace {
27
28
const int kMaxPossible = 14;
29
64.8k
bool possibleChiForward(const std::map<Piece, int>& counts, Piece p) {
30
64.8k
  if (p.isHonor() || !counts.contains(p) || !counts.contains(p + 1) ||
31
64.8k
      !counts.contains(p + 2)) {
32
55.8k
    return false;
33
55.8k
  }
34
9.01k
  return counts.at(p) > 0 && counts.at(p + 1) > 0 && counts.at(p + 2) > 0;
35
64.8k
}
36
37
20.9k
int possibleChis(const std::map<Piece, int>& counts, Piece p) {
38
20.9k
  int chi_count = possibleChiForward(counts, p) ? 1 : 0;
39
20.9k
  chi_count += possibleChiForward(counts, p - 1) ? 1 : 0;
40
20.9k
  chi_count += possibleChiForward(counts, p - 2) ? 1 : 0;
41
20.9k
  return chi_count;
42
20.9k
}
43
44
1.52k
bool anyPossibleChi(const std::map<Piece, int>& counts, Piece p) {
45
1.52k
  return possibleChis(counts, p) > 0;
46
1.52k
}
47
48
20.9k
bool possiblePair(const std::map<Piece, int>& counts, Piece p) {
49
20.9k
  return counts.contains(p) && counts.at(p) >= 2;
50
20.9k
}
51
52
20.9k
bool possiblePon(const std::map<Piece, int>& counts, Piece p) {
53
20.9k
  return counts.contains(p) && counts.at(p) >= 3;
54
20.9k
}
55
56
423
void countPieces(Breakdown* b) {
57
5.61k
  for (const auto& p : b->pieces) {
58
5.61k
    b->counts[p]++;
59
5.61k
  }
60
423
}
61
62
5.27k
Piece updatePossibilities(Breakdown* b) {
63
5.27k
  b->possibilities.clear();
64
5.27k
  int min_possible = kMaxPossible;
65
5.27k
  Piece min_possible_piece = kError;
66
19.4k
  for (const auto& piece : b->pieces) {
67
19.4k
    b->possibilities[piece] += possibleChis(b->counts, piece);
68
19.4k
    if (possiblePair(b->counts, piece)) {
69
8.71k
      b->possibilities[piece]++;
70
8.71k
    }
71
19.4k
    if (possiblePon(b->counts, piece)) {
72
1.49k
      b->possibilities[piece]++;
73
1.49k
    }
74
19.4k
    if (b->possibilities[piece] < min_possible) {
75
5.49k
      min_possible = b->possibilities[piece];
76
5.49k
      min_possible_piece = piece;
77
5.49k
    }
78
19.4k
  }
79
5.27k
  return min_possible_piece;
80
5.27k
}
81
82
501
void breakdownForwardChi(Breakdown* b, Piece piece) {
83
2.00k
  for (int i = 0; i < 3; i++) {
84
1.50k
    b->counts[piece + i]--;
85
1.50k
    if (b->counts[piece + i] == 0) {
86
1.41k
      b->pieces.erase(
87
1.41k
          std::remove(b->pieces.begin(), b->pieces.end(), piece + i),
88
1.41k
          b->pieces.end());
89
1.41k
    }
90
1.50k
  }
91
501
  b->currentNode = b->currentNode->addLeaf(piece, SetType::kChi, b->id++);
92
501
}
93
94
426
void breakdownPon(Breakdown* b, Piece piece) {
95
426
  b->counts[piece] -= 3;
96
426
  if (b->counts[piece] == 0) {
97
416
    b->pieces.erase(std::remove(b->pieces.begin(), b->pieces.end(), piece),
98
416
                    b->pieces.end());
99
416
  }
100
426
  b->currentNode = b->currentNode->addLeaf(piece, SetType::kPon, b->id++);
101
426
}
102
103
1.48k
void breakdownPair(Breakdown* b, Piece piece) {
104
1.48k
  b->counts[piece] -= 2;
105
1.48k
  if (b->counts[piece] == 0) {
106
1.06k
    b->pieces.erase(std::remove(b->pieces.begin(), b->pieces.end(), piece),
107
1.06k
                    b->pieces.end());
108
1.06k
  }
109
1.48k
  b->currentNode = b->currentNode->addLeaf(piece, SetType::kPair, b->id++);
110
1.48k
}
111
112
1.99k
void breakdownSingle(Breakdown* b, Piece piece) {
113
1.99k
  b->currentNode = b->currentNode->addLeaf(piece, SetType::kSingle, b->id++);
114
115
1.99k
  b->counts[piece]--;
116
1.99k
  if (b->counts[piece] == 0) {
117
1.99k
    b->pieces.erase(std::remove(b->pieces.begin(), b->pieces.end(), piece),
118
1.99k
                    b->pieces.end());
119
1.99k
  }
120
1.99k
}
121
122
445
void resetCounts(Breakdown* b, const Node* target) {
123
445
  if (b->currentNode == nullptr) {
124
0
    std::cerr << "reset Failure: current node nullptr." << '\n';
125
0
    std::ofstream os("error.gv");
126
0
    b->rootNode->DumpAsDot(os);
127
0
    os.close();
128
0
    throw -1;
129
0
  }
130
1.39k
  while (b->currentNode != target) {
131
951
    if (b->currentNode->parent() == nullptr) {
132
0
      std::cerr << "reset Failure: parent node nullptr." << '\n';
133
0
      std::ofstream os("error.gv");
134
0
      b->rootNode->DumpAsDot(os);
135
0
      os.close();
136
0
      throw -2;
137
0
    }
138
951
    if (b->currentNode->type() == SetType::kChi) {
139
144
      for (int i = 0; i < 3; i++) {
140
108
        if (b->counts[b->currentNode->start() + i] == 0) {
141
55
          b->pieces.push_back(Piece{b->currentNode->start() + i});
142
55
          std::sort(b->pieces.begin(), b->pieces.end());
143
55
        }
144
108
        b->counts[b->currentNode->start() + i]++;
145
108
      }
146
915
    } else {
147
915
      if (b->counts[b->currentNode->start()] == 0) {
148
681
        b->pieces.emplace_back(b->currentNode->start());
149
681
        std::sort(b->pieces.begin(), b->pieces.end());
150
681
      }
151
915
      if (b->currentNode->type() == SetType::kSingle) {
152
236
        b->counts[b->currentNode->start()]++;
153
236
      }
154
915
      if (b->currentNode->type() == SetType::kPair) {
155
253
        b->counts[b->currentNode->start()] += 2;
156
253
      }
157
915
      if (b->currentNode->type() == SetType::kPon) {
158
426
        b->counts[b->currentNode->start()] += 3;
159
426
      }
160
915
    }
161
951
    b->currentNode = b->currentNode->parent();
162
951
  }
163
445
}
164
165
// NOLINTNEXTLINE(misc-no-recursion)
166
1.31k
void driver(Breakdown* b) {
167
  // - Always process the tile into the fewest groupings first
168
  // - If a tile has only one way to be grouped, use it
169
  // - If a tile has multiple ways, try each possibility
170
  // - Continue until all tiles are in groups
171
172
  // While there are ungrouped pieces, get the piece with minimum possibilities
173
5.27k
  for (Piece current_piece = updatePossibilities(b); !b->pieces.empty();
174
3.96k
       current_piece = updatePossibilities(b)) {
175
3.96k
    if (b->possibilities[current_piece] == 0) {
176
      // No valid grouping possible, single piece
177
1.99k
      breakdownSingle(b, current_piece);
178
1.99k
      continue;
179
1.99k
    }
180
181
1.96k
    if (b->possibilities[current_piece] == 1) {
182
      // One way to be grouped, use it
183
1.52k
      if (anyPossibleChi(b->counts, current_piece)) {
184
        // A chi is possible, determine how it can be a part of one
185
646
        for (int i = 0; i < 3; i++) {
186
646
          const Piece chi_start = current_piece - i;
187
          // Check if we can start a chi with this piece
188
646
          if (possibleChiForward(b->counts, chi_start)) {
189
            // Find the position of chi_start in the pieces vector
190
479
            auto piece_itr =
191
479
                std::find(b->pieces.begin(), b->pieces.end(), chi_start);
192
479
            if (piece_itr != b->pieces.end()) {
193
479
              breakdownForwardChi(b, *piece_itr);
194
479
            }
195
479
            break;
196
479
          }
197
646
        }
198
479
        continue;
199
479
      }
200
1.04k
      if (possiblePon(b->counts, current_piece)) {
201
        // A pon is possible
202
0
        breakdownPon(b, current_piece);
203
0
        continue;
204
0
      }
205
1.04k
      if (possiblePair(b->counts, current_piece)) {
206
        // A pair is possible
207
1.04k
        breakdownPair(b, current_piece);
208
1.04k
        continue;
209
1.04k
      }
210
1.04k
    }
211
445
    if (b->possibilities[current_piece] >= 2) {
212
      // Tile has multiple groups, we need to check branches
213
214
      // Save current position in tree so we can backtrack
215
445
      auto* current = b->currentNode;
216
217
      // Check grouping possibilities
218
445
      const bool can_chi_start = possibleChiForward(b->counts, current_piece);
219
220
445
      bool can_chi_middle = false;
221
445
      Piece chi_middle_piece;
222
445
      if (possibleChiForward(b->counts, current_piece - 1)) {
223
4
        const Piece chi_start = current_piece - 1;
224
4
        auto it = std::find(b->pieces.begin(), b->pieces.end(), chi_start);
225
4
        if (it != b->pieces.end()) {
226
4
          can_chi_middle = true;
227
4
          chi_middle_piece = *it;
228
4
        }
229
4
      }
230
231
445
      bool can_chi_end = false;
232
445
      Piece chi_end_piece;
233
445
      if (possibleChiForward(b->counts, current_piece - 2)) {
234
1
        const Piece chi_start = current_piece - 2;
235
1
        auto it = std::find(b->pieces.begin(), b->pieces.end(), chi_start);
236
1
        if (it != b->pieces.end()) {
237
1
          can_chi_end = true;
238
1
          chi_end_piece = *it;
239
1
        }
240
1
      }
241
242
445
      const bool can_pon = possiblePon(b->counts, current_piece);
243
445
      const bool can_pair = possiblePair(b->counts, current_piece);
244
245
      // Count total branches that will actually execute
246
445
      int total_branches = 0;
247
445
      if (can_chi_start) {
248
17
        total_branches++;
249
17
      }
250
445
      if (can_chi_middle) {
251
4
        total_branches++;
252
4
      }
253
445
      if (can_chi_end) {
254
1
        total_branches++;
255
1
      }
256
445
      if (can_pon) {
257
426
        total_branches++;
258
426
      }
259
445
      if (can_pair) {
260
442
        total_branches++;
261
442
      }
262
263
      // Execute branches
264
445
      int current_branch = 0;
265
266
445
      if (can_chi_start) {
267
        // Can be the beginning of a chi
268
17
        current_branch++;
269
17
        breakdownForwardChi(b, current_piece);
270
17
        driver(b);
271
17
        if (current_branch < total_branches) {
272
17
          resetCounts(b, current);
273
17
        }
274
17
      }
275
276
445
      if (can_chi_middle) {
277
        // Can be the middle of a chi
278
4
        current_branch++;
279
4
        breakdownForwardChi(b, chi_middle_piece);
280
4
        driver(b);
281
4
        if (current_branch < total_branches) {
282
2
          resetCounts(b, current);
283
2
        }
284
4
      }
285
286
445
      if (can_chi_end) {
287
        // Can be the end of a chi
288
1
        current_branch++;
289
1
        breakdownForwardChi(b, chi_end_piece);
290
1
        driver(b);
291
1
        if (current_branch < total_branches) {
292
0
          resetCounts(b, current);
293
0
        }
294
1
      }
295
296
445
      if (can_pon) {
297
        // Can be a pon
298
426
        current_branch++;
299
426
        breakdownPon(b, current_piece);
300
426
        driver(b);
301
426
        if (current_branch < total_branches) {
302
426
          resetCounts(b, current);
303
426
        }
304
426
      }
305
306
445
      if (can_pair) {
307
        // Can be a pair
308
442
        current_branch++;
309
442
        breakdownPair(b, current_piece);
310
442
        driver(b);
311
442
        if (current_branch < total_branches) {
312
0
          resetCounts(b, current);
313
0
        }
314
442
      }
315
445
    }
316
445
  }
317
1.31k
}
318
319
}  // namespace
320
321
423
std::unique_ptr<Node> breakdownHand(const std::vector<Piece>& pieces) {
322
423
  Breakdown b;
323
423
  b.rootNode = std::make_unique<Node>(/*id=*/b.id++);
324
423
  b.currentNode = b.rootNode.get();
325
423
  b.pieces = pieces;
326
423
  countPieces(&b);
327
423
  std::sort(b.pieces.begin(), b.pieces.end());
328
423
  b.pieces.erase(std::unique(b.pieces.begin(), b.pieces.end()), b.pieces.end());
329
330
423
  driver(&b);
331
332
423
  return std::move(b.rootNode);
333
423
}
334
}  // namespace mahjong