Пример #1
0
        /// <summary>
        /// Declares a kan (opened). Does not discard a tile. Does not draw substitution tile.
        /// </summary>
        /// <param name="tile">The stolen tile.</param>
        /// <param name="stolenFrom">The wind which the tile has been stolen from.</param>
        /// <param name="fromOpenPon">The <see cref="TileComboPivot"/>, if the kan is called as an override of a previous pon call; <c>Null</c> otherwise.</param>
        /// <exception cref="ArgumentNullException"><paramref name="tile"/> is <c>Null</c>.</exception>
        /// <exception cref="InvalidOperationException"><see cref="Messages.InvalidCall"/></exception>
        internal void DeclareKan(TilePivot tile, WindPivot?stolenFrom, TileComboPivot fromOpenPon)
        {
            if (tile == null)
            {
                throw new ArgumentNullException(nameof(tile));
            }

            if (fromOpenPon == null)
            {
                CheckTilesForCallAndExtractCombo(_concealedTiles.Where(t => t == tile),
                                                 stolenFrom.HasValue ? 3 : 4,
                                                 stolenFrom.HasValue ? tile : null,
                                                 stolenFrom
                                                 );
            }
            else
            {
                int indexOfPon = _declaredCombinations.IndexOf(fromOpenPon);
                if (indexOfPon < 0 || stolenFrom.HasValue)
                {
                    throw new InvalidOperationException(Messages.InvalidCall);
                }

                var concealedTiles = new List <TilePivot>
                {
                    tile
                };
                concealedTiles.AddRange(fromOpenPon.Tiles.Where(t => !ReferenceEquals(t, fromOpenPon.OpenTile)));

                _declaredCombinations[indexOfPon] = new TileComboPivot(concealedTiles, fromOpenPon.OpenTile, fromOpenPon.StolenFrom);
                _concealedTiles.Remove(tile);
            }
        }
Пример #2
0
        /// <summary>
        /// Checks if <see cref="Yakus"/> and <see cref="YakusCombinations"/> have to be cancelled because of the temporary furiten rule.
        /// </summary>
        /// <param name="currentRound">The current round</param>
        /// <param name="playerIndex">The player index of the hand.</param>
        /// <returns><c>True</c> if temporary furiten; <c>False</c> otherwise.</returns>
        internal bool CancelYakusIfTemporaryFuriten(RoundPivot currentRound, int playerIndex)
        {
            if (currentRound == null)
            {
                return(false);
            }

            int i = 0;

            while (currentRound.PlayerIndexHistory.Count < i &&
                   currentRound.PlayerIndexHistory.ElementAt(i) == playerIndex.RelativePlayerIndex(-(i + 1)) &&
                   playerIndex.RelativePlayerIndex(-(i + 1)) != playerIndex)
            {
                // The tile discarded by the latest player is the tile we ron !
                if (i > 0)
                {
                    TilePivot lastFromDiscard = currentRound.GetDiscard(currentRound.PlayerIndexHistory.ElementAt(i)).LastOrDefault();
                    if (lastFromDiscard != null && IsCompleteFull(new List <TilePivot>(ConcealedTiles)
                    {
                        lastFromDiscard
                    }, DeclaredCombinations.ToList()))
                    {
                        Yakus             = null;
                        YakusCombinations = null;
                        return(true);
                    }
                }
                i++;
            }

            return(false);
        }
