1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// Copyright 2020 Zachary Stewart
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Types used for defining ships and their shapes.
use std::{fmt::Debug, hash::Hash};

use crate::board::Dimensions;

pub use self::linear::Line;

mod linear;

/// Trait for types that can be used as a Ship's ID within a single player's board.
/// IDs are treated as disposable and cheaply cloneable. If you need a complex ID type
/// that isn't cheap to clone, you may want to wrap it in `Rc` or `Arc`.
///
/// Auto-implemented for any type which implements `Debug`,`Clone`, `Eq`, and `Hash`.
pub trait ShipId: Debug + Clone + Eq + Hash {}
impl<T: Debug + Clone + Eq + Hash> ShipId for T {}

/// Trait for shapes that a ship can be.
pub trait ShipShape<D: Dimensions + ?Sized> {
    type ProjectIterState: ProjectIterState<D, ShipShape = Self>;

    /// Get an iterator over possible placements of this ship shap in the given
    /// dimensions. Does not in any way account for whether cells are already occupied or
    /// not.
    fn project<'a>(
        &'a self,
        coord: D::Coordinate,
        dim: &'a D,
    ) -> ProjectIter<D, Self::ProjectIterState> {
        ProjectIter {
            shape: self,
            dim: dim,
            state: Self::ProjectIterState::start(self, dim, coord),
        }
    }

    /// Return true if the given shape projection is a valid placement of this ship in the
    /// specified dimensions. Does not account for whether cells are already occupied.
    /// Shapes are free to reject any placement that they did not generate.
    fn is_valid_placement(&self, proj: &ShapeProjection<D::Coordinate>, dim: &D) -> bool;
}

/// Projection of a shape onto a coordinate system relative to a particular point. This is
/// a simple typedef of a `Vec`, however projections retrieved from a particular Ship-
/// Shape should not be modified, as shapes are free to reject any projection that they
/// did not generate.
pub type ShapeProjection<C> = Vec<C>;

/// State type for the ship projection iterator.
pub trait ProjectIterState<D: Dimensions + ?Sized> {
    type ShipShape: ShipShape<D> + ?Sized;

    /// Construct an instance of this iter state given the arguments.
    fn start(shape: &Self::ShipShape, dim: &D, coord: D::Coordinate) -> Self;

    /// Get the next possible projection of the ship's shape.
    fn next(&mut self, shape: &Self::ShipShape, dim: &D) -> Option<ShapeProjection<D::Coordinate>>;
}

/// Iterator over possible projections of a ship shape onto dimensions.
pub struct ProjectIter<'a, D, S>
where
    D: Dimensions + ?Sized,
    S: ProjectIterState<D>,
{
    shape: &'a S::ShipShape,
    dim: &'a D,
    state: S,
}

impl<'a, D, S> Iterator for ProjectIter<'a, D, S>
where
    D: Dimensions + ?Sized,
    S: ProjectIterState<D>,
{
    type Item = ShapeProjection<D::Coordinate>;

    fn next(&mut self) -> Option<Self::Item> {
        self.state.next(self.shape, self.dim)
    }
}