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/controllers/fasttanyao.cc
Line
Count
Source
1
#include "controllers/fasttanyao.h"
2
3
#include <algorithm>
4
#include <cstdint>
5
#include <utility>
6
#include <vector>
7
8
#include "controllers/controllermanager.h"
9
#include "types/event.h"
10
#include "types/piecetype.h"
11
#include "types/winds.h"
12
13
namespace mahjong {
14
REGISTER_PLAYER_CONTROLLER(FastTanyao);
15
16
0
bool FastTanyao::ShouldKeep(Piece piece) {
17
0
  return !piece.isHonor() && !piece.isTerminal();
18
0
}
19
20
0
void FastTanyao::OutputSet(uint8_t /*unused*/, const pieceSet& /*unused*/) {
21
  // std::cout << "(" << Piece(id).toStr() << ", " << unsigned(set.at(id)) << ")" << std::endl;
22
0
}
23
24
0
void FastTanyao::IncrementPiece(Piece piece, pieceSet& set) {
25
0
  IncrementPiece(piece, set, /*count=*/1);
26
0
}
27
28
void FastTanyao::IncrementPiece(Piece piece, pieceSet& set,
29
0
                                uint8_t /*unused*/) {
30
0
  auto set_contains_piece = set.find(piece.raw_value());
31
0
  if (set_contains_piece != set.end()) {
32
0
    set_contains_piece->second++;
33
0
    OutputSet(piece.raw_value(), set);
34
0
    return;
35
0
  }
36
37
0
  set.insert(std::pair<uint8_t, uint8_t>(piece.raw_value(), 1));
38
0
  OutputSet(piece.raw_value(), set);
39
0
}
40
41
0
void FastTanyao::DecrementPiece(Piece piece, pieceSet& set) {
42
0
  auto set_contains_piece = set.find(piece.raw_value());
43
0
  if (set_contains_piece != set.end()) {
44
0
    set_contains_piece->second--;
45
0
    if (set_contains_piece->second == 0) {
46
0
      set.erase(set_contains_piece);
47
0
    }
48
0
    return;
49
0
  }
50
0
}
51
52
0
void FastTanyao::ProcessNewPiece(Piece piece) {
53
0
  if (!ShouldKeep(piece)) {
54
0
    immediate_discard_.push_back(piece);
55
    // std::cout << Piece(piece).toStr() << " should be immediately discarded." << std::endl;
56
0
    return;
57
0
  }
58
59
0
  IncrementPiece(piece, possible_triples_);
60
0
}
61
62
void FastTanyao::RoundStart(std::vector<Piece> _hand, Wind /*seatWind*/,
63
0
                            Wind /*prevalentWind*/) {
64
0
  for (auto piece : _hand) {
65
0
    ProcessNewPiece(piece);
66
0
  }
67
0
  decided_decision_.type = Event::kDiscard;
68
0
}
69
70
0
Piece FastTanyao::ChooseDiscard() {
71
0
  if (!immediate_discard_.empty()) {
72
0
    auto discard = immediate_discard_.back();
73
0
    immediate_discard_.pop_back();
74
    // std::cout << "Chose piece " << discard.toStr() << " to discard (imm discard)" << std::endl;
75
0
    return discard;
76
0
  }
77
78
0
  pieceSet discard_options;
79
80
  // Document pieces in play, if anything adds up to 4 (nothing left), discard.
81
0
  for (const auto& possible_triple : possible_triples_) {
82
0
    auto piece = possible_triple.first;
83
0
    auto set_contains_piece = all_discards_.find(piece);
84
0
    if (set_contains_piece != all_discards_.end()) {
85
0
      if (possible_triple.second + set_contains_piece->second == 4 &&
86
0
          possible_triple.second < 3) {
87
0
        DecrementPiece(Piece(piece), possible_triples_);
88
        // std::cout << "Chose piece " << Piece(piece).toStr() << " to discard (all pieces in play)" << std::endl;
89
0
        return Piece(piece);
90
0
      }
91
0
      if (possible_triple.second > 0) {
92
0
        IncrementPiece(Piece(piece), discard_options, possible_triple.second);
93
0
      }
94
0
    }
95
0
  }
96
97
0
  auto min_value = *std::ranges::min_element(
98
0
      discard_options,
99
0
      [](const auto& l, const auto& r) { return l.second < r.second; });
100
  // std::cout << unsigned(minValue.first) << ", " << unsigned(minValue.second) << std::endl;
101
0
  auto discard_piece = Piece(min_value.first);
102
0
  DecrementPiece(discard_piece, possible_triples_);
103
  // std::cout << "Chose piece " << discardPiece.toStr() << " to discard (min risk)" << std::endl;
104
0
  return discard_piece;
105
0
}
106
107
0
void FastTanyao::ReceiveEvent(Event e) {
108
0
  const Piece event_piece = Piece(e.piece);
109
0
  if (e.type <= Event::kDiscard && e.decision) {
110
0
    if (e.type == Event::kDiscard || ShouldKeep(Piece(e.piece))) {
111
0
      if (e.type < decided_decision_.type) {
112
        // std::cout << "Choosing decision " << e.type << std::endl;
113
0
        decided_decision_ = e;
114
0
      }
115
0
    }
116
0
  }
117
118
0
  switch (e.type) {
119
0
    case Event::kDora:
120
0
      if (ShouldKeep(event_piece)) {
121
0
        valid_doras_.push_back(event_piece);
122
0
      }
123
0
      break;
124
0
    case Event::kPon:
125
0
      IncrementPiece(event_piece, all_discards_, /*count=*/3);
126
0
      break;
127
0
    case Event::kKan:
128
0
      IncrementPiece(event_piece, all_discards_, /*count=*/4);
129
0
      break;
130
0
    case Event::kChi:
131
0
      IncrementPiece(event_piece, all_discards_);
132
0
      IncrementPiece(event_piece + 1, all_discards_);
133
0
      IncrementPiece(event_piece + 2, all_discards_);
134
0
      break;
135
0
    case Event::kDiscard:
136
0
      if (e.decision) {
137
0
        ProcessNewPiece(event_piece);
138
0
      } else {
139
0
        IncrementPiece(event_piece, all_discards_);
140
0
      }
141
0
      break;
142
0
    default:
143
0
      break;
144
0
  }
145
0
}
146
147
0
Event FastTanyao::RetrieveDecision() {
148
0
  if (decided_decision_.type == Event::kDiscard) {
149
0
    decided_decision_.piece = ChooseDiscard().raw_value();
150
0
  }
151
152
0
  auto final_event = decided_decision_;
153
0
  decided_decision_.type = Event::kDiscard;
154
  // std::cout << "Sending Decision: " << finalEvent << std::endl;
155
0
  return final_event;
156
0
}
157
}  // namespace mahjong