Skip to main content

spacebattleship/game/uniform/
errors.rs

1// Copyright 2020 Zachary Stewart
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::{self, Debug};
16
17use thiserror::Error;
18
19use crate::board::{CannotShootReason as BoardCannotShootReason, ShotError as BoardShotError};
20
21/// Error returned when trying to add a ship that already existed.
22#[derive(Error)]
23#[error("player with id {id:?} already exists")]
24pub struct AddPlayerError<P: Debug, D> {
25    /// ID of the player that was attempted to be added.
26    id: P,
27    /// The dimensions of the player grid that was not added because the player ID was
28    /// already in use.
29    dim: D,
30}
31
32impl<P: Debug, D> AddPlayerError<P, D> {
33    /// Create an [`AddPlayerError`] for the player with the given ID and dimensions.
34    pub(super) fn new(id: P, dim: D) -> Self {
35        Self { id, dim }
36    }
37
38    /// The id of the player that was added.
39    pub fn id(&self) -> &P {
40        &self.id
41    }
42
43    /// The dimensions of the board that was attempted to be added.
44    pub fn dimensions(&self) -> &D {
45        &self.dim
46    }
47
48    /// Extract the ID and Dimensions from this error.
49    pub fn into_inner(self) -> (P, D) {
50        (self.id, self.dim)
51    }
52}
53
54impl<P: Debug, D> From<AddPlayerError<P, D>> for (P, D) {
55    /// Allows retrieving the inner id and shape from the error with into.
56    fn from(err: AddPlayerError<P, D>) -> Self {
57        err.into_inner()
58    }
59}
60
61impl<I: Debug, S> Debug for AddPlayerError<I, S> {
62    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63        fmt::Display::fmt(self, f)
64    }
65}
66
67/// Reason why a particular tile could not be shot.
68#[derive(Debug, Copy, Clone, Eq, PartialEq)]
69pub enum CannotShootReason {
70    /// The game is already over.
71    AlreadyOver,
72
73    /// The player being attacked is the player whose turn it is.
74    SelfShot,
75
76    /// The PlayerId given is not known to the board.
77    UnknownPlayer,
78
79    /// The player being attacked is already defeated.
80    AlreadyDefeated,
81
82    /// The shot was out of bounds on the grid.
83    OutOfBounds,
84
85    /// The tile specified was already shot.
86    AlreadyShot,
87}
88
89impl From<BoardCannotShootReason> for CannotShootReason {
90    fn from(reason: BoardCannotShootReason) -> Self {
91        match reason {
92            BoardCannotShootReason::AlreadyDefeated => CannotShootReason::AlreadyDefeated,
93            BoardCannotShootReason::OutOfBounds => CannotShootReason::OutOfBounds,
94            BoardCannotShootReason::AlreadyShot => CannotShootReason::AlreadyShot,
95        }
96    }
97}
98
99/// Error returned when trying to shoot a cell.
100#[derive(Debug, Error)]
101#[error("could not shoot player {player:?} at cell {coord:?}: {reason:?}")]
102pub struct ShotError<P: Debug, C: Debug> {
103    /// Reason why the cell could not be shot.
104    reason: CannotShootReason,
105
106    /// Id of the player that was attacked.
107    player: P,
108
109    /// Coordinates that were attacked.
110    coord: C,
111}
112
113impl<P: Debug, C: Debug> ShotError<P, C> {
114    /// Create a [`ShotError`] from a reason, player and coordinate.
115    pub(super) fn new(reason: CannotShootReason, player: P, coord: C) -> Self {
116        Self {
117            reason,
118            player,
119            coord,
120        }
121    }
122
123    /// Create a [`ShotError`] by adding a player ID as context to a [`BoardShotError`].
124    pub(super) fn add_context(cause: BoardShotError<C>, player: P) -> Self {
125        Self {
126            reason: cause.reason().into(),
127            player,
128            coord: cause.into_coord(),
129        }
130    }
131
132    /// Get the reason the shot failed.
133    pub fn reason(&self) -> CannotShootReason {
134        self.reason
135    }
136
137    /// Get the ID of the player that was shot at.
138    pub fn player(&self) -> &P {
139        &self.player
140    }
141
142    /// Get the coordinate that was shot at.
143    pub fn coord(&self) -> &C {
144        &self.coord
145    }
146
147    /// Extract the player ID and coordinates from the error.
148    pub fn into_inner(self) -> (P, C) {
149        (self.player, self.coord)
150    }
151}