Пример #3
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="discardRank">The <see cref="DiscardRank"/> value.</param>
        /// <param name="isDaburu">The <see cref="IsDaburu"/> value.</param>
        /// <param name="tile">The <see cref="Tile"/> value.</param>
        /// <param name="opponentsVirtualDiscardRank">The <see cref="_opponentsVirtualDiscardRank"/> value.</param>
        /// <exception cref="ArgumentNullException"><paramref name="tile"/> is <c>Null</c>.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="opponentsVirtualDiscardRank"/> is <c>Null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="discardRank"/> is out of range.</exception>
        /// <exception cref="ArgumentException"><see cref="Messages.InvalidDiscardRank"/></exception>
        /// <exception cref="ArgumentException"><see cref="Messages.InvalidOpponentsVirtualDiscardRank"/></exception>
        public RiichiPivot(int discardRank, bool isDaburu, TilePivot tile, IDictionary <int, int> opponentsVirtualDiscardRank)
        {
            if (opponentsVirtualDiscardRank == null)
            {
                throw new ArgumentNullException(nameof(opponentsVirtualDiscardRank));
            }

            if (discardRank < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(discardRank));
            }

            if (isDaburu && discardRank > 0)
            {
                throw new ArgumentException(Messages.InvalidDiscardRank, nameof(discardRank));
            }

            if (opponentsVirtualDiscardRank.Keys.Count != 3 || opponentsVirtualDiscardRank.Values.Any(v => v < 0))
            {
                throw new ArgumentException(Messages.InvalidOpponentsVirtualDiscardRank, nameof(opponentsVirtualDiscardRank));
            }

            DiscardRank = discardRank;
            IsDaburu    = isDaburu;
            Tile        = tile ?? throw new ArgumentNullException(nameof(tile));
            _opponentsVirtualDiscardRank = new Dictionary <int, int>(opponentsVirtualDiscardRank);
        }
Пример #4
0
 private bool IsDiscardedOrUnusable(TilePivot tile, int opponentPlayerIndex, List <TilePivot> deadtiles)
 {
     return(_round.GetDiscard(opponentPlayerIndex).Contains(tile) || (
                tile.IsHonor &&
                deadtiles.Count(t => t == tile) == 4 &&
                deadtiles.GroupBy(t => t).Any(t => t.Key != tile && t.Key.IsHonor && t.Count() == 4)
                ));
 }
Пример #5
0
        /// <summary>
        /// Declares a kan (concealed). Does not discard a tile. Does not draw substitution tile.
        /// </summary>
        /// <param name="tile">The tile, from the current hand, to make a square from.</param>
        /// <exception cref="ArgumentNullException"><paramref name="tile"/> is <c>Null</c>.</exception>
        /// <exception cref="InvalidOperationException"><see cref="Messages.InvalidCall"/></exception>
        internal void DeclareKan(TilePivot tile)
        {
            if (tile == null)
            {
                throw new ArgumentNullException(nameof(tile));
            }

            CheckTilesForCallAndExtractCombo(_concealedTiles.Where(t => t == tile), 4, null, null);
        }
Пример #6
0
        /// <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);
        }
Пример #7
0
        /// <summary>
        /// Sets <see cref="LatestPick"/> after a ron.
        /// </summary>
        /// <param name="ronTile">The ron tile.</param>
        internal void SetFromRon(TilePivot ronTile)
        {
            if (Yakus == null || YakusCombinations == null || ronTile == null)
            {
                return;
            }

            LatestPick = ronTile;
        }
Пример #8
0
        /// <summary>
        /// Tries to discard the specified tile.
        /// </summary>
        /// <param name="tile">The tile to discard; should obviously be contained in <see cref="_concealedTiles"/>.</param>
        /// <param name="afterStealing">Optionnal; indicates if the discard is made after stealing a tile; the default value is <c>False</c>.</param>
        /// <returns><c>False</c> if the discard is forbidden by the tile stolen; <c>True</c> otherwise.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="tile"/> is <c>Null</c>.</exception>
        /// <exception cref="InvalidOperationException"><see cref="Messages.ImpossibleDiscard"/></exception>
        /// <exception cref="ArgumentException"><see cref="Messages.ImpossibleStealingArgument"/></exception>
        internal bool Discard(TilePivot tile, bool afterStealing = false)
        {
            if (!CanDiscardTile(tile, afterStealing))
            {
                return(false);
            }

            _concealedTiles.Remove(tile);
            return(true);
        }
