Skip to content

Commit 383c649

Browse files
committed
Add Minesweeper solution
1 parent 5fe36fb commit 383c649

File tree

4 files changed

+254
-0
lines changed

4 files changed

+254
-0
lines changed

minesweeper/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[package]
2+
name = "minesweeper"
3+
version = "1.1.0"

minesweeper/README.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Minesweeper
2+
3+
Add the numbers to a minesweeper board.
4+
5+
Minesweeper is a popular game where the user has to find the mines using
6+
numeric hints that indicate how many mines are directly adjacent
7+
(horizontally, vertically, diagonally) to a square.
8+
9+
In this exercise you have to create some code that counts the number of
10+
mines adjacent to a square and transforms boards like this (where `*`
11+
indicates a mine):
12+
13+
+-----+
14+
| * * |
15+
| * |
16+
| * |
17+
| |
18+
+-----+
19+
20+
into this:
21+
22+
+-----+
23+
|1*3*1|
24+
|13*31|
25+
| 2*2 |
26+
| 111 |
27+
+-----+
28+
29+
## Rust Installation
30+
31+
Refer to the [exercism help page][help-page] for Rust installation and learning
32+
resources.
33+
34+
## Writing the Code
35+
36+
Execute the tests with:
37+
38+
```bash
39+
$ cargo test
40+
```
41+
42+
All but the first test have been ignored. After you get the first test to
43+
pass, open the tests source file which is located in the `tests` directory
44+
and remove the `#[ignore]` flag from the next test and get the tests to pass
45+
again. Each separate test is a function with `#[test]` flag above it.
46+
Continue, until you pass every test.
47+
48+
If you wish to run all tests without editing the tests source file, use:
49+
50+
```bash
51+
$ cargo test -- --ignored
52+
```
53+
54+
To run a specific test, for example `some_test`, you can use:
55+
56+
```bash
57+
$ cargo test some_test
58+
```
59+
60+
If the specific test is ignored use:
61+
62+
```bash
63+
$ cargo test some_test -- --ignored
64+
```
65+
66+
To learn more about Rust tests refer to the [online test documentation][rust-tests]
67+
68+
Make sure to read the [Modules](https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html) chapter if you
69+
haven't already, it will help you with organizing your files.
70+
71+
## Feedback, Issues, Pull Requests
72+
73+
The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the rust track team are happy to help!
74+
75+
If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md).
76+
77+
[help-page]: http://exercism.io/languages/rust
78+
[modules]: https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html
79+
[cargo]: https://doc.rust-lang.org/book/second-edition/ch14-00-more-about-cargo.html
80+
[rust-tests]: https://doc.rust-lang.org/book/second-edition/ch11-02-running-tests.html
81+
82+
## Submitting Incomplete Solutions
83+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

