/// <summary> /// Checks if the hand has finished on a closed wait. /// </summary> /// <returns><c>True</c> if contains a closed wait; <c>False</c> otherwise.</returns> public bool HandWithClosedWait() { if (YakusCombinations == null) { return(false); } // The combination with the last pick. TileComboPivot combo = YakusCombinations.FirstOrDefault(c => c.Tiles.Any(t => ReferenceEquals(t, LatestPick))); if (combo == null || combo.IsBrelanOrSquare) { return(false); } // Other concealed (and not declared) combinations with the same tile. List <TileComboPivot> otherCombos = YakusCombinations .Where(c => c != combo && !DeclaredCombinations.Contains(c) && c.Tiles.Contains(LatestPick)) .ToList(); // The real "LatestPick" is closed... bool isClosed = combo.IsPair || LatestPick.TileIsMiddleWait(combo) || LatestPick.TileIsEdgeWait(combo); // .. but there might be not-closed alternatives with the same tile as "LatestPick" in other combination. bool alternative1 = otherCombos.Any(c => c.IsBrelanOrSquare); bool alternative2 = otherCombos.Any(c => c.IsSequence && !LatestPick.TileIsMiddleWait(c) && !LatestPick.TileIsEdgeWait(c)); return(isClosed && !(alternative1 || alternative2)); }
/// <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); } }
/// <summary> /// Checks if the tile is on the closed edge of a sequence combination. /// </summary> /// <param name="combo">The combination.</param> /// <returns><c>True</c> if on the closed edge; <c>False</c> otherwise.</returns> public bool TileIsEdgeWait(TileComboPivot combo) { return(combo != null && combo.IsSequence && combo.Tiles.Contains(this) && ( (combo.SequenceFirstNumber == 1 && combo.SequenceLastNumber == Number) || (combo.SequenceLastNumber == 9 && combo.SequenceFirstNumber == Number) )); }
/// <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); }
/// <summary> /// Checks if the tile is in the middle of a sequence combination. /// </summary> /// <param name="combo">The combination.</param> /// <returns><c>True</c> if in the middle; <c>False</c> otherwise.</returns> public bool TileIsMiddleWait(TileComboPivot combo) { return(combo != null && combo.IsSequence && combo.Tiles.Contains(this) && combo.SequenceFirstNumber != Number && combo.SequenceLastNumber != Number); }