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