/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 |