public void TilesSetToTiles136Test() { var expected = new TileIds(new List <int> { 0, 32, 33, 36, 37, 68, 69, 72, 73, 104, 105, 108, 109, 132 }); var t = expected.ToTiles34(); var actual = t.ToTileIds(); for (var i = 0; i < expected.Count; i++) { AreEqual(expected[i].Value, actual[i].Value); } }
/// <summary> /// シャンテン数を計算する 0:テンパイ, -1: あがり /// </summary> /// <param name="tiles"></param> /// <param name="openSets"></param> /// <param name="chiitoitsu"></param> /// <param name="kokushi"></param> /// <returns></returns> public static int CalculateShanten(TileIds tiles, IList <TileKinds> openSets = null, bool chiitoitsu = true, bool kokushi = true) { var tiles34 = tiles.ToTiles34(); Init(tiles34); var countOfTiles = tiles34.Sum(); if (countOfTiles > 14) { throw new ArgumentException("牌の数が14個より多いです。", nameof(countOfTiles)); } if (!(openSets is null || openSets.Count == 0)) { var isolatedTiles = tiles_.FindIsolatedTileIndices(); foreach (var meld in openSets) { if (isolatedTiles.Count == 0) { break; } var lastIndex = isolatedTiles.Count - 1; var isolatedTile = isolatedTiles[lastIndex]; isolatedTiles.RemoveAt(lastIndex); tiles34[meld[0].Value] -= 1; tiles34[meld[1].Value] -= 1; tiles34[meld[2].Value] -= 1; tiles34[isolatedTile.Value] = 3; } } if (openSets is null || openSets.Count == 0) { minShanten_ = ScanChiitoitsuAndKokushi(chiitoitsu, kokushi); } RemoveCharacterTiles(countOfTiles); var initMentsu = (14 - countOfTiles) / 3; Scan(initMentsu); return(minShanten_); }
public void Tiles136ToTilesSet() { var tiles = new TileIds(new List <int> { 0, 34, 35, 36, 37, 70, 71, 72, 73, 106, 107, 108, 109, 134 }); var actual = tiles.ToTiles34(); AreEqual(1, actual[0]); AreEqual(2, actual[8]); AreEqual(2, actual[9]); AreEqual(2, actual[17]); AreEqual(2, actual[18]); AreEqual(2, actual[26]); AreEqual(2, actual[27]); AreEqual(1, actual[33]); AreEqual(14, actual.Sum()); }
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); }
internal static bool IsAgari(TileIds tiles, IList <TileKinds> openSets = null) { var tiles_ = tiles.ToTiles34(); if (!(openSets is null) && openSets.Count != 0) { var isolatedTiles = tiles_.FindIsolatedTileIndices(); foreach (var meld in openSets) { if (isolatedTiles.Count == 0) { break; } var lastIndex = isolatedTiles.Count - 1; var isolatedTile = isolatedTiles[lastIndex]; isolatedTiles.RemoveAt(lastIndex); tiles_[meld[0].Value] -= 1; tiles_[meld[1].Value] -= 1; tiles_[meld[2].Value] -= 1; tiles_[isolatedTile.Value] = 3; } } //jは4桁のバイナリ var j = (1 << tiles_[27]) | (1 << tiles_[28]) | (1 << tiles_[29]) | (1 << tiles_[30]) | (1 << tiles_[31]) | (1 << tiles_[32]) | (1 << tiles_[33]); //jに5桁目はない if (j >= 0x10) { return(false); } //国士無双判定 //1桁目:0-すべての字牌は1つ以上存在する //2桁目:1-すべての字牌が2つ以上であってはならない //么九牌のうち2つの牌が1種類、それ以外は1つ if ((j & 3) == 2 && tiles_[0] * tiles_[8] * tiles_[9] * tiles_[17] * tiles_[18] * tiles_[26] * tiles_[27] * tiles_[28] * tiles_[29] * tiles_[30] * tiles_[31] * tiles_[32] * tiles_[33] == 2) { return(true); } //七対子判定 //2,4桁目:0-すべての牌が0,2,4つのいずれか //2つの牌が7つ存在する if ((j & 10) == 0 && Range(0, 34).Count(i => tiles_[i] == 2) == 7) { return(true); } //2桁目:0-すべての字牌は2つ以上か0つである if ((j & 2) != 0) { return(false); } //(1,4,7),(2,5,8),(3,6,9)それぞれの個数を集めたもの //萬子 var n00 = tiles_[0] + tiles_[3] + tiles_[6]; var n01 = tiles_[1] + tiles_[4] + tiles_[7]; var n02 = tiles_[2] + tiles_[5] + tiles_[8]; //索子 var n10 = tiles_[9] + tiles_[12] + tiles_[15]; var n11 = tiles_[10] + tiles_[13] + tiles_[16]; var n12 = tiles_[11] + tiles_[14] + tiles_[17]; //筒子 var n20 = tiles_[18] + tiles_[21] + tiles_[24]; var n21 = tiles_[19] + tiles_[22] + tiles_[25]; var n22 = tiles_[20] + tiles_[23] + tiles_[26]; //対子があれば2, 面子のみなら0 var n0 = (n00 + n01 + n02) % 3; var n1 = (n10 + n11 + n12) % 3; var n2 = (n20 + n21 + n22) % 3; if (n0 == 1 || n1 == 1 || n2 == 1) { return(false); } //雀頭は1つだけ if (new List <int> { n0, n1, n2, tiles_[27], tiles_[28], tiles_[29], tiles_[30], tiles_[31], tiles_[32], tiles_[33] }.Count(n => n == 2) != 1) { return(false); } //面子を消す //(1,4,7)%3=1,(2,5,8)%3=2, (3,6,9)%3=0 //[123]=>(1*1+2*1=3)=0, [444]=>(1*3)%3=0 //0:(3,6,9)に,1;(2,5,8)に,2:(3,6,9)に雀頭がある //[77]=>(1*2)%3=2, [88]=>(2*2)%3=1,[99]=>(0*2)%3=0 var nn0 = (n00 * 1 + n01 * 2) % 3; var nn1 = (n10 * 1 + n11 * 2) % 3; var nn2 = (n20 * 1 + n21 * 2) % 3; var m0 = ToMeld(tiles_, 0); var m1 = ToMeld(tiles_, 9); var m2 = ToMeld(tiles_, 18); //3桁目:1-字牌に対子がある if ((j & 4) != 0) { //字牌以外面子しかもたない 以下も同様 return((n0 | nn0 | n1 | nn1 | n2 | nn2) == 0 && IsMentsu(m0) && IsMentsu(m1) && IsMentsu(m2)); } //萬子に対子がある if (n0 == 2) { return((n1 | nn1 | n2 | nn2) == 0 && IsMentsu(m1) && IsMentsu(m2) && IsAtamaMentsu(nn0, m0)); } //筒子に対子がある if (n1 == 2) { return((n2 | nn2 | n0 | nn0) == 0 && IsMentsu(m2) && IsMentsu(m0) && IsAtamaMentsu(nn1, m1)); } //索子に対子がある if (n2 == 2) { return((n0 | nn0 | n1 | nn1) == 0 && IsMentsu(m0) && IsMentsu(m1) && IsAtamaMentsu(nn2, m2)); } return(false); }