/// <summary> /// Constructor when winning. /// </summary> /// <param name="index">The <see cref="Index"/> value.</param> /// <param name="fanCount">The <see cref="FanCount"/> value.</param> /// <param name="fuCount">The <see cref="FuCount"/> value.</param> /// <param name="hand">The <see cref="_hand"/> value.</param> /// <param name="pointsGain">The <see cref="PointsGain"/> value.</param> /// <param name="doraCount">The <see cref="DoraCount"/> value.</param> /// <param name="uraDoraCount">The <see cref="UraDoraCount"/> value.</param> /// <param name="redDoraCount">The <see cref="RedDoraCount"/> value.</param> /// <param name="handPointsGain">The <see cref="HandPointsGain"/> value.</param> internal PlayerInformationsPivot(int index, int fanCount, int fuCount, HandPivot hand, int pointsGain, int doraCount, int uraDoraCount, int redDoraCount, int handPointsGain) { Index = index; FanCount = fanCount; FuCount = fuCount; PointsGain = pointsGain; DoraCount = doraCount; UraDoraCount = uraDoraCount; RedDoraCount = redDoraCount; HandPointsGain = handPointsGain; _hand = hand; }
/// <summary> /// Computes the fu count. /// </summary> /// <param name="hand">The hand.</param> /// <param name="isTsumo"><c>True</c> if the winning tile is concealed; <c>False</c> otherwise.</param> /// <param name="dominantWind">The dominant wind;</param> /// <param name="playerWind">The player wind.</param> /// <exception cref="ArgumentNullException"><paramref name="hand"/> is <c>Null</c>.</exception> public static int GetFuCount(HandPivot hand, bool isTsumo, WindPivot dominantWind, WindPivot playerWind) { if (hand == null) { throw new ArgumentNullException(nameof(hand)); } if (hand.Yakus.Any(y => y == YakuPivot.Chiitoitsu)) { return(CHIITOI_FU); } int fuCount = hand.YakusCombinations.Count(c => c.IsSquare && c.HasTerminalOrHonor) * HONOR_KAN_FU + hand.YakusCombinations.Count(c => c.IsSquare && !c.HasTerminalOrHonor) * REGULAR_KAN_FU + hand.YakusCombinations.Count(c => c.IsBrelan && c.HasTerminalOrHonor) * HONOR_PON_FU + hand.YakusCombinations.Count(c => c.IsBrelan && !c.HasTerminalOrHonor) * REGULAR_PON_FU; if (isTsumo && !hand.Yakus.Any(y => y == YakuPivot.Pinfu)) { fuCount += TSUMO_FU; } if (HandPivot.HandWithValuablePair(hand.YakusCombinations, dominantWind, playerWind)) { fuCount += VALUABLE_PAIR_FU; } if (hand.HandWithClosedWait()) { fuCount += CLOSED_WAIT_FU; } if (fuCount == 0 && !hand.Yakus.Any(y => y == YakuPivot.Pinfu)) { fuCount += OPEN_PINFU_FU; } int baseFu = (hand.IsConcealed && !isTsumo ? BASE_CONCEALED_RON_FU : BASE_FU) + fuCount; return(Convert.ToInt32(Math.Ceiling(baseFu / (decimal)10) * 10)); }
private bool HandCanBeOpened(int playerId, TilePivot tile, HandPivot hand) { var valuableWinds = new[] { _round.Game.GetPlayerCurrentWind(playerId), _round.Game.DominantWind }; var canPonForYakuhai = IsDragonOrValuableWind(tile, valuableWinds); // >= 75% of the tile family var closeToChinitsu = hand.ConcealedTiles.Count(_ => _.Family == tile.Family) >= 11; // how much pair (or better) of valuable honors ? var valuableHonorPairs = hand.ConcealedTiles.GroupBy(_ => _) .Count(_ => _.Key.IsHonor && _.Count() >= 2 && IsDragonOrValuableWind(_.Key, valuableWinds)); if (!canPonForYakuhai && valuableHonorPairs < 2 && !closeToChinitsu) { return(false); } int dorasCount = hand.ConcealedTiles .Sum(t => _round.DoraIndicatorTiles .Take(_round.VisibleDorasCount) .Count(d => t.IsDoraNext(d))); int redDorasCount = hand.ConcealedTiles.Count(t => t.IsRedDora); var hasValuablePair = hand.ConcealedTiles.GroupBy(_ => _) .Any(_ => _.Count() >= 2 && _.Key != tile && IsDragonOrValuableWind(_.Key, valuableWinds)); // >= 66% of one family or honor var closeToHonitsu = new[] { FamilyPivot.Bamboo, FamilyPivot.Caracter, FamilyPivot.Circle } .Any(f => hand.ConcealedTiles.Count(t => t.Family == f || t.IsHonor) > 9); return(hasValuablePair || (dorasCount + redDorasCount) > 0 || closeToHonitsu || valuableWinds[0] == WindPivot.East); }
/// <summary> /// Computes the list of yakus available with this sequence of combinations. /// If the list contains one or several yakumans, it can't contain any regular yakus. /// Two <see cref="YakuPivot"/> are not computed : /// <list type="bullet"> /// <item><see cref="KokushiMusou"/></item> /// <item><see cref="NagashiMangan"/></item> /// </list> /// </summary> /// <param name="combinationsSequence">Sequence of combinations.</param> /// <param name="context">Context.</param> /// <returns>List of yakus with this sequence of combinations.</returns> /// <exception cref="ArgumentNullException"><paramref name="combinationsSequence"/> is <c>Null</c>.</exception> /// <exception cref="ArgumentNullException"><paramref name="context"/> is <c>Null</c>.</exception> /// <exception cref="NotImplementedException">The <see cref="YakuPivot"/> to check is not implemented.</exception> public static List <YakuPivot> GetYakus(List <TileComboPivot> combinationsSequence, WinContextPivot context) { if (combinationsSequence == null) { throw new ArgumentNullException(nameof(combinationsSequence)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } var yakus = new List <YakuPivot>(); foreach (YakuPivot yaku in Yakus.Where(y => y.IsYakuman)) { bool addYaku = false; if (yaku == Daisangen) { addYaku = combinationsSequence.Count(c => c.IsBrelanOrSquare && c.Family == FamilyPivot.Dragon) == 3; } else if (yaku == Suuankou) { addYaku = combinationsSequence.Count(c => c.IsBrelanOrSquare && c.IsConcealed && (!c.Tiles.Contains(context.LatestTile) || context.DrawType.IsSelfDraw())) == 4; } else if (yaku == Shousuushii) { addYaku = combinationsSequence.Count(c => c.IsBrelanOrSquare && c.Family == FamilyPivot.Wind) == 3 && combinationsSequence.Any(c => c.IsPair && c.Family == FamilyPivot.Wind); } else if (yaku == Daisuushii) { addYaku = combinationsSequence.Count(c => c.IsBrelanOrSquare && c.Family == FamilyPivot.Wind) == 4; } else if (yaku == Tsuuiisou) { addYaku = combinationsSequence.All(c => c.IsHonor); } else if (yaku == Ryuuiisou) { addYaku = combinationsSequence.All(c => (c.Family == FamilyPivot.Bamboo && c.Tiles.All(t => new[] { 2, 3, 4, 6, 8 }.Contains(t.Number))) || (c.Family == FamilyPivot.Dragon && c.Tiles.First().Dragon == DragonPivot.Green) ); } else if (yaku == Chinroutou) { addYaku = combinationsSequence.All(c => c.IsTerminal); } else if (yaku == ChuurenPoutou) { if (combinationsSequence.All(c => c.IsConcealed) && combinationsSequence.Select(c => c.Family).Distinct().Count() == 1) { string numberPattern = string.Join(string.Empty, combinationsSequence.SelectMany(c => c.Tiles).Select(t => t.Number).OrderBy(i => i)); addYaku = new[] { "11112345678999", "11122345678999", "11123345678999", "11123445678999", "11123455678999", "11123456678999", "11123456778999", "11123456788999", "11123456789999" }.Contains(numberPattern); } } else if (yaku == Suukantsu) { addYaku = combinationsSequence.Count(c => c.IsSquare) == 4; } else if (yaku == Tenhou) { addYaku = context.IsTenhou(); } else if (yaku == Chiihou) { addYaku = context.IsChiihou(); } else if (yaku == Renhou) { addYaku = context.IsRenhou(); } else if (yaku == KokushiMusou) { // Do nothing here, but prevents the exception below. } else { throw new NotImplementedException(); } if (addYaku) { yakus.Add(yaku); } } // Remove yakumans with existant upgrade (it's an overkill as the only known case is "Shousuushii" vs. "Daisuushii"). yakus.RemoveAll(y => y.Upgrades.Any(yu => yakus.Contains(yu))); // Only return yakumans if any. if (yakus.Count >= 1) { return(yakus); } foreach (YakuPivot yaku in Yakus.Where(y => !y.IsYakuman)) { bool addYaku = false; int occurences = 1; if (yaku == Chiniisou) { addYaku = combinationsSequence.Select(c => c.Family).Distinct().Count() == 1 && !combinationsSequence.Any(c => c.IsHonor); } else if (yaku == Haitei) { addYaku = context.IsRoundLastTile; } else if (yaku == RinshanKaihou) { addYaku = context.DrawType == DrawTypePivot.Compensation; } else if (yaku == Chankan) { addYaku = context.DrawType == DrawTypePivot.OpponentKanCallOpen; } else if (yaku == Tanyao) { addYaku = combinationsSequence.All(c => !c.HasTerminalOrHonor); } else if (yaku == Yakuhai) { occurences = combinationsSequence.Count(c => c.IsBrelanOrSquare && ( c.Family == FamilyPivot.Dragon || ( c.Family == FamilyPivot.Wind && ( c.Tiles.First().Wind == context.DominantWind || c.Tiles.First().Wind == context.PlayerWind ) ) ) ); addYaku = occurences > 0; } else if (yaku == Riichi) { addYaku = context.IsRiichi; } else if (yaku == Ippatsu) { addYaku = context.IsIppatsu; } else if (yaku == MenzenTsumo) { addYaku = context.DrawType.IsSelfDraw() && combinationsSequence.All(c => c.IsConcealed); } else if (yaku == Honiisou) { addYaku = combinationsSequence.Where(c => !c.IsHonor).Select(c => c.Family).Distinct().Count() == 1; } else if (yaku == Pinfu) { addYaku = combinationsSequence.Count(c => c.IsSequence && c.IsConcealed) == 4 && !HandPivot.HandWithValuablePair(combinationsSequence, context.DominantWind, context.PlayerWind) && combinationsSequence.Any(c => c.IsSequence && c.Tiles.Contains(context.LatestTile) && !context.LatestTile.TileIsEdgeWait(c) && !context.LatestTile.TileIsMiddleWait(c)); } else if (yaku == Iipeikou) { int sequencesCount = combinationsSequence.Count(c => c.IsSequence); addYaku = combinationsSequence.All(c => c.IsConcealed) && sequencesCount >= 2 && combinationsSequence.Where(c => c.IsSequence).Distinct().Count() < sequencesCount; } else if (yaku == Shousangen) { addYaku = combinationsSequence.Count(c => c.IsBrelanOrSquare && c.Family == FamilyPivot.Dragon) == 2 && combinationsSequence.Any(c => c.IsPair && c.Family == FamilyPivot.Dragon); } else if (yaku == Honroutou) { addYaku = combinationsSequence.All(c => c.IsTerminal || c.IsHonor); } else if (yaku == Chiitoitsu) { addYaku = combinationsSequence.All(c => c.IsPair); } else if (yaku == Sankantsu) { addYaku = combinationsSequence.Count(c => c.IsSquare) == 3; } else if (yaku == SanshokuDoukou) { addYaku = combinationsSequence .Where(c => c.IsBrelanOrSquare && !c.IsHonor) .GroupBy(c => c.Tiles.First().Number) .FirstOrDefault(b => b.Count() >= 3)? .Select(b => b.Family)? .Distinct()? .Count() == 3; } else if (yaku == Sanankou) { addYaku = combinationsSequence.Count(c => c.IsBrelanOrSquare && c.IsConcealed && (!c.Tiles.Contains(context.LatestTile) || context.DrawType.IsSelfDraw())) == 3; } else if (yaku == Toitoi) { addYaku = combinationsSequence.Count(c => c.IsBrelanOrSquare) == 4; } else if (yaku == Ittsu) { List <TileComboPivot> ittsuFamilyCombos = combinationsSequence .Where(c => c.IsSequence) .GroupBy(c => c.Family) .FirstOrDefault(b => b.Count() >= 3)? .ToList(); addYaku = ittsuFamilyCombos != null && ittsuFamilyCombos.Any(c => c.SequenceFirstNumber == 1) && ittsuFamilyCombos.Any(c => c.SequenceFirstNumber == 4) && ittsuFamilyCombos.Any(c => c.SequenceFirstNumber == 7); } else if (yaku == SanshokuDoujun) { addYaku = combinationsSequence .Where(c => c.IsSequence) .GroupBy(c => c.SequenceFirstNumber) .FirstOrDefault(b => b.Count() >= 3)? .Select(b => b.Family)? .Distinct()? .Count() == 3; } else if (yaku == Chanta) { addYaku = combinationsSequence.All(c => c.HasTerminalOrHonor); } else if (yaku == DaburuRiichi) { addYaku = context.IsRiichi && context.IsFirstTurnRiichi; } else if (yaku == Ryanpeikou) { addYaku = combinationsSequence.All(c => c.IsConcealed) && combinationsSequence.Count(c => c.IsSequence) == 4 && combinationsSequence.Where(c => c.IsSequence).Distinct().Count() <= 2; } else if (yaku == Junchan) { addYaku = combinationsSequence.All(c => c.HasTerminal); } else if (yaku == NagashiMangan) { // Do nothing here, but prevents the exception below. } else { throw new NotImplementedException(); } if (addYaku) { for (int i = 0; i < occurences; i++) { yakus.Add(yaku); } } } // Remove yakus with existant upgrade. // It works because Upgrades is not recursive. yakus.RemoveAll(y => y.Upgrades.Any(yu => yakus.Contains(yu))); // On a concealed chanka, only Kokushi is allowed. if (context.DrawType == DrawTypePivot.OpponentKanCallConcealed && !yakus.Contains(KokushiMusou)) { yakus.Clear(); } return(yakus); }