/nix/store/i1aar97n7b4yf8rk94p66if25brfvvdx-gqvzl8a5pvrg3xj44q0msrndcripi8dk-source/src/statefunctions/decisionfunction.cc
Line | Count | Source |
1 | | #include "statefunctions/decisionfunction.h" |
2 | | |
3 | | #include <algorithm> |
4 | | #include <array> |
5 | | #include <vector> |
6 | | |
7 | | #include "analysis/util.h" |
8 | | #include "scoring/scoring.h" |
9 | | #include "scoring/yakus/thirteenorphans.h" |
10 | | |
11 | | #include "statefunctions/stateutilities.h" |
12 | | #include "types/gamestate.h" |
13 | | #include "types/piecetype.h" |
14 | | #include "types/sets.h" |
15 | | #include "types/walls.h" |
16 | | |
17 | | namespace mahjong { |
18 | | |
19 | | // TODO(#18): "I really hate this" - alice |
20 | 34 | bool CanRon(const GameState& state, int player) { |
21 | | // If the pending piece is your discard, you can't Ron |
22 | 34 | for (const auto& piece : state.hands.at(player).discards) { |
23 | 26 | if (state.pendingPiece == piece) { |
24 | 1 | return false; |
25 | 1 | } |
26 | 26 | } |
27 | | |
28 | | // Build the theoretical hand |
29 | 33 | auto& tmp_state = const_cast<GameState&>(state); |
30 | 33 | tmp_state.hands.at(player).live.push_back(state.pendingPiece); |
31 | 33 | tmp_state.hands.at(player).sort(); |
32 | | |
33 | | // If this Ron is occurring due to a concealed kan discard, |
34 | 33 | if (state.concealedKan) { |
35 | | // If it happens to be a ron for a thirteen orphans, |
36 | | // it's allowed and you can ron |
37 | 0 | if (yaku::isThirteenOrphans(state, player)) { |
38 | 0 | tmp_state.hands.at(player).live.erase( |
39 | 0 | std::find(state.hands.at(player).live.begin(), |
40 | 0 | state.hands.at(player).live.end(), state.pendingPiece)); |
41 | 0 | return true; |
42 | 0 | } |
43 | | |
44 | | // otherwise, you can't |
45 | 0 | tmp_state.hands.at(player).live.erase( |
46 | 0 | std::find(state.hands.at(player).live.begin(), |
47 | 0 | state.hands.at(player).live.end(), state.pendingPiece)); |
48 | 0 | return false; |
49 | 0 | } |
50 | | |
51 | | // if not a concealed kan, check if it's complete |
52 | 33 | const bool can_ron = isComplete(state, player); |
53 | 33 | tmp_state.hands.at(player).live.erase( |
54 | 33 | std::find(state.hands.at(player).live.begin(), |
55 | 33 | state.hands.at(player).live.end(), state.pendingPiece)); |
56 | 33 | return can_ron; |
57 | 33 | } |
58 | | |
59 | 33 | bool CanKan(const GameState& state, int player) { |
60 | 33 | if (state.walls.GetRemainingPieces() == 0) { |
61 | 0 | return false; |
62 | 0 | } |
63 | 33 | if (state.hands.at(player).riichi) { |
64 | 0 | return false; |
65 | 0 | } |
66 | 33 | return CountPieces(state, player, state.pendingPiece) == 3; |
67 | 33 | } |
68 | | |
69 | 34 | bool CanPon(const GameState& state, int player) { |
70 | 34 | if (state.hands.at(player).riichi) { |
71 | 0 | return false; |
72 | 0 | } |
73 | 34 | return CountPieces(state, player, state.pendingPiece) == 2; |
74 | 34 | } |
75 | | |
76 | 39 | bool CanChi(const GameState& state, int player) { |
77 | 39 | if (state.hands.at(player).riichi) { |
78 | 0 | return false; |
79 | 0 | } |
80 | 39 | if (state.pendingPiece.isHonor()) { |
81 | 13 | return false; |
82 | 13 | } |
83 | 26 | if (((state.currentPlayer + 1) % 4) != player) { |
84 | 15 | return false; |
85 | 15 | } |
86 | 11 | if (CountPieces(state, player, state.pendingPiece - 2) > 0 && |
87 | 11 | CountPieces(state, player, state.pendingPiece - 1) > 0) { |
88 | 2 | return true; |
89 | 2 | } |
90 | 9 | if (CountPieces(state, player, state.pendingPiece - 1) > 0 && |
91 | 9 | CountPieces(state, player, state.pendingPiece + 1) > 0) { |
92 | 1 | return true; |
93 | 1 | } |
94 | 8 | if (CountPieces(state, player, state.pendingPiece + 1) > 0 && |
95 | 8 | CountPieces(state, player, state.pendingPiece + 2) > 0) { |
96 | 1 | return true; |
97 | 1 | } |
98 | 7 | return false; |
99 | 8 | } |
100 | | |
101 | 10 | bool CanTsumo(const GameState& state, int player) { |
102 | 10 | return isComplete(state, player); |
103 | 10 | } |
104 | | |
105 | 10 | bool CanConvertedKan(const GameState& state, int player) { |
106 | 10 | if (state.walls.GetRemainingPieces() == 0) { |
107 | 0 | return false; |
108 | 0 | } |
109 | 10 | return std::any_of(state.hands.at(player).melds.begin(), |
110 | 10 | state.hands.at(player).melds.end(), [&](auto meld) { |
111 | 0 | return meld.type == SetType::kPon && |
112 | 0 | CountPieces(state, player, meld.start) == 1; |
113 | 0 | }); |
114 | 10 | } |
115 | | |
116 | 10 | bool CanConcealedKan(const GameState& state, int player) { |
117 | 10 | if (state.walls.GetRemainingPieces() == 0) { |
118 | 0 | return false; |
119 | 0 | } |
120 | | // TODO(#19): Allow riichi concealed kan under the right conditions |
121 | 10 | if (state.hands.at(player).riichi) { |
122 | 0 | return false; |
123 | 0 | } |
124 | 10 | return CountPieces(state, player, state.pendingPiece) == 4; |
125 | 10 | } |
126 | | |
127 | 10 | bool CanRiichi(const GameState& state, int player) { |
128 | 10 | if (state.hands.at(player).riichi) { |
129 | 0 | return false; |
130 | 0 | } |
131 | 10 | if (state.hands.at(player).open) { |
132 | 0 | return false; |
133 | 0 | } |
134 | 10 | return !isInTenpai(state.hands.at(player).live, /*allWaits=*/false).empty(); |
135 | 10 | } |
136 | | } // namespace mahjong |