public static (List <FuDetail>, int) CalculateFu(IList <TileKinds> hand, TileId winTile, TileKinds winGroup, HandConfig config, IList <int> valuedTiles = null, IList <Meld> melds = null) { var winTileKind = winTile.ToTileKind(); if (valuedTiles is null) { valuedTiles = new List <int>(); } if (melds is null) { melds = new List <Meld>(); } var fuDetails = new List <FuDetail>(); if (hand.Count == 7) { fuDetails = new List <FuDetail> { new FuDetail(25, BASE) }; return(fuDetails, 25); } var pair = hand.Where(x => x.IsPair) .ToList()[0]; var ponSets = hand.Where(x => x.IsPon); var copiedOpenedMelds = melds.Where(x => x.Type == MeldType.CHI) .Select(x => x.TileKinds) .ToList(); var closedChiSets = new List <TileKinds>(); foreach (var x in hand) { if (!copiedOpenedMelds.Contains(x)) { closedChiSets.Add(x); } else { copiedOpenedMelds.Remove(x); } } var IsOpenHand = melds.Any(x => x.Opened); if (closedChiSets.Contains(winGroup)) { var tileIndex = winTileKind.Simplify; //ペンチャン if (winGroup.ContainsTerminal()) { //ペン12 if (tileIndex == 2 && winGroup.IndexOf(winTileKind) == 2) { fuDetails.Add(new FuDetail(2, PENCHAN)); } //ペン89 else if (tileIndex == 6 && winGroup.IndexOf(winTileKind) == 0) { fuDetails.Add(new FuDetail(2, PENCHAN)); } } //カン57 if (winGroup.IndexOf(winTileKind) == 1) { fuDetails.Add(new FuDetail(2, KANCHAN)); } } //符あり雀頭 var countOfValuedPairs = valuedTiles.Count(x => x == pair[0].Value); if (countOfValuedPairs == 1) { fuDetails.Add(new FuDetail(2, VALUED_PAIR)); } //雀頭ダブ東南4符 if (countOfValuedPairs == 2) { fuDetails.Add(new FuDetail(2, VALUED_PAIR)); fuDetails.Add(new FuDetail(2, VALUED_PAIR)); } //シャンポン待ち if (winGroup.IsPair) { fuDetails.Add(new FuDetail(2, PAIR_WAIT)); } foreach (var setItem in ponSets) { var openMelds = melds.Where(x => setItem.Equals(x.TileKinds)) .ToList(); var openMeld = openMelds.Count == 0 ? null : openMelds[0]; var setWasOpen = !(openMeld is null) && openMeld.Opened; var isKan = !(openMeld is null) && (openMeld.Type == MeldType.KAN || openMeld.Type == MeldType.CHANKAN); var isYaochu = YAOCHU_INDICES.Contains(setItem[0].Value); if (!config.IsTsumo && setItem.Equals(winGroup)) { setWasOpen = true; } if (isYaochu) { if (isKan) { if (setWasOpen) { fuDetails.Add(new FuDetail(16, OPEN_TERMINAL_KAN)); } else { fuDetails.Add(new FuDetail(32, CLOSED_TERMINAL_KAN)); } } else { if (setWasOpen) { fuDetails.Add(new FuDetail(4, OPEN_TERMINAL_PON)); } else { fuDetails.Add(new FuDetail(8, CLOSED_TERMINAL_PON)); } } } else { if (isKan) { if (setWasOpen) { fuDetails.Add(new FuDetail(8, OPEN_KAN)); } else { fuDetails.Add(new FuDetail(16, CLOSED_KAN)); } } else { if (setWasOpen) { fuDetails.Add(new FuDetail(2, OPEN_PON)); } else { fuDetails.Add(new FuDetail(4, CLOSED_PON)); } } } } var addTsumoFu = fuDetails.Count > 0 || config.Options.FuForPinfuTsumo; if (config.IsTsumo && addTsumoFu) { fuDetails.Add(new FuDetail(2, TSUMO)); } if (IsOpenHand && fuDetails.Count == 0 && config.Options.FuForOpenPinfu) { fuDetails.Add(new FuDetail(2, HAND_WITHOUT_FU)); } if (IsOpenHand || config.IsTsumo) { fuDetails.Add(new FuDetail(20, BASE)); } else { fuDetails.Add(new FuDetail(30, BASE)); } return(fuDetails, RoundFu(fuDetails)); }
public static HandResponse EstimateHandValue(TileIds tiles, TileId winTile, List <Meld> melds = null, TileIds doraIndicators = null, HandConfig config = null) { if (melds is null) { melds = new List <Meld>(); } if (doraIndicators is null) { doraIndicators = new TileIds(); } config_ = config ?? new HandConfig(); var handYaku = new List <Yaku>(); var tiles34 = tiles.ToTiles34(); var openedMelds = melds.Where(x => x.Opened) .Select(x => x.TileKinds) .ToList(); var allMelds = melds.Select(x => x.TileKinds).ToList(); var isOpenHand = openedMelds.Count() > 0; if (config_.IsNagashiMangan) { handYaku.Add(new NagashiMangan()); var fu = 30; var han = new NagashiMangan().HanClosed; var cost = CalculateScores(han, fu, config_, false); return(new HandResponse(cost, han, fu, handYaku)); } if (!IsAgari(tiles, allMelds)) { return(new HandResponse(error: "Hand is not winning")); } if (!tiles.Contains(winTile)) { return(new HandResponse(error: "Win tile not in the hand")); } if (config_.IsRiichi && isOpenHand) { return(new HandResponse(error: "Riichi can't be declared with open hand")); } if (config_.IsDaburuRiichi && isOpenHand) { return(new HandResponse(error: "Daburu Riichi can't be declared with open hand")); } if (config_.IsIppatsu && isOpenHand) { return(new HandResponse(error: "Ippatsu can't be declared with open hand")); } if (config_.IsIppatsu && !config_.IsRiichi && !config_.IsDaburuRiichi) { return(new HandResponse(error: "Ippatsu can't be declared without riichi")); } var handOptions = DivideHand(tiles34, melds); var calculatedHands = new List <HandResponse>(); foreach (var hand in handOptions) { var isChiitoitsu = new Chiitoitsu().IsConditionMet(hand); var valuedTiles = new List <int> { HAKU, HATSU, CHUN, config_.PlayerWind, config_.RoundWind, }; var winGroups = FindWinGroups(winTile, hand, openedMelds); foreach (var winGroup in winGroups) { Cost cost = null; string error = null; handYaku = new List <Yaku>(); var han = 0; var(fuDetails, fu) = CalculateFu( hand, winTile, winGroup, config_, valuedTiles, melds); var isPinfu = fuDetails.Count == 1 && !isChiitoitsu && !isOpenHand; var ponSets = hand.Where(x => x.IsPon); var chiSets = hand.Where(x => x.IsChi); if (config_.IsTsumo) { if (!isOpenHand) { handYaku.Add(new Tsumo()); } } if (isPinfu) { handYaku.Add(new Pinfu()); } if (isChiitoitsu && isOpenHand) { continue; } if (isChiitoitsu) { handYaku.Add(new Chiitoitsu()); } var isDaisharin = new Daisharin().IsConditionMet(hand); if (config_.Options.HasDaisharin && isDaisharin) { handYaku.Add(new Daisharin()); } if (config_.IsRiichi && !config_.IsDaburuRiichi) { handYaku.Add(new Riichi()); } if (config_.IsDaburuRiichi) { handYaku.Add(new DaburuRiichi()); } var isTanyao = new Tanyao().IsConditionMet(hand); if (isOpenHand && !config_.Options.HasOpenTanyao) { isTanyao = false; } if (isTanyao) { handYaku.Add(new Tanyao()); } if (config_.IsIppatsu) { handYaku.Add(new Ippatsu()); } if (config_.IsRinshan) { handYaku.Add(new Rinshan()); } if (config_.IsChankan) { handYaku.Add(new Chankan()); } if (config_.IsHaitei) { handYaku.Add(new Haitei()); } if (config_.IsHoutei) { handYaku.Add(new Houtei()); } if (config_.IsRenhou) { if (config_.Options.RenhouAsYakuman) { handYaku.Add(new RenhouYakuman()); } else { handYaku.Add(new Renhou()); } } if (config_.IsTenhou) { handYaku.Add(new Tenhou()); } if (config_.IsChiihou) { handYaku.Add(new Chiihou()); } if (new Honitsu().IsConditionMet(hand)) { handYaku.Add(new Honitsu()); } if (new Chinitsu().IsConditionMet(hand)) { handYaku.Add(new Chinitsu()); } if (new Tsuuiisou().IsConditionMet(hand)) { handYaku.Add(new Tsuuiisou()); } if (new Honroto().IsConditionMet(hand)) { handYaku.Add(new Honroto()); } if (new Chinroutou().IsConditionMet(hand)) { handYaku.Add(new Chinroutou()); } if (new Ryuuiisou().IsConditionMet(hand)) { handYaku.Add(new Ryuuiisou()); } //順子が必要な役 if (chiSets.Count() != 0) { if (new Chanta().IsConditionMet(hand)) { handYaku.Add(new Chanta()); } if (new Junchan().IsConditionMet(hand)) { handYaku.Add(new Junchan()); } if (new Ittsu().IsConditionMet(hand)) { handYaku.Add(new Ittsu()); } if (!isOpenHand) { if (new Ryanpeikou().IsConditionMet(hand)) { handYaku.Add(new Ryanpeikou()); } else if (new Iipeiko().IsConditionMet(hand)) { handYaku.Add(new Iipeiko()); } } if (new Sanshoku().IsConditionMet(hand)) { handYaku.Add(new Sanshoku()); } } //刻子が必要な役 if (ponSets.Count() != 0) { if (new Toitoi().IsConditionMet(hand)) { handYaku.Add(new Toitoi()); } if (new Sanankou().IsConditionMet(hand, new object[] { winTile, melds, config_.IsTsumo })) { handYaku.Add(new Sanankou()); } if (new SanshokuDoukou().IsConditionMet(hand)) { handYaku.Add(new SanshokuDoukou()); } if (new Shosangen().IsConditionMet(hand)) { handYaku.Add(new Shosangen()); } if (new Haku().IsConditionMet(hand)) { handYaku.Add(new Haku()); } if (new Hatsu().IsConditionMet(hand)) { handYaku.Add(new Hatsu()); } if (new Chun().IsConditionMet(hand)) { handYaku.Add(new Chun()); } if (new YakuhaiEast().IsConditionMet(hand, new object[] { config_.PlayerWind, config_.RoundWind })) { if (config_.PlayerWind == EAST) { handYaku.Add(new YakuhaiOfPlace()); } if (config_.RoundWind == EAST) { handYaku.Add(new YakuhaiOfRound()); } } if (new YakuhaiSouth().IsConditionMet(hand, new object[] { config_.PlayerWind, config_.RoundWind })) { if (config_.PlayerWind == SOUTH) { handYaku.Add(new YakuhaiOfPlace()); } if (config_.RoundWind == SOUTH) { handYaku.Add(new YakuhaiOfRound()); } } if (new YakuhaiWest().IsConditionMet(hand, new object[] { config_.PlayerWind, config_.RoundWind })) { if (config_.PlayerWind == WEST) { handYaku.Add(new YakuhaiOfPlace()); } if (config_.RoundWind == WEST) { handYaku.Add(new YakuhaiOfRound()); } } if (new YakuhaiNorth().IsConditionMet(hand, new object[] { config_.PlayerWind, config_.RoundWind })) { if (config_.PlayerWind == NORTH) { handYaku.Add(new YakuhaiOfPlace()); } if (config_.RoundWind == NORTH) { handYaku.Add(new YakuhaiOfRound()); } } if (new Daisangen().IsConditionMet(hand)) { handYaku.Add(new Daisangen()); } if (new Shousuushii().IsConditionMet(hand)) { handYaku.Add(new Shousuushii()); } if (new DaiSuushi().IsConditionMet(hand)) { if (config_.Options.HasDoubleYakuman) { handYaku.Add(new DaiSuushi()); } else { handYaku.Add(new DaiSuushi { HanOpen = 13, HanClosed = 13 }); } } if (melds.Count() == 0 && new ChuurenPoutou().IsConditionMet(hand)) { if (tiles34[winTile.Value / 4] == 2 || tiles34[winTile.Value / 4] == 4) { if (config_.Options.HasDoubleYakuman) { handYaku.Add(new DaburuChuurenPoutou()); } else { handYaku.Add(new DaburuChuurenPoutou { HanClosed = 13 }); } } else { handYaku.Add(new ChuurenPoutou()); } } if (!isOpenHand && new Suuankou().IsConditionMet(hand, new object[] { winTile, config_.IsTsumo })) { if (tiles34[winTile.Value / 4] == 2) { if (config_.Options.HasDoubleYakuman) { handYaku.Add(new SuuankouTanki()); } else { handYaku.Add(new SuuankouTanki { HanClosed = 13 }); } } else { handYaku.Add(new Suuankou()); } } if (new SanKantsu().IsConditionMet(hand, new object[] { melds })) { handYaku.Add(new SanKantsu()); } if (new Suukantsu().IsConditionMet(hand, new object[] { melds })) { handYaku.Add(new Suukantsu()); } } //役満に役満以外の役は付かない var yakumanList = handYaku.Where(x => x.IsYakuman) .ToList(); if (yakumanList.Count != 0) { handYaku = yakumanList; } //翻を計算する foreach (var item in handYaku) { if (isOpenHand && item.HanOpen != 0) { han += item.HanOpen; } else { han += item.HanClosed; } } if (han == 0) { error = "There are no yaku in the hand."; cost = null; } //役満にドラは付かない if (yakumanList.Count == 0) { var tilesForDora = tiles.ToList(); foreach (var meld in melds) { if (meld.Type == MeldType.KAN || meld.Type == MeldType.CHANKAN) { tilesForDora.Add(meld.Tiles[3]); } } var countOfDora = 0; var countOfAkaDora = 0; foreach (var tile in tilesForDora) { countOfDora += PlusDora(tile, doraIndicators); } foreach (var tile in tilesForDora) { if (IsAkaDora(tile, config_.Options.HasAkaDora)) { countOfAkaDora++; } } if (countOfDora != 0) { handYaku.Add(new Dora { HanOpen = countOfDora, HanClosed = countOfDora }); han += countOfDora; } if (countOfAkaDora != 0) { handYaku.Add(new Akadora { HanOpen = countOfAkaDora, HanClosed = countOfAkaDora }); han += countOfAkaDora; } } if (string.IsNullOrEmpty(error)) { cost = CalculateScores(han, fu, config_, yakumanList.Count > 0); } calculatedHands.Add(new HandResponse( cost, han, fu, handYaku, error, fuDetails)); } } if (!isOpenHand && new KokushiMusou().IsConditionMet(null, new object[] { tiles34 })) { if (tiles34[winTile.Value / 4] == 2) { if (config_.Options.HasDoubleYakuman) { handYaku.Add(new DaburuKokushiMusou()); } else { handYaku.Add(new DaburuKokushiMusou { HanClosed = 13 }); } } else { handYaku.Add(new KokushiMusou()); } if (config_.IsRenhou && config_.Options.RenhouAsYakuman) { handYaku.Add(new RenhouYakuman()); } if (config_.IsTenhou) { handYaku.Add(new Tenhou()); } if (config_.IsChiihou) { handYaku.Add(new Chiihou()); } var han = 0; foreach (var item in handYaku) { if (isOpenHand && item.HanOpen != 0) { han += item.HanOpen; } else { han += item.HanClosed; } } var fu = 0; var cost = CalculateScores(han, fu, config_, handYaku.Count > 0); handYaku.Sort((x, y) => x.YakuId.CompareTo(y.YakuId)); calculatedHands.Add(new HandResponse( cost, han, fu, handYaku, null, new List <FuDetail>())); } calculatedHands.Sort((x, y) => x.Han <y.Han ? 1 : x.Han> y.Han ? -1 : x.Fu <y.Fu ? 1 : x.Fu> y.Fu ? -1 : 0); var resultHand = calculatedHands[0]; resultHand.Yaku.Sort((x, y) => x.YakuId.CompareTo(y.YakuId)); return(resultHand); }
public static Cost CalculateScores(int han, int fu, HandConfig config, bool isYakuman = false) { //数え役満 if (han >= 13 && !isYakuman) { if (config.Options.KazoeLimit == Kazoe.Limited) { han = 13; } else if (config.Options.KazoeLimit == Kazoe.Sanbaiman) { han = 12; } } //rounded: 一家あたりの点数 跳満(子)-12000なら3000点 int rounded, doubleRounded, fourRounded, sixRounded; if (han >= 5) { if (han >= 78) { rounded = 48000; } else if (han >= 65) { rounded = 40000; } else if (han >= 52) { rounded = 32000; } else if (han >= 39) { rounded = 24000; } //ダブル役満 else if (han >= 26) { rounded = 16000; } //役満 else if (han >= 13) { rounded = 8000; } //三倍満 else if (han >= 11) { rounded = 6000; } //倍満 else if (han >= 8) { rounded = 4000; } //跳満 else if (han >= 6) { rounded = 3000; } else { rounded = 2000; } doubleRounded = rounded * 2; fourRounded = doubleRounded * 2; sixRounded = doubleRounded * 3; } else { var basePoint = fu * Math.Pow(2, 2 + han); rounded = (int)((basePoint + 99) / 100) * 100; doubleRounded = (int)((2 * basePoint + 99) / 100) * 100; fourRounded = (int)((4 * basePoint + 99) / 100) * 100; sixRounded = (int)((6 * basePoint + 99) / 100) * 100; var IsKiriage = false; if (config.Options.Kiriage) { if (han == 4 && fu == 30) { IsKiriage = true; } if (han == 3 && fu == 60) { IsKiriage = true; } } //満貫 if (rounded > 2000 || IsKiriage) { rounded = 2000; doubleRounded = rounded * 2; fourRounded = doubleRounded * 2; sixRounded = doubleRounded * 3; } } return(config.IsTsumo ? new Cost(doubleRounded, config.IsDealer ? doubleRounded : rounded) : new Cost(config.IsDealer ? sixRounded : fourRounded, 0)); }