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}