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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
use core::{ num::{NonZeroUsize, NonZeroU8} }; #[cfg(feature = "serialization")] use serde::{Serialize, Deserialize}; use super::{ Field }; /// A Minesweeper tile. #[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub enum Tile { /// A tile which is empty but hasn't been opened yet. ClosedEmpty(Flag), /// A tile which has been opened and doesn't have neighboring mines. OpenEmpty, /// A tile which has been opened and has neighboring mines. OpenNumber(NonZeroU8), /// A tile which has a mine inside, and whether it's marked or not. Mine(Flag) } impl Tile { /// Returns `true` if the tile is closed, `false` otherwise. #[inline] pub fn is_closed(self) -> bool { match self { Self::ClosedEmpty(_) | Self::Mine(_) => true, _ => false } } /// Returns `true` if the tile is open, `false` otherwise. #[inline] pub fn is_open(self) -> bool { match self { Self::OpenEmpty | Self::OpenNumber(_) => true, _ => false } } /// Returns `true` if the tile contains a mine, `false` otherwise. #[inline] pub fn is_mine(self) -> bool { match self { Self::Mine(_) => true, _ => false } } /// Returns `true` if clicking this tile does not end the game, `false` otherwise. #[inline(always)] pub fn is_safe(self) -> bool { !self.is_mine() } /// Returns `true` if this tile has to be clicked in order for the game to successfully end, `false` otherwise. /// /// This is `false` for open mines — returns `true` only for `ClosedEmpty`. #[inline] pub fn is_required_to_open(self) -> bool { match self { Self::ClosedEmpty(_) => true, _ => false } } /// Returns the type of flag installed on this tile, or `None` if this tile is open and thus cannot hold a flag. #[inline] pub fn flag_state(self) -> Option<Flag> { match self { Self::ClosedEmpty(flag) => Some(flag), Self::Mine(flag) => Some(flag), _ => None } } /// Returns `true` if the `flag_state` is `Some(Flag::Flagged)`, `false` otherwise. #[inline] pub fn is_flagged(self) -> bool { if let Some(flag) = self.flag_state() { if let Flag::Flagged = flag { true } else { false } } else { false } } /// Returns a [`ClickOutcome`][co] from the data known only to this specific tile, or `None` if returning one requires access to the field. /// /// [co]: enum.ClickOutcome.html "ClickOutcome — the event produced after clicking a tile" #[inline] pub fn peek_local(self) -> Option<ClickOutcome> { match self { Self::ClosedEmpty(_) => None, Self::OpenEmpty => Some(ClickOutcome::OpenClearing), Self::OpenNumber(_) => Some(ClickOutcome::Chord), Self::Mine(_) => Some(ClickOutcome::Explosion) } } } impl Default for Tile { #[inline(always)] /// Returns the `ClosedEmpty` variant. fn default() -> Self { Self::ClosedEmpty(Flag::default()) } } impl PartialEq<Tile> for Tile { /// Compares two tiles. /// /// Two tiles are equal if they're both empty or they both contain a mine. Other factors, like the presence of a flag or amount of surrounding mines are not /// compared. fn eq(&self, other: &Self) -> bool { match self { Self::Mine(_) => { if let Self::Mine(_) = other { true } else {false} }, _ => { match other { Self::Mine(_) => false, _ => true } }, } } } impl Eq for Tile {} /// The implementation for the clearing traversal algorithm which works for both mutable usage and immutable usage. macro_rules! for_every_tile { ($field:expr, $anchor_location:expr, $f:expr, $include_shore:expr) => { // We're using this specific type as the type of a frame on the stack. It consists of two tuples: // - The location at which the "painter" is currently located. // - The state of the tile on the up, down, left and right directions. // True means we'd like to look there. // False means there's nothing of interest there, meaning that we've either looked there or there's a mine or a tile with a number. type StackFrame = ((usize, usize), (bool, bool, bool, bool)); // We're using a heap-based stack imposter instead of the thread stack to avoid // overflowing. For large clearings, this will cause minor lag instead of // crashing. For smaller ones, this will hardly make a difference at all, since // we're preallocating it for a recursion depth of 10. let mut stack = Vec::<StackFrame>::with_capacity(10); let mut stack_top // Start at the anchor location. = ($anchor_location, (true, true, true, true)); stack.push(stack_top); $f($field, stack_top.0); // Invoke the first run. loop { // While we haven't emptied the stack... let chosen_location = if stack_top .1 .0 {0} // Up, else if stack_top .1 .1 {1} // down, else if stack_top .1 .2 {2} // left, else if stack_top .1 .3 {3} // right. // If we have nowhere to go, return to where we came from. else if let Some(new_top) = stack.pop() { stack_top = new_top; continue; // If we have nowhere to return, we can stop! } else {break}; let location_to_peek // Now find the coordinates which we're about to peek. = if chosen_location == 0 {(stack_top .0 .0, stack_top .0 .1 + 1)} else if chosen_location == 1 {(stack_top .0 .0, stack_top .0 .1 - 1)} else if chosen_location == 2 {(stack_top .0 .0 - 1, stack_top .0 .1)} else if chosen_location == 3 {(stack_top .0 .0 + 1, stack_top .0 .1)} else {unreachable!()}; if let Some(outcome) = $field.peek(location_to_peek) { match outcome { ClickOutcome::OpenClearing | ClickOutcome::Nothing => { // We found more clear land! // First of all, let's push the current state so that we can return to it later. stack.push(stack_top); // Then we'll set up the stack top for the next iteration. stack_top.0 = location_to_peek; stack_top.1 = (true, true, true, true); $f($field, stack_top.0); // Run the closure, this is the point of our actions here. }, ClickOutcome::Chord | ClickOutcome::Explosion => {} ClickOutcome::OpenNumber(_) => { if $include_shore { stack.push(stack_top); stack_top.0 = location_to_peek; stack_top.1 = (true, true, true, true); $f($field, stack_top.0); } } } } match chosen_location { 0 => stack_top .1 .0 = false, 1 => stack_top .1 .1 = false, 2 => stack_top .1 .2 = false, 3 => stack_top .1 .3 = false, _ => unreachable!(), }; } }; } /// A clearing on the specified field. /// /// This is merely a reference to the area on a field which is known to be a clearing. Nothing is owned by this structure. #[derive(Copy, Clone)] pub struct Clearing<'f> { field: &'f Field, anchor_location: (usize, usize) } impl<'f> Clearing<'f> { /// Returns a `Clearing` on the specified `Field`, or `None` if the location has 1 or more neighboring mines or is out of bounds. pub fn new(field: &'f Field, anchor_location: (usize, usize)) -> Option<Self> { if field.get(anchor_location).is_some() { if field.count_neighboring_mines(anchor_location) > 0 { None } else { Some(Self { field, anchor_location }) } } else {None} } /// Returns the field on which this clearing is located. #[inline(always)] pub fn field(self) -> &'f Field { self.field } /// Returns the location around which this clearing is formed. /// /// This can be any location inside the clearing. More specifically, the one used during creation is returned. #[inline(always)] pub fn anchor_location(self) -> (usize, usize) { self.anchor_location } /// Executes the specified closure on every tile inside the clearing. Optionally can include the "shore" (tiles with numbers) as a part of the clearing. /// /// The closure takes a reference to the field as the first argument and the location of the tile as the second one. No return value is expected. #[cfg_attr(feature = "track_caller", track_caller)] pub fn for_every_tile<F>(self, include_shore: bool, mut f: F) where F: FnMut(&'f Field, (usize, usize)) { for_every_tile!(self.field, self.anchor_location, f, include_shore); } /// Returns the size of the clearing, in tiles. Optionally can include the "shore" (tiles with numbers) as a part of the clearing. #[cfg_attr(feature = "track_caller", track_caller)] #[must_use = "fully traversing a clearing is an expensive operation involving memory allocation"] pub fn size(self, include_shore: bool) -> NonZeroUsize { let mut counter = 0_usize; self.for_every_tile(include_shore, |_, _| counter += 1); NonZeroUsize::new(counter) .expect("unexpected zero clearing size (nonzero clearing size is a safety guarantee)") } /// Returns `true` if the given tile is inside the clearing, `false` otherwise. Optionally can include the "shore" (tiles with numbers) as a part of the clearing. #[cfg_attr(feature = "track_caller", track_caller)] #[must_use = "fully traversing a clearing is an expensive operation involving memory allocation"] pub fn includes(self, index: (usize, usize), include_shore: bool) -> bool { let mut includes = false; self.for_every_tile(include_shore, |_, here| if here == index {includes = true}); includes } } /// A **mutable** reference to a clearing on the specified field. /// /// This is merely a **mutable** reference to the area on a field which is known to be clear land. Nothing is owned by this structure. pub struct ClearingMut<'f> { field: &'f mut Field, anchor_location: (usize, usize) } impl<'f> ClearingMut<'f> { /// Returns a `ClearingMut` on the specified `Field`, or `None` if the location has 1 or more neighboring mines or is out of bounds. pub fn new(field: &'f mut Field, anchor_location: (usize, usize)) -> Option<Self> { if field.get(anchor_location).is_some() { if field.count_neighboring_mines(anchor_location) > 0 { None } else { Some(Self { field, anchor_location }) } } else {None} } /// Returns the field on which this clearing is located. #[inline(always)] pub fn field(self) -> &'f Field { self.field } /// Returns the location around which this clearing is formed. /// /// This can be any location inside the clearing. More specifically, the one used during creation is returned. #[inline(always)] pub fn anchor_location(self) -> (usize, usize) { self.anchor_location } /// Executes the specified closure on every tile inside the clearing. Optionally can include the "shore" (tiles with numbers) as a part of the clearing. /// /// The closure takes an **immutable** reference to the field as the first argument and the location of the tile as the second one. No return value is expected. /// /// This is a version of `for_every_tile_mut` which doesn't allow mutating the field. #[cfg_attr(feature = "track_caller", track_caller)] pub fn for_every_tile<F>(self, include_shore: bool, mut f: F) where F: FnMut(&'f Field, (usize, usize)) { for_every_tile!(self.field, self.anchor_location, f, include_shore); } /// Executes the specified closure on every tile inside the clearing. Optionally can include the "shore" (tiles with numbers) as a part of the clearing. /// /// The closure takes a **mutable** reference to the field as the first argument and the location of the tile as the second one. No return value is expected. #[cfg_attr(feature = "track_caller", track_caller)] pub fn for_every_tile_mut<F>(self, include_shore: bool, mut f: F) where F: FnMut(&mut Field, (usize, usize)) { for_every_tile!(self.field, self.anchor_location, f, include_shore); } /// Returns the size of the clearing, in tiles. Optionally can include the "shore" (tiles with numbers) as a part of the clearing. /// /// Use [`open`][opn] instead if you want to open the clearing afterwards, since it provides the size itself. /// /// [opn]: #method.open.html "open — fully opens the clearing on the field and returns the amount of tiles cleared" #[cfg_attr(feature = "track_caller", track_caller)] #[must_use = "fully traversing a clearing is an expensive operation involving memory allocation"] pub fn size(self, include_shore: bool) -> NonZeroUsize { let mut counter = 0_usize; self.for_every_tile(include_shore, |_, _| counter += 1); NonZeroUsize::new(counter) .expect("unexpected zero clearing size (nonzero clearing size is a safety guarantee)") } /// Returns `true` if the given tile is inside the clearing, `false` otherwise. Optionally can include the "shore" (tiles with numbers) as a part of the clearing. #[cfg_attr(feature = "track_caller", track_caller)] #[must_use = "fully traversing a clearing is an expensive operation involving memory allocation"] pub fn includes(self, index: (usize, usize), include_shore: bool) -> bool { let mut includes = false; self.for_every_tile(include_shore, |_, here| if here == index {includes = true}); includes } /// Fully opens the clearing on the field and returns the amount of tiles opened. Optionally can include the "shore" (tiles with numbers) as a part of the clearing. /// /// The first number is the amount of tiles which were opened, and the second one is the total size of the clearing, which includes both previously closed and previously open tiles. pub fn open(self, include_shore: bool) -> (usize, NonZeroUsize) { let [mut opened_size, mut total_size] = [0_usize; 2]; self.for_every_tile_mut(include_shore, |field, location| { total_size += 1; if let Tile::ClosedEmpty(_) = field[location] { field[location] = Tile::OpenEmpty; opened_size += 1; } }); (opened_size, NonZeroUsize::new(total_size) .expect("unexpected zero clearing size (nonzero clearing size is a safety guarantee)") ) } } impl<'f> From<ClearingMut<'f>> for Clearing<'f> { fn from(op: ClearingMut<'f>) -> Self { Self {field: op.field, anchor_location: op.anchor_location} } } /// Represents the state of a flag #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub enum Flag { /// The player is absolutely sure that the tile this flag is applied to contains a mine. Flagged, /// The player knows about a possible mine hiding here, but lacks enough evidence to be able to prove that there's indeed a mine. QuestionMark, /// The player didn't mark this tile yet. /// /// Returned by the `Default` trait implementation. NotFlagged, } impl Default for Flag { /// Returns the `NotFlagged` state. #[inline(always)] fn default() -> Self { Self::NotFlagged } } /// The event produced after clicking a tile. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))] pub enum ClickOutcome { /// Nothing happens. /// /// Produced when an empty tile without a number is clicked. Nothing, /// A clearing is opened. A clearing of only one empty numberless tile is still a clearing. /// /// **Cannot be obtained from a single `Tile`**, since it requires knowing about surrounding tiles. OpenClearing, /// An empty tile with a number is opened. /// /// **Cannot be obtained from a single `Tile`**, since it requires knowing about surrounding tiles. OpenNumber(NonZeroU8), /// A chord operation is invoked. /// /// Obtained from an `OpenNumber` tile. Chord, /// An explosion is triggered, ending the game. /// /// Obtained from a `Mine`. Explosion } impl Default for ClickOutcome { /// Returns the `Nothing` variant. #[inline(always)] fn default() -> Self { Self::Nothing } }