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