diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index b508be75..f99af4b6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -32,8 +32,8 @@ jobs: - name: Build # Build your program with the given configuration - run: sudo python setup.py install + run: sudo pip install . - name: Test - run: python -c "import pymahjong as pm;pm.test()" && wget https://github.com/Agony5757/mahjong/releases/download/v1.0.0/paipuxmls.7z && 7z x paipuxmls.7z && python -c "import pymahjong as pm;pm.paipu_replay(mode='debug')" + run: python -c "import pymahjong as pm;pm.test()" && wget https://github.com/Agony5757/mahjong/releases/download/v1.0.4/paipuxmls.7z && 7z x paipuxmls.7z && python -c "import pymahjong as pm;pm.paipu_replay(mode='debug')" diff --git a/Mahjong/Encoding/TrainingDataEncodingV1.cpp b/Mahjong/Encoding/TrainingDataEncodingV1.cpp index 5c530df6..62350008 100644 --- a/Mahjong/Encoding/TrainingDataEncodingV1.cpp +++ b/Mahjong/Encoding/TrainingDataEncodingV1.cpp @@ -256,23 +256,26 @@ namespace TrainingDataEncoding { for (auto& sa : actions) { switch (sa.action) { case BaseAction::Discard: - data[int(sa.correspond_tiles[0]->tile)] = 1; + if (sa.correspond_tiles[0]->red_dora) + data[int(sa.correspond_tiles[0]->tile) / 9 + 34] = 1; + else + data[int(sa.correspond_tiles[0]->tile)] = 1; break; case BaseAction::AnKan: - data[38] = 1; + data[45] = 1; break; case BaseAction::KaKan: - data[40] = 1; + data[47] = 1; break; case BaseAction::Riichi: - data[41] = 1; - data[45] = 1; + data[48] = 1; + data[52] = 1; break; case BaseAction::Tsumo: - data[43] = 1; + data[50] = 1; break; case BaseAction::Kyushukyuhai: - data[44] = 1; + data[51] = 1; break; default: throw runtime_error("Bad SelfAction (while encoding)."); @@ -285,24 +288,35 @@ namespace TrainingDataEncoding { for (auto& ra : actions) { switch (ra.action) { case BaseAction::Pass: - data[46] = 1; + data[53] = 1; break; case BaseAction::Chi: - if (action_tile > ra.correspond_tiles[0]->tile) - if (action_tile < ra.correspond_tiles[1]->tile) data[35] = 1; // middle - else data[36] = 1; // right - else data[34] = 1; // left + if (action_tile > ra.correspond_tiles[0]->tile){ + if (action_tile < ra.correspond_tiles[1]->tile){ + data[38] = 1; // middle + data[41] = 1; // middle_use_red_dora + } + else{ + data[39] = 1; // right + data[42] = 1; // right_use_red_dora + } + } + else{ + data[37] = 1; // left + data[40] = 1; // left_use_red_dora + } break; case BaseAction::Pon: - data[37] = 1; + data[43] = 1; + data[44] = 1; // use red dora break; case BaseAction::Kan: - data[39] = 1; + data[46] = 1; break; case BaseAction::Ron: case BaseAction::ChanKan: case BaseAction::ChanAnKan: - data[42] = 1; + data[49] = 1; break; default: throw runtime_error("Bad ResponseAction (while encoding)."); @@ -342,8 +356,8 @@ namespace TrainingDataEncoding { void encode_actions_vector_riichi_step2(dtype* data) { array buffer = { 0 }; - buffer[41] = 1; - buffer[46] = 1; + buffer[48] = 1; + buffer[52] = 1; memcpy(data, buffer.data(), buffer.size()); } diff --git a/Mahjong/Encoding/TrainingDataEncodingV1.h b/Mahjong/Encoding/TrainingDataEncodingV1.h index b4864e46..70a74958 100644 --- a/Mahjong/Encoding/TrainingDataEncodingV1.h +++ b/Mahjong/Encoding/TrainingDataEncodingV1.h @@ -42,7 +42,7 @@ namespace TrainingDataEncoding { constexpr size_t n_row = size_hand + size_fulu * size_player + size_river * size_player + size_field + size_last + size_action + size_hand * (size_player - 1); constexpr size_t n_col = n_tile_types; - constexpr size_t n_actions = 47; + constexpr size_t n_actions = 54; void encode_table(const Table& table, int pid, bool use_oracle, dtype* data); void encode_table_riichi_step2(const Table& table, BaseTile riichi_tile, dtype* data); diff --git a/Mahjong/Encoding/TrainingDataEncodingV2.cpp b/Mahjong/Encoding/TrainingDataEncodingV2.cpp index 4afbdced..04d442ab 100644 --- a/Mahjong/Encoding/TrainingDataEncodingV2.cpp +++ b/Mahjong/Encoding/TrainingDataEncodingV2.cpp @@ -5,6 +5,767 @@ namespace_mahjong namespace TrainingDataEncoding { namespace v2 { + bool TableEncoder::_require_update() + { + return record_count < table->gamelog.logsize(); + } + + void TableEncoder::init() + { + // After initializing Table, oya is ready to play (with 14 tiles in hand) + + // Set visible_tiles + // Initial dora is visible and will always be updated when "DoraReveal" is logged + Tile* dora_indicator = table->dora_indicator[0]; + if (dora_indicator->red_dora) + { + visible_tiles[locate_attribute(3, dora_indicator->tile)] = 1; + } + else + { + visible_tiles[locate_attribute(0, dora_indicator->tile)] = 1; + visible_tiles_count[dora_indicator->tile]++; + } + + size_t pos; + // Init static self information + + pos = (size_t)EnumSelfInformation::pos_dora_indicator_1; + self_infos[0][locate_attribute(pos, dora_indicator->tile)]++; + self_infos[1][locate_attribute(pos, dora_indicator->tile)]++; + self_infos[2][locate_attribute(pos, dora_indicator->tile)]++; + self_infos[3][locate_attribute(pos, dora_indicator->tile)]++; + + pos = (size_t)EnumSelfInformation::pos_dora_1; + self_infos[0][locate_attribute(pos, get_dora_next(dora_indicator->tile))]++; + self_infos[1][locate_attribute(pos, get_dora_next(dora_indicator->tile))]++; + self_infos[2][locate_attribute(pos, get_dora_next(dora_indicator->tile))]++; + self_infos[3][locate_attribute(pos, get_dora_next(dora_indicator->tile))]++; + + pos = (size_t)EnumSelfInformation::pos_self_wind; + for (int i = 0; i < 4; ++i) + { + auto wind_type = table->players[i].wind - Wind::East + BaseTile::_1z; + self_infos[i][locate_attribute(pos, wind_type)] = 1; + } + + pos = (size_t)EnumSelfInformation::pos_game_wind; + for (auto& self_info : self_infos) + { + auto wind_type = table->game_wind - Wind::East + BaseTile::_1z; + self_info[locate_attribute(pos, wind_type)] = 1; + } + + // Init static global information + pos = (size_t)EnumGlobalInformation::pos_game_number; + global_infos[0][pos] = + global_infos[1][pos] = + global_infos[2][pos] = + global_infos[3][pos] = (table->game_wind - Wind::East) * 4 + (table->oya); + + pos = (size_t)EnumGlobalInformation::pos_game_size; + global_infos[0][pos] = + global_infos[1][pos] = + global_infos[2][pos] = + global_infos[3][pos] = 7; + + pos = (size_t)EnumGlobalInformation::pos_honba; + global_infos[0][pos] = + global_infos[1][pos] = + global_infos[2][pos] = + global_infos[3][pos] = table->honba; + + pos = (size_t)EnumGlobalInformation::pos_kyoutaku; + global_infos[0][pos] = + global_infos[1][pos] = + global_infos[2][pos] = + global_infos[3][pos] = table->kyoutaku; + + pos = (size_t)EnumGlobalInformation::pos_self_wind; + global_infos[0][pos] = table->players[0].wind - Wind::East; + global_infos[1][pos] = table->players[1].wind - Wind::East; + global_infos[2][pos] = table->players[2].wind - Wind::East; + global_infos[3][pos] = table->players[3].wind - Wind::East; + + pos = (size_t)EnumGlobalInformation::pos_game_wind; + global_infos[0][pos] = + global_infos[1][pos] = + global_infos[2][pos] = + global_infos[3][pos] = table->game_wind - Wind::East; + + /* pos_player_{x}_point has a reverse-positional implementation. + * For example, + * pos_player_{x}_point for player0 + * x = 0 (player0 point) + * x = 1 (player3 point) Previous player + * x = 2 (player2 point) Opposite player + * x = 3 (player1 point) Next player + */ + pos = (size_t)EnumGlobalInformation::pos_player_0_point; + global_infos[0][pos] = table->players[0].score / 100; + global_infos[1][pos] = table->players[1].score / 100; + global_infos[2][pos] = table->players[2].score / 100; + global_infos[3][pos] = table->players[3].score / 100; + + pos = (size_t)EnumGlobalInformation::pos_player_1_point; + global_infos[0][pos] = table->players[3].score / 100; + global_infos[1][pos] = table->players[0].score / 100; + global_infos[2][pos] = table->players[1].score / 100; + global_infos[3][pos] = table->players[2].score / 100; + + pos = (size_t)EnumGlobalInformation::pos_player_2_point; + global_infos[0][pos] = table->players[2].score / 100; + global_infos[1][pos] = table->players[3].score / 100; + global_infos[2][pos] = table->players[0].score / 100; + global_infos[3][pos] = table->players[1].score / 100; + + pos = (size_t)EnumGlobalInformation::pos_player_3_point; + global_infos[0][pos] = table->players[1].score / 100; + global_infos[1][pos] = table->players[2].score / 100; + global_infos[2][pos] = table->players[3].score / 100; + global_infos[3][pos] = table->players[0].score / 100; + + pos = (size_t)EnumGlobalInformation::pos_remaining_tiles; + global_infos[0][pos] = + global_infos[1][pos] = + global_infos[2][pos] = + global_infos[3][pos] = table->get_remain_tile(); + + + _update_hand(0); + _update_hand(1); + _update_hand(2); + _update_hand(3); + _update_visible_tiles(); + + // The first record shouldn't be taken into account. + record_count = table->gamelog.logsize(); + } + + void TableEncoder::_update_from_ankan(const BaseGameLog& log) + { + // update visible tiles + auto tile = log.call_tiles[0]->tile; + visible_tiles[locate_attribute(0, tile)] = 1; + visible_tiles[locate_attribute(1, tile)] = 1; + visible_tiles[locate_attribute(2, tile)] = 1; + visible_tiles[locate_attribute(3, tile)] = 1; + visible_tiles_count[tile] = 4; + + // update the corresponding self_info + int player = log.player; + _update_hand(player); + _update_visible_tiles(); + } + + void TableEncoder::_update_from_call(const BaseGameLog& log) + { + // update visible tiles + for (auto tile : log.call_tiles) + { + auto basetile = tile->tile; + if (tile->red_dora) + visible_tiles[locate_attribute(3, basetile)] = 1; + else + { + visible_tiles[locate_attribute(visible_tiles_count[basetile]++, basetile)] = 1; + } + } + + // update the corresponding self_info + int player = log.player; + _update_hand(player); + _update_visible_tiles(); + } + + void TableEncoder::_update_from_kakan(const BaseGameLog& log) + { + // update visible tiles + auto tile = log.tile->tile; + visible_tiles[locate_attribute(0, tile)] = 1; + visible_tiles[locate_attribute(1, tile)] = 1; + visible_tiles[locate_attribute(2, tile)] = 1; + visible_tiles[locate_attribute(3, tile)] = 1; + visible_tiles_count[tile] = 4; + + // update the corresponding self_info + int player = log.player; + _update_hand(player); + _update_visible_tiles(); + } + + void TableEncoder::_update_from_discard(const BaseGameLog& log, bool fromhand) + { + auto tile = log.tile; + auto basetile = tile->tile; + int player = log.player; + if (tile->red_dora) + visible_tiles[locate_attribute(3, basetile)] = 1; + else + { + visible_tiles[locate_attribute(visible_tiles_count[basetile]++, basetile)] = 1; + } + + // set furiten area + // 对i来说,player是i的第p家,p = 0自家,p = 1下家,etc.. + // e.g. player = 2, i = 3, 则为上家,因此 player - i == -1 == 3 + for (int i = 0; i < 4; ++i) + { + int p = log.player - i; + p = (p < 0) ? (p + 4) : p; + size_t pos_discarded_by = p + (size_t)EnumSelfInformation::pos_discarded_by_player_1; + self_infos[i][locate_attribute(pos_discarded_by, basetile)] = 1; + } + + _update_hand(player); + _update_visible_tiles(); + } + + void TableEncoder::_update_from_riichi(const BaseGameLog& log, bool fromhand) + { + auto tile = log.tile; + auto basetile = tile->tile; + int player = log.player; + if (tile->red_dora) + visible_tiles[locate_attribute(3, basetile)] = 1; + else + { + visible_tiles[locate_attribute(visible_tiles_count[basetile]++, basetile)] = 1; + } + + // set furiten area + // 对i来说,player是i的第p家,p = 0自家,p = 1下家,etc.. + // e.g. player = 2, i = 3, 则为上家,因此 player - i == -1 == 3 + for (int i = 0; i < 4; ++i) + { + int p = log.player - i; + p = (p < 0) ? (p + 4) : p; + size_t pos_discarded_by = p + (size_t)EnumSelfInformation::pos_discarded_by_player_1; + self_infos[i][locate_attribute(pos_discarded_by, basetile)] = 1; + } + + _update_hand(player); + _update_visible_tiles(); + } + + void TableEncoder::_update_from_riichi_success(const BaseGameLog& log) + { + size_t pos_kyoutaku = (size_t)EnumGlobalInformation::pos_kyoutaku; + + // 对i来说,player是i的第p家,p = 0自家,p = 1下家,etc.. + // e.g. player = 2, i = 3, 则为上家,因此 player - i == -1 == 3 + for (int i = 0; i < 4; ++i) + { + int p = log.player - i; + p = (p < 0) ? (p + 4) : p; + size_t pos_player = (size_t)EnumGlobalInformation::pos_player_0_point + p; + global_infos[i][pos_player] -= 10; + } + global_infos[0][pos_kyoutaku] += 1; + global_infos[1][pos_kyoutaku] += 1; + global_infos[2][pos_kyoutaku] += 1; + global_infos[3][pos_kyoutaku] += 1; + } + + void TableEncoder::_update_hand(int player) + { + std::array ntiles = { 0 }; + std::array hand_cols = { 0 }; + std::array hand_cols_aka = { 0 }; + std::array tsumo_tile = { 0 }; + auto& hand = table->players[player].hand; + constexpr size_t offset_tile = (size_t)EnumSelfInformation::pos_hand_1; + constexpr size_t offset_akadora = (size_t)EnumSelfInformation::pos_aka_dora; + + for (size_t i = 0; i < hand.size(); ++i) { + int tile_type = (hand[i]->tile); + hand_cols[locate_attribute(ntiles[tile_type] + offset_tile, tile_type)] = 1; + ++ntiles[tile_type]; + + if (hand[i]->red_dora) + { + hand_cols_aka[tile_type] = 1; + } + } + + auto& self_info = self_infos[player]; + // overwrite self_info with hand_cols + constexpr size_t szbytes_hand = 4 * n_col_self_info * sizeof(decltype(hand_cols[0])); + memcpy(self_info.data(), hand_cols.data(), szbytes_hand); + + // overwrite self_info with hand_cols_aka + constexpr size_t szbytes_aka = n_col_self_info * sizeof(decltype(hand_cols[0])); + memcpy(self_info.data() + offset_akadora * n_col_self_info, hand_cols_aka.data(), szbytes_aka); + + constexpr size_t offset_tsumo = (size_t)EnumSelfInformation::pos_tsumo_tile; + if (hand.size() % 3 == 2 && table->is_self_acting()) + { + int tile_type = hand.back()->tile; + tsumo_tile[tile_type] = 1; + } + + // overwrite self_info with tsumo_tile + constexpr size_t szbytes_tsumo = n_col_self_info * sizeof(decltype(tsumo_tile[0])); + memcpy(self_info.data() + offset_tsumo * n_col_self_info, tsumo_tile.data(), szbytes_tsumo); + } + + void TableEncoder::_update_from_draw(const BaseGameLog& log, bool from_rinshan) + { + int player = log.player; + auto& self_info = self_infos[player]; + _update_hand(player); + + global_infos[0].back()--; + global_infos[1].back()--; + global_infos[2].back()--; + global_infos[3].back()--; + } + + void TableEncoder::_update_from_dora_reveal(const BaseGameLog& log) + { + auto tile = log.tile; + auto dora_indicator = tile->tile; + if (tile->red_dora) + visible_tiles[locate_attribute(3, dora_indicator)] = 1; + else + { + visible_tiles[locate_attribute(visible_tiles_count[dora_indicator]++, dora_indicator)] = 1; + } + _update_visible_tiles(); + + constexpr size_t offset_dora_indicator = (size_t)EnumSelfInformation::pos_dora_indicator_1; + constexpr size_t offset_dora = (size_t)EnumSelfInformation::pos_dora_indicator_1; + auto dora = get_dora_next(dora_indicator); + for (auto& self_info : self_infos) + { + self_info[locate_attribute(offset_dora_indicator, dora_indicator)]++; + self_info[locate_attribute(offset_dora, dora)]++; + } + } + + void TableEncoder::_update_visible_tiles() + { + constexpr size_t offset = (size_t)EnumSelfInformation::pos_discarded_number_1 * n_col_self_info; + constexpr size_t szbytes = 4 * n_col_self_info * sizeof(decltype(visible_tiles[0])); + + memcpy(self_infos[0].data() + offset, visible_tiles.data(), szbytes); + memcpy(self_infos[1].data() + offset, visible_tiles.data(), szbytes); + memcpy(self_infos[2].data() + offset, visible_tiles.data(), szbytes); + memcpy(self_infos[3].data() + offset, visible_tiles.data(), szbytes); + } + + void TableEncoder::_update_record(const BaseGameLog& log) + { + game_record_t record = { 0 }; + + static auto tile2idx = [](Tile* tile) -> size_t + { + if (tile->red_dora) + { + switch (tile->tile) + { + case _5m: + return n_tile_types; + case _5p: + return n_tile_types + 1; + case _5s: + return n_tile_types + 2; + default: + throw std::runtime_error("Bad tile."); + } + } + else + return (size_t)(tile->tile); + }; + + int player = log.player; + + /* Update the call tiles for all fuuro, kakan */ + if (log.call_tiles.size() != 0) + { + for (auto tile : log.call_tiles) + { + record[tile2idx(tile)] = 1; + } + } + + if (log.action != LogAction::Chi && + log.action != LogAction::DrawNormal && + log.action != LogAction::DrawRinshan && + log.tile) + { + // For chi, the tiles correspond only to the 2 chi-tiles + // For draw/drawrinshan, update in the last part. + // (Here only involves global update) + record[tile2idx(log.tile)] = 1; + } + + switch (log.action) + { + case LogAction::DrawNormal: + record[offset_action + (size_t)EnumGameRecordAction::DrawNormal] = 1; + break; + case LogAction::DrawRinshan: + record[offset_action + (size_t)EnumGameRecordAction::DrawRinshan] = 1; + break; + case LogAction::DiscardFromHand: + record[offset_action + (size_t)EnumGameRecordAction::DiscardFromHand] = 1; + break; + case LogAction::DiscardFromTsumo: + record[offset_action + (size_t)EnumGameRecordAction::DiscardFromTsumo] = 1; + break; + case LogAction::Chi: { + int chitile = log.tile->tile; + int handtile1 = log.call_tiles[0]->tile; + int handtile2 = log.call_tiles[1]->tile; + + if (handtile2 < handtile1) + { + std::swap(handtile1, handtile2); + throw std::runtime_error("An abnormal LogAction object."); + } + + if (chitile < handtile1) + record[offset_action + (size_t)EnumGameRecordAction::ChiLeft] = 1; + else if (chitile > handtile2) + record[offset_action + (size_t)EnumGameRecordAction::ChiRight] = 1; + else + record[offset_action + (size_t)EnumGameRecordAction::ChiMiddle] = 1; + break; + } + case LogAction::Pon: + record[offset_action + (size_t)EnumGameRecordAction::Pon] = 1; + break; + case LogAction::Kan: + record[offset_action + (size_t)EnumGameRecordAction::Kan] = 1; + break; + case LogAction::KaKan: + record[offset_action + (size_t)EnumGameRecordAction::Kakan] = 1; + break; + case LogAction::AnKan: + record[offset_action + (size_t)EnumGameRecordAction::Ankan] = 1; + break; + case LogAction::RiichiDiscardFromHand: + record[offset_action + (size_t)EnumGameRecordAction::RiichiFromHand] = 1; + break; + case LogAction::RiichiDiscardFromTsumo: + record[offset_action + (size_t)EnumGameRecordAction::RiichiFromTsumo] = 1; + break; + case LogAction::RiichiSuccess: + record[offset_action + (size_t)EnumGameRecordAction::RiichiSuccess] = 1; + break; + case LogAction::DoraReveal: + // this does not involve record update. + break; + default: + throw std::runtime_error("Bad LogAction (not handled in the _update_record)."); + } + + records[0].push_back(record); + records[1].push_back(record); + records[2].push_back(record); + records[3].push_back(record); + + // 对i来说,player是i的第p家,p = 0自家,p = 1下家,etc.. + // e.g. player = 2, i = 3, 则为上家,因此 player - i == -1 == 3 + for (int i = 0; i < 4; ++i) + { + int p = player - i; + p = (p < 0) ? (p + 4) : p; + records[i].back()[offset_player + p] = 1; + } + + /* Only player record the tile, other is empty */ + if (log.action == LogAction::DrawNormal || + log.action == LogAction::DrawRinshan) + records[player].back()[tile2idx(log.tile)] = 1; + } + + void TableEncoder::_update_ippatsu() + { + for (int player = 0; player < 4; ++player) { + // 对i来说,player是i的第p家,p = 0自家,p = 1下家,etc.. + // e.g. player = 2, i = 3, 则为上家,因此 player - i == -1 == 3 + for (int i = 0; i < 4; ++i) + { + int p = player - i; + p = (p < 0) ? (p + 4) : p; + size_t pos_ippatsu = p + (size_t)EnumGlobalInformation::pos_player_0_ippatsu; + global_infos[i][pos_ippatsu] = table->players[player].ippatsu ? 1 : 0; + } + } + + } + + void TableEncoder::update() + { + while (_require_update()) + { + auto& log = table->gamelog[record_count]; + switch (log.action) + { + case LogAction::AnKan: + _update_from_ankan(log); + break; + case LogAction::Pon: + case LogAction::Chi: + case LogAction::Kan: + _update_from_call(log); + break; + case LogAction::KaKan: + _update_from_kakan(log); + break; + case LogAction::DiscardFromHand: + _update_from_discard(log, DiscardFromHand); + break; + case LogAction::DiscardFromTsumo: + _update_from_discard(log, DiscardFromTsumo); + break; + case LogAction::RiichiDiscardFromHand: + _update_from_riichi(log, DiscardFromHand); + break; + case LogAction::RiichiDiscardFromTsumo: + _update_from_riichi(log, DiscardFromTsumo); + break; + case LogAction::RiichiSuccess: + _update_from_riichi_success(log); + break; + case LogAction::DrawNormal: + _update_from_draw(log, DrawNormally); + break; + case LogAction::DrawRinshan: + _update_from_draw(log, DrawFromRinshan); + break; + case LogAction::DoraReveal: + _update_from_dora_reveal(log); + break; + case LogAction::Kyushukyuhai: + case LogAction::Ron: + case LogAction::Tsumo: + ++record_count; + return; + default: + throw std::runtime_error("Bad LogAction."); + } + + _update_record(log); + _update_ippatsu(); + + ++record_count; + } + } + + void PassiveTableEncoder::encode_game_basic(int game_number, + int game_size, + int honba, + int kyoutaku, + int self_wind, + int game_wind) + { + // Init static global information + size_t pos; + pos = (size_t)EnumGlobalInformation::pos_game_number; + global_info[pos] = game_number; + + pos = (size_t)EnumGlobalInformation::pos_game_size; + global_info[pos] = game_size; + + pos = (size_t)EnumGlobalInformation::pos_honba; + global_info[pos] = honba; + + pos = (size_t)EnumGlobalInformation::pos_kyoutaku; + global_info[pos] = kyoutaku; + + pos = (size_t)EnumGlobalInformation::pos_self_wind; + global_info[pos] = self_wind - Wind::East; + + pos = (size_t)EnumGlobalInformation::pos_game_wind; + global_info[pos] = game_wind - Wind::East; + + pos = (size_t)EnumSelfInformation::pos_self_wind; + self_info[locate_attribute(pos, game_wind - Wind::East + BaseTile::_1z)] = 1; + + pos = (size_t)EnumSelfInformation::pos_game_wind; + self_info[locate_attribute(pos, game_wind - Wind::East + BaseTile::_1z)] = 1; + } + + void PassiveTableEncoder::encode_points(const std::array& points) + { + /* pos_player_{x}_point has a reverse-positional implementation. + * For example, + * pos_player_{x}_point for player0 + * x = 0 (player0 point) + * x = 1 (player3 point) Previous player + * x = 2 (player2 point) Opposite player + * x = 3 (player1 point) Next player + */ + size_t pos; + pos = (size_t)EnumGlobalInformation::pos_player_0_point; + global_info[pos] = points[0]; + + pos = (size_t)EnumGlobalInformation::pos_player_1_point; + global_info[pos] = points[3]; + + pos = (size_t)EnumGlobalInformation::pos_player_2_point; + global_info[pos] = points[2]; + + pos = (size_t)EnumGlobalInformation::pos_player_3_point; + global_info[pos] = points[1]; + } + + void PassiveTableEncoder::encode_remaining_tiles(int remain_tiles) + { + constexpr size_t pos = (size_t)EnumGlobalInformation::pos_remaining_tiles; + global_info[pos] = remain_tiles; + } + + void PassiveTableEncoder::encode_hand(const std::vector& hand, bool after_chipon) + { + std::array ntiles = { 0 }; + std::array hand_cols = { 0 }; + std::array hand_cols_aka = { 0 }; + std::array tsumo_tile = { 0 }; + // auto& hand = table->players[player].hand; + constexpr size_t offset_tile = (size_t)EnumSelfInformation::pos_hand_1; + constexpr size_t offset_akadora = (size_t)EnumSelfInformation::pos_aka_dora; + + for (size_t i = 0; i < hand.size(); ++i) { + int tile_type = hand[i].tile; + if (hand[i].red_dora) + hand_cols_aka[tile_type] = 1; + hand_cols[locate_attribute(ntiles[tile_type] + offset_tile, tile_type)] = 1; + ++ntiles[tile_type]; + } + + // overwrite self_info with hand_cols + constexpr size_t szbytes_hand = 4 * n_col_self_info * sizeof(decltype(hand_cols[0])); + memcpy(self_info.data(), hand_cols.data(), szbytes_hand); + + // overwrite self_info with hand_cols_aka + constexpr size_t szbytes_aka = n_col_self_info * sizeof(decltype(hand_cols[0])); + memcpy(self_info.data() + offset_akadora * n_col_self_info, hand_cols_aka.data(), szbytes_aka); + + constexpr size_t offset_tsumo = (size_t)EnumSelfInformation::pos_tsumo_tile; + if (!after_chipon && hand.size() % 3 == 2) + { + int tile_type = hand.back().tile; + tsumo_tile[tile_type] = 1; + } + + // overwrite self_info with tsumo_tile + constexpr size_t szbytes_tsumo = n_col_self_info * sizeof(decltype(tsumo_tile[0])); + memcpy(self_info.data() + offset_tsumo * n_col_self_info, tsumo_tile.data(), szbytes_tsumo); + } + + void PassiveTableEncoder::encode_river(const std::vector& river, int relative_position) + { + for (auto tile : river) + { + // record visible_tiles + visible_tiles[locate_attribute(visible_tiles_count[tile], tile)] += 1; + visible_tiles_count[tile]++; + + // update furiten area + size_t pos_discarded_by = (size_t)EnumSelfInformation::pos_discarded_by_player_1 + relative_position; + self_info[locate_attribute(pos_discarded_by, tile)] = 1; + } + // update visible_tiles + _update_visible_tiles(); + } + + void PassiveTableEncoder::encode_self_river(const std::vector& river) + { + encode_river(river, 0); + } + + void PassiveTableEncoder::encode_next_river(const std::vector& river) + { + encode_river(river, 1); + } + + void PassiveTableEncoder::encode_opposite_river(const std::vector& river) + { + encode_river(river, 2); + } + + void PassiveTableEncoder::encode_previous_river(const std::vector& river) + { + encode_river(river, 3); + } + + void PassiveTableEncoder::_update_visible_tiles() + { + constexpr size_t offset = (size_t)EnumSelfInformation::pos_discarded_number_1 * n_col_self_info; + constexpr size_t szbytes = 4 * n_col_self_info * sizeof(decltype(visible_tiles[0])); + + memcpy(self_info.data() + offset, visible_tiles.data(), szbytes); + } + + void PassiveTableEncoder::encode_fuuro(const std::vector& callgroups, int relative_position) + { + for (auto& group : callgroups) + { + for (auto tile_ : group.tiles) + { + // record visible_tiles + auto tile = tile_->tile; + visible_tiles[locate_attribute(visible_tiles_count[tile], tile)] += 1; + visible_tiles_count[tile]++; + } + } + _update_visible_tiles(); + } + + void PassiveTableEncoder::encode_self_fuuro(const std::vector& callgroups) + { + encode_fuuro(callgroups, 0); + } + + void PassiveTableEncoder::encode_next_fuuro(const std::vector& callgroups) + { + encode_fuuro(callgroups, 1); + } + + void PassiveTableEncoder::encode_opposite_fuuro(const std::vector& callgroups) + { + encode_fuuro(callgroups, 2); + } + + void PassiveTableEncoder::encode_previous_fuuro(const std::vector& callgroups) + { + encode_fuuro(callgroups, 3); + } + + void PassiveTableEncoder::encode_dora(const std::vector revealed_doras) + { + for (auto tile : revealed_doras) + { + // record visible_tiles + visible_tiles[locate_attribute(visible_tiles_count[tile], tile)] += 1; + visible_tiles_count[tile]++; + } + _update_visible_tiles(); + + for (auto tile : revealed_doras) + { + self_info[locate_attribute((size_t)EnumSelfInformation::pos_dora_indicator_1, tile)] += 1; + self_info[locate_attribute((size_t)EnumSelfInformation::pos_dora_1, get_dora_next(tile))] += 1; + } + } + + void PassiveTableEncoder::encode_riichi_states(const std::array& riichi_states) + { + // no information + } + + void PassiveTableEncoder::encode_ippatsu_states(const std::array& ippatsu_states) + { + for (int i = 0; i < 4; ++i) + { + size_t pos = (size_t)EnumGlobalInformation::pos_player_0_ippatsu + i; + global_info[pos] = ippatsu_states[i]; + } + } } } namespace_mahjong_end diff --git a/Mahjong/Encoding/TrainingDataEncodingV2.h b/Mahjong/Encoding/TrainingDataEncodingV2.h index 7d4105d1..920b8e90 100644 --- a/Mahjong/Encoding/TrainingDataEncodingV2.h +++ b/Mahjong/Encoding/TrainingDataEncodingV2.h @@ -9,6 +9,205 @@ namespace_mahjong namespace TrainingDataEncoding { namespace v2 { + constexpr size_t n_tile_types = 9 + 9 + 9 + 7; + using dtype = int16_t; + enum class EnumSelfInformation + { + pos_hand_1, + pos_hand_2, + pos_hand_3, + pos_hand_4, + pos_dora_1, + pos_dora_indicator_1, + pos_aka_dora, + pos_game_wind, + pos_self_wind, + pos_tsumo_tile, + pos_discarded_by_player_1, + pos_discarded_by_player_2, + pos_discarded_by_player_3, + pos_discarded_by_player_4, + pos_discarded_number_1, + pos_discarded_number_2, + pos_discarded_number_3, + pos_discarded_number_4, + n_self_information + }; + constexpr size_t n_row_self_info = (size_t)EnumSelfInformation::n_self_information; + constexpr size_t n_col_self_info = n_tile_types; + + using self_info_t = std::array; + + constexpr size_t locate_attribute(size_t attribute_row, size_t tile_type) + { + if (tile_type >= n_col_self_info) + { + throw std::runtime_error(fmt::format("Bad access to [{},{}]", attribute_row, tile_type)); + } + return n_tile_types * attribute_row + tile_type; + } + + constexpr size_t n_tile_types_include_aka = n_tile_types + 3; + enum class EnumGameRecordAction + { + DrawNormal, + DrawRinshan, + DiscardFromHand, + DiscardFromTsumo, + ChiLeft, + ChiMiddle, + ChiRight, + Pon, + Kan, + Ankan, + Kakan, + RiichiFromHand, + RiichiFromTsumo, + RiichiSuccess, + n_actions, + }; + constexpr size_t n_actions = (size_t)EnumGameRecordAction::n_actions; + constexpr size_t n_players = 4; + constexpr size_t n_step_actions = n_tile_types_include_aka + n_actions + n_players; + constexpr size_t offset_tile = 0; + constexpr size_t offset_action = offset_tile + n_tile_types_include_aka; + constexpr size_t offset_player = offset_action + n_actions; + + // a record structure for encoding + using game_record_t = std::array; + + enum class EnumGlobalInformation + { + pos_game_number, + pos_game_size, + pos_honba, + pos_kyoutaku, + pos_self_wind, + pos_game_wind, + pos_player_0_point, // self + pos_player_1_point, // next + pos_player_2_point, // opposite + pos_player_3_point, // previous + pos_player_0_ippatsu, // self + pos_player_1_ippatsu, // next + pos_player_2_ippatsu, // opposite + pos_player_3_ippatsu, // previous + pos_remaining_tiles, + n_global_information + }; + + using global_info_t = std::array; + + struct TableEncoder + { + std::array visible_tiles = { 0 }; + std::array visible_tiles_count = { 0 }; + Table* table = nullptr; + int record_count = 0; + + public: + std::array self_infos; + std::array, 4> records; + std::array global_infos; + + inline TableEncoder(Table* t) : table(t) { + self_infos.fill(self_info_t{ 0 }); + records.fill(std::vector()); + global_infos.fill(global_info_t{ 0 }); + } + + inline const auto& get_self_info(size_t player) const + { + return self_infos[player]; + } + + inline const auto& get_play_record(size_t player) const + { + return records[player]; + } + + inline const auto& get_global_info(size_t player) const + { + return global_infos[player]; + } + + bool _require_update(); + void _update_from_ankan(const BaseGameLog& log); + void _update_from_call(const BaseGameLog& log); + void _update_from_kakan(const BaseGameLog& log); + void _update_from_discard(const BaseGameLog& log, bool fromhand); + void _update_from_riichi(const BaseGameLog& log, bool fromhand); + void _update_from_riichi_success(const BaseGameLog& log); + void _update_from_draw(const BaseGameLog& log, bool from_rinshan); + void _update_from_dora_reveal(const BaseGameLog& log); + + void _update_hand(int player); + void _update_visible_tiles(); + void _update_record(const BaseGameLog& log); + void _update_ippatsu(); + + void init(); + void update(); + }; + + struct PassiveTableEncoder + { + std::array visible_tiles = { 0 }; + std::array visible_tiles_count = { 0 }; + + public: + self_info_t self_info; + std::vector records; + global_info_t global_info; + + inline PassiveTableEncoder() { + self_info.fill(0); + global_info.fill(0); + } + + inline const auto& get_self_info() const + { + return self_info; + } + + inline const auto& get_play_record() const + { + return records; + } + + inline const auto& get_global_info() const + { + return global_info; + } + + void encode_game_basic( + int game_number, + int game_size, + int honba, + int kyoutaku, + int self_wind, + int game_wind + ); + + void encode_hand(const std::vector& hand, bool after_chipon); + void _update_visible_tiles(); + void encode_self_river(const std::vector& river); + void encode_next_river(const std::vector& river); + void encode_opposite_river(const std::vector& river); + void encode_previous_river(const std::vector& river); + void encode_river(const std::vector& river, int relative_position); + void encode_self_fuuro(const std::vector &callgroups); + void encode_next_fuuro(const std::vector &callgroups); + void encode_opposite_fuuro(const std::vector &callgroups); + void encode_previous_fuuro(const std::vector &callgroups); + void encode_fuuro(const std::vector& callgroups, int relative_position); + void encode_dora(const std::vector revealed_doras); + + void encode_points(const std::array& points); + void encode_remaining_tiles(int remain_tiles); + void encode_riichi_states(const std::array& riichi_states); + void encode_ippatsu_states(const std::array& ippatsu_states); + }; } } diff --git a/Mahjong/GameLog.cpp b/Mahjong/GameLog.cpp index 32ffc9ce..8b19bf76 100644 --- a/Mahjong/GameLog.cpp +++ b/Mahjong/GameLog.cpp @@ -7,104 +7,139 @@ using namespace std; namespace_mahjong BaseGameLog::BaseGameLog(int p1, int p2, LogAction action, Tile* tile, - vector CallGroup) - :player(p1), player2(p2), action(action), tile(tile), call_tiles(CallGroup) + const vector &callgroup) + : player(p1), player2(p2), action(action), tile(tile), call_tiles(callgroup) { } -BaseGameLog::BaseGameLog(array scores) +BaseGameLog::BaseGameLog(const array &scores) { score = scores; } string BaseGameLog::to_string() { - stringstream ss; - ss << "p" << player; switch (action) { + case LogAction::KaKan: + return fmt::format("p{} Kakan {}", player, tile->to_string()); case LogAction::AnKan: - return fmt::format("AnKan {}", tiles_to_string(call_tiles)); - /*ss << "AnKan" << tiles_to_string(call_tiles); - return ss.str();*/ + return fmt::format("p{} AnKan {}", player, tiles_to_string(call_tiles)); case LogAction::Pon: - return fmt::format("Pon {} with {}", tile->to_string(), tiles_to_string(call_tiles)); - /*ss << "Pon" << tile->to_string() << "with" << tiles_to_string(call_tiles); - return ss.str();*/ + return fmt::format("p{} Pon {} with {}", player, tile->to_string(), tiles_to_string(call_tiles)); case LogAction::Chi: - return fmt::format("Chi {} with {}", tile->to_string(), tiles_to_string(call_tiles)); - /*ss << "Chi" << tile->to_string() << "with" << tiles_to_string(call_tiles); - return ss.str();*/ + return fmt::format("p{} Chi {} with {}", player, tile->to_string(), tiles_to_string(call_tiles)); + case LogAction::Kan: + return fmt::format("p{} Kan {} with {}", player, tile->to_string(), tiles_to_string(call_tiles)); case LogAction::DiscardFromHand: - return fmt::format("Discard (te giri) {}", tile->to_string()); - /*ss << "Discard (te giri)" << tile->to_string(); - return ss.str();*/ + return fmt::format("p{} Discard (from hand) {}", player, tile->to_string()); case LogAction::DiscardFromTsumo: - return fmt::format("Discard (tsumo giri) {}", tile->to_string()); - /*ss << "Discard (tsumo giri)" << tile->to_string(); - return ss.str();*/ - case LogAction::Draw: - ss << "Draw" << tile->to_string(); - return ss.str(); + return fmt::format("p{} Discard (from tsumo) {}", player, tile->to_string()); + case LogAction::DrawNormal: + return fmt::format("p{} Draw {}", player, tile->to_string()); + case LogAction::DrawRinshan: + return fmt::format("p{} Draw (rinshan) {}", player, tile->to_string()); case LogAction::RiichiDiscardFromHand: - ss << "Riichi Discard (te giri)" << tile->to_string(); - return ss.str(); + return fmt::format("p{} Riichi discard (from hand) {}", player, tile->to_string()); case LogAction::RiichiDiscardFromTsumo: - ss << "Riichi Discard (tsumo giri)" << tile->to_string(); - return ss.str(); + return fmt::format("p{} Riichi discard (from tsumo) {}", player, tile->to_string()); case LogAction::Kyushukyuhai: - ss << "Kyushukyuhai"; - return ss.str(); + return fmt::format("p{} Kyushukyuhai", player); case LogAction::RiichiSuccess: - ss << "Riichi Success " << score_to_string(score); - return ss.str(); + return fmt::format("p{} Riichi Success {}", player, score_to_string(score)); + case LogAction::DoraReveal: + return fmt::format("Reveal dora: {}", tile->to_string()); default: - throw runtime_error("Invalid LogAction. BaseAction: " + std::to_string(int(action))); + throw runtime_error("Invalid LogAction. LogAction: " + std::to_string(int(action))); } } string GameLog::to_string() { stringstream ss; - ss << "搴勫: Player " << oya << endl - << "鍦洪: " << wind_to_string(game_wind) << endl - << start_honba << "鏈満 " << start_kyoutaku << "绔嬬洿妫" << endl - << "鐐规暟:" << score_to_string(start_scores) << endl - << "鐗屽北:" << yama << endl; + ss << fmt::format( + "Oya: Player {}\n" + "Game wind: {}\n" + "{} honba, {} kyoutaku\n" + "Initial points: {}\n" + "Initial yama: {}\n" + "Hand0: {}\n" + "Hand1: {}\n" + "Hand2: {}\n" + "Hand3: {}\n" + , + oya, + wind_to_string(game_wind), + start_honba, start_kyoutaku, + start_scores, + vector_tile_to_string(init_yama), + vector_tile_to_string(init_hands[0]), + vector_tile_to_string(init_hands[1]), + vector_tile_to_string(init_hands[2]), + vector_tile_to_string(init_hands[3]) + ); + + //ss << "Oya: Player " << oya << endl + // << "鍦洪: " << wind_to_string(game_wind) << endl + // << start_honba << "鏈満 " << start_kyoutaku << "绔嬬洿妫" << endl + // << "鐐规暟:" << score_to_string(start_scores) << endl + // << "鐗屽北:" << init_yama << endl; for (auto log : logs) { ss << log.to_string() << endl; } - ss << "缁撴灉:" << result.to_string() << endl; + if (result.result_type != ResultType::Error) + ss << "Result:" << result.to_string() << endl; return ss.str(); } void GameLog::_log(BaseGameLog log) { + FunctionProfiler; logs.push_back(log); } void GameLog::log_game_start( - int start_honba_, int start_kyoutaku_, int oya_, Wind game_wind_, string yama_, - array scores) + int start_honba_, int start_kyoutaku_, int oya_, Wind game_wind_, + const std::vector& yama_, const std::array &scores, + const std::vector init_hand0, + const std::vector init_hand1, + const std::vector init_hand2, + const std::vector init_hand3) { start_honba = start_honba_; start_kyoutaku = start_kyoutaku_; oya = oya_; game_wind = game_wind_; - yama = yama_; + init_yama = yama_; start_scores = scores; + init_hands = { + init_hand0, + init_hand1, + init_hand2, + init_hand3 + }; + for (const auto& hand : init_hands) + { + if (hand.size() != 13) + throw std::runtime_error("Game init log failed. Hand size is not 13 at the beginning."); + } +} + +void GameLog::_log_draw_normal(int player, Tile* tile) +{ + _log({ player, -1, LogAction::DrawNormal, tile, {} }); } -void GameLog::log_draw(int player, Tile* tile) +void GameLog::_log_draw_rinshan(int player, Tile* tile) { - _log({ player, -1, LogAction::Draw, tile, {} }); + _log({ player, -1, LogAction::DrawRinshan, tile, {} }); } -void GameLog::log_discard_from_tsumo(int player, Tile* tile) +void GameLog::_log_discard_from_tsumo(int player, Tile* tile) { _log({ player, -1, LogAction::DiscardFromTsumo, tile, {} }); } -void GameLog::log_discard_from_hand(int player, Tile* tile) +void GameLog::_log_discard_from_hand(int player, Tile* tile) { _log({ player, -1, LogAction::DiscardFromHand, tile, {} }); } @@ -165,6 +200,21 @@ void GameLog::log_kyushukyuhai(int player, Result result) log_gameover(result); } +void GameLog::log_tsumo(int player) +{ + _log({ player, -1, LogAction::Tsumo, nullptr, {} }); +} + +void GameLog::log_ron(int player_win, int player_lose, Tile* tile) +{ + _log({ player_win, player_lose, LogAction::Tsumo, tile, {} }); +} + +void GameLog::log_reveal_dora(Tile* tile) +{ + _log({ -1, -1, LogAction::DoraReveal, tile, {} }); +} + void GameLog::log_gameover(Result _result) { result = _result; diff --git a/Mahjong/GameLog.h b/Mahjong/GameLog.h index 87bdd62e..e9d06ee5 100644 --- a/Mahjong/GameLog.h +++ b/Mahjong/GameLog.h @@ -9,42 +9,44 @@ namespace_mahjong enum class LogAction { - AnKan, - Pon, - Chi, - Kan, - KaKan, - DiscardFromHand, - DiscardFromTsumo, - Draw, - RiichiDiscardFromHand, - RiichiDiscardFromTsumo, - Kyushukyuhai, - Ron, - Tsumo, - RiichiSuccess, + AnKan, // 鏆楁潬 + Pon, // 纰 + Chi, // 鍚 + Kan, // 鏉 + KaKan, // 鍔犳潬 + DiscardFromHand, // 鎵嬪垏 + DiscardFromTsumo,// 鎽稿垏 + RiichiDiscardFromHand, // 瀹e憡鎵嬪垏绔 + RiichiDiscardFromTsumo,// 瀹e憡鎽稿垏绔 + RiichiSuccess, // 绔嬬洿閫氳繃 + DrawNormal, // 姝e父鎽哥墝 + DrawRinshan, // 鎽稿箔涓婄墝 + DoraReveal, // 缈1寮犲疂鐗 + Kyushukyuhai, // 瀹e憡涔濈涔濈墝 + Ron, // 瀹e憡Ron + Tsumo, // 瀹e憡鑷懜 + InvalidLogAction, }; constexpr bool DiscardFromTsumo = false; constexpr bool DiscardFromHand = true; +constexpr bool DrawNormally = false; +constexpr bool DrawFromRinshan = true; class Table; struct BaseGameLog { - -protected: - BaseGameLog() {} - -public: - int player; - int player2; - LogAction action; - Tile* tile; + BaseGameLog() = delete; + int player = 0; + int player2 = 0; + LogAction action = LogAction::InvalidLogAction; + Tile* tile = nullptr; std::vector call_tiles; - std::array score; + std::array score = { 0 }; - BaseGameLog(int, int, LogAction, Tile*, std::vector); - BaseGameLog(std::array scores); + BaseGameLog(int p1, int p2, LogAction action, Tile* tile, + const std::vector& callgroup); + BaseGameLog(const std::array &scores); virtual std::string to_string(); }; @@ -53,31 +55,53 @@ class GameLog { std::vector winner; std::vector loser; std::array start_scores; - std::string yama; + std::vector init_yama; + std::array, 4> init_hands; int start_honba; - int end_honba; + int end_honba = -1; int start_kyoutaku; - int end_kyoutaku; + int end_kyoutaku = -1; int oya; Wind game_wind; Result result; std::vector logs; + GameLog() + { + /* avoid reallocation */ + logs.reserve(128); + } + void _log(BaseGameLog log); + void log_game_start(int start_honba, int start_kyoutaku, int oya, Wind game_wind, - std::string yama, - std::array); - void log_draw(int player, Tile*); - void log_discard_from_tsumo(int player, Tile*); - void log_discard_from_hand(int player, Tile*); + const std::vector &yama, const std::array &start_scores, + const std::vector init_hand0, + const std::vector init_hand1, + const std::vector init_hand2, + const std::vector init_hand3 + ); + + void _log_draw_normal(int player, Tile* tile); + void _log_draw_rinshan(int player, Tile* tile); + inline void log_draw(int player, Tile* tile, bool from_rinshan) + { + if (from_rinshan) + _log_draw_rinshan(player, tile); + else + _log_draw_normal(player, tile); + } + + void _log_discard_from_tsumo(int player, Tile* tile); + void _log_discard_from_hand(int player, Tile* tile); inline void log_discard(int player, Tile* tile, bool fromhand) { if (fromhand == DiscardFromHand) - log_discard_from_hand(player, tile); + _log_discard_from_hand(player, tile); else - log_discard_from_tsumo(player, tile); + _log_discard_from_tsumo(player, tile); } void log_riichi_discard_from_tsumo(int player, Tile*); @@ -95,9 +119,16 @@ class GameLog { void log_kakan(int player, Tile*); void log_ankan(int player, std::vector tiles); - void log_riichi_success(Table* table); + void log_riichi_success(Table* table); void log_kyushukyuhai(int player, Result result); + void log_tsumo(int player); + void log_ron(int player_win, int player_lose, Tile* tile); + void log_reveal_dora(Tile* tile); void log_gameover(Result result); + inline size_t logsize() const { return logs.size(); } + inline auto& operator[](size_t loc) const { return logs[loc]; } + inline auto& operator[](size_t loc) { return logs[loc]; } + std::string to_string(); }; diff --git a/Mahjong/GameResult.cpp b/Mahjong/GameResult.cpp index 570ee41a..95db7985 100644 --- a/Mahjong/GameResult.cpp +++ b/Mahjong/GameResult.cpp @@ -7,9 +7,9 @@ using namespace std; namespace_mahjong -static Result ryukyouku_interval(Table *table) { +static Result _ryukyouku_interval(Table *table, ResultType type) { Result result; - result.result_type = ResultType::Ryukyouku_Interval; + result.result_type = type; for (int i = 0; i < 4; ++i) result.score[i] = table->players[i].score; @@ -22,22 +22,27 @@ static Result ryukyouku_interval(Table *table) { Result generate_result_9hai(Table *table) { - return ryukyouku_interval(table); + return _ryukyouku_interval(table, ResultType::Ryukyouku_Interval_9Hai); } Result generate_result_4wind(Table *table) { - return ryukyouku_interval(table); + return _ryukyouku_interval(table, ResultType::Ryukyouku_Interval_4Wind); } Result generate_result_4riichi(Table *table) { - return ryukyouku_interval(table); + return _ryukyouku_interval(table, ResultType::Ryukyouku_Interval_4Riichi); } Result generate_result_4kan(Table *table) { - return ryukyouku_interval(table); + return _ryukyouku_interval(table, ResultType::Ryukyouku_Interval_4Kan); +} + +Result generate_result_3ron(Table* table) +{ + return _ryukyouku_interval(table, ResultType::Ryukyouku_Interval_3Ron); } bool is_nagashi_mangan(River r) { @@ -51,11 +56,89 @@ bool is_nagashi_mangan(River r) { }); } -Result generate_result_notile(Table * table) +void nagashimangan_handler(Result& result, bool nagashi_mangan_status[4], + int n_nagashimangan, bool tenpai_status[4], Table* table) { - //cout << "Warning: 缃氱 is not considered" << endl; - //cout << "Warning: 娴佸眬婊¤疮 is not considered" << endl; + result.result_type = ResultType::NagashiMangan; + if (tenpai_status[table->oya]) + result.renchan = true; + else + result.renchan = false; + result.n_honba = table->honba + 1; + result.n_riichibo = table->kyoutaku; + if (n_nagashimangan == 4) { + // 涓嶆秹鍙婂埌鍒嗘暟鏀粯 + } + else if (n_nagashimangan == 3) { + if (nagashi_mangan_status[table->oya]) + { + result.score[table->oya] += 2000; + for (int i = 0; i < 4; ++i) { + if (nagashi_mangan_status[i]) + result.score[i] += 2000; + else + result.score[i] -= 8000; + } + } + else + { + for (int i = 0; i < 4; ++i) { + if (nagashi_mangan_status[i]) + result.score[i] += 4000; + else + result.score[i] -= 8000; + } + } + } + else if (n_nagashimangan == 2) { + if (nagashi_mangan_status[table->oya]) + { + result.score[table->oya] += 4000; + for (int i = 0; i < 4; ++i) { + if (nagashi_mangan_status[i]) + result.score[i] += 4000; + else + result.score[i] -= 6000; + } + } + else + { + result.score[table->oya] -= 4000; + for (int i = 0; i < 4; ++i) { + if (nagashi_mangan_status[i]) + result.score[i] += 6000; + else + result.score[i] -= 4000; + } + } + } + else if (n_nagashimangan == 1) { + if (nagashi_mangan_status[table->oya]) + { + for (int i = 0; i < 4; ++i) { + if (nagashi_mangan_status[i]) + result.score[i] += 12000; + else + result.score[i] -= 4000; + } + } + else + { + result.score[table->oya] -= 2000; + for (int i = 0; i < 4; ++i) { + if (nagashi_mangan_status[i]) + result.score[i] += 8000; + else + result.score[i] -= 2000; + } + } + } + +} + +Result generate_result_notile(Table *table) +{ Result result; for (int i = 0; i < 4; ++i) @@ -70,139 +153,70 @@ Result generate_result_notile(Table * table) n_nagashimangan++; } } - - if (n_nagashimangan > 0) { - result.result_type = ResultType::NagashiMangan; - if (nagashi_mangan_status[table->oya]) - result.renchan = true; - else - result.renchan = false; - result.n_honba = table->honba + 1; - result.n_riichibo = table->kyoutaku; - if (n_nagashimangan == 4) { - // 涓嶆秹鍙婂埌鍒嗘暟鏀粯 - } - else if (n_nagashimangan == 3) { - if (nagashi_mangan_status[table->oya]) - { - result.score[table->oya] += 2000; - for (int i = 0; i < 4; ++i) { - if (nagashi_mangan_status[i]) - result.score[i] += 2000; - else - result.score[i] -= 8000; - } - } - else - { - for (int i = 0; i < 4; ++i) { - if (nagashi_mangan_status[i]) - result.score[i] += 4000; - else - result.score[i] -= 8000; - } - } - } - else if (n_nagashimangan == 2){ - if (nagashi_mangan_status[table->oya]) - { - result.score[table->oya] += 4000; - for (int i = 0; i < 4; ++i) { - if (nagashi_mangan_status[i]) - result.score[i] += 4000; - else - result.score[i] -= 6000; - } - } - else - { - result.score[table->oya] -= 4000; - for (int i = 0; i < 4; ++i) { - if (nagashi_mangan_status[i]) - result.score[i] += 6000; - else - result.score[i] -= 4000; - } - } + // the number of tenpai players + int n_tenpai = 0; + // the atari status + bool tenpai_status[4] = { false, false, false, false }; + + for (int i = 0; i < 4; ++i) { + auto atari_hai = get_atari_hai(convert_tiles_to_basetiles(table->players[i].hand), + table->players[i].get_false_atari_hai()); + + if (atari_hai.size() > 0) { + tenpai_status[i] = true; + n_tenpai++; } - else if (n_nagashimangan == 1) { - if (nagashi_mangan_status[table->oya]) - { - for (int i = 0; i < 4; ++i) { - if (nagashi_mangan_status[i]) - result.score[i] += 12000; - else - result.score[i] -= 4000; - } - } - else - { - result.score[table->oya] -= 2000; - for (int i = 0; i < 4; ++i) { - if (nagashi_mangan_status[i]) - result.score[i] += 8000; - else - result.score[i] -= 2000; - } - } + else { + tenpai_status[i] = false; } } - else { - result.result_type = ResultType::Ryukyouku_Notile; - // 缁熻缃氱 - // 寮濮嬬粺璁″洓浜哄惉鐗岀殑鐘舵 + + /* Nagashi mangan special handler */ + if (n_nagashimangan > 0) { + nagashimangan_handler(result, nagashi_mangan_status, n_nagashimangan, tenpai_status, table); + return result; + } - // the number of tenpai players - int n_tenpai = 0; - // the atari status - bool tenpai_status[4] = {false, false, false, false}; + /* Normal ryukyouku */ - for (int i = 0; i < 4; ++i) { - if (get_atari_hai(convert_tiles_to_basetiles(table->players[i].hand)).size() > 0) { - tenpai_status[i] = true; - n_tenpai++; - } - else { - tenpai_status[i] = false; - } - } - // cout << "Warning: 绌哄惉 is not considered" << endl; + result.result_type = ResultType::Ryukyouku_Notile; + // 缁熻缃氱 + // 寮濮嬬粺璁″洓浜哄惉鐗岀殑鐘舵 - result.n_honba = table->honba + 1; - result.n_riichibo = table->kyoutaku; - if (tenpai_status[table->oya]) - result.renchan = true; - else - result.renchan = false; + result.n_honba = table->honba + 1; + result.n_riichibo = table->kyoutaku; + if (tenpai_status[table->oya]) + result.renchan = true; + else + result.renchan = false; - if (n_tenpai == 4) { } - else if (n_tenpai == 0) { } - else if (n_tenpai == 1) { - for (int i = 0; i < 4; ++i) { - if (tenpai_status[i]) - result.score[i] += 3000; - else - result.score[i] -= 1000; - } + if (n_tenpai == 4) { } + else if (n_tenpai == 0) { } + else if (n_tenpai == 1) { + for (int i = 0; i < 4; ++i) { + if (tenpai_status[i]) + result.score[i] += 3000; + else + result.score[i] -= 1000; } - else if (n_tenpai == 2) { - for (int i = 0; i < 4; ++i) { - if (tenpai_status[i]) - result.score[i] += 1500; - else - result.score[i] -= 1500; - } + } + else if (n_tenpai == 2) { + for (int i = 0; i < 4; ++i) { + if (tenpai_status[i]) + result.score[i] += 1500; + else + result.score[i] -= 1500; } - else if (n_tenpai == 3) { - for (int i = 0; i < 4; ++i) { - if (tenpai_status[i]) - result.score[i] += 1000; - else - result.score[i] -= 3000; - } - } } + else if (n_tenpai == 3) { + for (int i = 0; i < 4; ++i) { + if (tenpai_status[i]) + result.score[i] += 1000; + else + result.score[i] -= 3000; + } + } return result; } @@ -277,7 +291,7 @@ Result generate_result_tsumo(Table * table) return result; } -Result generate_result_ron(Table *table, Tile *agari_tile, std::vector response_player, bool chankan, bool chanankan) +Result generate_result_ron(Table *table, Tile *agari_tile, const std::vector &response_player, bool chankan, bool chanankan) { Result result; result.result_type = ResultType::RonAgari; @@ -325,12 +339,12 @@ Result generate_result_ron(Table *table, Tile *agari_tile, std::vector resp return result; } -Result generate_result_chanankan(Table * table, Tile* agari_tile, std::vector response_player) +Result generate_result_chanankan(Table * table, Tile* agari_tile, const std::vector &response_player) { return generate_result_ron(table, agari_tile, response_player, false, true); } -Result generate_result_chankan(Table * table, Tile* agari_tile, std::vector response_player) +Result generate_result_chankan(Table * table, Tile* agari_tile, const std::vector &response_player) { return generate_result_ron(table, agari_tile, response_player, true, false); } diff --git a/Mahjong/GameResult.h b/Mahjong/GameResult.h index 55f26078..c1f16db4 100644 --- a/Mahjong/GameResult.h +++ b/Mahjong/GameResult.h @@ -15,7 +15,11 @@ enum class ResultType { TsumoAgari, Ryukyouku_Notile, NagashiMangan, - Ryukyouku_Interval, + Ryukyouku_Interval_9Hai, + Ryukyouku_Interval_4Wind, + Ryukyouku_Interval_4Riichi, + Ryukyouku_Interval_4Kan, + Ryukyouku_Interval_3Ron, }; inline const char* result_type_str(ResultType type) @@ -26,11 +30,21 @@ inline const char* result_type_str(ResultType type) "TsumoAgari", "Ryukyouku_Notile", "NagashiMangan", - "Ryukyouku_Interval", + "Ryukyouku_Interval_9Hai", + "Ryukyouku_Interval_4Wind", + "Ryukyouku_Interval_4Riichi", + "Ryukyouku_Interval_4Kan", + "Ryukyouku_Interval_3Ron", }; + + /* obtain number of result types (5) */ constexpr size_t len = sizeof(result_type_strs) / sizeof(result_type_strs[0]); - return (int(type) >= len || int(type) < 0) ? - "Error type" : result_type_strs[(int)type]; + + /* only accept 0~4 */ + if ((int)type >= 0 && (int)type < len) + return result_type_strs[(int)type]; + else + throw std::runtime_error("Cannot identify the ResultType."); } struct Result { @@ -40,7 +54,7 @@ struct Result { std::vector winner; std::vector loser; std::array score; - int n_yakuman[4]; + int n_yakuman[4] = { 0 }; int n_riichibo; int n_honba; bool renchan; // 閫h崢 @@ -67,11 +81,12 @@ Result generate_result_9hai(Table* table); Result generate_result_4wind(Table* table); Result generate_result_4riichi(Table* table); Result generate_result_4kan(Table* table); +Result generate_result_3ron(Table* table); Result generate_result_notile(Table* table); Result generate_result_tsumo(Table* table); -Result generate_result_ron(Table* table, Tile* ,std::vector response_player, bool chankan = false, bool chanankan = false); -Result generate_result_chanankan(Table* table, Tile*, std::vector response_player); -Result generate_result_chankan(Table* table, Tile*, std::vector response_player); +Result generate_result_ron(Table* table, Tile* tile, const std::vector &response_player, bool chankan = false, bool chanankan = false); +Result generate_result_chanankan(Table* table, Tile* tile, const std::vector &response_player); +Result generate_result_chankan(Table* table, Tile*, const std::vector &response_player); namespace_mahjong_end diff --git a/Mahjong/Player.cpp b/Mahjong/Player.cpp index b8333248..29278b4b 100644 --- a/Mahjong/Player.cpp +++ b/Mahjong/Player.cpp @@ -71,7 +71,7 @@ string Player::tenpai_to_string() const void Player::update_atari_tiles() { vector bt = convert_tiles_to_basetiles(hand); - atari_tiles = get_atari_hai(bt); + atari_tiles = get_atari_hai(bt, get_false_atari_hai()); } void Player::update_furiten_river() @@ -239,8 +239,11 @@ vector Player::get_riichi() const { vector actions; - auto riichi_tiles = is_riichi_able(hand, menzen); + auto riichi_tiles = is_riichi_able(hand, get_false_atari_hai(), menzen); for (auto riichi_tile : riichi_tiles) { + + /* Todo: remove the riichi_tile when the tenpai is not available*/ + SelfAction action; action.action = BaseAction::Riichi; action.correspond_tiles.push_back(riichi_tile); @@ -511,7 +514,8 @@ vector Player::riichi_get_ankan() else return false; }), copyhand.end()); - auto new_atari_hai = get_atari_hai(convert_tiles_to_basetiles(copyhand)); + auto new_atari_hai = get_atari_hai(convert_tiles_to_basetiles(copyhand), + get_false_atari_hai()); if (is_same_container(new_atari_hai, atari_tiles)) { SelfAction action; @@ -538,8 +542,8 @@ vector Player::riichi_get_discard() void Player::execute_naki(vector tiles, Tile* tile, int relative_position) { - CallGroup call_group; menzen = false; + CallGroup call_group; if (is_koutsu({ tiles[0]->tile, tiles[1]->tile, tile->tile }) && tiles.size() == 2) { // 纰扮殑鎯呭喌 @@ -665,6 +669,22 @@ void Player::execute_kakan(Tile* tile) remove_from_hand(tile); } +std::vector Player::get_false_atari_hai() const +{ + // add the tiles with four in hands + static std::array tile_counter; + + std::vector except_tiles; + tile_counter.fill(0); + for (auto tile : hand) + { + tile_counter[int(tile->tile)]++; + if (tile_counter[int(tile->tile)] == 4) + except_tiles.push_back(tile->tile); + } + return except_tiles; +} + void Player::execute_discard(Tile* tile, int& number, bool on_riichi, bool fromhand) { remove_from_hand(tile); diff --git a/Mahjong/Player.h b/Mahjong/Player.h index ab478e92..da5191db 100644 --- a/Mahjong/Player.h +++ b/Mahjong/Player.h @@ -81,7 +81,6 @@ class Player { public: bool double_riichi = false; bool riichi = false; - bool menzen = true; Wind wind; bool oya; @@ -102,6 +101,9 @@ class Player { inline bool is_riichi() const { return riichi || double_riichi; } inline bool is_furiten() const { return furiten_round || furiten_river || furiten_riichi; } inline std::vector get_fuuros() { return call_groups; } + inline bool is_menzen() { return menzen; } + inline bool is_tenpai() { return atari_tiles.size() > 0; } + inline River get_river() { return river; } std::string hand_to_string() const; std::string river_to_string() const; @@ -112,7 +114,7 @@ class Player { void update_furiten_river(); void remove_atari_tiles(BaseTile t); - inline void 瑙侀() { + inline void minogashi() { if (riichi) furiten_riichi = true; else furiten_round = true; } @@ -146,10 +148,14 @@ class Player { void execute_ankan(BaseTile tile); void execute_kakan(Tile* tile); + /* 鍋囧惉鐗 */ + std::vector get_false_atari_hai() const; + /* execute discard whenever it is called by others. */ void execute_discard(Tile* tile, int& number, bool on_riichi, bool fromhand); inline void set_not_remained() { + /* 灏嗘墦鍑虹殑鐗岃缃负鈥滀笉瀛樺湪浜嗏 */ river.set_not_remain(); } diff --git a/Mahjong/RoundToWin.cpp b/Mahjong/RoundToWin.cpp index afe004f7..0481aa94 100644 --- a/Mahjong/RoundToWin.cpp +++ b/Mahjong/RoundToWin.cpp @@ -70,7 +70,7 @@ void Syanten::hand_to_code(const std::vector& hand, uint32_t* code){ } } -int Syanten::_check_normal(const uint32_t* hand_code, int num_鍓湶) { +int Syanten::_check_normal(const uint32_t* hand_code, int n_call_groups) { int ptm = 0, ptt = 0; for (int j = 0; j < 3; j++) { int pt1m, pt1t, pt2m, pt2t; @@ -94,12 +94,12 @@ int Syanten::_check_normal(const uint32_t* hand_code, int num_鍓湶) { ptt++; // 闆澶 } } - while (ptm + ptt > 4 - num_鍓湶 && ptt > 0) ptt--; - while (ptm + ptt > 4 - num_鍓湶) ptm--; - return 9 - ptm * 2 - ptt - num_鍓湶 * 2; + while (ptm + ptt > 4 - n_call_groups && ptt > 0) ptt--; + while (ptm + ptt > 4 - n_call_groups) ptm--; + return 9 - ptm * 2 - ptt - n_call_groups * 2; } -int Syanten::normal_round_to_win(const std::vector& hand, int num_鍓湶) { +int Syanten::normal_round_to_win(const std::vector& hand, int n_call_groups) { if (!is_loaded) load_syanten_map(); int result = 14; @@ -109,11 +109,11 @@ int Syanten::normal_round_to_win(const std::vector& hand, int num_鍓湶) int num = 0b111 & (hand_code[i / (_1p - _1m)] >> (3 * (i % (_1p - _1m)))); if (num >= 2) { hand_code[i / (_1p - _1m)] -= 2 * tile_to_bit[i % (_1p - _1m)]; - result = std::min(result, _check_normal(hand_code, num_鍓湶) - 1); + result = std::min(result, _check_normal(hand_code, n_call_groups) - 1); hand_code[i / (_1p - _1m)] += 2 * tile_to_bit[i % (_1p - _1m)]; } } - result = std::min(result, _check_normal(hand_code, num_鍓湶)); + result = std::min(result, _check_normal(hand_code, n_call_groups)); return result; } diff --git a/Mahjong/Rule.cpp b/Mahjong/Rule.cpp index 987f9de0..e4e2297b 100644 --- a/Mahjong/Rule.cpp +++ b/Mahjong/Rule.cpp @@ -404,7 +404,7 @@ std::vector get_kokushi_atari_hai(std::vector tiles) return atari_tiles; } -vector get_atari_hai(vector tiles, vector except_tiles) +vector get_atari_hai(vector tiles, const std::vector& except_tiles) { profiler _("Rule.cpp/getAtariHai");; vector atari_tiles; @@ -429,7 +429,7 @@ vector get_atari_hai(vector tiles, vector except_t return atari_tiles; } -bool is_tenpai(vector tiles, vector except_tiles) +bool is_tenpai(vector tiles, const std::vector& except_tiles) { profiler _("Rule.cpp/isTenpai"); for (int i = BaseTile::_1m; i <= BaseTile::_7z; ++i) { @@ -467,7 +467,8 @@ bool is_agari_shape(std::vector tiles) return false; } -std::vector is_riichi_able(std::vector hands, bool Menzen) +std::vector is_riichi_able(const std::vector &hands, + const std::vector& except_tiles, bool Menzen) { std::vector play_tiles; if (!Menzen) return play_tiles; @@ -477,7 +478,7 @@ std::vector is_riichi_able(std::vector hands, bool Menzen) std::vector copy_hand(hands.begin(), hands.end()); copy_hand.erase(copy_hand.begin() + i); auto s = convert_tiles_to_basetiles(copy_hand); - auto tenhai = get_atari_hai(s); + auto tenhai = get_atari_hai(s, except_tiles); if (tenhai.size() != 0) { play_tiles.push_back(hands[i]); } @@ -485,15 +486,16 @@ std::vector is_riichi_able(std::vector hands, bool Menzen) return play_tiles; } -bool can_ron(std::vector hands, Tile *get_tile) +bool can_ron(const std::vector &hands_, Tile *get_tile) { + std::vector hands(hands_); hands.push_back(get_tile); if (is_agari_shape(convert_tiles_to_basetiles(hands))) return true; return false; } -bool can_tsumo(std::vector hands) +bool can_tsumo(const std::vector &hands) { if (is_agari_shape(convert_tiles_to_basetiles(hands))) return true; diff --git a/Mahjong/Rule.h b/Mahjong/Rule.h index 3f9842b1..f1d9fb0a 100644 --- a/Mahjong/Rule.h +++ b/Mahjong/Rule.h @@ -164,14 +164,15 @@ bool is_churen_9_shape(std::vector tiles); bool is_churen_shape(std::vector tiles); // 涓嶈冭檻鏃犲焦鐨勫惉鐗屾儏鍐 -std::vector get_atari_hai(std::vector tiles, std::vector except_tiles = {}); -bool is_tenpai(std::vector tiles, std::vector except_tiles = {}); +std::vector get_atari_hai(std::vector tiles, const std::vector &except_tiles); +bool is_tenpai(std::vector tiles, const std::vector &except_tiles); bool is_agari_shape(std::vector tiles); -std::vector is_riichi_able(std::vector hands, bool Menzen); +std::vector is_riichi_able(const std::vector &hands, + const std::vector& basetiles, bool Menzen); -bool can_ron(std::vector hands, Tile* get_tile); -bool can_tsumo(std::vector hands); +bool can_ron(const std::vector &hands, Tile* get_tile); +bool can_tsumo(const std::vector &hands); namespace_mahjong_end diff --git a/Mahjong/ScoreCounter.cpp b/Mahjong/ScoreCounter.cpp index d49d03f4..169ecb39 100644 --- a/Mahjong/ScoreCounter.cpp +++ b/Mahjong/ScoreCounter.cpp @@ -7,152 +7,152 @@ namespace_mahjong using namespace std; -#define REGISTER_SCORE(浜, 鑷懜, score_閾充翰, score_浜茶嚜鎽竉all, score_閾冲瓙, score_瀛愯嚜鎽竉浜, score_瀛愯嚜鎽竉瀛) \ -if (浜) {if (鑷懜){score1=score_浜茶嚜鎽竉all;} else{score1=score_閾充翰;}} \ -else {if (鑷懜) {score1=score_瀛愯嚜鎽竉浜; score2=score_瀛愯嚜鎽竉瀛;} else{score1=score_閾冲瓙;}} return; +#define REGISTER_SCORE(oya, tsumo, score_oya, score_oya_tsumo_all, score_kodomo, score_kodomo_tsumo_oya, score_kodomo_tsumo_kodomo) \ +if (oya) {if (tsumo){score1=score_oya_tsumo_all;} else{score1=score_oya;}} \ +else {if (tsumo) {score1=score_kodomo_tsumo_oya; score2=score_kodomo_tsumo_kodomo;} else{score1=score_kodomo;}} return; -void CounterResult::calculate_score(bool 浜, bool 鑷懜) +void CounterResult::calculate_score(bool oya, bool tsumo) { if (fan == 6 * 13) { // 6鍊 褰规弧 - REGISTER_SCORE(浜, 鑷懜, 48000 * 6, 16000 * 6, 32000 * 6, 16000 * 6, 8000 * 6); + REGISTER_SCORE(oya, tsumo, 48000 * 6, 16000 * 6, 32000 * 6, 16000 * 6, 8000 * 6); } else if (fan == 5 * 13) { // 5鍊 褰规弧 - REGISTER_SCORE(浜, 鑷懜, 48000 * 5, 16000 * 5, 32000 * 5, 16000 * 5, 8000 * 5); + REGISTER_SCORE(oya, tsumo, 48000 * 5, 16000 * 5, 32000 * 5, 16000 * 5, 8000 * 5); } else if (fan == 4 * 13) { // 4鍊 褰规弧 - REGISTER_SCORE(浜, 鑷懜, 48000 * 4, 16000 * 4, 32000 * 4, 16000 * 4, 8000 * 4); + REGISTER_SCORE(oya, tsumo, 48000 * 4, 16000 * 4, 32000 * 4, 16000 * 4, 8000 * 4); } else if (fan == 3 * 13) { // 3鍊 褰规弧 - REGISTER_SCORE(浜, 鑷懜, 48000 * 3, 16000 * 3, 32000 * 3, 16000 * 3, 8000 * 3); + REGISTER_SCORE(oya, tsumo, 48000 * 3, 16000 * 3, 32000 * 3, 16000 * 3, 8000 * 3); } else if (fan == 2 * 13) { // 2鍊 褰规弧 - REGISTER_SCORE(浜, 鑷懜, 48000 * 2, 16000 * 2, 32000 * 2, 16000 * 2, 8000 * 2); + REGISTER_SCORE(oya, tsumo, 48000 * 2, 16000 * 2, 32000 * 2, 16000 * 2, 8000 * 2); } else if (fan >= 13) { // 褰规弧 - REGISTER_SCORE(浜, 鑷懜, 48000, 16000, 32000, 16000, 8000); + REGISTER_SCORE(oya, tsumo, 48000, 16000, 32000, 16000, 8000); } else if (fan >= 11) { - REGISTER_SCORE(浜, 鑷懜, 36000, 12000, 24000, 12000, 6000); + REGISTER_SCORE(oya, tsumo, 36000, 12000, 24000, 12000, 6000); } else if (fan >= 8) { - REGISTER_SCORE(浜, 鑷懜, 24000, 8000, 16000, 8000, 4000); + REGISTER_SCORE(oya, tsumo, 24000, 8000, 16000, 8000, 4000); } else if (fan >= 6) { - REGISTER_SCORE(浜, 鑷懜, 18000, 6000, 12000, 6000, 3000); + REGISTER_SCORE(oya, tsumo, 18000, 6000, 12000, 6000, 3000); } else if (fan == 5) { - REGISTER_SCORE(浜, 鑷懜, 12000, 4000, 8000, 4000, 2000); + REGISTER_SCORE(oya, tsumo, 12000, 4000, 8000, 4000, 2000); } else if (fan == 4) { // 40绗︿互涓婃弧璐 if (fu >= 40) { - REGISTER_SCORE(浜, 鑷懜, 12000, 4000, 8000, 4000, 2000); + REGISTER_SCORE(oya, tsumo, 12000, 4000, 8000, 4000, 2000); } else if (fu == 30) { - REGISTER_SCORE(浜, 鑷懜, 11600, 3900, 7700, 3900, 2000); + REGISTER_SCORE(oya, tsumo, 11600, 3900, 7700, 3900, 2000); } else if (fu == 25) { - REGISTER_SCORE(浜, 鑷懜, 9600, 3200, 6400, 3200, 1600); + REGISTER_SCORE(oya, tsumo, 9600, 3200, 6400, 3200, 1600); } else if (fu == 20) { - REGISTER_SCORE(浜, 鑷懜, 7700, 2600, 5200, 2600, 1300); + REGISTER_SCORE(oya, tsumo, 7700, 2600, 5200, 2600, 1300); } } else if (fan == 3) { // 70绗︿互涓婃弧璐 if (fu >= 70) { - REGISTER_SCORE(浜, 鑷懜, 12000, 4000, 8000, 4000, 2000); + REGISTER_SCORE(oya, tsumo, 12000, 4000, 8000, 4000, 2000); } else if (fu == 60) { - REGISTER_SCORE(浜, 鑷懜, 11600, 3900, 7700, 3900, 2000); + REGISTER_SCORE(oya, tsumo, 11600, 3900, 7700, 3900, 2000); } else if (fu == 50) { - REGISTER_SCORE(浜, 鑷懜, 9600, 3200, 6400, 3200, 1600); + REGISTER_SCORE(oya, tsumo, 9600, 3200, 6400, 3200, 1600); } else if (fu == 40) { - REGISTER_SCORE(浜, 鑷懜, 7700, 2600, 5200, 2600, 1300); + REGISTER_SCORE(oya, tsumo, 7700, 2600, 5200, 2600, 1300); } else if (fu == 30) { - REGISTER_SCORE(浜, 鑷懜, 5800, 2000, 3900, 2000, 1000); + REGISTER_SCORE(oya, tsumo, 5800, 2000, 3900, 2000, 1000); } else if (fu == 25) { - REGISTER_SCORE(浜, 鑷懜, 4800, 1600, 3200, 1600, 800); + REGISTER_SCORE(oya, tsumo, 4800, 1600, 3200, 1600, 800); } else if (fu == 20) { - REGISTER_SCORE(浜, 鑷懜, 3900, 1300, 2600, 1300, 700); + REGISTER_SCORE(oya, tsumo, 3900, 1300, 2600, 1300, 700); } } else if (fan == 2) { if (fu >= 110) { - REGISTER_SCORE(浜, 鑷懜, 10600, 3600, 7100, 3600, 1800); + REGISTER_SCORE(oya, tsumo, 10600, 3600, 7100, 3600, 1800); } else if (fu == 100) { - REGISTER_SCORE(浜, 鑷懜, 9600, 3200, 6400, 3200, 1600); + REGISTER_SCORE(oya, tsumo, 9600, 3200, 6400, 3200, 1600); } else if (fu == 90) { - REGISTER_SCORE(浜, 鑷懜, 8700, 2900, 5800, 2900, 1500); + REGISTER_SCORE(oya, tsumo, 8700, 2900, 5800, 2900, 1500); } else if (fu == 80) { - REGISTER_SCORE(浜, 鑷懜, 7700, 2600, 5200, 2600, 1300); + REGISTER_SCORE(oya, tsumo, 7700, 2600, 5200, 2600, 1300); } else if (fu == 70) { - REGISTER_SCORE(浜, 鑷懜, 6800, 2300, 4500, 2300, 1200); + REGISTER_SCORE(oya, tsumo, 6800, 2300, 4500, 2300, 1200); } else if (fu == 60) { - REGISTER_SCORE(浜, 鑷懜, 5800, 2000, 3900, 2000, 1000); + REGISTER_SCORE(oya, tsumo, 5800, 2000, 3900, 2000, 1000); } else if (fu == 50) { - REGISTER_SCORE(浜, 鑷懜, 4800, 1600, 3200, 1600, 800); + REGISTER_SCORE(oya, tsumo, 4800, 1600, 3200, 1600, 800); } else if (fu == 40) { - REGISTER_SCORE(浜, 鑷懜, 3900, 1300, 2600, 1300, 700); + REGISTER_SCORE(oya, tsumo, 3900, 1300, 2600, 1300, 700); } else if (fu == 30) { - REGISTER_SCORE(浜, 鑷懜, 2900, 1000, 2000, 1000, 500); + REGISTER_SCORE(oya, tsumo, 2900, 1000, 2000, 1000, 500); } else if (fu == 25) { - REGISTER_SCORE(浜, 鑷懜, 2400, -1, 1600, -1, -1); + REGISTER_SCORE(oya, tsumo, 2400, -1, 1600, -1, -1); } else if (fu == 20) { - REGISTER_SCORE(浜, 鑷懜, 2000, 700, 1300, 700, 400); + REGISTER_SCORE(oya, tsumo, 2000, 700, 1300, 700, 400); } } else if (fan == 1) { if (fu >= 110) { - REGISTER_SCORE(浜, 鑷懜, 5300, -1, 3600, -1, -1); + REGISTER_SCORE(oya, tsumo, 5300, -1, 3600, -1, -1); } else if (fu == 100) { - REGISTER_SCORE(浜, 鑷懜, 4800, 1600, 3200, 1600, 800); + REGISTER_SCORE(oya, tsumo, 4800, 1600, 3200, 1600, 800); } else if (fu == 90) { - REGISTER_SCORE(浜, 鑷懜, 4400, 1500, 2900, 1500, 800); + REGISTER_SCORE(oya, tsumo, 4400, 1500, 2900, 1500, 800); } else if (fu == 80) { - REGISTER_SCORE(浜, 鑷懜, 3900, 1300, 2600, 1300, 700); + REGISTER_SCORE(oya, tsumo, 3900, 1300, 2600, 1300, 700); } else if (fu == 70) { - REGISTER_SCORE(浜, 鑷懜, 3400, 1200, 2300, 1200, 600); + REGISTER_SCORE(oya, tsumo, 3400, 1200, 2300, 1200, 600); } else if (fu == 60) { - REGISTER_SCORE(浜, 鑷懜, 2900, 1000, 2000, 1000, 500); + REGISTER_SCORE(oya, tsumo, 2900, 1000, 2000, 1000, 500); } else if (fu == 50) { - REGISTER_SCORE(浜, 鑷懜, 2400, 800, 1600, 800, 400); + REGISTER_SCORE(oya, tsumo, 2400, 800, 1600, 800, 400); } else if (fu == 40) { - REGISTER_SCORE(浜, 鑷懜, 2000, 700, 1300, 700, 400); + REGISTER_SCORE(oya, tsumo, 2000, 700, 1300, 700, 400); } else if (fu == 30) { - REGISTER_SCORE(浜, 鑷懜, 1500, 500, 1000, 500, 300); + REGISTER_SCORE(oya, tsumo, 1500, 500, 1000, 500, 300); } else if (fu == 20) { - REGISTER_SCORE(浜, 鑷懜, 1000, -1, -1, -1, -1); + REGISTER_SCORE(oya, tsumo, 1000, -1, -1, -1, -1); } } throw runtime_error(fmt::format("Error fan & fu cases. {} fan {} fu.", fan, fu)); @@ -160,15 +160,15 @@ void CounterResult::calculate_score(bool 浜, bool 鑷懜) int calculate_fan(const vector &yakus) { - bool 褰规弧 = false; + bool yakuman = false; for (auto yaku : yakus) { if (yaku > Yaku::Yaku_mangan && yaku < Yaku::Yakuman_Double) { - 褰规弧 = true; + yakuman = true; break; } } int fan = 0; - if (褰规弧) { + if (yakuman) { for (auto yaku : yakus) { if (yaku < Yaku::Yaku_mangan) continue; // 璺宠繃鎵鏈変笉鏄焦婊$殑 if (yaku > Yaku::Yaku_mangan && yaku < Yaku::Yakuman) fan += 13; @@ -213,41 +213,50 @@ inline static bool is_ankan_str(const std::string& s) { return s.size() == 4 && inline static bool tile_group_match(const std::string& s1, const std::string& s2) { return s1[0] == s2[0] && s1[1] == s2[1] && s1[2] == s2[2]; } /* 鐗屽瀷瀵/椤/鍒/鏉狅紝鍚瓧鐗 */ -inline static bool 甯﹀瓧鐗(const std::string& s) { return s[1] == 'z'; } +inline static bool is_z_str(const std::string& s) { return s[1] == 'z'; } /* 鐗屽瀷瀵/椤/鍒/鏉狅紝鍚嚦灏1寮犲购涔濓紝涓嶅寘鎷瓧鐗岋紝渚嬪789s 1111m */ -inline static bool 甯﹀购涔(const std::string& s) { - if (甯﹀瓧鐗(s)) return false; +inline static bool is_yaochu_str(const std::string& s) { + if (is_z_str(s)) return false; if (s[2] == 'K' || s[2] == ':' || s[2] == '|') return s[0] == '1' || s[0] == '9'; if (s[2] == 'S') return s[0] == '1' || s[0] == '7'; throw std::runtime_error(fmt::format("Bad tile group string (input = {})", s)); } /* 鐗屽瀷瀵/鍒/鏉狅紝鍏ㄥ购涔濓紝涓嶅寘鎷瓧鐗岋紝渚嬪999p 1111m */ -inline static bool 绾佸ご(const std::string& s) { - if (s[2] == 'K' || s[2] == ':' || s[2] == '|') return s[0] == '1' || s[0] == '9'; - return false; +inline static bool pure_yaochu_str(const std::string& s) { + bool no_shuntsu = (s[2] == 'K' || s[2] == ':' || s[2] == '|'); + bool all_19 = (s[0] == '1' || s[0] == '9'); + bool no_z = (!is_z_str(s)); + return no_shuntsu && all_19 && no_z; } -inline static bool 绾豢鐗(const std::string& s) { - const char* green_types[] = { "2sK", "3sK", "4sK", "2sS", "6sK", "8sK", "6zK", - "2s:", "3s:", "4s:", "6s:", "8s:", "6z:" }; - return std::all_of(std::begin(green_types), std::end(green_types), +/* 鍏ㄧ豢鐗 */ +inline static bool pure_green_str(const std::string& s) { + const char* green_types[] = { + "2sK", "3sK", "4sK", /* 222s 333s 444s */ + "6sK", "8sK", "6zK", /* 666s 888s 666z */ + "2sS", /* 234s */ + "2s:", "3s:", "4s:", "6s:", "8s:", "6z:", /* 2s2s, 3s3s, 4s4s, 6s6s, 8s8s, 6z6z */ + "2s|", "3s|", "4s|", "6s|", "8s|", "6z|", /* 2222s, 3333s, 4444s, 6666s, 8888s, 6666z */ + }; + + return std::any_of(begin(green_types), end(green_types), [&s](const char* green) {return tile_group_match(s, green); }); } /* 鐢ㄤ簬澶勭悊鍏ㄥ甫锛屼笉鍖呮嫭鏉犲瓙 */ -inline static bool 甯﹀购涔濆瓧(const std::string& s) { return 甯﹀瓧鐗(s) || 甯﹀购涔(s); } +inline static bool has_yaochu_or_z_str(const std::string& s) { return is_z_str(s) || is_yaochu_str(s); } /* 鐢ㄤ簬璁扮锛屽浜庡焦鐗屽瀛愮殑璁℃暟锛屽寘鎷繛椋庡瀛 */ -inline static int is褰圭墝瀵瑰瓙(std::string s, Wind 鑷, Wind 鍦洪) { +inline static int is_yakuhai_toitsu(const std::string &s, Wind self_wind, Wind game_wind) { if (s[2] != ':') return 0; //涓嶆槸瀵瑰瓙 if (s[1] != 'z') return 0; //涓嶆槸瀛楃墝 int cases = 0; int number = s[0] - '1'; // 0 1 2 3鍒嗗埆涓轰笢鍗楄タ鍖 - if (number == 鑷) cases++; - if (number == 鍦洪) cases++; + if (number == self_wind) cases++; + if (number == game_wind) cases++; if (number >= 4) cases++; // 4,5,6涓虹櫧鍙戜腑 return cases; } @@ -270,7 +279,6 @@ string make_tilegroup(BaseTile t, char mark1, char mark2) return ret; }; - vector> ScoreCounter::generate_tile_group_strings(const CompletedTiles &ct, const vector &callgroups, bool tsumo, BaseTile last_tile) { profiler _("ScoreCounter.cpp/generate_tgs"); @@ -376,137 +384,136 @@ static inline vector remove_4(const vector &strs) { return retstr; } -vector ScoreCounter::get_鎵嬪焦_褰规弧(const vector &tile_group_string, Wind 鑷, Wind 鍦洪, bool &褰规弧) { +vector ScoreCounter::get_hand_yakuman(const vector &tile_group_string, Wind self_wind, Wind game_wind, bool &yakuman) { vector yakus; // 缁熻Z涓鑹 - bool 瀛椾竴鑹 = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { - return 甯﹀瓧鐗(s); + bool z_pure_type = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + return is_z_str(s); }); // 缁熻鎵鏈夌殑瀛楃墝鍒诲瓙鍜屽瀛 - bool 瀛楃墝鍒诲瓙[7] = { false }; - bool 瀛楃墝瀵瑰瓙[7] = { false }; - for_each(tile_group_string.begin(), tile_group_string.end(), [&瀛楃墝鍒诲瓙, &瀛楃墝瀵瑰瓙](const string& s) { + bool z_koutsu[7] = { false }; + bool z_toitsu[7] = { false }; + for_each(tile_group_string.begin(), tile_group_string.end(), [&z_koutsu, &z_toitsu](const string& s) { if (s[1] == 'z') { if (s[2] == 'K' || s[2] == '|') { - 瀛楃墝鍒诲瓙[s[0] - '1'] = true; + z_koutsu[s[0] - '1'] = true; } if (s[2] == ':') { - 瀛楃墝瀵瑰瓙[s[0] - '1'] = true; + z_toitsu[s[0] - '1'] = true; } } }); // 鍒ゆ柇鍗曢獞 - bool 鍗曢獞 = any_of(tile_group_string.begin(), tile_group_string.end(), [](const string &s) { + bool call_single = any_of(tile_group_string.begin(), tile_group_string.end(), [](const string &s) { return s[2] == ':' && s.size() == 4; // 琛ㄧず娑夊強鍒拌儭鐗岀殑瀵瑰瓙 }); // 缁熻鏆楀埢鏁 - int num_鏆楀埢 = count_if(tile_group_string.begin(), tile_group_string.end(), [&num_鏆楀埢](const string& s) { + int num_ankou = count_if(tile_group_string.begin(), tile_group_string.end(), [&num_ankou](const string& s) { return (s.size() == 3 && s[2] == 'K') || (s.size() == 4 && s[2] == 'K' && (s[3] == '!' || s[3] == '@' || s[3] == '#')) || (s[2] == '|' && s[3] == '+'); }); // 缁熻鏉犲瓙鏁 - int num_鏉犲瓙 = count_if(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { return s[2] == '|'; }); - + int num_kantsu = count_if(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { return s[2] == '|'; }); // 鍒ゆ柇娓呰佸ご娣疯佸ご - bool 娓呰佸ご = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { - return 绾佸ご(s); + bool chinroutou = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + return pure_yaochu_str(s); }); // 鍒ゆ柇缁夸竴鑹 - bool 缁夸竴鑹 = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { - return 绾豢鐗(s); + bool pure_green = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + return pure_green_str(s); }); /* 寮濮嬪垽瀹 */ - if (瀛椾竴鑹) { + if (z_pure_type) { yakus.push_back(Yaku::Tsuiisou); - 褰规弧 = true; + yakuman = true; } - if (瀛楃墝鍒诲瓙[4] && 瀛楃墝鍒诲瓙[5] && 瀛楃墝鍒诲瓙[6]) { + if (z_koutsu[4] && z_koutsu[5] && z_koutsu[6]) { yakus.push_back(Yaku::Daisangen); - 褰规弧 = true; + yakuman = true; } - if (瀛楃墝鍒诲瓙[0] && 瀛楃墝鍒诲瓙[1] && 瀛楃墝鍒诲瓙[2] && 瀛楃墝鍒诲瓙[3]) { + if (z_koutsu[0] && z_koutsu[1] && z_koutsu[2] && z_koutsu[3]) { yakus.push_back(Yaku::Daisuushi); - 褰规弧 = true; + yakuman = true; } else { - if (瀛楃墝瀵瑰瓙[0] && 瀛楃墝鍒诲瓙[1] && 瀛楃墝鍒诲瓙[2] && 瀛楃墝鍒诲瓙[3]) { + if (z_toitsu[0] && z_koutsu[1] && z_koutsu[2] && z_koutsu[3]) { yakus.push_back(Yaku::Shousuushi); - 褰规弧 = true; + yakuman = true; } - if (瀛楃墝鍒诲瓙[0] && 瀛楃墝瀵瑰瓙[1] && 瀛楃墝鍒诲瓙[2] && 瀛楃墝鍒诲瓙[3]) { + if (z_koutsu[0] && z_toitsu[1] && z_koutsu[2] && z_koutsu[3]) { yakus.push_back(Yaku::Shousuushi); - 褰规弧 = true; + yakuman = true; } - if (瀛楃墝鍒诲瓙[0] && 瀛楃墝鍒诲瓙[1] && 瀛楃墝瀵瑰瓙[2] && 瀛楃墝鍒诲瓙[3]) { + if (z_koutsu[0] && z_koutsu[1] && z_toitsu[2] && z_koutsu[3]) { yakus.push_back(Yaku::Shousuushi); - 褰规弧 = true; + yakuman = true; } - if (瀛楃墝鍒诲瓙[0] && 瀛楃墝鍒诲瓙[1] && 瀛楃墝鍒诲瓙[2] && 瀛楃墝瀵瑰瓙[3]) { + if (z_koutsu[0] && z_koutsu[1] && z_koutsu[2] && z_toitsu[3]) { yakus.push_back(Yaku::Shousuushi); - 褰规弧 = true; + yakuman = true; } } - if (num_鏆楀埢 == 4) { - if (鍗曢獞) { + if (num_ankou == 4) { + if (call_single) { yakus.push_back(Yaku::Siiankou_1); - 褰规弧 = true; + yakuman = true; } else { yakus.push_back(Yaku::Siiankou); - 褰规弧 = true; + yakuman = true; } } - if (num_鏉犲瓙 == 4) { + if (num_kantsu == 4) { yakus.push_back(Yaku::Siikantsu); - 褰规弧 = true; + yakuman = true; } - if (娓呰佸ご) { + if (chinroutou) { yakus.push_back(Yaku::Chinroutou); - 褰规弧 = true; + yakuman = true; } - if (缁夸竴鑹) { + if (pure_green) { yakus.push_back(Yaku::Ryuiisou); - 褰规弧 = true; + yakuman = true; } return yakus; } // 杩欎釜鍑芥暟涓嶅垽鏂細7瀵癸紝鍥藉+鏃犲弻鍙13闈紝涔濊幉瀹濈伅涓庣函姝o紝鎵鏈夊疂鐗屽焦锛屾墍鏈夊満褰瑰寘鎷珛鐩达紝涓ょ珛鐩达紝闂ㄦ竻鑷懜锛屾姠鏉狅紝娴峰簳锛屾渤搴曪紝涓鍙戯紝宀笂锛屼互鍙婃墍鏈夊焦婊 -pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_string, Wind 鑷, Wind 鍦洪, bool 闂ㄦ竻) { +pair, int> ScoreCounter::get_hand_yakus(const vector &tile_group_string, Wind self_wind, Wind game_wind, bool menzen) { vector yakus; yakus.reserve(16); // 闅忎究鍒嗛厤涓浜涚┖闂翠互鍏嶅悗鏈熼噸鏂板垎閰 int fu = 20; // 鍒ゆ柇鍗曢獞 - bool 鍗曢獞 = any_of(tile_group_string.begin(), tile_group_string.end(), [](const string &s) { + bool call_single = any_of(tile_group_string.begin(), tile_group_string.end(), [](const string &s) { return s[2] == ':' && s.size() == 4; // 琛ㄧず娑夊強鍒拌儭鐗岀殑瀵瑰瓙 }); // DEBUG : 鍒ゆ柇闂ㄦ竻銆傜‘淇濇澶勬棤闂鍙互娉ㄩ噴鎺夎繖閮ㄥ垎浠g爜 - bool _闂ㄦ竻 = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + bool _menzen = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { if (s.size() == 3) return true; if (s.size() == 4) return s[3] != '-'; throw runtime_error("??"); }); - if (_闂ㄦ竻 != 闂ㄦ竻) { + if (_menzen != menzen) { throw runtime_error(fmt::format( "Debug: Player闂ㄦ竻鐘舵佷笌鐗屽瀷鍒ゅ畾鍑芥暟涓嶇鍚!\n" "tile_group_string = {}", fmt::join(tile_group_string, " "))); @@ -518,108 +525,110 @@ pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_strin } // 鍒ゆ柇鏈夋病鏈夐『瀛 - bool has椤哄瓙 = any_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + bool has_shuntsu = any_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { if (s[2] == 'S') return true; else return false; }); - if (!has椤哄瓙 && tile_group_string.size() != 7) { + if (!has_shuntsu && tile_group_string.size() != 7) { yakus.push_back(Yaku::Toitoiho); } // 鍒ゆ柇鏄笉鏄柇骞轰節 - bool 鏂购涔 = none_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { - return 甯﹀购涔(s) || 甯﹀瓧鐗(s); + bool tanyao = none_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + return is_yaochu_str(s) || is_z_str(s); }); // 缁熻m娓呬竴鑹 - bool M娓呬竴鑹 = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + bool Mchinitsu = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { if (s[1] == 'm') return true; return false; }); - bool M娣蜂竴鑹 = (!M娓呬竴鑹) && all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + bool Mhonitsu = (!Mchinitsu) && all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { if (s[1] == 'm' || s[1] == 'z') return true; return false; }); // 缁熻P娓呬竴鑹 - bool P娓呬竴鑹 = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + bool Pchinitsu = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { if (s[1] == 'p') return true; return false; }); - bool P娣蜂竴鑹 = (!P娓呬竴鑹) && all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + bool Phonitsu = (!Pchinitsu) && all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { if (s[1] == 'p' || s[1] == 'z') return true; return false; }); // 缁熻S娓呬竴鑹 - bool S娓呬竴鑹 = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + bool Schinitsu = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { if (s[1] == 's') return true; return false; }); - bool S娣蜂竴鑹 = (!S娓呬竴鑹) && all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + bool Shonitsu = (!Schinitsu) && all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { if (s[1] == 's' || s[1] == 'z') return true; return false; }); // 缁熻鎵鏈夌殑瀛楃墝鍒诲瓙鍜屽瀛 - bool 瀛楃墝鍒诲瓙[7] = { false }; - bool 瀛楃墝瀵瑰瓙[7] = { false }; - for_each(tile_group_string.begin(), tile_group_string.end(), [&瀛楃墝鍒诲瓙, &瀛楃墝瀵瑰瓙](const string& s) { + bool z_koutsu[7] = { false }; + bool z_toitsu[7] = { false }; + for_each(tile_group_string.begin(), tile_group_string.end(), [&z_koutsu, &z_toitsu](const string& s) { if (s[1] == 'z') { if (s[2] == 'K' || s[2] == '|') { - 瀛楃墝鍒诲瓙[s[0] - '1'] = true; + z_koutsu[s[0] - '1'] = true; } if (s[2] == ':') { - 瀛楃墝瀵瑰瓙[s[0] - '1'] = true; + z_toitsu[s[0] - '1'] = true; } } }); // 缁熻鏆楀埢鏁 - int num_鏆楀埢 = count_if(tile_group_string.begin(), tile_group_string.end(), [&num_鏆楀埢](const string& s) { + int n_ankou = count_if(tile_group_string.begin(), tile_group_string.end(), [&n_ankou](const string& s) { return (s.size() == 3 && s[2] == 'K') || (s.size() == 4 && s[2] == 'K' && (s[3] == '!' || s[3] == '@' || s[3] == '#')) || (s[2] == '|' && s[3] == '+'); }); // 缁熻鏉犲瓙鏁 - int num_鏉犲瓙 = count_if(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { return s[2] == '|'; }); + int n_kantsu = count_if(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { return s[2] == '|'; }); // 鍒ゆ柇娓呰佸ご娣疯佸ご - bool 娣疯佸ご = all_of(tile_group_string.begin(), tile_group_string.end(), [&娣疯佸ご](const string& s) { - return 绾佸ご(s) || 甯﹀瓧鐗(s); + bool honroutou = all_of(tile_group_string.begin(), tile_group_string.end(), [&honroutou](const string& s) { + return pure_yaochu_str(s) || is_z_str(s); }); - // 缁熻骞轰節鐘舵 - bool 绾叏甯﹀购涔 = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { - return 甯﹀购涔(s); + // 缁熻鍏ㄥ甫骞轰節鐘舵 + // 绾叏 + bool junchan = all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + return is_yaochu_str(s); } ); - bool 娣峰叏甯﹀购涔 = (!娣疯佸ご) && (!绾叏甯﹀购涔) && all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { - return 甯﹀购涔(s) || 甯﹀瓧鐗(s); + // 娣峰叏 + bool chanta = (!honroutou) && (!junchan) && all_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { + return is_yaochu_str(s) || is_z_str(s); } ); // 鍒ゆ柇骞冲拰 - bool 骞冲拰 = true; + bool pinfu = true; - 骞冲拰 &= 闂ㄦ竻; - 骞冲拰 &= (!鍗曢獞); + pinfu &= menzen; + pinfu &= (!call_single); // 瀵瑰瓙涓嶆槸褰圭墝 - 骞冲拰 &= none_of(tile_group_string.begin(), tile_group_string.end(), [&鑷, &鍦洪](const string& s) { - return is褰圭墝瀵瑰瓙(s, 鑷, 鍦洪); + pinfu &= none_of(tile_group_string.begin(), tile_group_string.end(), [&self_wind, &game_wind](const string& s) { + return is_yakuhai_toitsu(s, self_wind, game_wind); }); // 瀵瑰瓙 椤哄瓙 - 骞冲拰 &= all_of(tile_group_string.begin(), tile_group_string.end(), [&鑷, &鍦洪](const string& s) { + pinfu &= all_of(tile_group_string.begin(), tile_group_string.end(), [&self_wind, &game_wind](const string& s) { if (s[2] == ':') return true; if (s[2] == 'S') return true; return false; }); // 鍜岀墝鍨 - 骞冲拰 &= all_of(tile_group_string.begin(), tile_group_string.end(), [&鑷, &鍦洪](const string &s) { + pinfu &= all_of(tile_group_string.begin(), tile_group_string.end(), [&self_wind, &game_wind](const string &s) { if (s.size() == 3) return true; if (s[3] == '@') return false; if (s[3] == '%') return false; @@ -628,67 +637,67 @@ pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_strin if ((s[3] == '!' || s[3] == '$') && s[0] == '7') return false; return true; }); - 骞冲拰 &= (tile_group_string.size() == 5); + pinfu &= (tile_group_string.size() == 5); auto tile_group_string_no_4 = remove_4(tile_group_string); // 鍒ゆ柇涓夎壊鍚岄『 - bool 涓夎壊鍚岄『 = false; - const static string 涓夎壊鍚岄『tiles[] = + bool sanshouku_shuntsu = false; + const static string sanshouku_shuntsu_tiles[] = { "1mS","2mS","3mS","4mS","5mS","6mS","7mS", "1pS","2pS","3pS","4pS","5pS","6pS","7pS", "1sS","2sS","3sS","4sS","5sS","6sS","7sS", }; for (int i = 0; i < 7; ++i) { - if (is_in(tile_group_string_no_4, 涓夎壊鍚岄『tiles[i]) && - is_in(tile_group_string_no_4, 涓夎壊鍚岄『tiles[i + 7]) && - is_in(tile_group_string_no_4, 涓夎壊鍚岄『tiles[i + 14])) { - 涓夎壊鍚岄『 = true; + if (is_in(tile_group_string_no_4, sanshouku_shuntsu_tiles[i]) && + is_in(tile_group_string_no_4, sanshouku_shuntsu_tiles[i + 7]) && + is_in(tile_group_string_no_4, sanshouku_shuntsu_tiles[i + 14])) { + sanshouku_shuntsu = true; break; } } // 鍒ゆ柇涓夎壊鍚屽埢 - bool 涓夎壊鍚屽埢 = false; - const static string 涓夎壊鍚屽埢tiles[] = + bool sanshouku_koutsu = false; + const static string sanshouku_koutsu_tiles[] = { "1mK","2mK","3mK","4mK","5mK","6mK","7mK","8mK","9mK", "1pK","2pK","3pK","4pK","5pK","6pK","7pK","8pK","9pK", "1sK","2sK","3sK","4sK","5sK","6sK","7sK","8sK","9sK", }; for (int i = 0; i < 9; ++i) { - if (is_in(tile_group_string_no_4, 涓夎壊鍚屽埢tiles[i]) && - is_in(tile_group_string_no_4, 涓夎壊鍚屽埢tiles[i + 9]) && - is_in(tile_group_string_no_4, 涓夎壊鍚屽埢tiles[i + 18])) { - 涓夎壊鍚屽埢 = true; + if (is_in(tile_group_string_no_4, sanshouku_koutsu_tiles[i]) && + is_in(tile_group_string_no_4, sanshouku_koutsu_tiles[i + 9]) && + is_in(tile_group_string_no_4, sanshouku_koutsu_tiles[i + 18])) { + sanshouku_koutsu = true; break; } } // 鍒ゆ柇涓姘旈氳疮 - bool 涓姘旈氳疮 = false; - const static string 涓姘旈氳疮M[] = { "1mS", "4mS", "7mS" }; - const static string 涓姘旈氳疮P[] = { "1pS", "4pS", "7pS" }; - const static string 涓姘旈氳疮S[] = { "1sS", "4sS", "7sS" }; + bool ittsu = false; + const static string M_ittsu[] = { "1mS", "4mS", "7mS" }; + const static string P_ittsu[] = { "1pS", "4pS", "7pS" }; + const static string S_ittsu[] = { "1sS", "4sS", "7sS" }; - 涓姘旈氳疮 |= all_of(begin(涓姘旈氳疮M), end(涓姘旈氳疮M), + ittsu |= all_of(begin(M_ittsu), end(M_ittsu), [&tile_group_string_no_4](string tiles) {return is_in(tile_group_string_no_4, tiles); }); - 涓姘旈氳疮 |= all_of(begin(涓姘旈氳疮P), end(涓姘旈氳疮P), + ittsu |= all_of(begin(P_ittsu), end(P_ittsu), [&tile_group_string_no_4](string tiles) {return is_in(tile_group_string_no_4, tiles); }); - 涓姘旈氳疮 |= all_of(begin(涓姘旈氳疮S), end(涓姘旈氳疮S), + ittsu |= all_of(begin(S_ittsu), end(S_ittsu), [&tile_group_string_no_4](string tiles) {return is_in(tile_group_string_no_4, tiles); }); - static const string 椤哄瓙鐗屽瀷[] = { + static const string shuntsu_strs[] = { "1sS", "2sS" ,"3sS" ,"4sS" ,"5sS" ,"6sS" ,"7sS", "1pS", "2pS" ,"3pS" ,"4pS" ,"5pS" ,"6pS" ,"7pS", "1mS", "2mS" ,"3mS" ,"4mS" ,"5mS" ,"6mS" ,"7mS" }; // 鍒ゆ柇浜屾澂鍙 && 涓鏉彛 int n鏉彛 = 0; - if (闂ㄦ竻) { - for (const string &tiles : 椤哄瓙鐗屽瀷) { + if (menzen) { + for (const string &tiles : shuntsu_strs) { if (count_if(tile_group_string_no_4.begin(), tile_group_string_no_4.end(), [&tiles](const string &s) {return s == tiles; }) @@ -701,88 +710,88 @@ pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_strin if (n鏉彛 == 2) yakus.push_back(Yaku::Rianpeikou); else if (n鏉彛 == 1) yakus.push_back(Yaku::Ippeikou); - if (涓姘旈氳疮) { - if (闂ㄦ竻) yakus.push_back(Yaku::Ikkitsuukan); + if (ittsu) { + if (menzen) yakus.push_back(Yaku::Ikkitsuukan); else yakus.push_back(Yaku::Ikkitsuukan_Naki); } - if (涓夎壊鍚岄『) { - if (闂ㄦ竻) yakus.push_back(Yaku::Sanshokudoujun); + if (sanshouku_shuntsu) { + if (menzen) yakus.push_back(Yaku::Sanshokudoujun); else yakus.push_back(Yaku::Sanshokudoujun_Naki); } - if (涓夎壊鍚屽埢) yakus.push_back(Yaku::Sanshokudoukou); + if (sanshouku_koutsu) yakus.push_back(Yaku::Sanshokudoukou); - if (S娓呬竴鑹 || M娓呬竴鑹 || P娓呬竴鑹) { - if (闂ㄦ竻) yakus.push_back(Yaku::Chinitsu); + if (Schinitsu || Mchinitsu || Pchinitsu) { + if (menzen) yakus.push_back(Yaku::Chinitsu); else yakus.push_back(Yaku::Chinitsu_Naki); } else { - if (S娣蜂竴鑹 || M娣蜂竴鑹 || P娣蜂竴鑹) { - if (闂ㄦ竻) yakus.push_back(Yaku::Honitsu); + if (Shonitsu || Mhonitsu || Phonitsu) { + if (menzen) yakus.push_back(Yaku::Honitsu); else yakus.push_back(Yaku::Honitsu_Naki); } } - if (num_鏆楀埢 == 3) yakus.push_back(Yaku::Sanankou); - if (num_鏉犲瓙 == 3) yakus.push_back(Yaku::Sankantsu); - if (娣疯佸ご) yakus.push_back(Yaku::Honroutou); + if (n_ankou == 3) yakus.push_back(Yaku::Sanankou); + if (n_kantsu == 3) yakus.push_back(Yaku::Sankantsu); + if (honroutou) yakus.push_back(Yaku::Honroutou); - if (鏂购涔) + if (tanyao) yakus.push_back(Yaku::Tanyao); - if (绾叏甯﹀购涔) { - if (闂ㄦ竻) yakus.push_back(Yaku::Junchantaiyaochu); + if (junchan) { + if (menzen) yakus.push_back(Yaku::Junchantaiyaochu); else yakus.push_back(Yaku::Junchantaiyaochu_Naki); } - else if (娣峰叏甯﹀购涔 && !娣疯佸ご) { - if (闂ㄦ竻) yakus.push_back(Yaku::Honchantaiyaochu); + else if (chanta && !honroutou) { + if (menzen) yakus.push_back(Yaku::Honchantaiyaochu); else yakus.push_back(Yaku::Honchantaiyaochu_Naki); } // 灏忎笁鍏 - if (瀛楃墝鍒诲瓙[4] && 瀛楃墝鍒诲瓙[5] && 瀛楃墝瀵瑰瓙[6]) { + if (z_koutsu[4] && z_koutsu[5] && z_toitsu[6]) { yakus.push_back(Yaku::Shousangen); } - else if (瀛楃墝鍒诲瓙[4] && 瀛楃墝瀵瑰瓙[5] && 瀛楃墝鍒诲瓙[6]) { + else if (z_koutsu[4] && z_toitsu[5] && z_koutsu[6]) { yakus.push_back(Yaku::Shousangen); } - else if (瀛楃墝瀵瑰瓙[4] && 瀛楃墝鍒诲瓙[5] && 瀛楃墝鍒诲瓙[6]) { + else if (z_toitsu[4] && z_koutsu[5] && z_koutsu[6]) { yakus.push_back(Yaku::Shousangen); } // 鍒ゆ柇褰圭墝 - if (瀛楃墝鍒诲瓙[4]) + if (z_koutsu[4]) yakus.push_back(Yaku::Yakuhai_Haku); - if (瀛楃墝鍒诲瓙[5]) + if (z_koutsu[5]) yakus.push_back(Yaku::Yakuhai_Hatsu); - if (瀛楃墝鍒诲瓙[6]) + if (z_koutsu[6]) yakus.push_back(Yaku::Yakuhai_Chu); - if (鍦洪 == Wind::East) - if (瀛楃墝鍒诲瓙[0]) yakus.push_back(Yaku::Bakaze_Ton); - if (鍦洪 == Wind::South) - if (瀛楃墝鍒诲瓙[1]) yakus.push_back(Yaku::Bakaze_Nan); - if (鍦洪 == Wind::West) - if (瀛楃墝鍒诲瓙[2]) yakus.push_back(Yaku::Bakaze_Sha); - if (鍦洪 == Wind::North) - if (瀛楃墝鍒诲瓙[3]) yakus.push_back(Yaku::Bakaze_Pei); - - if (鑷 == Wind::East) - if (瀛楃墝鍒诲瓙[0]) yakus.push_back(Yaku::Jikaze_Ton); - if (鑷 == Wind::South) - if (瀛楃墝鍒诲瓙[1]) yakus.push_back(Yaku::Jikaze_Nan); - if (鑷 == Wind::West) - if (瀛楃墝鍒诲瓙[2]) yakus.push_back(Yaku::Jikaze_Sha); - if (鑷 == Wind::North) - if (瀛楃墝鍒诲瓙[3]) yakus.push_back(Yaku::Jikaze_Pei); - - if (骞冲拰) yakus.push_back(Yaku::Pinfu); + if (game_wind == Wind::East) + if (z_koutsu[0]) yakus.push_back(Yaku::Bakaze_Ton); + if (game_wind == Wind::South) + if (z_koutsu[1]) yakus.push_back(Yaku::Bakaze_Nan); + if (game_wind == Wind::West) + if (z_koutsu[2]) yakus.push_back(Yaku::Bakaze_Sha); + if (game_wind == Wind::North) + if (z_koutsu[3]) yakus.push_back(Yaku::Bakaze_Pei); + + if (self_wind == Wind::East) + if (z_koutsu[0]) yakus.push_back(Yaku::Jikaze_Ton); + if (self_wind == Wind::South) + if (z_koutsu[1]) yakus.push_back(Yaku::Jikaze_Nan); + if (self_wind == Wind::West) + if (z_koutsu[2]) yakus.push_back(Yaku::Jikaze_Sha); + if (self_wind == Wind::North) + if (z_koutsu[3]) yakus.push_back(Yaku::Jikaze_Pei); + + if (pinfu) yakus.push_back(Yaku::Pinfu); // 璁$畻绗︽暟 // 鍚墝鍨 - if (鍗曢獞) fu += 2; + if (call_single) fu += 2; if (any_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { // 鍧庡紶 if (s.size() == 4) @@ -809,7 +818,7 @@ pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_strin if (s[3] == '!' || s[3] == '@' || s[3] == '#') return true; } return false; - }) && !骞冲拰) + }) && !pinfu) fu += 2; if (any_of(tile_group_string.begin(), tile_group_string.end(), [](const string& s) { @@ -818,19 +827,19 @@ pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_strin if (s[3] == '$' || s[3] == '%' || s[3] == '^') return true; } return false; - }) && 闂ㄦ竻) + }) && menzen) fu += 10; // 闆澶寸 - for_each(tile_group_string.begin(), tile_group_string.end(), [&fu, &鑷, &鍦洪](const string &s) { + for_each(tile_group_string.begin(), tile_group_string.end(), [&fu, &self_wind, &game_wind](const string &s) { // 鑽e拰 - fu += (is褰圭墝瀵瑰瓙(s, 鑷, 鍦洪) * 2); + fu += (is_yakuhai_toitsu(s, self_wind, game_wind) * 2); }); //闈㈠瓙绗 for_each(tile_group_string.begin(), tile_group_string.end(), [&fu](const string& s) { if (s.size() == 3 && s[2] == 'K') { - if (甯﹀购涔濆瓧(s)) fu += 8; + if (has_yaochu_or_z_str(s)) fu += 8; else fu += 4; } if (s.size() == 4) { @@ -842,25 +851,25 @@ pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_strin case '!': case '@': case '#': - if (甯﹀购涔濆瓧(s)) fu += 8; + if (has_yaochu_or_z_str(s)) fu += 8; else fu += 4; break; case '$': case '%': case '^': case '-': - if (甯﹀购涔濆瓧(s)) fu += 4; + if (has_yaochu_or_z_str(s)) fu += 4; else fu += 2; break; } break; case '|': if (s[3] == '-') { - if (甯﹀购涔濆瓧(s)) fu += 16; + if (has_yaochu_or_z_str(s)) fu += 16; else fu += 8; } else if (s[3] == '+') { - if (甯﹀购涔濆瓧(s)) fu += 32; + if (has_yaochu_or_z_str(s)) fu += 32; else fu += 16; } } @@ -874,7 +883,7 @@ pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_strin if (s[3] == '$' || s[3] == '%' || s[3] == '^') return true; } return false; - }) && (!闂ㄦ竻)) { + }) && (!menzen)) { if (fu == 20) fu = 30; } @@ -885,7 +894,7 @@ pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_strin if (s[3] == '!' || s[3] == '@' || s[3] == '#') return true; } return false; - }) && (骞冲拰)) { + }) && (pinfu)) { fu = 20; } @@ -901,8 +910,8 @@ pair, int> ScoreCounter::get_鎵嬪焦(vector tile_group_strin return { yakus, fu }; } -pair, int> ScoreCounter::get_max_鎵嬪焦( - const CompletedTiles &ct, const vector &callgroups, Tile *correspond_tile, BaseTile tsumo_tile, Wind 鑷, Wind 鍦洪, bool 闂ㄦ竻, bool& 褰规弧) +pair, int> ScoreCounter::get_max_hand_yakus( + const CompletedTiles &ct, const vector &callgroups, Tile *correspond_tile, BaseTile tsumo_tile, Wind self_wind, Wind game_wind, bool menzen, bool& yakuman) { bool tsumo = false; // 鏄嚜鎽稿悧 @@ -924,8 +933,8 @@ pair, int> ScoreCounter::get_max_鎵嬪焦( auto tile_group_strings = generate_tile_group_strings(ct, callgroups, tsumo, last_tile); for (auto tile_group : tile_group_strings) { - const auto& yakus = get_鎵嬪焦_褰规弧(tile_group, 鑷, 鍦洪, 褰规弧); - if (褰规弧) { + const auto& yakus = get_hand_yakuman(tile_group, self_wind, game_wind, yakuman); + if (yakuman) { int fan = calculate_fan(yakus); if (fan > max_fan) { max_yaku_fus = { yakus, 20 }; @@ -933,7 +942,7 @@ pair, int> ScoreCounter::get_max_鎵嬪焦( } } else { - const auto &yaku_fu = get_鎵嬪焦(tile_group, 鑷, 鍦洪, 闂ㄦ竻); + const auto &yaku_fu = get_hand_yakus(tile_group, self_wind, game_wind, menzen); max_yaku_fus = max(max_yaku_fus, yaku_fu, &compare_yaku_fu); } @@ -941,20 +950,20 @@ pair, int> ScoreCounter::get_max_鎵嬪焦( return max_yaku_fus; } -bool ScoreCounter::get_澶╁湴鍜() { +bool ScoreCounter::get_tenhou_chihou() { if (player->first_round && tsumo) { - 鏈夊焦 = true; + have_yaku = true; if (player->oya) { - 澶╁湴鍜.push_back(Yaku::Tenhou); - 褰规弧 = true; - 褰规弧鍊嶆暟 += 1; + tenhou_chihou_yakus.push_back(Yaku::Tenhou); + yakuman = true; + yakuman_fold += 1; } else { - 澶╁湴鍜.push_back(Yaku::Chihou); + tenhou_chihou_yakus.push_back(Yaku::Chihou); } - 褰规弧 = true; - 褰规弧鍊嶆暟 += 1; + yakuman = true; + yakuman_fold += 1; return true; } else { @@ -962,8 +971,8 @@ bool ScoreCounter::get_澶╁湴鍜() { } } -bool ScoreCounter::get_鍥藉+() { - if (闂ㄦ竻) { +bool ScoreCounter::get_kokushi() { + if (menzen) { if (is_kokushi_shape(basetiles)) { // 鍒ゅ畾13闈 { @@ -975,97 +984,97 @@ bool ScoreCounter::get_鍥藉+() { sort(copybasetiles.begin(), copybasetiles.end()); if (is_same_container(raw, basetiles)) { - 鏈澶ф墜褰筥鐣.first.push_back(Yaku::Koukushimusou_13); - 鍥藉+13 = true; + max_hand_yakus_fan_fu.first.push_back(Yaku::Koukushimusou_13); + kokushi_13 = true; } else { - 鏈澶ф墜褰筥鐣.first.push_back(Yaku::Kokushimusou); - 鍥藉+ = true; + max_hand_yakus_fan_fu.first.push_back(Yaku::Kokushimusou); + kokushi = true; } } - 褰规弧 = true; + yakuman = true; } } return false; } -void ScoreCounter::get涓鑹() { - mpsz涓鑹 = basetiles[0] / 9; +void ScoreCounter::get_pure_type() { + mpsz_pure_type = basetiles[0] / 9; for (size_t i = 1; i < 14; ++i) - if (basetiles[i] / 9 != mpsz涓鑹) { + if (basetiles[i] / 9 != mpsz_pure_type) { // 娓呬竴鑹查兘涓嶆槸 - mpsz涓鑹 = -1; + mpsz_pure_type = -1; break; } } -bool ScoreCounter::get_涔濊幉() { - if (!闂ㄦ竻) return false; - if (mpsz涓鑹 < 0 && mpsz涓鑹 > 2) return false; +bool ScoreCounter::get_churen() { + if (!menzen) return false; + if (mpsz_pure_type < 0 && mpsz_pure_type > 2) return false; if (is_churen_9_shape(basetiles)) { - 涔濊幉绾 = true; - 鏈澶ф墜褰筥鐣.first.push_back(Yaku::Chuurenpoutou_9); - 褰规弧 = true; + churen_pure = true; + max_hand_yakus_fan_fu.first.push_back(Yaku::Chuurenpoutou_9); + yakuman = true; return true; } else if (is_churen_shape(basetiles)) { - 涔濊幉 = true; - 鏈澶ф墜褰筥鐣.first.push_back(Yaku::Chuurenpoutou); - 褰规弧 = true; + churen = true; + max_hand_yakus_fan_fu.first.push_back(Yaku::Chuurenpoutou); + yakuman = true; return true; } return false; } -void ScoreCounter::get_绔嬬洿() { +void ScoreCounter::get_riichi() { if (player->double_riichi) - 鍦哄焦.push_back(Yaku::Dabururiichi); + state_yakus.push_back(Yaku::Dabururiichi); else if (player->riichi) - 鍦哄焦.push_back(Yaku::Riichi); + state_yakus.push_back(Yaku::Riichi); } -void ScoreCounter::get_娴峰簳娌冲簳() { +void ScoreCounter::get_haitei_hotei() { /* 娴峰簳鐨勬潯浠舵槸1. remain_tile == 0, 2. 涓婁竴鎵嬩笉鏄潬鐩稿叧 */ if (tsumo && table->get_remain_tile() == 0 && table->last_action != BaseAction::AnKan && table->last_action != BaseAction::Kan && table->last_action != BaseAction::KaKan) { - 鍦哄焦.push_back(Yaku::Haiteiraoyue); + state_yakus.push_back(Yaku::Haiteiraoyue); } if (!tsumo && table->get_remain_tile() == 0) { - 鍦哄焦.push_back(Yaku::Houteiraoyu); + state_yakus.push_back(Yaku::Houteiraoyu); } } -void ScoreCounter::get_鎶㈡潬() { +void ScoreCounter::get_chankan() { /* 濡傛灉鏄姠鏉狅紝閭d箞璁$畻鎶㈡潬 */ - if (鎶㈡潬) { - 鍦哄焦.push_back(Yaku::Chankan); + if (chankan) { + state_yakus.push_back(Yaku::Chankan); } } -void ScoreCounter::get_宀笂() { +void ScoreCounter::get_rinshan() { /* 濡傛灉涓婁竴杞姩浣滄槸鏉狅紝杩欎竴杞槸tsumo锛岄偅涔堝氨鏄箔涓 */ if (table->last_action == BaseAction::AnKan || table->last_action == BaseAction::Kan || table->last_action == BaseAction::KaKan) { if (tsumo) { - 鍦哄焦.push_back(Yaku::Rinshankaihou); + state_yakus.push_back(Yaku::Rinshankaihou); } } } -inline void ScoreCounter::get_闂ㄦ竻鑷懜() { +inline void ScoreCounter::get_menzentsumo() { /*闂ㄦ竻鑷懜*/ if (tsumo && player->menzen) { - 鍦哄焦.push_back(Yaku::Menzentsumo); + state_yakus.push_back(Yaku::Menzentsumo); } } -void ScoreCounter::get_涓鍙() { +void ScoreCounter::get_ippatsu() { /* 濡傛灉淇濆瓨鏈変竴鍙戠姸鎬 */ if (player->ippatsu) { - 鍦哄焦.push_back(Yaku::Ippatsu); + state_yakus.push_back(Yaku::Ippatsu); } } @@ -1073,14 +1082,14 @@ void ScoreCounter::get_aka_dora() { // 鎺ヤ笅鏉ョ粺璁$孩瀹濈墝鏁伴噺 for (auto tile : tiles) { if (tile->red_dora == true) { - Dora褰.push_back(Yaku::Akadora); + dora_yakus.push_back(Yaku::Akadora); } } for (auto fulu : player->call_groups) { for (auto tile : fulu.tiles) { if (tile->red_dora == true) { - Dora褰.push_back(Yaku::Akadora); + dora_yakus.push_back(Yaku::Akadora); } } } @@ -1096,14 +1105,14 @@ void ScoreCounter::get_dora() { for (auto doratile : doratiles) { for (auto tile : tiles) { if (tile->tile == doratile) { - Dora褰.push_back(Yaku::Dora); + dora_yakus.push_back(Yaku::Dora); } } for (auto fulu : player->call_groups) { for (auto tile : fulu.tiles) { if (tile->tile == doratile) { - Dora褰.push_back(Yaku::Dora); + dora_yakus.push_back(Yaku::Dora); } } } @@ -1121,14 +1130,14 @@ void ScoreCounter::get_ura_dora() { for (const auto& doratile : doratiles) { for (const auto& tile : tiles) { if (tile->tile == doratile) { - Dora褰.push_back(Yaku::Uradora); + dora_yakus.push_back(Yaku::Uradora); } } for (const auto& fulu : player->call_groups) { for (const auto& tile : fulu.tiles) { if (tile->tile == doratile) { - Dora褰.push_back(Yaku::Uradora); + dora_yakus.push_back(Yaku::Uradora); } } } @@ -1145,45 +1154,27 @@ CounterResult ScoreCounter::yaku_counter() // 棣栧厛 鍋囪杩涘叆鍒拌繖涓猚ounter闃舵鐨勶紝鑷冲皯婊¤冻浜嗗拰鐗屾潯浠剁殑鐗屽瀷 // 浠ュ強锛屾槸鍚︽湁鏌愮褰规槸涓嶇‘瀹氱殑 - // 浠庢渶绠鍗曠殑鍦哄焦寮濮嬭绠 - - // tsumo = (correspond_tile == nullptr); - // 闂ㄦ竻 = player.闂ㄦ竻; - - // 褰 = 鎵嬪焦+鍦哄焦+Dora銆傛墜褰规牴鎹墝鐨勮В閲婁笉鍚岃屼笉鍚屻 - // vector, int>> all_yaku_fu_cases; CounterResult final_result; - // auto handcopy = player.hand; - // if (correspond_tile != nullptr) - // handcopy.push_back(correspond_tile); - - // auto hand_basetile = convert_tiles_to_basetiles(handcopy); - - 褰规弧 = false; - // vector 澶╁湴鍜; - // vector 鍦哄焦; - // vector Dora褰; - // vector, int>> &all_鎵嬪焦 = all_yaku_fu_cases; // 鎵鏈夋墜褰瑰彲鑳芥 - + yakuman = false; /* 澶╁湴鍜岀殑鏉′欢鏄紝鍦ㄧ涓宸★紝涓旀病浜洪福鐗*/ if (player->first_round && tsumo) { if (table->oya == table->turn) { - 澶╁湴鍜.push_back(Yaku::Tenhou); - 褰规弧 = true; + tenhou_chihou_yakus.push_back(Yaku::Tenhou); + yakuman = true; } else { - 澶╁湴鍜.push_back(Yaku::Chihou); - 褰规弧 = true; + tenhou_chihou_yakus.push_back(Yaku::Chihou); + yakuman = true; } } - get_鍥藉+(); - if (!鍥藉+ && !鍥藉+13) - get_涔濊幉(); + get_kokushi(); + if (!kokushi && !kokushi_13) + get_churen(); - if (!鍥藉+ && !鍥藉+13 && !涔濊幉 && !涔濊幉绾) { + if (!kokushi && !kokushi_13 && !churen && !churen_pure) { /* 骞堕潪浠ヤ笂鎯呭喌鐨勬墜褰瑰垽鏂 */ sort(basetiles.begin(), basetiles.end()); // 瀵圭墝杩涜鎷嗚В 锛堝凡缁弖nique锛 @@ -1205,36 +1196,36 @@ CounterResult ScoreCounter::yaku_counter() // 鎺ヤ笅鏉 for (const auto &complete_tiles : complete_tiles_list) { - auto yaku_fus = get_max_鎵嬪焦(complete_tiles, player->call_groups, win_tile, player->hand.back()->tile, 鑷, 鍦洪, 闂ㄦ竻, 褰规弧); - 鏈澶ф墜褰筥鐣 = max(鏈澶ф墜褰筥鐣, yaku_fus, &compare_yaku_fu); + auto yaku_fus = get_max_hand_yakus(complete_tiles, player->call_groups, win_tile, player->hand.back()->tile, self_wind, game_wind, menzen, yakuman); + max_hand_yakus_fan_fu = max(max_hand_yakus_fan_fu, yaku_fus, &compare_yaku_fu); } } - if (褰规弧) { - merge_into(鏈澶ф墜褰筥鐣.first, 澶╁湴鍜); + if (yakuman) { + // 褰规弧鎯呭喌涓嬶紝涓嶅垽鏂姸鎬佸焦鍜宒ora褰 + merge_into(max_hand_yakus_fan_fu.first, tenhou_chihou_yakus); } else { - get_鍦哄焦_Dora褰(); - merge_into(鏈澶ф墜褰筥鐣.first, 鍦哄焦); - merge_into(鏈澶ф墜褰筥鐣.first, Dora褰); + get_state_yaku_dora_yaku(); + merge_into(max_hand_yakus_fan_fu.first, state_yakus); + merge_into(max_hand_yakus_fan_fu.first, dora_yakus); } - if (!can_agari(鏈澶ф墜褰筥鐣.first)) { + if (!can_agari(max_hand_yakus_fan_fu.first)) { // 璇存槑鏃犲焦 final_result.yakus.push_back(Yaku::None); } else { - - final_result.yakus = 鏈澶ф墜褰筥鐣.first; + final_result.yakus = max_hand_yakus_fan_fu.first; final_result.fan = calculate_fan(final_result.yakus); - final_result.fu = 鏈澶ф墜褰筥鐣.second; + final_result.fu = max_hand_yakus_fan_fu.second; final_result.calculate_score(player->oya, tsumo); } return final_result; } -ScoreCounter::ScoreCounter(const Table* t, const Player* p, Tile* win, bool 鎶㈡潬_, bool 鎶㈡殫鏉燺) - : 鎶㈡潬(鎶㈡潬_), 鎶㈡殫鏉(鎶㈡殫鏉燺), 闂ㄦ竻(p->menzen) +ScoreCounter::ScoreCounter(const Table* t, const Player* p, Tile* win, bool chankan_, bool chanankan_) + : chankan(chankan_), chanankan(chanankan_), menzen(p->menzen) { table = t; player = p; @@ -1248,18 +1239,8 @@ ScoreCounter::ScoreCounter(const Table* t, const Player* p, Tile* win, bool 鎶 tiles.push_back(win_tile); } basetiles = convert_tiles_to_basetiles(tiles); - 鑷 = p->wind; - 鍦洪 = t->game_wind; -} - -bool ScoreCounter::check_鏈夊焦() -{ - if (鎶㈡潬 || 鎶㈡殫鏉) return true; - // roughly check - if (get_澶╁湴鍜()) return true; - if (get_鍥藉+()) return true; - - return false; + self_wind = p->wind; + game_wind = t->game_wind; } namespace_mahjong_end diff --git a/Mahjong/ScoreCounter.h b/Mahjong/ScoreCounter.h index 9fc93a07..23278a7d 100644 --- a/Mahjong/ScoreCounter.h +++ b/Mahjong/ScoreCounter.h @@ -78,10 +78,6 @@ constexpr char mark_ron_1st = '$'; constexpr char mark_ron_2nd = '%'; constexpr char mark_ron_3rd = '^'; - -//constexpr static BaseTile raw[] = { _1m, _9m, _1s, _9s, _1p, _9p, -// _1z, _2z, _3z, _4z, _5z, _6z, _7z }; - class ScoreCounter { public: std::vector tiles; @@ -90,26 +86,39 @@ class ScoreCounter { std::vector> completedtiles_list; - std::vector 澶╁湴鍜; - std::vector 鍦哄焦; - std::vector Dora褰; - // int 鏈澶ф墜褰圭暘 = 0; - std::pair, int> 鏈澶ф墜褰筥鐣 = { {}, 20 }; + // 澶╁湴鑳 + std::vector tenhou_chihou_yakus; + + // 鍩轰簬鐘舵佺殑褰癸紝鍖呮嫭绔嬬洿銆佷竴鍙戙佹捣搴曘佹渤搴曘佹姠鏉犮佸箔涓娿侀棬娓呰嚜鎽 + std::vector state_yakus; + + // dora褰 + std::vector dora_yakus; + + // 鏈澶ф墜褰圭暘&绗 + std::pair, int> max_hand_yakus_fan_fu = { {}, 20 }; const Table* table = nullptr; const Player* player = nullptr; - bool 鏈夊焦 = false; + // 鏈夊焦 + bool have_yaku = false; bool tsumo = false; - bool 闂ㄦ竻 = false; - bool 鍥藉+ = false, 鍥藉+13 = false; - bool 涔濊幉 = false, 涔濊幉绾 = false; - bool 褰规弧 = false; - bool 鎶㈡潬 = false, 鎶㈡殫鏉 = false; - int 褰规弧鍊嶆暟 = 0; - int mpsz涓鑹 = 0; - Wind 鑷, 鍦洪; + bool menzen = false; + bool kokushi = false, kokushi_13 = false; + bool churen = false, churen_pure = false; + + bool chankan = false, chanankan = false; + // 褰规弧鐘舵 + bool yakuman = false; + + // 鍑犲嶅焦婊 + int yakuman_fold = 0; + + // 娓呬竴鑹 or 瀛椾竴鑹 (mpsz = 0,1,2,3, respectively) + int mpsz_pure_type = 0; + Wind self_wind, game_wind; /* Arguments: Table *t, @@ -118,38 +127,53 @@ class ScoreCounter { bool chankan, bool chanankan */ - ScoreCounter(const Table* t, const Player* p, Tile* win, bool 鎶㈡潬_, bool 鎶㈡殫鏉燺); + ScoreCounter(const Table* t, const Player* p, Tile* win, bool chankan, bool chanankan); - bool get_澶╁湴鍜(); - bool get_鍥藉+(); - inline void get涓鑹(); - bool get_涔濊幉(); + bool get_tenhou_chihou(); + bool get_kokushi(); + bool get_churen(); + + // 鏇存柊娓呬竴鑹 or 瀛椾竴鑹, mpsz_pure_type + void get_pure_type(); std::vector> generate_tile_group_strings(const CompletedTiles& ct, const std::vector& callgroups, bool tsumo, BaseTile last_tile); - std::vector get_鎵嬪焦_褰规弧(const std::vector& tile_group_string, Wind 鑷, Wind 鍦洪, bool& 褰规弧); - std::pair, int> get_鎵嬪焦(std::vector tile_group_string, Wind 鑷, Wind 鍦洪, bool 闂ㄦ竻); - std::pair, int> get_max_鎵嬪焦(const CompletedTiles& ct, const std::vector& callgroups, Tile* correspond_tile, BaseTile tsumo_tile, Wind 鑷, Wind 鍦洪, bool 闂ㄦ竻, bool& 褰规弧); - - void get_绔嬬洿(); - void get_娴峰簳娌冲簳(); - void get_鎶㈡潬(); - void get_宀笂(); - void get_闂ㄦ竻鑷懜(); - void get_涓鍙(); + std::vector get_hand_yakuman(const std::vector& tile_group_string, Wind self_wind, Wind game_wind, bool& yakuman); + std::pair, int> get_hand_yakus(const std::vector &tile_group_string, Wind self_wind, Wind game_wind, bool menzen); + std::pair, int> get_max_hand_yakus(const CompletedTiles& ct, const std::vector& callgroups, Tile* correspond_tile, BaseTile tsumo_tile, Wind self_wind, Wind game_wind, bool menzen, bool& yakuman); + + void get_riichi(); + + // 娴峰簳&娌冲簳 + void get_haitei_hotei(); + void get_chankan(); + void get_rinshan(); + void get_menzentsumo(); + void get_ippatsu(); void get_aka_dora(); void get_dora(); void get_ura_dora(); - inline void get_鍦哄焦_Dora褰() + /* 鐘舵佸焦 state yaku + * 婊¤冻涓瀹氭潯浠跺悗鍙互鏃犳潯浠惰儭鐗岀殑褰癸紝鍖呮嫭涓鍙戯紝浣嗕笉鍖呮嫭澶╁湴鑳 + * 鍖呮嫭 + * - 绔嬬洿 + * - 娴峰簳 + * - 娌冲簳 + * - 鎶㈡潬 + * - 宀笂 + * - 涓鍙 + * - 闂ㄦ竻鑷懜 + */ + inline void get_state_yaku_dora_yaku() { - get_绔嬬洿(); - get_娴峰簳娌冲簳(); + get_riichi(); + get_haitei_hotei(); - get_鎶㈡潬(); - get_宀笂(); + get_chankan(); + get_rinshan(); - get_涓鍙(); - get_闂ㄦ竻鑷懜(); + get_ippatsu(); + get_menzentsumo(); get_aka_dora(); get_dora(); @@ -157,8 +181,7 @@ class ScoreCounter { } CounterResult yaku_counter(); - bool check_鏈夊焦(); - + }; namespace_mahjong_end diff --git a/Mahjong/Table.cpp b/Mahjong/Table.cpp index e357bd60..5416d99c 100644 --- a/Mahjong/Table.cpp +++ b/Mahjong/Table.cpp @@ -9,7 +9,7 @@ using namespace std; namespace_mahjong -static bool check_瑙侀(const vector& responses, int selection) +static bool check_minogashi(const vector& responses, int selection) { for (int i = 0; i < responses.size();++i) { if (responses[i].action == BaseAction::Ron || @@ -22,6 +22,11 @@ static bool check_瑙侀(const vector& responses, int selection) return false; } +void Table::new_dora() { + n_active_dora++; + gamelog.log_reveal_dora(dora_indicator[n_active_dora - 1]); +} + vector Table::get_dora() const { vector doratiles; @@ -58,11 +63,14 @@ void Table::init_red_dora_3() void Table::shuffle_tiles() { - static std::default_random_engine rd(time(nullptr)); - if (use_seed) { rd.seed(seed); } + else + { + seed = time(nullptr); + rd.seed(seed); + } std::shuffle(yama.begin(), yama.end(), rd); if (write_log_mode) { @@ -143,7 +151,8 @@ void Table::init_before_playing() } debug_replay_init(); - + gamelog.log_game_start(honba, kyoutaku, oya, game_wind, yama, + get_scores(), players[0].hand, players[1].hand, players[2].hand, players[3].hand); from_beginning(); } @@ -164,6 +173,63 @@ void Table::game_init() { init_before_playing(); } +void Table::game_init_with_config(const std::vector& yama, const std::vector& init_scores, int kyoutaku_, int honba_, int game_wind_, int oya_) +{ + if (oya_ >= 0 && oya_ <= 3) + oya = oya_; + else + oya = 0; + + if (game_wind_ >= 0 && game_wind_ <= 3) + game_wind = Wind(game_wind_); + else + game_wind = Wind::East; + + if (kyoutaku >= 0) + kyoutaku = kyoutaku_; + else + kyoutaku = 0; + + if (honba >= 0) + honba = honba_; + else + honba = 0; + + init_tiles(); + init_red_dora_3(); + + if (yama.size() == N_TILES) + import_yama(yama); + else if (yama.size() == 0) + { + init_yama(); + shuffle_tiles(); + } + else + throw std::runtime_error("Yama size is not 136."); + + yama_log = yama; + init_dora(); + draw_tenhou_style(); + + if (init_scores.size() == 4) + { + for (int i = 0; i < 4; ++i) { + players[i].score = init_scores[i]; + } + } + else if (init_scores.size() == 0) + { + for (int i = 0; i < 4; ++i) { + players[i].score = 25000; + } + } + else + throw std::runtime_error("init_scores size is not 4."); + + init_before_playing(); +} + void Table::game_init_for_replay(const std::vector &yama, const std::vector &init_scores, int kyoutaku_, int honba_, int game_wind_, int oya_) { oya = oya_; @@ -255,14 +321,14 @@ void Table::game_init_with_metadata(unordered_map metadata) auto val = metadata["deal"]; if (val == "from_oya") { for (int i = oya; i < oya + 4; ++i) { - draw(i % 4, 13); + draw_n_normal(i % 4, 13); } } else if (val == "from_0") { - draw(0, 13); - draw(1, 13); - draw(2, 13); - draw(3, 13); + draw_n_normal(0, 13); + draw_n_normal(1, 13); + draw_n_normal(2, 13); + draw_n_normal(3, 13); } else if (val == "tenhou") { draw_tenhou_style(); @@ -301,12 +367,12 @@ void Table::next_turn(int nextturn) player.update_furiten_river(); } } - if (selected_base_action == BaseAction::AnKan) { + else if (selected_base_action == BaseAction::AnKan) { player.update_atari_tiles(); player.remove_atari_tiles(selected_action.correspond_tiles[0]->tile); player.update_furiten_river(); } - if (selected_base_action == BaseAction::KaKan) { + else if (selected_base_action == BaseAction::KaKan) { player.update_atari_tiles(); player.remove_atari_tiles(selected_action.correspond_tiles[0]->tile); player.update_furiten_river(); @@ -326,13 +392,13 @@ void Table::from_beginning() if (phase == GAME_OVER) return; - const static auto 鍥涢杩炴墦鐗 = [](const array& players) { + const static auto siifurenda_test = [](const array& players) { BaseTile t0 = players[0].river[0].tile->tile; BaseTile t1 = players[1].river[0].tile->tile; BaseTile t2 = players[2].river[0].tile->tile; BaseTile t3 = players[3].river[0].tile->tile; - return t0 == t1 && t2 == t3 && t0 == t2 && t1 >= BaseTile::_1z && t1 <= BaseTile::_4z; + return t0 == t1 && t0 == t2 && t0 == t3 && t1 >= BaseTile::_1z && t1 <= BaseTile::_4z; }; // 鍥涢杩炴墦鍒ゅ畾 @@ -344,7 +410,7 @@ void Table::from_beginning() players[1].call_groups.size() == 0 && players[2].call_groups.size() == 0 && players[3].call_groups.size() == 0 && - 鍥涢杩炴墦鐗(players)) + siifurenda_test(players)) { result = generate_result_4wind(this); gamelog.log_gameover(result); @@ -412,49 +478,90 @@ void Table::from_beginning() phase = (PhaseEnum)turn; } -/* 濡傛灉杩樻湁2/4寮犲箔涓婄墝锛屽垯鎽稿掓暟绗2寮狅紙鍥犱负鏈鍚庝竴寮犲帇鍦ㄤ笅闈級*/ -void Table::draw_rinshan(int i_player) +void Table::draw_tenhou_style() { - n_active_dora++; // 鍏堢炕dora - int n_kan = get_remain_kan_tile(); - auto iter = yama.begin(); - if (n_kan % 2 == 0) ++iter; - players[i_player].hand.push_back(*iter); - yama.erase(iter); + // 姣忔鎽4涓 + for (int round = 0; round < 3; ++round) { + for (int i = 0; i < 4; ++i) { + draw_n_normal((oya + i) % 4, 4); + } + } + // 璺崇珷 + // 姣忔鎽1涓 + for (int i = 0; i < 4; ++i) { + draw_normal_no_record((oya + i) % 4); + } } -void Table::draw(int i_player) +void Table::draw_normal(int i_player) { players[i_player].hand.push_back(yama.back()); - gamelog.log_draw(i_player, yama.back()); + gamelog.log_draw(i_player, yama.back(), false); yama.pop_back(); } -void Table::draw(int i_player, int n_tiles) +void Table::draw_normal_no_record(int i_player) +{ + players[i_player].hand.push_back(yama.back()); + yama.pop_back(); +} + +void Table::draw_n_normal(int i_player, int n_tiles) { for (int i = 0; i < n_tiles; ++i) { - draw(i_player); + draw_normal_no_record(i_player); } } -void Table::draw_tenhou_style() +/* 濡傛灉杩樻湁2/4寮犲箔涓婄墝锛屽垯鎽稿掓暟绗2寮狅紙鍥犱负鏈鍚庝竴寮犲帇鍦ㄤ笅闈級*/ +void Table::draw_rinshan(int i_player) { - // 姣忔鎽4涓 - for (int round = 0; round < 3; ++round) { - for (int i = 0; i < 4; ++i) { - draw((oya + i) % 4, 4); - } - } - // 璺崇珷 - // 姣忔鎽1涓 - for (int i = 0; i < 4; ++i) { - draw((oya + i) % 4); - } + int n_kan = get_remain_kan_tile(); + auto iter = yama.begin(); + if (n_kan % 2 == 0) ++iter; + players[i_player].hand.push_back(*iter); + gamelog.log_draw(i_player, *iter, true); + yama.erase(iter); } -void Table::draw_normal(int i_player) +void Table::reshuffle_yama(unsigned int seed) { - draw(i_player); + // remember to avoid reshuffle the revealed dora + + int first_dora_pos = std::find(yama.begin(), yama.end(), dora_indicator[0]) - yama.begin(); + + // shuffle all + rd.seed(seed); + std::shuffle(yama.begin(), yama.end(), rd); + + // 褰掕繕宸茬炕dora + for (int i = 0; i < n_active_dora; ++i) + { + // dora鐗岃娲楀埌鐨勬柊浣嶇疆浣嶇疆 + auto dora_current_iter = std::find(yama.begin(), yama.end(), dora_indicator[i]); + + // 褰撳墠dora鎸囩ず鐗屼綅缃紙first_dora_pos + 2 * i锛 + auto dora_pos_iter = yama.begin() + first_dora_pos + 2 * i; + + std::iter_swap(dora_current_iter, dora_pos_iter); + } + + // 鏇存柊dora_indicator鍜寀radora_indicator + dora_indicator = { + yama[first_dora_pos], + yama[first_dora_pos + 2], + yama[first_dora_pos + 4], + yama[first_dora_pos + 6], + yama[first_dora_pos + 8], + }; + + uradora_indicator = { + yama[first_dora_pos - 1], + yama[first_dora_pos + 1], + yama[first_dora_pos + 3], + yama[first_dora_pos + 5], + yama[first_dora_pos + 7], + }; } string Table::to_string() const @@ -643,8 +750,8 @@ void Table::_handle_response_action() { // 瑙侀冨垽鏂 int i = phase - P1_RESPONSE; - if (check_瑙侀(response_actions, selection)) { - players[i].瑙侀(); + if (check_minogashi(response_actions, selection)) { + players[i].minogashi(); } // 鍦0,1,2澶勭悊瀹屼箣鍚庯紝涓轰笅涓涓帺瀹剁敓鎴愬搴旂殑鍔ㄤ綔锛屽苟鏀瑰彉瀵瑰簲鐨刾hase if (i < 3) @@ -657,10 +764,8 @@ void Table::_handle_response_action() } else { // 瀵逛簬鎵鏈夊叾浠栦汉 - bool is涓嬪 = false; - if (i == (turn + 1) % 4) - is涓嬪 = true; - response_actions = _generate_response_actions(i, tile, is涓嬪); + bool is_next = (i == (turn + 1) % 4); + response_actions = _generate_response_actions(i, tile, is_next); } phase = PhaseEnum(phase + 1); } @@ -670,8 +775,8 @@ void Table::_handle_response_chankan_action() { // 瑙侀冨垽鏂 int i = phase - P1_CHANKAN_RESPONSE; - if (check_瑙侀(response_actions, selection)) { - players[i].瑙侀(); + if (check_minogashi(response_actions, selection)) { + players[i].minogashi(); } // 鍦0,1,2澶勭悊瀹屼箣鍚庯紝涓轰笅涓涓帺瀹剁敓鎴愬搴旂殑鍔ㄤ綔锛屽苟鏀瑰彉瀵瑰簲鐨刾hase @@ -684,10 +789,6 @@ void Table::_handle_response_chankan_action() response_actions = { ra }; } else { - // 瀵逛簬鎵鏈夊叾浠栦汉 - bool is涓嬪 = false; - if (i == (turn + 1) % 4) - is涓嬪 = true; response_actions = _generate_chankan_response_actions(i, tile); } phase = PhaseEnum(phase + 1); @@ -698,8 +799,8 @@ void Table::_handle_response_chanankan_action() { // 瑙侀冨垽鏂 int i = phase - P1_CHANANKAN_RESPONSE; - if (check_瑙侀(response_actions, selection)) { - players[i].瑙侀(); + if (check_minogashi(response_actions, selection)) { + players[i].minogashi(); } // 鍦0,1,2澶勭悊瀹屼箣鍚庯紝涓轰笅涓涓帺瀹剁敓鎴愬搴旂殑鍔ㄤ綔锛屽苟鏀瑰彉瀵瑰簲鐨刾hase @@ -712,10 +813,6 @@ void Table::_handle_response_chanankan_action() response_actions = { ra }; } else { - // 瀵逛簬鎵鏈夊叾浠栦汉 - bool is涓嬪 = false; - if (i == (turn + 1) % 4) - is涓嬪 = true; response_actions = _generate_chanankan_response_actions(i, tile); } phase = PhaseEnum(phase + 1); @@ -725,13 +822,13 @@ void Table::_handle_response_chanankan_action() void Table::_handle_response_final_execution() { // 閫夋嫨浼樺厛绾э紝骞朵笖杩涜response缁撶畻 - vector response_player; + vector response_players; for (int i = 0; i < 4; ++i) { if (actions[i].action == final_action) { - response_player.push_back(i); + response_players.push_back(i); } } - int response = response_player[0]; + int response = response_players[0]; // 鏍规嵁鏈缁堢殑final_action鏉ヨ繘琛屽垽鏂 // response_player閲岄潰淇濆瓨浜嗘墍鏈夋渶缁坅ction鍜宖inal_action鐩稿悓鐨勭帺瀹 // 鍙湁鍦╬ass鍜岃崳鍜岀殑鏃跺欐墠浼氬嚭鐜拌繖绉嶆儏鍐 @@ -739,67 +836,22 @@ void Table::_handle_response_final_execution() switch (final_action) { case BaseAction::Pass: - - // 鏉狅紝鎵撳嚭鐗屼箣鍚庝笖鍏朵粬浜簆ass - // if (after_鏉()) { dora_spec++; } - - if (selected_action.action == BaseAction::Riichi) { - // 绔嬬洿鎴愬姛 - if (players[turn].first_round) { - players[turn].double_riichi = true; - } - players[turn].riichi = true; - kyoutaku++; - players[turn].score -= 1000; - players[turn].ippatsu = true; - } - - // 浠涔堥兘涓嶅仛銆傚皢action瀵瑰簲鐨勭墝浠庢墜鐗岀Щ鍔ㄥ埌鐗屾渤閲岄潰 - // players[turn].move_from_hand_to_river_really(tile, river_counter, FROM_鎵嬪垏鎽稿垏); - - // 娑堥櫎绗竴宸 - players[turn].first_round = false; - next_turn((turn + 1) % 4); - last_action = selected_action.action; - break; + { + _handle_response_final_pass_impl(); + return; + } case BaseAction::Chi: case BaseAction::Pon: case BaseAction::Kan: - - // 鏄庢潬锛屾墦鍑虹墝涔嬪悗涓斿叾浠栦汉鍚冪 - // if (after_鏉()) { dora_spec++; } - if (selected_action.action == BaseAction::Riichi) { - // 绔嬬洿鎴愬姛 - if (players[turn].first_round) { - players[turn].double_riichi = true; - } - players[turn].riichi = true; - kyoutaku++; - players[turn].score -= 1000; - // 绔嬬洿鍗抽福鐗岋紝涓瀹氭病鏈塱ppatsu - } - - players[turn].set_not_remained(); - - players[response].menzen = false; - players[response].execute_naki( - actions[response].correspond_tiles, tile, (turn - response) % 4); - - // 杩欐槸楦g墝锛屾秷闄ゆ墍鏈変汉绗竴宸″拰ippatsu - for (int i = 0; i < 4; ++i) { - players[i].first_round = false; - players[i].ippatsu = false; - } - - // 鍒囨崲turn - next_turn(response); - last_action = final_action; - break; - + { + _handle_response_final_chiponkan_impl(response); + return; + } case BaseAction::Ron: - result = generate_result_ron(this, selected_action.correspond_tiles[0], response_player); - phase = GAME_OVER; + { + _handle_response_final_ron_impl(response_players); return; + } default: throw runtime_error("Invalid Selection."); } @@ -807,20 +859,37 @@ void Table::_handle_response_final_execution() void Table::_handle_response_final_chankan_execution() { - // 杩涜response缁撶畻 + // 杩涜chankan缁撶畻 vector response_player; for (int i = 0; i < 4; ++i) { if (actions[i].action == BaseAction::ChanKan) { response_player.push_back(i); } } - if (response_player.size() != 0) { - // 鏈変汉鎶㈡潬鍒欒繘琛岀粨绠楋紝闄ら潪鍔犳潬瀹e憡鎴愬姛锛屽惁鍒檌ppatsu鐘舵佷粛鐒跺瓨鍦 - result = generate_result_chankan(this, tile, response_player); + + // 瑕佷箞鏈夋姠鏉狅紝瑕佷箞鏄痯ass + // 杩欓噷鏄湁鎶㈡潬 + // 鏈変汉鎶㈡潬鍒欒繘琛岀粨绠楋紝闄ら潪鍔犳潬瀹e憡鎴愬姛锛屽惁鍒檌ppatsu鐘舵佷粛鐒跺瓨鍦 + if (response_player.size() > 0) + { + for (int player : response_player) + gamelog.log_ron(player, turn, tile); + + /* 3鍝 */ + if (response_player.size() == 3) + { + result = generate_result_3ron(this); + } + else { + result = generate_result_chankan(this, tile, response_player); + } + gamelog.log_gameover(result); phase = GAME_OVER; return; } - players[turn].execute_kakan(selected_action.correspond_tiles[0]); + + // 杩欓噷鏄叏閮╬ass + players[turn].execute_kakan(tile); last_action = BaseAction::KaKan; // 杩欐槸楦g墝锛屾秷闄ゆ墍鏈変汉绗竴宸″拰ippatsu @@ -829,121 +898,236 @@ void Table::_handle_response_final_chankan_execution() players[i].ippatsu = false; } next_turn(turn); + + // 杩欓噷杩樹笉鎬ョ潃缈籨ora锛屽厛璁板綍last_action } void Table::_handle_response_final_chanankan_execution() -{ - // 杩涜response缁撶畻 +{ + // 杩涜chanankan缁撶畻 vector response_player; for (int i = 0; i < 4; ++i) { if (actions[i].action == BaseAction::ChanAnKan) { response_player.push_back(i); } } - if (response_player.size() != 0) { - // 鏈変汉鎶㈡殫鏉犲垯杩涜缁撶畻 - result = generate_result_chanankan(this, tile, response_player); + + // 瑕佷箞鏈夋姠鏉狅紝瑕佷箞鏄痯ass + // 杩欓噷鏄湁鎶㈡潬 + if (response_player.size() > 0) + { + for (int player : response_player) + gamelog.log_ron(player, turn, tile); + + /* 3鍝 */ + if (response_player.size() == 3) + { + result = generate_result_3ron(this); + } + else { + result = generate_result_chanankan(this, tile, response_player); + } + gamelog.log_gameover(result); phase = GAME_OVER; return; } - players[turn].execute_ankan(selected_action.correspond_tiles[0]->tile); + + // 杩欓噷鏄叏閮╬ass + players[turn].execute_ankan(tile->tile); last_action = BaseAction::AnKan; - // 绔嬪嵆缈诲疂鐗屾寚绀虹墝 - // dora_spec++; // 杩欐槸鏆楁潬锛屾秷闄ゆ墍鏈変汉绗竴宸″拰ippatsu for (int i = 0; i < 4; ++i) { players[i].first_round = false; players[i].ippatsu = false; } + next_turn(turn); } +void Table::_handle_self_action_discard_riichi_impl() +{ + // 鍐冲畾涓嶈儭鐗岋紝鍒欎笉鍏锋湁ippatsu鐘舵 + players[turn].ippatsu = false; + + // 涓婁釜鍔ㄤ綔鏄潬/鍔犳潬锛屽垯鍦╯elf action鍐冲畾鍚庣炕dora + if (last_action == BaseAction::Kan || last_action == BaseAction::KaKan) + new_dora(); + + tile = selected_action.correspond_tiles[0]; + // 绛夊緟鍥炲 + + // 澶ч儴鍒嗘儏鍐甸兘鏄墜鍒, 闄や簡涓婁竴姝ュ姩浣滀负 鍑虹墝 鍔犳潬 鏆楁潬 + // 骞朵笖鍒ゅ畾鎶夋嫨寮冪墝鏄笉鏄渶鍚庝竴寮犵墝 + + bool is_from_hand = DiscardFromHand; + if (last_action != BaseAction::Chi && + last_action != BaseAction::Pon) { + // tile鏄笉鏄渶鍚庝竴寮 + if (tile == players[turn].hand.back()) + is_from_hand = DiscardFromTsumo; + } + players[turn].execute_discard(tile, river_counter, + selected_action.action == BaseAction::Riichi, is_from_hand); + + /* Log discard before any response */ + if (selected_action.action == BaseAction::Discard) + gamelog.log_discard(turn, tile, is_from_hand); + else if (selected_action.action == BaseAction::Riichi) + gamelog.log_riichi_discard(turn, tile, is_from_hand); + + phase = P1_RESPONSE; + if (0 == turn) { + ResponseAction ra; + ra.action = BaseAction::Pass; + response_actions = { ra }; + } + else { + // 瀵逛簬鎵鏈夊叾浠栦汉 + bool is_next = false; + if (0 == (turn + 1) % 4) + is_next = true; + response_actions = _generate_response_actions(0, tile, is_next); + } +} + +void Table::_handle_self_action_kakan_impl() +{ + // 涓婁釜鍔ㄤ綔鏄潬/鍔犳潬锛屽垯鍦╯elf action鍐冲畾鍚庣炕dora + if (last_action == BaseAction::Kan || last_action == BaseAction::KaKan) + new_dora(); + + tile = selected_action.correspond_tiles[0]; + + /* log kakan before response*/ + gamelog.log_kakan(turn, tile); + + // 绗竴宸℃秷闄 + players[turn].first_round = false; + // 绛夊緟鍥炲 + phase = P1_CHANKAN_RESPONSE; + if (0 == turn) { + ResponseAction ra; + ra.action = BaseAction::Pass; + response_actions = { ra }; + } + else { + response_actions = _generate_chankan_response_actions(0, tile); + } +} + +inline void Table::_handle_self_action_ankan_impl() +{ + // 涓婁釜鍔ㄤ綔鏄潬/鍔犳潬锛屽垯鍦╯elf action鍐冲畾鍚庣炕dora + if (last_action == BaseAction::Kan || last_action == BaseAction::KaKan) + new_dora(); + + tile = selected_action.correspond_tiles[0]; + + /* log ankan before response*/ + gamelog.log_ankan(turn, selected_action.correspond_tiles); + + // 绗竴宸℃秷闄 + players[turn].first_round = false; + // 绛夊緟鍥炲 + phase = P1_CHANANKAN_RESPONSE; + if (turn == 0) { + ResponseAction ra; + ra.action = BaseAction::Pass; + response_actions = { ra }; + } + else { + response_actions = _generate_chanankan_response_actions(0, tile); + } + // 鏆楁潬涓嶇瓑response锛岀洿鎺ョ炕dora + new_dora(); +} + +void Table::_handle_response_final_pass_impl() +{ + if (selected_action.action == BaseAction::Riichi) { + // 绔嬬洿鎴愬姛 + if (players[turn].first_round) { + players[turn].double_riichi = true; + } + players[turn].riichi = true; + kyoutaku++; + players[turn].score -= 1000; + players[turn].ippatsu = true; + + /* log riichi success if no ron */ + gamelog.log_riichi_success(this); + } + + // 娑堥櫎绗竴宸 + players[turn].first_round = false; + next_turn((turn + 1) % 4); + last_action = selected_action.action; +} + +void Table::_handle_response_final_chiponkan_impl(int response) +{ + if (selected_action.action == BaseAction::Riichi) { + // 绔嬬洿鎴愬姛 + if (players[turn].first_round) { + players[turn].double_riichi = true; + } + players[turn].riichi = true; + kyoutaku++; + players[turn].score -= 1000; + // 绔嬬洿鍗抽福鐗岋紝涓瀹氭病鏈塱ppatsu + + /* log riichi success if no ron */ + gamelog.log_riichi_success(this); + } + + players[turn].set_not_remained(); + + players[response].execute_naki( + actions[response].correspond_tiles, tile, (turn - response) % 4); + + gamelog.log_call(response, turn, tile, actions[response].correspond_tiles, final_action); + + // 杩欐槸楦g墝锛屾秷闄ゆ墍鏈変汉绗竴宸″拰ippatsu + for (int i = 0; i < 4; ++i) { + players[i].first_round = false; + players[i].ippatsu = false; + } + + // 鍒囨崲turn + next_turn(response); + last_action = final_action; +} + void Table::_handle_self_action() { switch (selected_action.action) { case BaseAction::Kyushukyuhai: - result = generate_result_9hai(this); gamelog.log_kyushukyuhai(turn, result); - + result = generate_result_9hai(this); + gamelog.log_gameover(result); phase = GAME_OVER; return; case BaseAction::Tsumo: + gamelog.log_tsumo(turn); result = generate_result_tsumo(this); + gamelog.log_gameover(result); phase = GAME_OVER; return; case BaseAction::Discard: case BaseAction::Riichi: { - // 鍐冲畾涓嶈儭鐗岋紝鍒欎笉鍏锋湁ippatsu鐘舵 - players[turn].ippatsu = false; - - tile = selected_action.correspond_tiles[0]; - // 绛夊緟鍥炲 - - // 澶ч儴鍒嗘儏鍐甸兘鏄墜鍒, 闄や簡涓婁竴姝ュ姩浣滀负 鍑虹墝 鍔犳潬 鏆楁潬 - // 骞朵笖鍒ゅ畾鎶夋嫨寮冪墝鏄笉鏄渶鍚庝竴寮犵墝 - - bool is_from_hand = DiscardFromHand; - if (last_action == BaseAction::Discard || - last_action == BaseAction::KaKan || - last_action == BaseAction::AnKan) { - // tile鏄笉鏄渶鍚庝竴寮 - if (tile == players[turn].hand.back()) - is_from_hand = DiscardFromTsumo; - } - players[turn].execute_discard(tile, river_counter, selected_action.action == BaseAction::Riichi, is_from_hand); - - phase = P1_RESPONSE; - if (0 == turn) { - ResponseAction ra; - ra.action = BaseAction::Pass; - response_actions = { ra }; - } - else { - // 瀵逛簬鎵鏈夊叾浠栦汉 - bool is_next = false; - if (0 == (turn + 1) % 4) - is_next = true; - response_actions = _generate_response_actions(0, tile, is_next); - } + _handle_self_action_discard_riichi_impl(); return; } case BaseAction::KaKan: { - tile = selected_action.correspond_tiles[0]; - - // 绗竴宸℃秷闄 - players[turn].first_round = false; - // 绛夊緟鍥炲 - phase = P1_CHANKAN_RESPONSE; - if (0 == turn) { - ResponseAction ra; - ra.action = BaseAction::Pass; - response_actions = { ra }; - } - else { - response_actions = _generate_chankan_response_actions(0, tile); - } + _handle_self_action_kakan_impl(); return; } case BaseAction::AnKan: { - tile = selected_action.correspond_tiles[0]; - - // 绗竴宸℃秷闄 - players[turn].first_round = false; - // 绛夊緟鍥炲 - phase = P1_CHANANKAN_RESPONSE; - if (turn == 0) { - ResponseAction ra; - ra.action = BaseAction::Pass; - response_actions = { ra }; - } - else { - response_actions = _generate_chanankan_response_actions(0, tile); - } - + _handle_self_action_ankan_impl(); return; } default: @@ -979,9 +1163,12 @@ void Table::make_selection(int selection_) case P4_ACTION: _handle_self_action(); - /* ready for response/chankan_response/chanankan_response */ - actions.resize(0); - final_action = BaseAction::Pass; + if (phase != GAME_OVER) + { + /* ready for response/chankan_response/chanankan_response */ + actions.resize(0); + final_action = BaseAction::Pass; + } return; // P1 P2 P3渚濇鍋氬嚭鎶夋嫨锛屾帹鍏ctions锛屽苟涓斾负涓嬩竴浣嶇帺瀹剁敓鎴愭妷鎷╋紝鏀瑰彉phase diff --git a/Mahjong/Table.h b/Mahjong/Table.h index dbe20ece..b69fe7b5 100644 --- a/Mahjong/Table.h +++ b/Mahjong/Table.h @@ -9,10 +9,13 @@ #include "Player.h" #include "fmt/os.h" #include +#include +#include namespace_mahjong -constexpr auto N_TILES = (34 * 4); +constexpr auto N_BASETILES = 34; +constexpr auto N_TILES = N_BASETILES * 4; class Table { @@ -25,7 +28,7 @@ class Table // 鐗屽北鐨勮捣濮嬫槸宀笂鐗(0,1,2,3) // 鐒跺悗鏍囪瀹濈墝鍜寀ra鐨勪綅缃(5,7,9,11,13)銆(4,6,8,10,12) - // 鍒濆鎯呭喌dora_spec = 1锛屾瘡娆$炕瀹濈墝鍙渶瑕乨ora_spec++ + // 鍒濆鎯呭喌n_active_dora = 1锛屾瘡娆$炕瀹濈墝鍙渶瑕乶_active_dora++ // 鏉犵殑鏃跺欐墍鏈夌墝鍦╲ector鐗屽北涓綅缃彉鍖栫殑 // 骞朵笉浼氬奖鍝嶅埌瀹濈墝/ura鐨勪綅缃紝鍥犱负瀹冧滑涓寮濮嬪氨琚爣璁拌繃浜 // 褰撶墝灞.size() <= 14鐨勬椂鍊欑粨鏉熸父鎴 @@ -45,6 +48,7 @@ class Table std::vector yama_log; std::string log_buffer_prefix; std::vector selection_log; + std::default_random_engine rd; /* Set seed (set_seed) before init*/ bool use_seed = false; @@ -53,7 +57,7 @@ class Table public: Table() = default; - inline void new_dora() { n_active_dora++; } + void new_dora(); std::vector get_dora() const; std::vector get_ura_dora() const; @@ -70,17 +74,30 @@ class Table void init_yama(); void init_dora(); void init_before_playing(); - // void import_yama(std::string yama); void import_yama(const std::vector &yama); std::string export_yama(); void init_wind(); - void draw(int i_player); - void draw(int i_player, int n_tiles); + // game init draw with tenhou style void draw_tenhou_style(); + + // Draw from the head (the normal sequence) void draw_normal(int i_player); + + // Draw from the head without record. + // This is for initializing the game + void draw_normal_no_record(int i_player); + + // Draw n tiles from the head (the normal sequence) + // **No Record**, implemented by ``draw_normal_no_record'' + void draw_n_normal(int i_player, int n_tiles); + + // Draw from the tail (from rinshan) void draw_rinshan(int i_player); + // Reshuffle the remaining yama to allow replay from interval + void reshuffle_yama(unsigned int seed); + void next_turn(int nextturn); /* debug mode @@ -164,8 +181,36 @@ class Table public: // ---------------------Manual Mode------------------------------ - // The following part is for the manual instead of the automatic. - // Never mix using GameProcess and using the following functions. + // Stage: + // self_action - response(0) - response(1) - response(2) - response(3) - execute + // + // Usage: + // 0. get_phase() : to obtain the PhaseEnum + // 1. get_self_actions() / get_response_actions() + // 2. make_selection(int) : to input the selection and automatically move to the next stage + // + // Implementation: + // ... (previous steps) + // -> from_beginning + // -> _generate_self_actions + // make_selection + // -> _handle_self_action + // -> _generate_response_actions (0) + // make_selection + // -> _handle_response_action (0) + // -> _generate_response_actions (1) + // make_selection + // -> _handle_response_action (1) + // -> _generate_response_actions (2) + // make_selection + // -> _handle_response_action (2) + // -> _generate_response_actions (3) + // make_selection + // -> _handle_response_action (3) + // -> _handle_response_final_execution + // -> next_turn + // -> from_beginning (loop 猬) + // // -------------------------------------------------------------- enum PhaseEnum { P1_ACTION, P2_ACTION, P3_ACTION, P4_ACTION, @@ -174,23 +219,32 @@ class Table P1_CHANANKAN_RESPONSE, P2_CHANANKAN_RESPONSE, P3_CHANANKAN_RESPONSE, P4_CHANANKAN_RESPONSE, GAME_OVER, UNINITIALIZED, }; - std::vector self_actions; - std::vector response_actions; + std::vector self_actions; // prepared list for self actions + std::vector response_actions; // prepared list for response actions Result result; PhaseEnum phase = UNINITIALIZED; // initialized to UNINITIALIZED to avoid illegal gameplay. int selection = -1; // initialized to -1 to avoid illegal gameplay. - SelfAction selected_action; - Tile* tile = nullptr; + SelfAction selected_action; // the action selected in the self_action stage + Tile* tile = nullptr; // the corresponding tile of the self_action std::vector actions; // response actions - // bool FROM_鎵嬪垏鎽稿垏 = false; // global variable for river log - BaseAction final_action = BaseAction::Pass; + BaseAction final_action = BaseAction::Pass; // final response action (ron > pon/kan > chi > pass) void from_beginning(); // Initialize the game. void game_init(); - void game_init_for_replay(const std::vector &yama, const std::vector &init_scores, int 绔嬬洿妫, int 鏈満, int 鍦洪, int 浜插); + + // Initialize the game with configurable configs + // yama: Empty for random shuffle, or size=136 for fixed yama + // init_scores: Empty for 25000 init scores, or size=4 + // kyoutaku: -1 for default (0) or set to the initial kyoutaku + // honba: -1 for default (0) or set to the initial honba + // game_wind: 0~3 to set the initial game wind, or default (East) + // oya: 0~3 to set the oya, or default (player 0) + void game_init_with_config(const std::vector& yama, const std::vector& init_scores, int kyoutaku, int honba, int game_wind, int oya); + + void game_init_for_replay(const std::vector &yama, const std::vector &init_scores, int kyoutaku, int honba, int game_wind, int oya); void game_init_with_metadata(std::unordered_map metadata); // Get the phase of the game @@ -236,6 +290,32 @@ class Table void _handle_response_final_chankan_execution(); void _handle_response_final_chanankan_execution(); + void _handle_self_action_discard_riichi_impl(); + void _handle_self_action_kakan_impl(); + void _handle_self_action_ankan_impl(); + void _handle_response_final_pass_impl(); + void _handle_response_final_chiponkan_impl(int response); + void _handle_response_final_ron_impl(const std::vector& response_players) + { + /* 涓烘瘡涓涓儭鐗岀殑鐜╁鐢熸垚gamelog */ + for (int player : response_players) + { + gamelog.log_ron(player, turn, selected_action.correspond_tiles[0]); + } + + /* 3鍝 */ + if (response_players.size() == 3) + { + result = generate_result_3ron(this); + } + else + { + result = generate_result_ron(this, selected_action.correspond_tiles[0], response_players); + } + gamelog.log_gameover(result); + phase = GAME_OVER; + } + // Get Information. Return the table itself. (For python wrapper) inline Table* get_info() { return this; } diff --git a/Mahjong/Tile.h b/Mahjong/Tile.h index 738abff2..a8b3cd62 100644 --- a/Mahjong/Tile.h +++ b/Mahjong/Tile.h @@ -233,6 +233,18 @@ inline std::string wind_to_string(Wind wind) { } } +inline std::string vector_tile_to_string(const std::vector& tiles) +{ + std::stringstream ss; + ss << "["; + for (auto tile : tiles) + { + ss << tile->to_string(); + } + ss << "]"; + return ss.str(); +} + struct CallGroup { enum Type { Chi, diff --git a/Pybinder/EncodingPy.cpp b/Pybinder/EncodingPy.cpp index a1472091..e4e661a9 100644 --- a/Pybinder/EncodingPy.cpp +++ b/Pybinder/EncodingPy.cpp @@ -37,7 +37,6 @@ namespace TrainingDataEncoding { { return get_riichi_tiles(table); } - } } diff --git a/Pybinder/EncodingPy.h b/Pybinder/EncodingPy.h index 878f2430..d2bedba3 100644 --- a/Pybinder/EncodingPy.h +++ b/Pybinder/EncodingPy.h @@ -2,6 +2,7 @@ #define ENCODINGPY_H #include "Encoding/TrainingDataEncodingV1.h" +#include "Encoding/TrainingDataEncodingV2.h" #include "pybind11/numpy.h" #include "Table.h" @@ -20,5 +21,6 @@ namespace TrainingDataEncoding { std::vector py_get_riichi_tiles(const Table& table); } } + namespace_mahjong_end #endif \ No newline at end of file diff --git a/Pybinder/MahjongPy.cpp b/Pybinder/MahjongPy.cpp index afe7bf11..209beec6 100644 --- a/Pybinder/MahjongPy.cpp +++ b/Pybinder/MahjongPy.cpp @@ -158,14 +158,16 @@ PYBIND11_MODULE(MahjongPyWrapper, m) .def_readonly("riichi_furiten", &Player::furiten_riichi) .def_readonly("score", &Player::score) .def_readonly("hand", &Player::hand) - // .def_readonly("fulus", &Player::鍓湶s) - // .def_readonly("river", &Player::river) .def_readonly("ippatsu", &Player::ippatsu) .def_readonly("first_round", &Player::first_round) + .def_readonly("atari_tiles", &Player::atari_tiles) // 鍑芥暟浠 .def("get_fuuros", &Player::get_fuuros) .def("get_river", &Player::get_river) + .def("is_tenpai", &Player::is_tenpai) + .def("is_menzen", &Player::is_menzen) + .def("is_riichi", &Player::is_riichi) .def("is_furiten", &Player::is_furiten) .def("to_string", &Player::to_string) .def("hand_to_string", &Player::to_string) @@ -179,7 +181,9 @@ PYBIND11_MODULE(MahjongPyWrapper, m) // 鍑芥暟浠 .def("game_init", &Table::game_init) + .def("game_init_with_config", &Table::game_init_with_config) .def("game_init_with_metadata", &Table::game_init_with_metadata) + .def("reshuffle_yama", &Table::reshuffle_yama) .def("get_phase", &Table::get_phase) .def("make_selection", &Table::make_selection) .def("get_selection_from_action_tile", &Table::get_selection_from_action_tile) @@ -229,9 +233,13 @@ PYBIND11_MODULE(MahjongPyWrapper, m) py::enum_(m, "ResultType") .value("RonAgari", ResultType::RonAgari) .value("TsumoAgari", ResultType::TsumoAgari) - .value("IntervalRyuuKyoku", ResultType::Ryukyouku_Interval) .value("NoTileRyuuKyoku", ResultType::Ryukyouku_Notile) .value("NagashiMangan", ResultType::NagashiMangan) + .value("Ryukyouku_Interval_9Hai", ResultType::Ryukyouku_Interval_9Hai) + .value("Ryukyouku_Interval_4Wind", ResultType::Ryukyouku_Interval_4Wind) + .value("Ryukyouku_Interval_4Riichi", ResultType::Ryukyouku_Interval_4Riichi) + .value("Ryukyouku_Interval_4Kan", ResultType::Ryukyouku_Interval_4Kan) + .value("Ryukyouku_Interval_3Ron", ResultType::Ryukyouku_Interval_3Ron) ; py::class_(m, "Result") @@ -240,6 +248,9 @@ PYBIND11_MODULE(MahjongPyWrapper, m) .def_readonly("score", &Result::score) .def_readonly("winner", &Result::winner) .def_readonly("loser", &Result::loser) + .def_readonly("n_riichibo", &Result::n_riichibo) + .def_readonly("n_honba", &Result::n_honba) + .def_readonly("renchan", &Result::renchan) .def("to_string", &Result::to_string) ; @@ -258,7 +269,7 @@ PYBIND11_MODULE(MahjongPyWrapper, m) .value("GameWind_West", Yaku::Bakaze_Sha) .value("GameWind_North", Yaku::Bakaze_Pei) .value("Yakuhai_haku", Yaku::Yakuhai_Haku) - .value("Yakuhai_hastu", Yaku::Yakuhai_Hatsu) + .value("Yakuhai_hatsu", Yaku::Yakuhai_Hatsu) .value("Yakuhai_chu", Yaku::Yakuhai_Chu) .value("Pinfu", Yaku::Pinfu) .value("Yiipeikou", Yaku::Ippeikou) @@ -271,7 +282,7 @@ PYBIND11_MODULE(MahjongPyWrapper, m) .value("UraDora", Yaku::Uradora) .value("AkaDora", Yaku::Akadora) .value("Chantai_", Yaku::Honchantaiyaochu_Naki) - .value("Ikkitsukan_", Yaku::Ikkitsuukan_Naki) + .value("Ikkitsuukan_", Yaku::Ikkitsuukan_Naki) .value("Sanshokudoujun_", Yaku::Sanshokudoujun_Naki) .value("DoubleRiichi", Yaku::Dabururiichi) @@ -367,6 +378,33 @@ PYBIND11_MODULE(MahjongPyWrapper, m) .def("generate_yama", &TenhouShuffle::generate_yama) ; + py::class_(m, "BaseGameLog") + .def_readonly("player", &BaseGameLog::player) + .def_readonly("player2", &BaseGameLog::player2) + .def_readonly("action", &BaseGameLog::action) + .def_readonly("tile", &BaseGameLog::tile) + .def_readonly("call_tiles", &BaseGameLog::call_tiles) + .def_readonly("score", &BaseGameLog::score) + .def("to_string", &BaseGameLog::to_string) + ; + + py::class_(m, "GameLog") + .def_readonly("winner", &GameLog::winner) + .def_readonly("loser", &GameLog::loser) + .def_readonly("start_scores", &GameLog::start_scores) + .def_readonly("init_yama", &GameLog::init_yama) + .def_readonly("init_hands", &GameLog::init_hands) + .def_readonly("start_honba", &GameLog::start_honba) + .def_readonly("end_honba", &GameLog::end_honba) + .def_readonly("start_kyoutaku", &GameLog::start_kyoutaku) + .def_readonly("end_kyoutaku", &GameLog::end_kyoutaku) + .def_readonly("oya", &GameLog::oya) + .def_readonly("game_wind", &GameLog::game_wind) + .def_readonly("result", &GameLog::result) + .def_readonly("logs", &GameLog::logs) + .def("to_string", &GameLog::to_string) + ; + auto get_self_action_index = [](const std::vector &actions, BaseAction action_type, std::vector correspond_tiles, bool use_red_dora) { return get_action_index(actions, action_type, correspond_tiles, use_red_dora); }; @@ -384,6 +422,42 @@ PYBIND11_MODULE(MahjongPyWrapper, m) m.def("encv1_encode_action", &encv1::py_encode_action); m.def("encv1_encode_action_riichi_step2", &encv1::py_encode_action_riichi_step2); m.def("encv1_get_riichi_tiles", &encv1::py_get_riichi_tiles); + + namespace encv2 = TrainingDataEncoding::v2; + + py::class_(m, "TableEncoder") + .def(py::init()) + .def_readonly("self_infos", &encv2::TableEncoder::self_infos) + .def_readonly("records", &encv2::TableEncoder::records) + .def_readonly("global_infos", &encv2::TableEncoder::global_infos) + .def("init", &encv2::TableEncoder::init) + .def("update", &encv2::TableEncoder::update) + .def("_require_update", &encv2::TableEncoder::_require_update) + .def_readonly("record_count", &encv2::TableEncoder::record_count) + ; + + py::class_(m, "PassiveTableEncoder") + .def(py::init<>()) + .def_readonly("self_info", &encv2::PassiveTableEncoder::self_info) + .def_readonly("records", &encv2::PassiveTableEncoder::records) + .def_readonly("global_info", &encv2::PassiveTableEncoder::global_info) + .def("encode_game_basic", &encv2::PassiveTableEncoder::encode_game_basic) + .def("encode_hand", &encv2::PassiveTableEncoder::encode_hand) + .def("encode_self_river", &encv2::PassiveTableEncoder::encode_self_river) + .def("encode_next_river", &encv2::PassiveTableEncoder::encode_next_river) + .def("encode_opposite_river", &encv2::PassiveTableEncoder::encode_opposite_river) + .def("encode_river", &encv2::PassiveTableEncoder::encode_river) + .def("encode_self_fuuro", &encv2::PassiveTableEncoder::encode_self_fuuro) + .def("encode_next_fuuro", &encv2::PassiveTableEncoder::encode_next_fuuro) + .def("encode_opposite_fuuro", &encv2::PassiveTableEncoder::encode_opposite_fuuro) + .def("encode_previous_fuuro", &encv2::PassiveTableEncoder::encode_previous_fuuro) + .def("encode_fuuro", &encv2::PassiveTableEncoder::encode_fuuro) + .def("encode_dora", &encv2::PassiveTableEncoder::encode_dora) + .def("encode_points", &encv2::PassiveTableEncoder::encode_points) + .def("encode_remaining_tiles", &encv2::PassiveTableEncoder::encode_remaining_tiles) + .def("encode_riichi_states", &encv2::PassiveTableEncoder::encode_riichi_states) + .def("encode_ippatsu_states", &encv2::PassiveTableEncoder::encode_ippatsu_states) + ; } #ifdef __GNUC__ diff --git a/pymahjong/env_pymahjong.py b/pymahjong/env_pymahjong.py index 42e7175a..ea1674c9 100644 --- a/pymahjong/env_pymahjong.py +++ b/pymahjong/env_pymahjong.py @@ -6,31 +6,36 @@ np.set_printoptions(threshold=np.inf) - class MahjongEnv(gym.Env): PLAYER_OBS_DIM = 93 ORACLE_OBS_DIM = 18 - ACTION_DIM = 47 + ACTION_DIM = 54 MAHJONG_TILE_TYPES = 34 INIT_POINTS = 25000 # ACTION INDICES - CHILEFT = 34 - CHIMIDDLE = 35 - CHIRIGHT = 36 - PON = 37 - ANKAN = 38 - MINKAN = 39 - KAKAN = 40 - - RIICHI = 41 - RON = 42 - TSUMO = 43 - PUSH = 44 - - PASS_RESPONSE = 45 - PASS_RIICHI = 46 + CHILEFT = 37 + CHIMIDDLE = 38 + CHIRIGHT = 39 + + CHILEFT_USERED = 40 + CHIMIDDLE_USERED = 41 + CHIRIGHT_USERED = 42 + + PON = 43 + PON_USERED = 44 + ANKAN = 45 + MINKAN = 46 + KAKAN = 47 + + RIICHI = 48 + RON = 49 + TSUMO = 50 + PUSH = 51 + + PASS_RIICHI = 52 + PASS_RESPONSE = 53 # corresponding to self.t.get_phase() Phases = ("P1_ACTION", "P2_ACTION", "P3_ACTION", "P4_ACTION", "P1_RESPONSE", "P2_RESPONSE", "P3_RESPONSE", @@ -39,7 +44,7 @@ class MahjongEnv(gym.Env): "P1_DRAW, P2_DRAW, P3_DRAW, P4_DRAW") # pymahjhong.BaseAction - ACTION_TYPES = [pm.BaseAction.Discard] * MAHJONG_TILE_TYPES + [pm.BaseAction.Chi] * 3 + [pm.BaseAction.Pon] \ + ACTION_TYPES = [pm.BaseAction.Discard] * (MAHJONG_TILE_TYPES + 3) + [pm.BaseAction.Chi] * 6 + [pm.BaseAction.Pon] * 2 \ + [pm.BaseAction.AnKan] + [pm.BaseAction.Kan] + [pm.BaseAction.KaKan] \ + [pm.BaseAction.Riichi] + [pm.BaseAction.Ron] + [pm.BaseAction.Tsumo] \ + [pm.BaseAction.Kyushukyuhai] + [pm.BaseAction.Pass] * 2 @@ -60,6 +65,8 @@ def __init__(self): self.obs_container = np.zeros([self.PLAYER_OBS_DIM + self.ORACLE_OBS_DIM, self.MAHJONG_TILE_TYPES], dtype=np.int8) self.act_container = np.zeros([self.ACTION_DIM], dtype=np.int8) + self.use_red_dora = False + def _check_player(self, player_id): if not player_id == self.t.who_make_selection(): raise ValueError("You are trying to obtain information from a player who is not making decision !!!! \ @@ -93,7 +100,24 @@ def _get_num_aval_actions(self): return len(aval_actions) - def reset(self, oya=None, game_wind=None, seed=None, debug_mode=None): + def reset( + self, *, + oya=None, + game_wind=None, + scores=None, + seed=None, + kyoutaku=0, + honba=0, + debug_mode=None + ): + if scores is None: + scores = [25000, 25000, 25000, 25000] + else: + assert len(scores) == 4, "scores should be a list of length 4" + + assert isinstance(kyoutaku, int) + assert isinstance(honba, int) + if oya is None: oya = self.game_count % 4 # Each player alternatively be the "Oya" (parent) else: @@ -107,14 +131,22 @@ def reset(self, oya=None, game_wind=None, seed=None, debug_mode=None): self.t = pm.Table() if seed is not None: self.t.seed = seed - + if debug_mode is not None: self.t.set_debug_mode(debug_mode) - self.t.game_init_with_metadata({"oya": str(oya), "wind": game_wind}) + self.t.game_init_with_config( + [], + scores, + kyoutaku, + honba, + ["east", "south", "west", "north"].index(game_wind), + oya, + ) + + # self.t.game_init_with_metadata({"oya": str(oya), "wind": game_wind}) self.riichi_stage2 = False self.may_riichi_tile_id = None - self.game_count += 1 self._proceed() @@ -127,6 +159,8 @@ def step(self, player_id: int, action: int): # Use get_obs(player_id) or get_full_obs(player_id) or get_oracle_obs(player_id) to get observation # For rewards, after the game is over, one may use .get_payoffs + # self.use_red_dora = False + if not player_id == self.get_curr_player_id(): raise ValueError("current acting player ID is {}, but you are trying to ask player {} to act !!".format( self.get_curr_player_id(), player_id)) @@ -156,30 +190,63 @@ def step(self, player_id: int, action: int): if action < self.MAHJONG_TILE_TYPES: corresponding_tiles = [action] - - elif action in (self.CHILEFT, self.CHIMIDDLE, self.CHIRIGHT): + self.use_red_dora = False + elif action == 34: # red 5m + corresponding_tiles = [4] + self.use_red_dora = True + elif action == 35: # red 5p + corresponding_tiles = [13] + self.use_red_dora = True + elif action == 36: # red 5s + corresponding_tiles = [22] + self.use_red_dora = True + + elif action in (self.CHILEFT, self.CHILEFT_USERED, self.CHIMIDDLE, self.CHIMIDDLE_USERED, self.CHIRIGHT, self.CHIRIGHT_USERED): chi_tile_id = int(self.t.get_selected_action_tile().tile) if action == self.CHILEFT: corresponding_tiles = [chi_tile_id + 1, chi_tile_id + 2] + self.use_red_dora = False elif action == self.CHIMIDDLE: corresponding_tiles = [chi_tile_id - 1, chi_tile_id + 1] + self.use_red_dora = False elif action == self.CHIRIGHT: corresponding_tiles = [chi_tile_id - 2, chi_tile_id - 1] + self.use_red_dora = False + if action == self.CHILEFT_USERED: + corresponding_tiles = [chi_tile_id + 1, chi_tile_id + 2] + self.use_red_dora = True + elif action == self.CHIMIDDLE_USERED: + corresponding_tiles = [chi_tile_id - 1, chi_tile_id + 1] + self.use_red_dora = True + elif action == self.CHIRIGHT_USERED: + corresponding_tiles = [chi_tile_id - 2, chi_tile_id - 1] + self.use_red_dora = True elif action == self.PON: pon_tile_id = int(self.t.get_selected_action_tile().tile) corresponding_tiles = [pon_tile_id, pon_tile_id] + self.use_red_dora = False + + elif action == self.PON_USERED: + pon_tile_id = int(self.t.get_selected_action_tile().tile) + corresponding_tiles = [pon_tile_id, pon_tile_id] + self.use_red_dora = True elif action == self.MINKAN: kan_tile_id = int(self.t.get_selected_action_tile().tile) corresponding_tiles = [kan_tile_id] * 3 - # Here we have an approximation, it a player has multiple options to KaKan or AnKan, - # the player will random select one if action == ANKAN or KAKAN - # However, this case should be very rare in normal play - elif action == self.ANKAN: - kan_tile_id = np.random.choice(np.argwhere(self.get_obs(curr_pid)[3]).flatten()) + if self.t.players[player_id].double_riichi or self.t.players[player_id].riichi: + # If has riichi, the player can only ANKAN the latest drawed tile + kan_tile_id = int(self.t.players[player_id].hand[-1].tile) + else: + + # Here we have an approximation, it a player has multiple options to KaKan or AnKan, + # the player will random select one if action == ANKAN or KAKAN + # However, this case should be very rare in normal play + kan_tile_id = np.random.choice(np.argwhere(self.get_obs(curr_pid)[3]).flatten()) + corresponding_tiles = [kan_tile_id] * 4 elif action == self.KAKAN: @@ -203,12 +270,17 @@ def step(self, player_id: int, action: int): for ron_type in [pm.BaseAction.Ron, pm.BaseAction.ChanKan, pm.BaseAction.ChanAnKan]: try: self.t.make_selection_from_action_basetile( - ron_type, corresponding_tiles, action >= self.MAHJONG_TILE_TYPES) + ron_type, corresponding_tiles, self.use_red_dora) except: pass else: - self.t.make_selection_from_action_basetile( - action_type, corresponding_tiles, action >= self.MAHJONG_TILE_TYPES) + try: + self.t.make_selection_from_action_basetile( + action_type, corresponding_tiles, self.use_red_dora) + except: + # If riichi and ANKAN the latest drawed tile failed + self.t.make_selection_from_action_basetile( + pm.BaseAction.Discard, [int(self.t.players[player_id].hand[-1].tile)], self.t.players[player_id].hand[-1].red_dora) except Exception as inst: print("-------------- execption in make_selection_from_action_basetile ------------------") @@ -226,7 +298,7 @@ def step(self, player_id: int, action: int): else: action_type = pm.BaseAction.Discard - self.t.make_selection_from_action_basetile(action_type, [self.may_riichi_tile_id], False) + self.t.make_selection_from_action_basetile(action_type, [self.may_riichi_tile_id], self.use_red_dora) self.riichi_stage2 = False self.may_riichi_tile_id = None @@ -334,9 +406,9 @@ def __init__(self, opponent_agent="random"): self.opponent_agent.device = device self.opponent_agent.to(device=device) print("----- CUDA detected, using CUDA for inference. -----") - + self.opponent_agent.eval() # not necessary now, but remained for future - + except Exception as e: print(e) raise FileNotFoundError("opponent_agent should be 'random' or the path of a pre-trained model (You can download the pre-trained model from pymahjong github release: https://github.com/Agony5757/mahjong/releases ") diff --git a/pymahjong/test/testV2.ipynb b/pymahjong/test/testV2.ipynb new file mode 100644 index 00000000..c1f462cc --- /dev/null +++ b/pymahjong/test/testV2.ipynb @@ -0,0 +1,316 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "acf9f02e-043f-4e4c-ab6d-25ebf3c91d20", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import numpy as np\n", + "import pymahjong as pm\n", + "from pymahjong import MahjongEnv\n", + "import traceback\n", + "\n", + "env = MahjongEnv()\n", + "num_games = 1\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe73466f-2438-4f1e-a382-43cfc6c01150", + "metadata": {}, + "outputs": [], + "source": [ + "UNICODE_TILES = \"\"\"\n", + " 馃 馃 馃 馃 馃 馃 馃 馃 馃 \n", + " 馃 馃 馃 馃 馃 馃 馃 馃 馃n", + " 馃 馃 馃 馃 馃 馃 馃 馃 馃榎n", + " 馃 馃 馃 馃僜n", + " 馃 馃 馃刓n", + " 馃 馃 馃擻n", + "\"\"\".split()\n", + "\n", + "ACTIONS = [\"鎽哥墝\", \"鎽告潬鐗孿", \"鎵嬪垏\", \"鎽稿垏\", \"鍚僉\", \"鍚僊\", \"鍚僐\", \"纰癨", \"鏄庢潬\", \"鏆楁潬\", \"鍔犳潬\", \"鎽稿垏绔嬬洿\", \"鎵嬪垏绔嬬洿\", \"绔嬬洿閫氳繃\", \"鍏宠仈鑷\", \"鍏宠仈涓嬪\", \"鍏宠仈瀵瑰\", \"鍏宠仈涓婂\"] \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ffe75f4-9a95-4734-bc5f-86b7514cbcd0", + "metadata": {}, + "outputs": [], + "source": [ + "def render_global_info(global_info):\n", + " explains = [\"灞鏁癨", \"鏈缁堝眬\", \"鏈満鏁癨", \"鍦轰緵鏁癨", \"鑷鐗孿", \"鍦洪鐗孿", \"鑷鐐规暟\", \"涓嬪鐐规暟\", \"瀵归潰鐐规暟\", \"涓婂鐐规暟\", \"鑷涓鍙慭", \"涓嬪涓鍙慭", \"瀵归潰涓鍙慭", \"涓婂涓鍙慭", \"鍓╀綑鐗屾暟\"]\n", + " for i, e in enumerate(explains):\n", + " print(e + \":\" + str(global_info[i]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d554900-9fde-46e8-aa90-d7a0ea5723c3", + "metadata": {}, + "outputs": [], + "source": [ + "def render_encoding_record(record):\n", + " a = np.argwhere(np.array(record)).reshape([-1])\n", + " action_strs = []\n", + " for i in a:\n", + " if i < 37:\n", + " action_strs.append(UNICODE_TILES[i])\n", + " else:\n", + " action_strs.append(ACTIONS[i - 37])\n", + " \n", + " print(\", \".join(action_strs))\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "528781c9-7483-424c-bc78-cfa6a83b86f7", + "metadata": {}, + "outputs": [], + "source": [ + "def render_encoding_self_info(self_info):\n", + " # 0-3\n", + " \n", + " hands = list(np.argwhere(self_info[:, 0]).reshape([-1])) + list(np.argwhere(self_info[:, 1]).reshape([-1])) + list(np.argwhere(self_info[:, 2]).reshape([-1])) + list(np.argwhere(self_info[:, 3]).reshape([-1]))\n", + " hands.sort()\n", + " print(\"-------- Hand -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in hands]))\n", + " \n", + " hand_akas = list(np.argwhere(self_info[:, 6]).reshape([-1])) \n", + " print(\"-------- Aka -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in hand_akas]))\n", + " \n", + " \n", + " doras = list(np.argwhere(self_info[:, 4]).reshape([-1]))\n", + " print(\"-------- Dora -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in doras]))\n", + " \n", + " dora_indicators = list(np.argwhere(self_info[:, 5]).reshape([-1]))\n", + " print(\"-------- Dora Indicator -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in dora_indicators]))\n", + " \n", + " changfengs = list(np.argwhere(self_info[:, 7]).reshape([-1]))\n", + " print(\"-------- Game Wind -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in changfengs]))\n", + " \n", + " zifengs = list(np.argwhere(self_info[:, 8]).reshape([-1]))\n", + " print(\"-------- Self Wind -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in zifengs]))\n", + " \n", + " \n", + " zifengs = list(np.argwhere(self_info[:, 9]).reshape([-1]))\n", + " print(\"-------- Tsumo Tiles -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in zifengs]))\n", + " \n", + " tmp = list(np.argwhere(self_info[:, 10]).reshape([-1]))\n", + " print(\"-------- Self Discarded -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in tmp]))\n", + " \n", + " tmp = list(np.argwhere(self_info[:, 11]).reshape([-1]))\n", + " print(\"-------- Next Discarded -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in tmp]))\n", + " \n", + " tmp = list(np.argwhere(self_info[:, 12]).reshape([-1]))\n", + " print(\"-------- Opposite Discarded -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in tmp]))\n", + " \n", + " tmp = list(np.argwhere(self_info[:, 13]).reshape([-1]))\n", + " print(\"-------- Previous Discarded -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in tmp]))\n", + " \n", + " \n", + " tmp = list(np.argwhere(self_info[:, 14]).reshape([-1])) + list(np.argwhere(self_info[:, 15]).reshape([-1])) + list(np.argwhere(self_info[:, 16]).reshape([-1])) + list(np.argwhere(self_info[:, 17]).reshape([-1]))\n", + " tmp.sort()\n", + " print(\"-------- Disclosed Tiles -------------\")\n", + " print(\"\".join([UNICODE_TILES[i] for i in tmp]))\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de0622cd-cecb-4f79-9b94-9075fd9c5c6d", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d185f77f-39cf-4daf-be55-39cea4addf5b", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "\n", + "\n", + "start_time = time.time()\n", + "game = 0\n", + "success_games = 0\n", + "\n", + "winds = [\"east\", \"south\", \"west\", \"north\"]\n", + "\n", + "while game < num_games:\n", + " \n", + " try:\n", + " \n", + " # env.reset(oya=game % 4, game_wind=winds[game % 3], debug_mode=1)\n", + " env.reset(oya=2, game_wind='south', debug_mode=1)\n", + "\n", + " # encoder\n", + " te = pm.TableEncoder(env.t)\n", + " te.init()\n", + " \n", + " te.update()\n", + "\n", + " # print(np.array(te.self_infos[0]).reshape([18, 34]).swapaxes(0, 1))\n", + " for i in range(4):\n", + " print(\"涓滈灞锛屽簞瀹舵槸 {}, player {} global info\".format(game % 4, i), te.global_infos[i])\n", + " \n", + " while not env.is_over():\n", + "\n", + " curr_player_id = env.get_curr_player_id()\n", + "\n", + " # --------- get decision information -------------\n", + "\n", + " valid_actions_mask = env.get_valid_actions(nhot=True)\n", + " executor_obs = env.get_obs(curr_player_id)\n", + "\n", + " # oracle_obs = env.get_oracle_obs(curr_player_id)\n", + " # full_obs = env.get_full_obs(curr_player_id)\n", + " # full_obs = np.concatenate([executor_obs, oracle_obs], axis=0)\n", + "\n", + " # --------- make decision -------------\n", + "\n", + " a = np.random.choice(np.argwhere(\n", + " valid_actions_mask).reshape([-1]))\n", + " \n", + " env.step(curr_player_id, a)\n", + " \n", + " # ------- update state encoding ------------\n", + " \n", + " env.render()\n", + " te.update()\n", + " \n", + " for i in range(4):\n", + " \n", + " print(\"==========================================================\")\n", + " print(\"==================== Turn {} Player {} =====================\".format(env.t.turn, i))\n", + " print(\"==========================================================\")\n", + " \n", + " print(\"---------- Self Info --------------\")\n", + " obs = np.array(te.self_infos[i]).reshape([18, 34]).swapaxes(0, 1)\n", + " print(obs)\n", + " render_encoding_self_info(obs)\n", + " \n", + " print(\"---------- Records Info --------------\")\n", + " rcd = np.array(te.records[i][-1])\n", + " print(rcd)\n", + " render_encoding_record(rcd)\n", + " \n", + " print(\"---------- Global Info --------------\")\n", + " gin = np.array(te.global_infos[i])\n", + " print(gin)\n", + " render_global_info(gin)\n", + " \n", + " \n", + " # print(\"涓滈灞锛屽簞瀹舵槸 {}, player {} global info\".format(game % 4, i), te.global_infos[i])\n", + " \n", + " # break\n", + " # print(te.records[0][-1])\n", + "\n", + " # ----------------------- get result ---------------------------------\n", + "\n", + " payoffs = np.array(env.get_payoffs())\n", + " print(\"Game {}, payoffs: {}\".format(game, payoffs))\n", + " # env.render()\n", + "\n", + " success_games += 1\n", + " game += 1\n", + "\n", + " except Exception as inst:\n", + " game += 1\n", + " time.sleep(0.1)\n", + " print(\n", + " \"-------------- execption in game {} -------------------------\".format(game))\n", + " print('Exception: ', inst)\n", + " print(\"----------------- Traceback ---------------------------------\")\n", + " traceback.print_exc()\n", + " env.render()\n", + " print(\"-------------- replayable log -------------------------------\")\n", + " env.t.print_debug_replay()\n", + " continue\n", + "\n", + "print(\"Total {} random-play games, {} games without error, takes {} s\".format(\n", + " num_games, success_games, time.time() - start_time))\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bf99a847-5838-4329-a403-c7eb4a1b34f1", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a830100a-9679-4894-828e-d69eb7420a42", + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "x = torch.zeros([2,4])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7607cba7-8216-4399-be32-3e73da68145b", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df4307f3-d361-4583-8cf4-e0ccdc49b472", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/setup.py b/setup.py index 33fa54e4..a37a2279 100644 --- a/setup.py +++ b/setup.py @@ -145,7 +145,7 @@ def build_extension(self, ext): setup( name = "pymahjong", - version = "1.0.3", + version = "1.0.4", author = "Agony", author_email = "chenzhaoyun@iai.ustc.edu.cn", description= "A Japanese Mahjong environment for decision AI research.", @@ -158,7 +158,7 @@ def build_extension(self, ext): }, classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f], packages = ['pymahjong'], - install_requires=['numpy', 'gym'], + install_requires=['numpy', 'gym<=0.26.2'], zip_safe = False, python_requires='>=3.6', ) \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp index 8250963c..2edc1dc2 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -291,10 +291,61 @@ void test_random_play(int games = 10) fmt::print("{}", profiler::get_all_profiles_v2()); } +void test_random_play_v2(int games = 1000) +{ + namespace enc = TrainingDataEncoding::v2; + + std::default_random_engine reng; + reng.seed(time(nullptr)); + std::uniform_real_distribution ud(0, 1); + + static auto random_action = [&reng, &ud](auto actions) { + return size_t(ud(reng) * actions.size()); + }; + timer t; + + for (int i = 0; i < games; ++i) { + fmt::print("Game {} / {}\n", i + 1, games); + Table t; + enc::TableEncoder encoder(&t); + t.set_debug_mode(Table::debug_close); + t.game_init(); + encoder.init(); + encoder.update(); + /*fmt::print("{}\n", encoder.self_infos[0]); + fmt::print("{}\n", encoder.self_infos[1]); + fmt::print("{}\n", encoder.self_infos[2]); + fmt::print("{}\n", encoder.self_infos[3]);*/ + do { + int selection; + if (t.is_self_acting()) { + auto actions = t.get_self_actions(); + selection = random_action(actions); + } + else { + auto actions = t.get_response_actions(); + selection = random_action(actions); + } + t.make_selection(selection); + if (!t.is_over()) + encoder.update(); + } while (!t.is_over()); + //fmt::print( + // "------ Game Log ------\n" + // "{}\n" + // "---- Game Log (end) ----", t.gamelog.to_string()); + } + + double time = t.get(sec); + fmt::print("{} random plays passed, duration = {:3f} s ({:3f} s avg.)", games, time, time / games); + fmt::print("{}", profiler::get_all_profiles_v2()); +} + int main() { // test_random_play(); // test_tenhou_yama(); - test_tenhou_game(); + // test_tenhou_game(); + test_random_play_v2(); getchar(); return 0; }