Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,17 @@ if(libmahjong_build_tests)
add_subdirectory(tests)
endif()

if(libmahjong_use_clang_utils)
find_program(clang_format_path NAMES clang-format)
if(clang_format_path)
set(clang_format_flags -i)
get_target_property(libmahjong_sources libmahjong SOURCES)
get_target_property(libmahjong_includes libmahjong HEADER_SET)
set(format_files ${libmahjong_sources} ${libmahjong_includes}
${test_sources} ${tool_sources})
add_custom_target(
format
COMMAND ${clang_format_path} ${clang_format_flags} ${format_files}
WORKING_DIRECTORY ${libmahjong_SOURCE_DIR}
VERBATIM)
set_target_properties(format PROPERTIES FOLDER code_utils)
endif()
find_program(clang_format_path NAMES clang-format)
if(clang_format_path)
set(clang_format_flags -i)
get_target_property(libmahjong_sources libmahjong SOURCES)
get_target_property(libmahjong_includes libmahjong HEADER_SET)
set(format_files ${libmahjong_sources} ${libmahjong_includes} ${test_sources}
${tool_sources})
add_custom_target(
format
COMMAND ${clang_format_path} ${clang_format_flags} ${format_files}
WORKING_DIRECTORY ${libmahjong_SOURCE_DIR}
VERBATIM)
set_target_properties(format PROPERTIES FOLDER code_utils)
endif()
9 changes: 5 additions & 4 deletions src/analysis/analysis.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <algorithm>
#include <memory>
#include <span>
#include <utility>
#include <vector>