minesweeper/src/lib.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use std::char;
2+
3+
const ADJACENCIES: [(i8, i8); 8] = [
4+
(-1, -1),
5+
(-1, 0),
6+
(-1, 1),
7+
(0, -1),
8+
(0, 1),
9+
(1, -1),
10+
(1, 0),
11+
(1, 1),
12+
];
13+
14+
pub fn annotate(input: &[&str]) -> Vec<String> {
15+
if input.is_empty() {
16+
return vec![];
17+
}
18+
let board = Board::new(input);
19+
board
20+
.data
21+
.iter()
22+
.enumerate()
23+
.map(|(pos_0, row)| {
24+
row
25+
.iter()
26+
.enumerate()
27+
.map(|(pos_1, elem)| {
28+
if *elem != '*' {
29+
let count = board.neighbours((pos_0 as u8, pos_1 as u8));
30+
if count > 0 {
31+
char::from_digit(u32::from(count), 10).unwrap()
32+
} else {
33+
' '
34+
}
35+
} else {
36+
*elem
37+
}
38+
})
39+
.collect::<String>()
40+
})
41+
.collect()
42+
}
43+
44+
#[derive(Debug)]
45+
struct Board {
46+
width: u8,
47+
height: u8,
48+
data: Vec<Vec<char>>,
49+
}
50+
51+
impl Board {
52+
fn new(input: &[&str]) -> Self {
53+
let data = input
54+
.into_iter()
55+
.map(|row| row.chars().collect::<Vec<_>>())
56+
.collect::<Vec<_>>();
57+
let height = data.len() as u8;
58+
let width = data[0].len() as u8;
59+
Board {
60+
width,
61+
height,
62+
data,
63+
}
64+
}
65+
fn neighbours(&self, position: (u8, u8)) -> u8 {
66+
let position = (position.0 as i8, position.1 as i8);
67+
ADJACENCIES
68+
.iter()
69+
.map(|adjacent| (position.0 + adjacent.0, position.1 + adjacent.1))
70+
.filter(|neighbour| {
71+
neighbour.0 >= 0
72+
&& neighbour.1 >= 0
73+
&& neighbour.0 < self.height as i8
74+
&& neighbour.1 < self.width as i8
75+
})
76+
.fold(0, |acc, pos| {
77+
if self.data[pos.0 as usize][pos.1 as usize] == '*' {
78+
acc + 1
79+
} else {
80+
acc
81+
}
82+
})
83+
}
84+
}

minesweeper/tests/minesweeper.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
extern crate minesweeper;
2+
3+
use minesweeper::annotate;
4+
5+
fn remove_annotations(board: &[&str]) -> Vec<String> {
6+
board.iter().map(|r| remove_annotations_in_row(r)).collect()
7+
}
8+
9+
fn remove_annotations_in_row(row: &str) -> String {
10+
row
11+
.chars()
12+
.map(|ch| match ch {
13+
'*' => '*',
14+
_ => ' ',
15+
})
16+
.collect()
17+
}
18+
19+
fn run_test(test_case: &[&str]) {
20+
let cleaned = remove_annotations(test_case);
21+
let cleaned_strs = cleaned.iter().map(|r| &r[..]).collect::<Vec<_>>();
22+
let expected = test_case.iter().map(|&r| r.to_string()).collect::<Vec<_>>();
23+
assert_eq!(expected, annotate(&cleaned_strs));
24+
}
25+
26+
#[test]
27+
fn no_rows() {
28+
run_test(&[]);
29+
}
30+
31+
#[test]
32+
fn no_columns() {
33+
run_test(&[""]);
34+
}
35+
36+
#[test]
37+
fn no_mines() {
38+
run_test(&[" ", " ", " "]);
39+
}
40+
41+
#[test]
42+
fn board_with_only_mines() {
43+
run_test(&["***", "***", "***"]);
44+
}
45+
46+
#[test]
47+
fn mine_surrounded_by_spaces() {
48+
run_test(&["111", "1*1", "111"]);
49+
}
50+
51+
#[test]
52+
fn space_surrounded_by_mines() {
53+
run_test(&["***", "*8*", "***"]);
54+
}
55+
56+
#[test]
57+
fn horizontal_line() {
58+
run_test(&["1*2*1"]);
59+
}
60+
61+
#[test]
62+
fn horizontal_line_mines_at_edges() {
63+
run_test(&["*1 1*"]);
64+
}
65+
66+
#[test]
67+
fn vertical_line() {
68+
run_test(&["1", "*", "2", "*", "1"]);
69+
}
70+
71+
#[test]
72+
fn vertical_line_mines_at_edges() {
73+
run_test(&["*", "1", " ", "1", "*"]);
74+
}
75+
76+
#[test]
77+
fn cross() {
78+
run_test(&[" 2*2 ", "25*52", "*****", "25*52", " 2*2 "]);
79+
}
80+
81+
#[test]
82+
fn large_board() {
83+
run_test(&["1*22*1", "12*322", " 123*2", "112*4*", "1*22*2", "111111"]);
84+
}

0 commit comments

Comments
 (0)