Пример #9
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="playerIndex">The <see cref="PlayerIndex"/> value.</param>
        /// <param name="tile">The <see cref="Tile"/> value.</param>
        /// <exception cref="ArgumentNullException"><paramref name="tile"/> is <c>Null</c>.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="playerIndex"/> is out of range.</exception>
        internal TileEventArgs(int playerIndex, TilePivot tile)
        {
            if (playerIndex < 0 || playerIndex > 3)
            {
                throw new ArgumentOutOfRangeException(nameof(playerIndex));
            }

            PlayerIndex = playerIndex;
            Tile        = tile ?? throw new ArgumentNullException(nameof(tile));
        }
Пример #10
0
        private IEnumerable <TilePivot> GetSujisFromDiscard(TilePivot tile, int opponentPlayerIndex)
        {
            if (tile.IsHonor)
            {
                return(Enumerable.Empty <TilePivot>());
            }

            return(_round
                   .GetDiscard(opponentPlayerIndex)
                   .Where(_ => _.Family == tile.Family && (_.Number == tile.Number + 3 || _.Number == tile.Number - 3)));
        }
Пример #11
0
        /// <summary>
        /// Picks a tile from the wall (or from the treasure as compensation of a kan) and adds it to the hand.
        /// </summary>
        /// <param name="tile">The tile picked.</param>
        /// <exception cref="ArgumentNullException"><paramref name="tile"/> is <c>Null</c>.</exception>
        /// <exception cref="InvalidOperationException"><see cref="Messages.InvalidDraw"/></exception>
        internal void Pick(TilePivot tile)
        {
            if (_concealedTiles.Count + _declaredCombinations.Count * 3 != 13)
            {
                throw new InvalidOperationException(Messages.InvalidDraw);
            }

            LatestPick = tile ?? throw new ArgumentNullException(nameof(tile));
            _concealedTiles.Add(tile);
            _concealedTiles.Sort();
        }
Пример #12
0
        /// <summary>
        /// Checks if a tile can be discarded, but does not discard it.
        /// </summary>
        /// <param name="tile">The tile to discard; should obviously be contained in <see cref="_concealedTiles"/>.</param>
        /// <param name="afterStealing">Optionnal; indicates if the discard is made after stealing a tile; the default value is <c>False</c>.</param>
        /// <returns><c>False</c> if the discard is forbidden by the tile stolen; <c>True</c> otherwise.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="tile"/> is <c>Null</c>.</exception>
        /// <exception cref="InvalidOperationException"><see cref="Messages.ImpossibleDiscard"/></exception>
        /// <exception cref="ArgumentException"><see cref="Messages.ImpossibleStealingArgument"/></exception>
        internal bool CanDiscardTile(TilePivot tile, bool afterStealing = false)
        {
            if (tile == null)
            {
                throw new ArgumentNullException(nameof(tile));
            }

            if (!_concealedTiles.Contains(tile))
            {
                throw new InvalidOperationException(Messages.ImpossibleDiscard);
            }

            if (afterStealing)
            {
                TileComboPivot lastCombination = _declaredCombinations.LastOrDefault();
                if (lastCombination == null || lastCombination.IsConcealed)
                {
                    throw new ArgumentException(Messages.ImpossibleStealingArgument, nameof(afterStealing));
                }
                TilePivot stolenTile = lastCombination.OpenTile;
                if (stolenTile == tile)
                {
                    return(false);
                }
                else if (lastCombination.IsSequence &&
                         tile.Family == lastCombination.Family &&
                         lastCombination.OpenTile.Number == lastCombination.SequenceFirstNumber &&
                         tile.Number == lastCombination.SequenceFirstNumber + 3)
                {
                    return(false);
                }
                else if (lastCombination.IsSequence &&
                         tile.Family == lastCombination.Family &&
                         lastCombination.OpenTile.Number == lastCombination.SequenceLastNumber &&
                         tile.Number == lastCombination.SequenceLastNumber - 3)
                {
                    return(false);
                }
            }

            return(true);
        }
