예제 #1
0
        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
                    }
                });
            }
        }
예제 #2
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);
        }
예제 #3
0
 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);
         }
     }
 }
예제 #4
0
 public static void aotenjou_filter_yaku(List <Yaku> hand_yaku, HandConfig config)
 {
     // do nothing
 }
예제 #5
0
        //
        //         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));
        }