internal void PlaceShip(BoardIndex ix, int shipLength, Direction direction) { for (var i = 0; i < shipLength; i++) { this[ix] = SquareContent.Ship; ix.TryNext(direction, out ix); } }
/// <inheritdoc/> public bool TryPlaceShip(BoardIndex ix, int shipLength, Direction direction) { if (!CanPlaceShip(ix, shipLength, direction, (c, r) => this[new BoardIndex(c, r)] == SquareContent.Water)) { return(false); } PlaceShip(ix, shipLength, direction); return(true); }
/// <summary> /// Finds out if a ship can be places at given coordinates. /// </summary> /// <param name="ix">Coordinates where the ship should be placed</param> /// <param name="shipLength">Length of the ship (max. 10)</param> /// <param name="direction">Direction of the ship</param> /// <param name="isWater">Callback to find out if a given suqare is water before placing the ship</param> /// <returns> /// <c>true</c> if the ship can be placed here, otherwise <c>false</c>. /// </returns> /// <remarks> /// Each ship occupies a number of consecutive squares on the battleship board, arranged either horizontally /// or vertically. The ships cannot overlap (i.e., only one ship can occupy any given square on the board). /// There has to be at least one square with water between each ship (i.e., ships must not be place /// directly adjacent to each other). /// </remarks> /// <exception cref="ArgumentOutOfRangeException">Thrown in case of invalid argument</exception> internal static bool CanPlaceShip(BoardIndex ix, int shipLength, Direction direction, IsWater isWater) { #region Check input parameter // This is a public method, so we have to check that parameters // contain valid values. // Check if ship isn't too long if (shipLength > 10) { throw new ArgumentOutOfRangeException(nameof(shipLength), "Maximum length of ship is 10"); } if (!Enum.IsDefined(typeof(Direction), direction)) { throw new ArgumentOutOfRangeException(nameof(direction), "Unknown direction"); }
/// <summary> /// Initializes a new <see cref="BoardIndexRange"/> instance. /// </summary> /// <param name="from">Starting point</param> /// <param name="to">End point</param> /// <remarks> /// If <paramref name="from"/> is greater than <paramref name="to"/>, from and to are switched /// so that <see cref="Length"/> is always positive. /// </remarks> public BoardIndexRange(BoardIndex from, BoardIndex to) { if (from.Column == to.Column) { this.from = to.Row > from.Row ? from : to; this.to = to.Row > from.Row ? to : from; } else if (from.Row == to.Row) { this.from = to.Column > from.Column ? from : to; this.to = to.Column > from.Column ? to : from; } else { throw new ArgumentException("Row or column or from and to have to match (e.g. width = 1)"); } }
/// <inheritdoc/> public SquareContent this[BoardIndex ix] { get => boardContent[ix];
/// <summary> /// Find out whether there is a ship on a given location and return its boundaries /// </summary> /// <param name="board">Board on which to find the ship</param> /// <param name="ix">Location to check</param> /// <param name="shipRange">Boundaries of the found ship</param> /// <returns> /// The method returns <see cref="ShipFindingResult.NoShip"/> if there is no ship on the specified cell. /// It returns <see cref="ShipFindingResult.PartialShip"/> if there is a ship on the specified cell, but it /// cannot be said for sure how long it is (because of <see cref="SquareContent.Unknown"/> cells in front or /// behind the ship). If a ship was found and the size of the ship could be determined, the function returns /// <see cref="ShipFindingResult.CompleteShip"/>. /// </returns> public static ShipFindingResult TryFindShip(this IReadOnlyBoard board, BoardIndex ix, out BoardIndexRange shipRange) { // Note pattern matching if (board[ix] is not SquareContent.HitShip and not SquareContent.Ship) { // No ship at specified index shipRange = new BoardIndexRange(); return(ShipFindingResult.NoShip); } // Note local method returning tuple (BoardIndex ix, bool complete) FindShipEdge(BoardIndex current, Direction direction, bool prev) { bool canMoveFurther; do { BoardIndex next; if (prev) { canMoveFurther = current.TryPrevious(direction, out next); } else { canMoveFurther = current.TryNext(direction, out next); } if (canMoveFurther) { current = next; } }while (canMoveFurther && board[current] is not SquareContent.Water and not SquareContent.Unknown); var complete = board[current] is not SquareContent.Unknown; if (board[current] is not SquareContent.Water and not SquareContent.Unknown) { return(current, complete); } if (!prev) { return(direction == Direction.Horizontal ? current.PreviousColumn() : current.PreviousRow(), complete); } return(direction == Direction.Horizontal ? current.NextColumn() : current.NextRow(), complete); } // Note local method receiving tuple bool TryDirection(Direction direction, out (BoardIndexRange range, ShipFindingResult complete) result) { var(beginningIx, beginningComplete) = FindShipEdge(ix, direction, true); var(endIx, endComplete) = FindShipEdge(ix, direction, false); result = (new BoardIndexRange(beginningIx, endIx), beginningComplete&& endComplete ? ShipFindingResult.CompleteShip : ShipFindingResult.PartialShip); return(result.range.Length > 1); } // Go left and find first water if (TryDirection(Direction.Horizontal, out var resultHorizontal)) { shipRange = resultHorizontal.range; return(resultHorizontal.complete); } // Note discard operator to indicate that result is not relevant _ = TryDirection(Direction.Vertical, out var resultVertical); shipRange = resultVertical.range; return(resultVertical.complete); }
// Discuss: C# deconstructors. Read more at https://docs.microsoft.com/en-us/dotnet/csharp/deconstruct /// <summary> /// Deconstructs the instance /// </summary> /// <param name="from">Starting point</param> /// <param name="to">End point</param> public readonly void Deconstruct(out BoardIndex from, out BoardIndex to) => (from, to) = (this.from, this.to);
/// <summary> /// Find out whether there is a ship on a given location and return its boundaries /// </summary> /// <param name="board">Board on which to find the ship</param> /// <param name="ix">Location to check</param> /// <param name="shipRange">Boundaries of the found ship</param> /// <returns> /// The method returns <see cref="ShipFindingResult.NoShip"/> if there is no ship on the specified cell. /// It returns <see cref="ShipFindingResult.PartialShip"/> if there is a ship on the specified cell, but it /// cannot be said for sure how long it is (because of <see cref="SquareContent.Unknown"/> cells in front or /// behind the ship). If a ship was found and the size of the ship could be determined, the function returns /// <see cref="ShipFindingResult.CompleteShip"/>. /// </returns> public static ShipFindingResult TryFindShip(this IReadOnlyBoard board, BoardIndex ix, out BoardIndexRange shipRange) { // Note pattern matching if (board[ix] is not SquareContent.HitShip and not SquareContent.Ship and not SquareContent.SunkenShip) { // No ship at specified index shipRange = new BoardIndexRange(); return(ShipFindingResult.NoShip); } // Note local method returning tuple (BoardIndex ix, bool complete) FindShipEdge(BoardIndex current, Direction direction, bool prev) { bool canMoveFurther; do { BoardIndex next; if (prev) { canMoveFurther = current.TryPrevious(direction, out next); } else { canMoveFurther = current.TryNext(direction, out next); } if (canMoveFurther) { current = next; } }while (canMoveFurther && board[current] is not SquareContent.Water and not SquareContent.Unknown); var complete = board[current] is not SquareContent.Unknown; if (board[current] is not SquareContent.Water and not SquareContent.Unknown) { return(current, complete); } if (!prev) { return(direction == Direction.Horizontal ? current.PreviousColumn() : current.PreviousRow(), complete); } return(direction == Direction.Horizontal ? current.NextColumn() : current.NextRow(), complete); } // Note local method receiving tuple bool TryDirection(Direction direction, out (BoardIndexRange range, ShipFindingResult complete) result) { var(beginningIx, beginningComplete) = FindShipEdge(ix, direction, true); var(endIx, endComplete) = FindShipEdge(ix, direction, false); result = (new BoardIndexRange(beginningIx, endIx), beginningComplete&& endComplete ? ShipFindingResult.CompleteShip : ShipFindingResult.PartialShip); return(result.range.Length > 1); } // Check if the ship is placed horizontally var isHorizontal = TryDirection(Direction.Horizontal, out var resultHorizontal); if (isHorizontal) { shipRange = resultHorizontal.range; return(resultHorizontal.complete); } // Check if the ship is placed vertically var isVertical = TryDirection(Direction.Vertical, out var resultVertical); shipRange = resultVertical.range; // When only a single square of a ship is known, no orientation can be determined and therefore the ship is only partial. if (!isHorizontal && !isVertical) { return(ShipFindingResult.PartialShip); } return(resultVertical.complete); }