Пример #13
0
        /// <summary>
        /// Checks if any CPU player can make a pon call, and computes its decision if any.
        /// </summary>
        /// <returns>The player index who makes the call; <c>-1</c> is none.</returns>
        public int PonDecision()
        {
            int opponentPlayerId = _round.OpponentsCanCallPon();

            if (opponentPlayerId > -1)
            {
                TilePivot tile = _round.GetDiscard(_round.PreviousPlayerIndex).Last();
                // Call the pon if :
                // - the hand is already open
                // - it's valuable (see "HandCanBeOpened")
                var opponentHand = _round.GetHand(opponentPlayerId);
                if (!opponentHand.IsConcealed || HandCanBeOpened(opponentPlayerId, tile, opponentHand))
                {
                    return(opponentPlayerId);
                }
                opponentPlayerId = -1;
            }

            return(opponentPlayerId);
        }
Пример #14
0
        /// <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;
        }
Пример #15
0
        /// <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);
        }
Пример #16
0
        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);
        }
Пример #17
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="concealedTiles">List of concealed tiles.</param>
        /// <param name="openTile">Optionnal; the <see cref="OpenTile"/> value; default value is <c>Null</c>.</param>
        /// <param name="stolenFrom">Optionnal; the <see cref="StolenFrom"/> value; default value is <c>Null</c>.</param>
        /// <exception cref="ArgumentNullException"><paramref name="concealedTiles"/> is <c>Null</c>.</exception>
        /// <exception cref="ArgumentException"><see cref="Messages.InvalidTilesCount"/></exception>
        /// <exception cref="ArgumentException"><see cref="Messages.InvalidCombination"/></exception>
        /// <exception cref="ArgumentException"><see cref="Messages.StolenFromNotSpecified"/></exception>
        public TileComboPivot(IEnumerable <TilePivot> concealedTiles, TilePivot openTile = null, WindPivot?stolenFrom = null)
        {
            if (concealedTiles is null)
            {
                throw new ArgumentNullException(nameof(concealedTiles));
            }

            var tiles = new List <TilePivot>(concealedTiles);

            if (openTile != null)
            {
                tiles.Add(openTile);
            }

            if (tiles.Count() < 2 || tiles.Count() > 4)
            {
                throw new ArgumentException(Messages.InvalidTilesCount, nameof(concealedTiles));
            }

            if (openTile != null && !stolenFrom.HasValue)
            {
                throw new ArgumentException(Messages.StolenFromNotSpecified, nameof(stolenFrom));
            }

            OpenTile   = openTile;
            StolenFrom = stolenFrom;

            // The sort is important here...
            _tiles = tiles.OrderBy(t => t).ToList();

            // ...to check the validity of a potential sequence
            if (!IsValidCombination())
            {
                throw new ArgumentException(Messages.InvalidCombination, nameof(tiles));
            }
        }
Пример #18
0
        // Assumes that all tiles are from the same family, and this family is caracter / circle / bamboo.
        // Also assumes that referenced tile is included in the list.
        private static List <TileComboPivot> GetCombinationsForTile(TilePivot tile, IEnumerable <TilePivot> tiles)
        {
            var combinations = new List <TileComboPivot>();

            List <TilePivot> sameNumber = tiles.Where(t => t.Number == tile.Number).ToList();

            if (sameNumber.Count > 1)
            {
                // Can make a pair.
                combinations.Add(new TileComboPivot(new List <TilePivot>
                {
                    tile,
                    tile
                }));
                if (sameNumber.Count > 2)
                {
                    // Can make a brelan.
                    combinations.Add(new TileComboPivot(new List <TilePivot>
                    {
                        tile,
                        tile,
                        tile
                    }));
                }
            }

            TilePivot secondLow  = tiles.FirstOrDefault(t => t.Number == tile.Number - 2);
            TilePivot firstLow   = tiles.FirstOrDefault(t => t.Number == tile.Number - 1);
            TilePivot firstHigh  = tiles.FirstOrDefault(t => t.Number == tile.Number + 1);
            TilePivot secondHigh = tiles.FirstOrDefault(t => t.Number == tile.Number + 2);

            if (secondLow != null && firstLow != null)
            {
                // Can make a sequence.
                combinations.Add(new TileComboPivot(new List <TilePivot>
                {
                    secondLow,
                    firstLow,
                    tile
                }));
            }
            if (firstLow != null && firstHigh != null)
            {
                // Can make a sequence.
                combinations.Add(new TileComboPivot(new List <TilePivot>
                {
                    firstLow,
                    tile,
                    firstHigh
                }));
            }
            if (firstHigh != null && secondHigh != null)
            {
                // Can make a sequence.
                combinations.Add(new TileComboPivot(new List <TilePivot>
                {
                    tile,
                    firstHigh,
                    secondHigh
                }));
            }

            return(combinations);
        }