Expand All @@ -24,7 +25,7 @@ void countPieces(Breakdown* b) {
std::vector<Branch> AnalyzeHand(const Hand& hand, const bool only_complete) {
Branch base_branch;
base_branch.open = !hand.melds.empty();
for (const Meld& meld : hand.melds) {
for (const Meld& meld : hand.melds_range()) {
switch (meld.type) {
case SetType::kChi:
base_branch.chis.emplace_back(meld.start);
Expand All @@ -41,7 +42,7 @@ std::vector<Branch> AnalyzeHand(const Hand& hand, const bool only_complete) {
}
}
std::vector<Branch> branches;
const std::unique_ptr<Node> root = breakdownHand(hand.live);
const std::unique_ptr<Node> root = breakdownHand(hand.live_range());
for (const auto& branch_vec : Node::AsBranchVectors(root.get())) {
branches.push_back(base_branch);
Branch& branch = branches.back();
Expand Down Expand Up @@ -73,11 +74,11 @@ std::vector<Branch> AnalyzeHand(const Hand& hand, const bool only_complete) {
return branches;
}

std::unique_ptr<Node> breakdownHand(const std::vector<Piece>& pieces) {
std::unique_ptr<Node> breakdownHand(const std::span<const Piece>& pieces) {
Breakdown b;
b.rootNode = std::make_unique<Node>(/*id=*/b.id++);
b.currentNode = b.rootNode.get();
b.pieces = pieces;
b.pieces = std::vector<Piece>(pieces.begin(), pieces.end());
countPieces(&b);
std::ranges::sort(b.pieces);
auto [begin, end] = std::ranges::unique(b.pieces);
Expand Down
2 changes: 1 addition & 1 deletion src/analysis/analysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ struct Branch {
bool complete = true;
};

std::unique_ptr<Node> breakdownHand(const std::vector<Piece>& pieces);
std::unique_ptr<Node> breakdownHand(const std::span<const Piece>& pieces);
std::vector<Branch> AnalyzeHand(const Hand& hand, bool only_complete = false);
} // namespace mahjong
10 changes: 7 additions & 3 deletions src/analysis/util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ std::vector<Piece> completeSet(const Piece a, const Piece b) {

std::vector<Piece> getWaits(const Hand& hand) {
// 13, 10, 7, 4, 1 are possible piece counts for non-melded tiles
if (hand.live.empty() || hand.live.size() % 3 != 1) {
if (hand.live_count == 0 || hand.live_count % 3 != 1) {
return {};
}
constexpr int kMaxSingles = 2;
Expand All @@ -75,13 +75,17 @@ std::vector<Piece> getWaits(const Hand& hand) {

std::vector<Piece> getWaits(const Hand& hand, const Piece& piece) {
Hand new_hand = hand;
new_hand.live.erase(std::ranges::find(new_hand.live, piece));
// This will move all pieces matching piece to the end of the array, but
// we will only decrease the count by 1 so only a single piece will be
// removed.
std::ranges::remove(new_hand.live, piece);
new_hand.live_count--;
return getWaits(new_hand);
}

std::map<Piece, std::vector<Piece>> getPossibleWaits(const Hand& hand) {
// 14, 11, 8, 5, 2 are the possible piece counts for non-melded tiles.
if (hand.live.empty() || hand.live.size() % 3 != 2) {
if (hand.live_count == 0 || hand.live_count % 3 != 2) {
return {};
}
constexpr int kMaxSingles = 3;
Expand Down
14 changes: 7 additions & 7 deletions src/api/gamestate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "controllers/controllermanager.h"
#include "statefunctions/statecontroller.h"
#include "types/gamestate.h"
#include "types/hand.h"
#include "types/settings.h"
#include "types/statefunction.h"

Expand Down Expand Up @@ -140,23 +141,23 @@ CObservedGameState ObserveGameState(mahjong::GameState* state) {

// Player data
for (int i = 0; i < 4; i++) {
observed.scores[i] = state->scores[i];
observed.points[i] = state->players[i].points;
observed.hasRonned[i] = state->hasRonned[i];
observed.scores[i] = state->hands[i].score;
observed.points[i] = state->hands[i].points;
observed.hasRonned[i] = state->hands[i].hasRonned;

// Convert Hand to CHand
const auto& cpp_hand = state->hands[i];
CHand& c_hand = observed.hands[i];

// Live pieces
const int live_piece_count = static_cast<int>(cpp_hand.live.size());
const int live_piece_count = static_cast<int>(cpp_hand.live_count);
c_hand.livePieceCount = std::min(live_piece_count, kMaxLiveHandSize);
for (int j = 0; j < c_hand.livePieceCount; j++) {
c_hand.livePieces[j] = static_cast<CPiece>(cpp_hand.live[j].toUint8_t());
}

// Melds
const int meld_count = static_cast<int>(cpp_hand.melds.size());
const int meld_count = cpp_hand.meld_count;
c_hand.meldCount = std::min(meld_count, kMaxMeldsPerHand);
for (int j = 0; j < c_hand.meldCount; j++) {
c_hand.melds[j].type = static_cast<CMeldType>(cpp_hand.melds[j].type);
Expand All @@ -166,8 +167,7 @@ CObservedGameState ObserveGameState(mahjong::GameState* state) {

// Discards
const auto& discards = cpp_hand.discards;
const int discards_size = static_cast<int>(discards.size());
c_hand.discardCount = std::min(discards_size, kMaxDiscardsPerPlayer);
c_hand.discardCount = cpp_hand.discards_count;
for (auto j = 0; j < c_hand.discardCount; j++) {
c_hand.discards[j] = static_cast<CPiece>(discards[j].toUint8_t());
}
Expand Down
3 changes: 2 additions & 1 deletion src/api/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ extern "C" {
static const int kMaxLiveHandSize = 14; // Maximum 14 pieces (13 + 1 drawn)
static const int kMaxMeldsPerHand = 4; // Maximum 4 melds possible
static const int kMaxDiscardsPerPlayer =
21; // Maximum 21 discards per player ((136 - 13*4 hands) / 4)
27; // Flow interruption from calls can allow the max discards to go up
// to 27.

using CPiece = int;
using CWind = int;
Expand Down
1 change: 0 additions & 1 deletion src/controllers/playercontroller.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#pragma once
#include <array>
#include <string>
#include <vector>

Expand Down
56 changes: 29 additions & 27 deletions src/scoring/scoring.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
#include "scoring/yakus/sevenpairs.h"
#include "scoring/yakus/thirteenorphans.h"
#include "types/gamestate.h"
#include "types/hand.h"
#include "types/pieces.h"
#include "types/piecetype.h"
#include "types/score.h"
#include "types/sets.h"
#include "types/walls.h"

namespace mahjong {

Expand Down Expand Up @@ -57,13 +59,13 @@ const int kFuRounding = 10;

} // namespace

Score scoreHand(const GameState& state, int player) {
auto root = breakdownHand(state.hands.at(player).live);
Score scoreHand(const GameState& state, const Hand& hand) {
auto root = breakdownHand(hand.live_range());
Score s;
s.han = 0;
s.yakuman = 0;
s.fu = 0;
if (!root->IsComplete() && !yaku::isThirteenOrphans(state, player)) {
if (!root->IsComplete() && !yaku::isThirteenOrphans(state, hand)) {
return s;
}
for (const auto& branch : Node::AsBranchVectors(root.get())) {
Expand All @@ -75,10 +77,10 @@ Score scoreHand(const GameState& state, int player) {
}

for (const auto& yaku : Yakus::Instance().GetYakus()) {
if (yaku.type == Yaku::kClosed && state.hands[player].open) {
if (yaku.type == Yaku::kClosed && hand.open) {
continue;
}
if (!yaku.is_yaku_func(state, player, branch)) {
if (!yaku.is_yaku_func(state, hand, branch)) {
continue;
}
switch (yaku.type) {
Expand All @@ -87,21 +89,21 @@ Score scoreHand(const GameState& state, int player) {
branchscore.han += yaku.value;
break;
case Yaku::kBonusWhenClosed:
branchscore.han += yaku.value + (state.hands[player].open ? 0 : 1);
branchscore.han += yaku.value + (hand.open ? 0 : 1);
break;
case Yaku::kYakuman:
branchscore.yakuman++;
break;
}
}

for (const auto& dora : state.walls.GetDoras()) {
for (const auto& p : state.hands.at(player).live) {
for (const auto& dora : Walls::GetDoras(state)) {
for (const auto& p : hand.live_range()) {
if (p == dora) {
branchscore.han++;
}
}
for (const auto& meld : state.hands.at(player).melds) {
for (const auto& meld : hand.melds_range()) {
if (meld.start == dora) {
branchscore.han++;
}
Expand All @@ -115,7 +117,7 @@ Score scoreHand(const GameState& state, int player) {
}
}
}
branchscore.fu = getFu(state, player, branch);
branchscore.fu = getFu(state, hand, branch);
if (getBasicPoints(branchscore) > getBasicPoints(s)) {
s = branchscore;
}
Expand Down Expand Up @@ -154,30 +156,30 @@ int getBasicPoints(Score s) {

const int kSevenpairs = 25;

int getFu(const GameState& state, int player,
int getFu(const GameState& state, const Hand& hand,
const std::vector<const mahjong::Node*>& branch) {
if (yaku::isSevenPairs(state, player, branch)) {
if (yaku::isSevenPairs(state, hand, branch)) {
return kSevenpairs;
}
if (yaku::isPinfu(state, player, branch)) {
if (state.hasRonned.at(player)) {
if (yaku::isPinfu(state, hand, branch)) {
if (hand.hasRonned) {
return kPinfuDiscard;
}
return kPinfuSelfdraw;
}
int fu = 0;
if (!state.hands.at(player).open && state.hasRonned.at(player)) {
if (!hand.open && hand.hasRonned) {
fu = kConcealedDiscard;
} else {
fu = kOpenOrSelfdraw;
}
const bool open = state.hands.at(player).open;
if (isOpenPinfu(state, player, branch)) {
const bool open = hand.open;
if (isOpenPinfu(state, hand, branch)) {
fu += kOpenpinfu;
} else if (!state.hasRonned.at(player)) {
} else if (!hand.hasRonned) {
fu += kSelfdraw;
}
for (const auto& meld : state.hands.at(player).melds) {
for (const auto& meld : hand.melds_range()) {
if (meld.type == SetType::kKan) {
if (!meld.start.isHonor() && !meld.start.isTerminal()) {
fu += open ? kSimplekan : kCsimplekan;
Expand Down Expand Up @@ -231,7 +233,7 @@ int getFu(const GameState& state, int player,
(kFuRounding - (fu % kFuRounding)); // rounding up to multiple of ten
}

bool isOpenPinfu(const GameState& state, int player,
bool isOpenPinfu(const GameState& state, const Hand& hand,
const std::vector<const mahjong::Node*>& branch) {
for (const auto* node : branch) {
if (node->type() == SetType::kPon) {
Expand All @@ -250,17 +252,17 @@ bool isOpenPinfu(const GameState& state, int player,
}
}
}
for (const auto& meld : state.hands.at(player).melds) {
for (const auto& meld : hand.melds_range()) {
if (meld.type > SetType::kChi) {
return false;
}
}
return getWaits(state.hands[player], state.pendingPiece).size() >= 2;
return getWaits(hand, state.pendingPiece).size() >= 2;
}

bool isComplete(const GameState& state, int player) {
auto root = breakdownHand(state.hands.at(player).live);
if (!root->IsComplete() && !yaku::isThirteenOrphans(state, player)) {
bool isComplete(const GameState& state, const Hand& hand) {
auto root = breakdownHand(hand.live_range());
if (!root->IsComplete() && !yaku::isThirteenOrphans(state, hand)) {
return false;
}
for (const auto& branch : Node::AsBranchVectors(root.get())) {
Expand All @@ -270,8 +272,8 @@ bool isComplete(const GameState& state, int player) {
continue;
}
if (std::ranges::any_of(Yakus::Instance().GetYakus(),
[&state, player, &branch](const auto& yaku) {
return yaku.is_yaku_func(state, player, branch);
[&state, hand, &branch](const auto& yaku) {
return yaku.is_yaku_func(state, hand, branch);
})) {
return true;
}
Expand Down
9 changes: 5 additions & 4 deletions src/scoring/scoring.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@

#include "analysis/handnode.h"
#include "types/gamestate.h"
#include "types/hand.h"
#include "types/score.h"

namespace mahjong {
Score scoreHand(const GameState& state, int player);
Score scoreHand(const GameState& state, const Hand& hand);

int getBasicPoints(Score s);

int getFu(const GameState& state, int player,
int getFu(const GameState& state, const Hand& hand,
const std::vector<const mahjong::Node*>& branch);

bool isOpenPinfu(const GameState& state, int player,
bool isOpenPinfu(const GameState& state, const Hand& hand,
const std::vector<const mahjong::Node*>& branch);

bool isComplete(const GameState& state, int player);
bool isComplete(const GameState& state, const Hand& hand);
} // namespace mahjong
5 changes: 3 additions & 2 deletions src/scoring/yakus/afterakan.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
#include "analysis/handnode.h"
#include "scoring/yakus.h"
#include "types/gamestate.h"
#include "types/hand.h"
#include "types/statefunction.h"
#include "types/yaku.h"

namespace mahjong::yaku {
bool isAfterAKan(const GameState& state, int player,
bool isAfterAKan(const GameState& state, const Hand& hand,
const std::vector<const mahjong::Node*>& /*branch*/) {
if (state.currentPlayer != player) {
if (state.currentPlayer != hand.id) {
return false;
}
if (state.prevState == StateFunctionType::kReplacement) {
Expand Down
3 changes: 2 additions & 1 deletion src/scoring/yakus/afterakan.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

#include "analysis/handnode.h"
#include "types/gamestate.h"
#include "types/hand.h"

namespace mahjong::yaku {
bool isAfterAKan(const GameState& state, int player,
bool isAfterAKan(const GameState& state, const Hand& hand,
const std::vector<const mahjong::Node*>& /*unused*/ = {});

}
Loading