// Creates a panel for the specified combination. private StackPanel CreateCombinationPanel(int pIndex, TileComboPivot combo) { StackPanel panel = new StackPanel { Orientation = (pIndex == 0 || pIndex == 2 ? Orientation.Horizontal : Orientation.Vertical) }; WindPivot pWind = _game.GetPlayerCurrentWind(pIndex); int i = 0; List <Tuple <TilePivot, bool> > tileTuples = combo.GetSortedTilesForDisplay(pWind); if (pIndex > 0 && pIndex < 3) { tileTuples.Reverse(); } foreach (Tuple <TilePivot, bool> tileTuple in tileTuples) { panel.Children.Add(tileTuple.Item1.GenerateTileButton(null, (AnglePivot)(tileTuple.Item2 ? pIndex.RelativePlayerIndex(1) : pIndex), combo.IsConcealedDisplay(i))); i++; } return(panel); }
/// <summary> /// Gets the list of tiles from the combination, sorted by wind logic for display. /// </summary> /// <param name="ownerWind">The current wind of the owner.</param> /// <returns>List of tiles tuple; the second item is <c>True</c> when the tile is the opened one.</returns> public List <Tuple <TilePivot, bool> > GetSortedTilesForDisplay(WindPivot ownerWind) { if (!StolenFrom.HasValue) { return(Tiles.Select(t => new Tuple <TilePivot, bool>(t, false)).ToList()); } var concealedOnly = new List <TilePivot>(_tiles); concealedOnly.Remove(OpenTile); int i = 0; var tiles = new List <Tuple <TilePivot, bool> > { GetTileForSortedListAtSpecifiedWind(ownerWind.Left(), concealedOnly, ref i), GetTileForSortedListAtSpecifiedWind(ownerWind.Opposite(), concealedOnly, ref i) }; // For a square, the third tile is never from an opponent. if (IsSquare) { tiles.Add(new Tuple <TilePivot, bool>(concealedOnly[i], false)); i++; } tiles.Add(GetTileForSortedListAtSpecifiedWind(ownerWind.Right(), concealedOnly, ref i)); return(tiles); }
/// <summary> /// Checks if the hand contains a valuable pair (dragon, dominant wind, player wind). /// </summary> /// <param name="combinations">Lsit of combinations.</param> /// <param name="dominantWind">The dominant wind.</param> /// <param name="playerWind">The player wind.</param> /// <returns><c>True</c> if vluable pair in the hand; <c>False</c> otherwise.</returns> public static bool HandWithValuablePair(List <TileComboPivot> combinations, WindPivot dominantWind, WindPivot playerWind) { return(combinations != null && combinations.Any(c => c.IsPair && ( c.Family == FamilyPivot.Dragon || (c.Family == FamilyPivot.Wind && (c.Tiles.First().Wind == dominantWind || c.Tiles.First().Wind == playerWind)) ))); }
// Constructor. private PlayerPivot(string name, WindPivot initialWind, InitialPointsRulePivot initialPointsRulePivot, bool isCpu) { Name = name; InitialWind = initialWind; Points = initialPointsRulePivot.GetInitialPointsFromRule(); IsCpu = isCpu; }
/// <summary> /// Declares a pon. Does not discard a tile. /// </summary> /// <param name="tile">The stolen tile.</param> /// <param name="stolenFrom">The wind which the tile has been stolen from.</param> /// <exception cref="ArgumentNullException"><paramref name="tile"/> is <c>Null</c>.</exception> /// <exception cref="InvalidOperationException"><see cref="Messages.InvalidCall"/></exception> internal void DeclarePon(TilePivot tile, WindPivot stolenFrom) { if (tile == null) { throw new ArgumentNullException(nameof(tile)); } CheckTilesForCallAndExtractCombo(_concealedTiles.Where(t => t == tile), 2, tile, stolenFrom); }
// Gets the tile corresponding to the specified wind in the purpose to create a sorted list for display. private Tuple <TilePivot, bool> GetTileForSortedListAtSpecifiedWind(WindPivot wind, List <TilePivot> concealedOnly, ref int i) { if (wind == StolenFrom.Value) { return(new Tuple <TilePivot, bool>(OpenTile, true)); } else { i++; return(new Tuple <TilePivot, bool>(concealedOnly[i - 1], false)); } }
/// <summary> /// Extension; gets the wind to the left of the specified wind. /// </summary> /// <param name="origin">The wind.</param> /// <returns>The left wind.</returns> public static WindPivot Left(this WindPivot origin) { switch (origin) { case WindPivot.East: return(WindPivot.North); case WindPivot.South: return(WindPivot.East); case WindPivot.West: return(WindPivot.South); default: return(WindPivot.West); } }
/// <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)); }
/// <summary> /// Gets a japanese caracter which represents the specified wind. /// </summary> /// <param name="wind">The wind to display.</param> /// <returns>The associated japanese caracter.</returns> /// <exception cref="NotImplementedException">The wind is not implemented.</exception> internal static string ToWindDisplay(this WindPivot wind) { switch (wind) { case WindPivot.East: return("東"); case WindPivot.South: return("南"); case WindPivot.West: return("西"); case WindPivot.North: return("北"); default: throw new NotImplementedException(); } }
/// <summary> /// Constructor. /// </summary> /// <param name="latestTile">The <see cref="LatestTile"/> value.</param> /// <param name="drawType">The <see cref="DrawType"/> value.</param> /// <param name="dominantWind">The <see cref="DominantWind"/> value.</param> /// <param name="playerWind">The <see cref="PlayerWind"/> value.</param> /// <param name="isFirstOrLast">Optionnal; indicates a win at the first turn without any call made (<c>True</c>) or at the last tile of the round (<c>Null</c>); default value is <c>False</c>.</param> /// <param name="isRiichi">Optionnal; indicates if riichi (<c>True</c>) or riichi at first turn without any call made (<c>Null</c>); default value is <c>False</c>.</param> /// <param name="isIppatsu">Optionnal; indicates if it's a win by ippatsu (<paramref name="isRiichi"/> can't be <c>False</c> in such case); default value is <c>False</c>.</param> /// <param name="useRenhou">Optionnal; the <see cref="_useRenhou"/> value; default value is <c>False</c>.</param> /// <exception cref="ArgumentNullException"><paramref name="latestTile"/> is <c>Null</c>.</exception> /// <exception cref="ArgumentException"><see cref="Messages.InvalidContextIppatsuValue"/></exception> public WinContextPivot(TilePivot latestTile, DrawTypePivot drawType, WindPivot dominantWind, WindPivot playerWind, bool?isFirstOrLast = false, bool?isRiichi = false, bool isIppatsu = false, bool useRenhou = false) { if (isRiichi == false && isIppatsu) { throw new ArgumentException(Messages.InvalidContextIppatsuValue, nameof(isIppatsu)); } LatestTile = latestTile ?? throw new ArgumentNullException(nameof(latestTile)); IsRoundLastTile = isFirstOrLast == null; IsRiichi = isRiichi != false; IsFirstTurnRiichi = isRiichi == null; IsIppatsu = isIppatsu; DominantWind = dominantWind; PlayerWind = playerWind; IsFirstTurnDraw = isFirstOrLast == true; DrawType = drawType; _useRenhou = useRenhou; IsNagashiMangan = false; }
/// <summary> /// Wind family constructor. /// </summary> /// <param name="wind"><see cref="WindPivot"/>.</param> public TilePivot(WindPivot wind) { Wind = wind; Family = FamilyPivot.Wind; switch (wind) { case WindPivot.East: Graphic = DllRsc.vent_est; break; case WindPivot.South: Graphic = DllRsc.vent_sud; break; case WindPivot.West: Graphic = DllRsc.vent_ouest; break; case WindPivot.North: Graphic = DllRsc.vent_nord; break; } }
/// <summary> /// Declares a chii. Does not discard a tile. /// </summary> /// <param name="tile">The stolen tile.</param> /// <param name="stolenFrom">The wind which the tile has been stolen from.</param> /// <param name="startNumber">The sequence first number.</param> /// <exception cref="ArgumentNullException"><paramref name="tile"/> is <c>Null</c>.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="startNumber"/> is out of range.</exception> /// <exception cref="InvalidOperationException"><see cref="Messages.InvalidCall"/></exception> internal void DeclareChii(TilePivot tile, WindPivot stolenFrom, int startNumber) { if (tile == null) { throw new ArgumentNullException(nameof(tile)); } if (startNumber < 1 || startNumber > 7) { throw new ArgumentOutOfRangeException(nameof(startNumber)); } if (tile.Number < startNumber || tile.Number > startNumber + 2) { throw new InvalidOperationException(Messages.InvalidCall); } CheckTilesForCallAndExtractCombo(Enumerable .Range(startNumber, 3) .Where(i => i != tile.Number) .Select(i => _concealedTiles.FirstOrDefault(t => t.Family == tile.Family && t.Number == i)) .Where(t => t != null), 2, tile, stolenFrom); }
/// <summary> /// Computes the number of points for a winner, without honba and riichi pending count. /// </summary> /// <param name="fanCount">Fan count.</param> /// <param name="fuCount">Fu count.</param> /// <param name="isTsumo"><c>True</c> if win by tsumo; <c>False</c> otherwise.</param> /// <param name="playerWind">The current player wind.</param> /// <returns> /// - Number of points lost by east players (or one of the three remaining if the winner is east; or the specific loser if ron). /// - Number of points lost by the two other players. /// </returns> public static Tuple <int, int> GetPoints(int fanCount, int fuCount, bool isTsumo, WindPivot playerWind) { int v1 = 0; int v2 = 0; bool east = playerWind == WindPivot.East; if ((fanCount == 4 && fuCount >= 40) || (fanCount == 3 && fuCount >= 70)) { fanCount = 5; } if (fanCount > 4) { int basePoints = OVER_FOUR_FAN.Last(k => k.Key <= fanCount).Value *(east ? 2 : 1); // in case of several yakumans. if (fanCount > 13) { basePoints += (basePoints * ((fanCount - 13) / 13)); } if (isTsumo) { v1 = basePoints * (east ? 1 : 2); v2 = basePoints; } else { v1 = basePoints * (east ? 3 : 4); v2 = 0; } } else if (east) { var basePts = CHART_EAST.Last(k => k.Item1 <= fanCount && k.Item2 <= fuCount); if (isTsumo) { v1 = basePts.Item4; v2 = basePts.Item4; } else { v1 = basePts.Item3; v2 = 0; } } else { var basePts = CHART_OTHER.Last(k => k.Item1 <= fanCount && k.Item2 <= fuCount); if (isTsumo) { v1 = basePts.Item4; v2 = basePts.Item5; } else { v1 = basePts.Item3; v2 = 0; } } return(new Tuple <int, int>(v1, v2)); }
private static Tuple <List <SubstitutionGroup>, List <Substitution> > BackgroundHandlerForOneTileAway(WindPivot dominantWind, WindPivot seatWind, List <TilePivot> handTileWithLast, List <TilePivot> availableTiles, List <TilePivot> forbiddenTiles) { var substitutionsAttemps = new List <Substitution>(); var rawResults = new List <SubstitutionGroup>(); foreach (TilePivot subbedTile in handTileWithLast.Distinct()) { foreach (TilePivot subTile in availableTiles.Distinct()) { if (subbedTile.Equals(subTile) || forbiddenTiles.Contains(subTile)) { continue; } substitutionsAttemps.Add(new Substitution(subbedTile, subTile, availableTiles)); List <TilePivot> handTilesWithSub = new List <TilePivot>(handTileWithLast); handTilesWithSub.Remove(subbedTile); var hand1 = new FullHandPivot(handTilesWithSub, dominantWind, seatWind, subTile); HandYakuListPivot tmpResults = hand1.ComputeHandYakus()?.FirstOrDefault(); if (tmpResults != null) { var subSeq = new SubstitutionSequence(); subSeq.AddSubstitution(new Substitution(subbedTile, subTile, availableTiles)); var finded = rawResults.FirstOrDefault(x => x.Yakus.Equals(tmpResults)); if (finded == null) { rawResults.Add(new SubstitutionGroup(tmpResults)); finded = rawResults.Last(); } finded.AddSubstitutionSequence(subSeq); } } } return(new Tuple <List <SubstitutionGroup>, List <Substitution> >(rawResults, substitutionsAttemps)); }
private bool ExtractFromForm(out List <TilePivot> tiles, out TilePivot latestPickTile, out WindPivot dominantWind, out WindPivot seatWind) { tiles = new List <TilePivot>(); latestPickTile = null; dominantWind = WindPivot.East; seatWind = WindPivot.East; for (int i = 1; i <= 14; i++) { if (GetCombo(i).SelectedItem == null) { MessageBox.Show("Some slots are empty.", "LionelRiichiStats - Error"); return(false); } TilePivot tile = GetCombo(i).SelectedItem as TilePivot; if (GetRadio(i).IsChecked == true && latestPickTile == null) { latestPickTile = tile; } else { tiles.Add(tile); } } if (latestPickTile == null) { MessageBox.Show("The latest tile has not been selected.", "LionelRiichiStats - Error"); return(false); } if (tiles.Concat(new List <TilePivot> { latestPickTile }).GroupBy(t => t).Any(tg => tg.Count() > 4)) { MessageBox.Show("One tile is selected more than four times.", "LionelRiichiStats - Error"); return(false); } if (CbbDominantWind.SelectedIndex == -1) { MessageBox.Show("The dominant wind has not been selected.", "LionelRiichiStats - Error"); return(false); } if (CbbSeatWind.SelectedIndex == -1) { MessageBox.Show("The seat wind has not been selected.", "LionelRiichiStats - Error"); return(false); } dominantWind = (WindPivot)CbbDominantWind.SelectedItem; seatWind = (WindPivot)CbbSeatWind.SelectedItem; return(true); }
/// <summary> /// Constructor. /// </summary> /// <param name="tiles"><see cref="ConcealedTiles"/>.</param> /// <param name="dominantWind"><see cref="DominantWind"/>.</param> /// <param name="seatWind"><see cref="SeatWind"/>.</param> /// <param name="latestTile"><see cref="LatestTile"/>.</param> /// <param name="isRon">Optionnal; <see cref="IsRon"/>.</param> /// <param name="openedSets">Optionnal; <see cref="OpenedSets"/>.</param> /// <param name="concealedKans">Optionnal; <see cref="ConcealedKans"/>.</param> /// <param name="isRiichi">Optionnal; <see cref="IsRiichi"/>.</param> /// <param name="isDoubleRiichi">Optionnal; <see cref="IsDoubleRiichi"/>.</param> /// <param name="isIppatsu">Optionnal; <see cref="IsIppatsu"/>.</param> /// <param name="isHaitei">Optionnal; <see cref="IsHaitei"/>.</param> /// <param name="isRinshankaihou">Optionnal ; <see cref="IsRinshankaihou"/>.</param> /// <param name="isChankan">Optionnal; <see cref="IsChankan"/>.</param> /// <param name="isInitialDraw">Optionnal; <see cref="IsInitialDraw"/>.</param> /// <exception cref="ArgumentException"><see cref="Messages.InvalidTilesCountInHandError"/>.</exception> /// <exception cref="ArgumentException"><see cref="Messages.InvalidHandArgumentsConsistencyError"/>.</exception> public FullHandPivot(List <TilePivot> tiles, WindPivot dominantWind, WindPivot seatWind, TilePivot latestTile, bool isRon = false, List <SetPivot> openedSets = null, List <SetPivot> concealedKans = null, bool isRiichi = false, bool isDoubleRiichi = false, bool isIppatsu = false, bool isHaitei = false, bool isRinshankaihou = false, bool isChankan = false, bool isInitialDraw = false) { tiles = tiles ?? new List <TilePivot>(); openedSets = openedSets ?? new List <SetPivot>(); concealedKans = concealedKans ?? new List <SetPivot>(); if ((openedSets.Count * 3 + concealedKans.Count * 3 + 1 + tiles.Count) != 14) { throw new ArgumentException(Messages.InvalidTilesCountInHandError); } if (openedSets.Count > 0 && (isRiichi || isDoubleRiichi || isIppatsu || openedSets.Any(set => set.IsPair))) { throw new ArgumentException(Messages.InvalidHandArgumentsConsistencyError, nameof(openedSets)); } if (concealedKans.Count > 0 && concealedKans.Any(set => !set.IsKan)) { throw new ArgumentException(Messages.InvalidHandArgumentsConsistencyError, nameof(concealedKans)); } if (concealedKans.Count == 0 && !openedSets.Any(set => set.IsKan) && isRinshankaihou) { throw new ArgumentException(Messages.InvalidHandArgumentsConsistencyError, nameof(isRinshankaihou)); } if (IsHaitei && isRinshankaihou) { throw new ArgumentException(Messages.InvalidHandArgumentsConsistencyError, nameof(IsHaitei)); } if (isChankan && (isRinshankaihou || !isRon)) { throw new ArgumentException(Messages.InvalidHandArgumentsConsistencyError, nameof(isChankan)); } if (isIppatsu && (isRinshankaihou || (IsRon && isHaitei))) { throw new ArgumentException(Messages.InvalidHandArgumentsConsistencyError, nameof(isIppatsu)); } // TODO : implement controls to prevent buggy initial draw ConcealedTiles = tiles.OrderBy(t => t).ToList(); DominantWind = dominantWind; SeatWind = seatWind; LatestTile = latestTile ?? throw new ArgumentNullException(nameof(latestTile)); IsRon = isRon; OpenedSets = openedSets; ConcealedKans = concealedKans; IsRiichi = isRiichi || isDoubleRiichi; IsIppatsu = isIppatsu; IsDoubleRiichi = isDoubleRiichi; IsChankan = isChankan; IsHaitei = isHaitei; IsRinshankaihou = isRinshankaihou; IsInitialDraw = IsInitialDraw; }
// Constructor for wind. private TilePivot(WindPivot wind) { Family = FamilyPivot.Wind; Wind = wind; }
/// <summary> /// Gets the player index for the specified wind. /// </summary> /// <param name="wind">The wind.</param> /// <returns>The player index.</returns> public int GetPlayerIndexByCurrentWind(WindPivot wind) { return(Enumerable.Range(0, 4).First(i => GetPlayerCurrentWind(i) == wind)); }