Пример #19
0
 // Builds a pair, brelan or square of the specified tile.
 private static TileComboPivot Build(TilePivot tile, int k)
 {
     return(new TileComboPivot(Enumerable.Range(0, k).Select(i => tile)));
 }
Пример #20
0
 /// <summary>
 /// Builds a square from the specified tile.
 /// </summary>
 /// <param name="tile">The tile.</param>
 /// <returns>The square.</returns>
 public static TileComboPivot BuildSquare(TilePivot tile)
 {
     return(Build(tile, 4));
 }
Пример #21
0
 /// <summary>
 /// Builds a brelan from the specified tile.
 /// </summary>
 /// <param name="tile">The tile.</param>
 /// <returns>The brelan.</returns>
 public static TileComboPivot BuildBrelan(TilePivot tile)
 {
     return(Build(tile, 3));
 }
Пример #22
0
 /// <summary>
 /// Builds a pair from the specified tile.
 /// </summary>
 /// <param name="tile">The tile.</param>
 /// <returns>The pair.</returns>
 public static TileComboPivot BuildPair(TilePivot tile)
 {
     return(Build(tile, 2));
 }
Пример #23
0
 // suji on the edge (safer)
 private bool IsOutsiderSuji(TilePivot tile, int opponentPlayerIndex)
 {
     return(GetSujisFromDiscard(tile, opponentPlayerIndex)
            .Any(_ => new[] { 4, 5, 6 }.Contains(_.Number)));
 }
Пример #24
0
 private static bool IsDragonOrValuableWind(TilePivot tile, WindPivot[] winds)
 {
     return(tile.Family == FamilyPivot.Dragon ||
            (tile.Family == FamilyPivot.Wind && winds.Contains(tile.Wind.Value)));
 }
Пример #25
0
        // Creates a declared combination from the specified tiles
        private void CheckTilesForCallAndExtractCombo(IEnumerable <TilePivot> tiles, int expectedCount, TilePivot tile, WindPivot?stolenFrom)
        {
            if (tiles.Count() < expectedCount)
            {
                throw new InvalidOperationException(Messages.InvalidCall);
            }

            List <TilePivot> tilesPick = tiles.Take(expectedCount).ToList();

            _declaredCombinations.Add(new TileComboPivot(tilesPick, tile, stolenFrom));
            tilesPick.ForEach(t => _concealedTiles.Remove(t));
        }
Пример #26
0
 // a family is over-represented in the opponent discard (at least 9 overal, and at least 3 distinct values)
 private bool IsMaxedFamilyInDiscard(TilePivot tile, int opponentPlayerIndex)
 {
     return(!tile.IsHonor && _round.GetDiscard(opponentPlayerIndex).Count(_ => _.Family == tile.Family) >= 9 &&
            _round.GetDiscard(opponentPlayerIndex).Where(_ => _.Family == tile.Family).Distinct().Count() >= 3);
 }
Пример #27
0
 // suji on both side
 private bool IsDoubleInsiderSuji(TilePivot tile, int opponentPlayerIndex)
 {
     return(GetSujisFromDiscard(tile, opponentPlayerIndex).Distinct().Count() > 1);
 }