public override Dictionary <string, int> calculate_scores(int han, int fu, HandConfig config, bool is_yakuman = false) { int base_points = fu * (int)Math.Pow(2, 2 + han); int rounded = (base_points + 99) / 100 * 100; int double_rounded = (2 * base_points + 99) / 100 * 100; int four_rounded = (4 * base_points + 99) / 100 * 100; int six_rounded = (6 * base_points + 99) / 100 * 100; if (config.is_tsumo) { return(new Dictionary <string, int> { { "main", double_rounded }, { "additional", config.is_dealer ? double_rounded : rounded } }); } else { return(new Dictionary <string, int> { { "main", config.is_dealer ? six_rounded : four_rounded }, { "additional", 0 } }); } }
// // Calculate how much scores cost a hand with given han and fu // :param han: int // :param fu: int // :param config: HandConfig object // :param is_yakuman: boolean // :return: a dictionary with following keys: // 'main': main cost (honba number / tsumi bou not included) // 'additional': additional cost (honba number not included) // 'main_bonus': extra cost due to honba number to be added on main cost // 'additional_bonus': extra cost due to honba number to be added on additional cost // 'kyoutaku_bonus': the points taken from accumulated riichi 1000-point bous (kyoutaku) // 'total': the total points the winner is to earn // 'yaku_level': level of yaku (e.g. yakuman, mangan, nagashi mangan, etc) // // for ron, main cost is the cost for the player who triggers the ron, and additional cost is always = 0 // for dealer tsumo, main cost is the same as additional cost, which is the cost for any other player // for non-dealer (player) tsumo, main cost is cost for dealer and additional is cost for player // // examples: // 1. dealer tsumo 2000 ALL in 2 honba, with 3 riichi bous on desk // {'main': 2000, 'additional': 2000, // 'main_bonus': 200, 'additional_bonus': 200, // 'kyoutaku_bonus': 3000, 'total': 9600, 'yaku_level': ''} // // 2. player tsumo 3900-2000 in 4 honba, with 1 riichi bou on desk // {'main': 3900, 'additional': 2000, // 'main_bonus': 400, 'additional_bonus': 400, // 'kyoutaku_bonus': 1000, 'total': 10100, 'yaku_level': ''} // // 3. dealer (or player) ron 12000 in 5 honba, with no riichi bou on desk // {'main': 12000, 'additional': 0, // 'main_bonus': 1500, 'additional_bonus': 0, // 'kyoutaku_bonus': 0, 'total': 13500} // // public virtual Dictionary <string, int> calculate_scores(int han, int fu, HandConfig config, bool is_yakuman = false) { int additional; int additional_bonus; int main_bonus; int rounded; int double_rounded; int four_rounded; int six_rounded; int main; int yaku_level = (int)YakuLevel.None; // kazoe hand if (han >= 13 && !is_yakuman) { // Hands over 26+ han don't count as double yakuman if (config.options.kazoe_limit == HandConfig.KAZOE_LIMITED) { han = 13; yaku_level = (int)YakuLevel.KazoeYakuman; } else if (config.options.kazoe_limit == HandConfig.KAZOE_SANBAIMAN) { // Hands over 13+ is a sanbaiman han = 12; yaku_level = (int)YakuLevel.KazoeSanbaiman; } } if (han >= 5) { if (han >= 78) { yaku_level = (int)YakuLevel.Yakuman6x; if (config.options.limit_to_sextuple_yakuman) { rounded = 48000; } else { int quot = Math.DivRem(han - 78, 13, out _); var extra_han = quot; rounded = 48000 + extra_han * 8000; } } else if (han >= 65) { yaku_level = (int)YakuLevel.Yakuman5x; rounded = 40000; } else if (han >= 52) { yaku_level = (int)YakuLevel.Yakuman4x; rounded = 32000; } else if (han >= 39) { yaku_level = (int)YakuLevel.Yakuman3x; rounded = 24000; } else if (han >= 26) { // double yakuman yaku_level = (int)YakuLevel.Yakuman2x; rounded = 16000; } else if (han >= 13) { // yakuman yaku_level = (int)YakuLevel.Yakuman; rounded = 8000; } else if (han >= 11) { // sanbaiman yaku_level = (int)YakuLevel.Sanbaiman; rounded = 6000; } else if (han >= 8) { // baiman yaku_level = (int)YakuLevel.Baiman; rounded = 4000; } else if (han >= 6) { // haneman yaku_level = (int)YakuLevel.Haneman; rounded = 3000; } else { yaku_level = (int)YakuLevel.Mangan; rounded = 2000; } double_rounded = rounded * 2; four_rounded = double_rounded * 2; six_rounded = double_rounded * 3; } else { // han < 5 int base_points = fu * (int)Math.Pow(2, 2 + han); rounded = (base_points + 99) / 100 * 100; double_rounded = (2 * base_points + 99) / 100 * 100; four_rounded = (4 * base_points + 99) / 100 * 100; six_rounded = (6 * base_points + 99) / 100 * 100; var is_kiriage = false; if (config.options.kiriage) { if (han == 4 && fu == 30 || han == 3 && fu == 60) { yaku_level = (int)YakuLevel.KiriageMangan; is_kiriage = true; } } else { // kiriage not supported if (rounded > 2000) { yaku_level = (int)YakuLevel.Mangan; } } // mangan if (rounded > 2000 || is_kiriage) { rounded = 2000; double_rounded = rounded * 2; four_rounded = double_rounded * 2; six_rounded = double_rounded * 3; } else { // below mangan } } if (config.is_tsumo) { main = double_rounded; main_bonus = 100 * config.tsumi_number; additional_bonus = main_bonus; if (config.is_dealer) { additional = main; } else { // player additional = rounded; } } else { // ron additional = 0; additional_bonus = 0; main_bonus = 300 * config.tsumi_number; if (config.is_dealer) { main = six_rounded; } else { // player main = four_rounded; } } var kyoutaku_bonus = 1000 * config.kyoutaku_number; var total = main + main_bonus + 2 * (additional + additional_bonus) + kyoutaku_bonus; if (config.is_nagashi_mangan) { yaku_level = (int)YakuLevel.NagashiMangan; } var ret_dict = new Dictionary <string, int> { { "main", main }, { "main_bonus", main_bonus }, { "additional", additional }, { "additional_bonus", additional_bonus }, { "kyoutaku_bonus", kyoutaku_bonus }, { "total", total }, { "yaku_level", yaku_level } }; return(ret_dict); }
public new static void aotenjou_filter_yaku(List <Yaku> hand_yaku, HandConfig config) { // in aotenjou yakumans are normal yaku // but we need to filter lower yaku that are precursors to yakumans if (hand_yaku.Contains(config.yaku.daisangen)) { // for daisangen precursors are all dragons and shosangen hand_yaku.Remove(config.yaku.chun); hand_yaku.Remove(config.yaku.hatsu); hand_yaku.Remove(config.yaku.haku); hand_yaku.Remove(config.yaku.shosangen); } if (hand_yaku.Contains(config.yaku.tsuisou)) { // for tsuuiisou we need to remove toitoi and honroto hand_yaku.Remove(config.yaku.toitoi); hand_yaku.Remove(config.yaku.honroto); } if (hand_yaku.Contains(config.yaku.shosuushi)) { // for shosuushi we do not need to remove anything } if (hand_yaku.Contains(config.yaku.daisuushi)) { // for daisuushi we need to remove toitoi hand_yaku.Remove(config.yaku.toitoi); } if (hand_yaku.Contains(config.yaku.suuankou) || hand_yaku.Contains(config.yaku.suuankou_tanki)) { // for suu ankou we need to remove toitoi and sanankou (sanankou is already removed by default) if (hand_yaku.Contains(config.yaku.toitoi)) { // toitoi is "optional" in closed suukantsu, maybe a bug? or toitoi is not given when it's kans? hand_yaku.Remove(config.yaku.toitoi); } } if (hand_yaku.Contains(config.yaku.chinroto)) { // for chinroto we need to remove toitoi and honroto hand_yaku.Remove(config.yaku.toitoi); hand_yaku.Remove(config.yaku.honroto); } if (hand_yaku.Contains(config.yaku.suukantsu)) { // for suukantsu we need to remove toitoi and sankantsu (sankantsu is already removed by default) if (hand_yaku.Contains(config.yaku.toitoi)) { // same as above? hand_yaku.Remove(config.yaku.toitoi); } } if (hand_yaku.Contains(config.yaku.chuuren_poutou) || hand_yaku.Contains(config.yaku.daburu_chuuren_poutou)) { // for chuuren poutou we need to remove chinitsu hand_yaku.Remove(config.yaku.chinitsu); } if (hand_yaku.Contains(config.yaku.daisharin)) { // for daisharin we need to remove chinitsu, pinfu, tanyao, ryanpeiko, chiitoitsu hand_yaku.Remove(config.yaku.chinitsu); if (hand_yaku.Contains(config.yaku.pinfu)) { hand_yaku.Remove(config.yaku.pinfu); } hand_yaku.Remove(config.yaku.tanyao); if (hand_yaku.Contains(config.yaku.ryanpeiko)) { hand_yaku.Remove(config.yaku.ryanpeiko); } if (hand_yaku.Contains(config.yaku.chiitoitsu)) { hand_yaku.Remove(config.yaku.chiitoitsu); } } if (hand_yaku.Contains(config.yaku.ryuisou)) { // for ryuisou we need to remove honitsu, if it is there if (hand_yaku.Contains(config.yaku.honitsu)) { hand_yaku.Remove(config.yaku.honitsu); } } }
public static void aotenjou_filter_yaku(List <Yaku> hand_yaku, HandConfig config) { // do nothing }
// // Calculate hand fu with explanations // :param hand: // :param win_tile: 136 tile format // :param win_group: one set where win tile exists // :param config: HandConfig object // :param valued_tiles: dragons, player wind, round wind // :param melds: opened sets // :return: // public static (List <(int, string)>, int) calculate_fu( List <List <int> > hand, int win_tile, List <int> win_group, HandConfig config, List <int> valued_tiles = null, List <Meld> melds = null) { var win_tile_34 = win_tile / 4; if (valued_tiles == null) { valued_tiles = new List <int>(); } if (melds == null) { melds = new List <Meld>(); } var fu_details = new List <(int, string)>(); if (hand.Count == 7) { return(new List <(int, string)>() { (25, BASE) }, 25); } var pair = (from x in hand where U.is_pair(x) select x).ToList()[0]; var pon_sets = (from x in hand where U.is_pon_or_kan(x) select x).ToList(); var copied_opened_melds = (from x in melds where x.type == Meld.CHI select x.tiles_34).ToList(); var closed_chi_sets = new List <List <int> >(); foreach (var x in hand) { if (!copied_opened_melds.Contains(x)) { closed_chi_sets.Add(x); } else { copied_opened_melds.Remove(x); } } var is_open_hand = (from x in melds select x.opened).Any(); if (closed_chi_sets.Contains(win_group)) { var tile_index = U.simplify(win_tile_34); // penchan if (U.contains_terminals(win_group)) { // 1-2-... wait if (tile_index == 2 && win_group.FindIndex(x => x == win_tile_34) == 2) { fu_details.Add((2, PENCHAN)); } else if (tile_index == 6 && win_group.FindIndex(x => x == win_tile_34) == 0) { // 8-9-... wait fu_details.Add((2, PENCHAN)); } } // kanchan waiting 5-...-7 if (win_group.FindIndex(x => x == win_tile_34) == 1) { fu_details.Add((2, KANCHAN)); } } // valued pair var count_of_valued_pairs = valued_tiles.Count(x => x == pair[0]); if (count_of_valued_pairs == 1) { fu_details.Add((2, VALUED_PAIR)); } // east-east pair when you are on east gave double fu if (count_of_valued_pairs == 2) { fu_details.Add((4, DOUBLE_VALUED_PAIR)); } // pair wait if (U.is_pair(win_group)) { fu_details.Add((2, PAIR_WAIT)); } foreach (var set_item in pon_sets) { var open_meld = (from x in melds where set_item == x.tiles_34 select x).ToList().FirstOrDefault(); var set_was_open = open_meld != null && open_meld.opened; var is_kan_set = open_meld != null && (open_meld.type == Meld.KAN || open_meld.type == Meld.SHOUMINKAN); var is_honor = (C.TERMINAL_INDICES.Concat(C.HONOR_INDICES)).Contains(set_item[0]); // we win by ron on the third pon tile, our pon will be count as open if (!config.is_tsumo && set_item == win_group) { set_was_open = true; } if (is_honor) { if (is_kan_set) { if (set_was_open) { fu_details.Add((16, OPEN_TERMINAL_KAN)); } else { fu_details.Add((32, CLOSED_TERMINAL_KAN)); } } else if (set_was_open) { fu_details.Add((4, OPEN_TERMINAL_PON)); } else { fu_details.Add((8, CLOSED_TERMINAL_PON)); } } else if (is_kan_set) { if (set_was_open) { fu_details.Add((8, OPEN_KAN)); } else { fu_details.Add((16, CLOSED_KAN)); } } else if (set_was_open) { fu_details.Add((2, OPEN_PON)); } else { fu_details.Add((4, CLOSED_PON)); } } var add_tsumo_fu = fu_details.Count > 0 || config.options.fu_for_pinfu_tsumo; if (config.is_tsumo && add_tsumo_fu) { // 2 additional fu for tsumo (but not for pinfu) fu_details.Add((2, TSUMO)); } if (is_open_hand && fu_details.Count == 0 && config.options.fu_for_open_pinfu) { // there is no 1-20 hands, so we have to add additional fu fu_details.Add((2, HAND_WITHOUT_FU)); } if (is_open_hand || config.is_tsumo) { fu_details.Add((20, BASE)); } else { fu_details.Add((30, BASE)); } return(fu_details, round_fu(fu_details)); }