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/gamestates/playerhand.cc
Line
Count
Source
1
#include <array>
2
#include <cstdint>
3
#include <iostream>
4
#include <memory>
5
#include <vector>
6
7
#include "controllers/playercontroller.h"
8
#include "statefunctions/decisionfunction.h"
9
#include "statefunctions/router.h"
10
#include "statefunctions/stateutilities.h"
11
#include "types/event.h"
12
#include "types/gamestate.h"
13
#include "types/piecetype.h"
14
#include "types/statefunction.h"
15
16
namespace mahjong {
17
18
namespace {
19
10
std::unique_ptr<GameState> PlayerHand(std::unique_ptr<GameState> state) {
20
10
  const std::vector<PossibleDecision> decisions = {
21
10
      PossibleDecision{.type = Event::kTsumo, .func = CanTsumo},
22
10
      PossibleDecision{.type = Event::kConcealedKan, .func = CanConcealedKan},
23
10
      PossibleDecision{.type = Event::kConvertedKan, .func = CanConvertedKan},
24
10
      PossibleDecision{.type = Event::kRiichi, .func = CanRiichi},
25
10
      PossibleDecision{.type = Event::kDiscard,
26
10
                       .func = [](const GameState& state, int) {
27
10
                         return !state.hands.at(state.currentPlayer).riichi;
28
10
                       }}};
29
30
10
  bool decision_asked = false;
31
50
  for (const auto& [decision, decisionIsPossible] : decisions) {
32
50
    if (decisionIsPossible(*state, state->currentPlayer)) {
33
10
      decision_asked = true;
34
10
      state->players.at(state->currentPlayer)
35
10
          .controller->ReceiveEvent(Event{
36
10
              .type = decision,                // type
37
10
              .player = state->currentPlayer,  // player
38
10
              .piece = static_cast<int16_t>(
39
10
                  state->pendingPiece.toUint8_t()),  // piece
40
10
              .decision = true,                      // decision
41
10
          });
42
10
    }
43
50
  }
44
45
10
  Event decision;
46
10
  if (!decision_asked) {
47
0
    decision.type = Event::kDiscard;
48
0
    decision.piece = static_cast<uint16_t>(state->pendingPiece.toUint8_t());
49
0
    decision.player = state->currentPlayer;
50
0
    decision.decision = true;
51
0
    state->nextState = StateFunctionType::kDiscard;
52
10
  } else {
53
10
    decision =
54
10
        GetValidDecisionOrThrow(*state, state->currentPlayer, /*inHand=*/true);
55
10
  }
56
57
  // note riichi handling is a lil borked on the player agency side
58
  // checkout riichi.cpp for more info
59
10
  if (decision.type == Event::kDiscard) {
60
10
    state->pendingPiece = Piece(decision.piece);
61
10
  }
62
63
10
  switch (decision.type) {
64
0
    case Event::kTsumo:
65
0
      state->nextState = StateFunctionType::kTsumo;
66
0
      break;
67
0
    case Event::kConcealedKan:
68
0
      state->nextState = StateFunctionType::kConcealedKan;
69
0
      break;
70
0
    case Event::kConvertedKan:
71
0
      state->nextState = StateFunctionType::kConvertedKan;
72
0
      break;
73
0
    case Event::kRiichi:
74
0
      state->nextState = StateFunctionType::kRiichi;
75
0
      break;
76
10
    case Event::kDiscard:
77
10
      state->nextState = StateFunctionType::kDiscard;
78
10
      break;
79
0
    default:
80
0
      std::cerr << "Invalid Decision Type in playerhand: " << decision.type
81
0
                << '\n';
82
0
      state->nextState = StateFunctionType::kError;
83
0
      break;
84
10
  }
85
86
10
  return state;
87
10
}
88
}  // namespace
89
90
REGISTER_ROUTE(PlayerHand, StateFunctionType::kPlayerHand);
91
}  // namespace mahjong