Skip to content

Commit

Permalink
feat: add minesweeper exercise (#767)
Browse files Browse the repository at this point in the history
  • Loading branch information
vaeng authored Jan 12, 2024
1 parent 29458db commit e8662f9
Show file tree
Hide file tree
Showing 12 changed files with 18,246 additions and 0 deletions.
9 changes: 9 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,15 @@
],
"difficulty": 3,
"topics": []
},
{
"slug": "minesweeper",
"name": "Minesweeper",
"uuid": "3753a72a-78b7-429f-b79a-f68d55a00387",
"practices": [],
"prerequisites": [],
"difficulty": 5,
"topics": []
}
],
"foregone": [
Expand Down
32 changes: 32 additions & 0 deletions exercises/practice/minesweeper/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Instructions

Add the mine counts to a completed Minesweeper board.

Minesweeper is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square.

In this exercise you have to create some code that counts the number of mines adjacent to a given empty square and replaces that square with the count.

The board is a rectangle composed of blank space (' ') characters.
A mine is represented by an asterisk (`*`) character.

If a given space has no adjacent mines at all, leave that square blank.

## Examples

For example you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen):

```text
·*·*·
··*··
··*··
·····
```

And your code will transform it into this:

```text
1*3*1
13*31
·2*2·
·111·
```
19 changes: 19 additions & 0 deletions exercises/practice/minesweeper/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"vaeng"
],
"files": {
"solution": [
"minesweeper.cpp",
"minesweeper.h"
],
"test": [
"minesweeper_test.cpp"
],
"example": [
".meta/example.cpp",
".meta/example.h"
]
},
"blurb": "Add the numbers to a minesweeper board."
}
42 changes: 42 additions & 0 deletions exercises/practice/minesweeper/.meta/example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "minesweeper.h"

namespace minesweeper {
int score(const std::vector<std::string>& minefield, size_t row, size_t column) {
int mines{};
// north
if(row > 0 && minefield.at(row - 1).at(column) == '*') ++mines;
// north-east
if(row > 0 && column < minefield.at(0).size() - 1 && minefield.at(row - 1).at(column + 1) == '*') ++mines;
// north-west
if(row > 0 && column > 0 && minefield.at(row - 1).at(column - 1) == '*') ++mines;
// east
if(column < minefield.at(0).size() - 1 && minefield.at(row).at(column + 1) == '*') ++mines;
// west
if(column > 0 && minefield.at(row).at(column - 1) == '*') ++mines;
// south
if(row < minefield.size() - 1 && minefield.at(row + 1).at(column) == '*') ++mines;
// south-east
if(row < minefield.size() - 1 && column < minefield.at(0).size() - 1 && minefield.at(row + 1).at(column + 1) == '*') ++mines;
// south-west
if(row < minefield.size() - 1 && column > 0 && minefield.at(row + 1).at(column - 1) == '*') ++mines;
return mines;
}

std::vector<std::string> annotate(const std::vector<std::string>& minefield) {
std::vector<std::string> annotation{};
for(size_t row{}; row < minefield.size(); ++row) {
std::string annotaded_line{};
for(size_t column{}; column < minefield.at(0).size(); ++column) {
if (minefield.at(row).at(column) == '*') {
annotaded_line += '*';
} else {
int score_number = score(minefield, row, column);
char score_char = (score_number == 0) ? ' ' : char(score_number) + '0';
annotaded_line += score_char;
}
}
annotation.emplace_back(annotaded_line);
}
return annotation;
}
} // namespace minesweeper
8 changes: 8 additions & 0 deletions exercises/practice/minesweeper/.meta/example.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

#include <vector>
#include <string>

namespace minesweeper {
std::vector<std::string> annotate(const std::vector<std::string>& minefield);
} // namespace minesweeper
46 changes: 46 additions & 0 deletions exercises/practice/minesweeper/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[0c5ec4bd-dea7-4138-8651-1203e1cb9f44]
description = "no rows"

[650ac4c0-ad6b-4b41-acde-e4ea5852c3b8]
description = "no columns"

[6fbf8f6d-a03b-42c9-9a58-b489e9235478]
description = "no mines"

[61aff1c4-fb31-4078-acad-cd5f1e635655]
description = "minefield with only mines"

[84167147-c504-4896-85d7-246b01dea7c5]
description = "mine surrounded by spaces"

[cb878f35-43e3-4c9d-93d9-139012cccc4a]
description = "space surrounded by mines"

[7037f483-ddb4-4b35-b005-0d0f4ef4606f]
description = "horizontal line"

[e359820f-bb8b-4eda-8762-47b64dba30a6]
description = "horizontal line, mines at edges"

[c5198b50-804f-47e9-ae02-c3b42f7ce3ab]
description = "vertical line"

[0c79a64d-703d-4660-9e90-5adfa5408939]
description = "vertical line, mines at edges"

[4b098563-b7f3-401c-97c6-79dd1b708f34]
description = "cross"

[04a260f1-b40a-4e89-839e-8dd8525abe0e]
description = "large minefield"
64 changes: 64 additions & 0 deletions exercises/practice/minesweeper/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Get the exercise name from the current directory
get_filename_component(exercise ${CMAKE_CURRENT_SOURCE_DIR} NAME)

# Basic CMake project
cmake_minimum_required(VERSION 3.5.1)

# Name the project after the exercise
project(${exercise} CXX)

# Get a source filename from the exercise name by replacing -'s with _'s
string(REPLACE "-" "_" file ${exercise})

# Implementation could be only a header
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${file}.cpp)
set(exercise_cpp ${file}.cpp)
else()
set(exercise_cpp "")
endif()

# Use the common Catch library?
if(EXERCISM_COMMON_CATCH)
# For Exercism track development only
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h $<TARGET_OBJECTS:catchlib>)
elseif(EXERCISM_TEST_SUITE)
# The Exercism test suite is being run, the Docker image already
# includes a pre-built version of Catch.
find_package(Catch2 REQUIRED)
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h)
target_link_libraries(${exercise} PRIVATE Catch2::Catch2WithMain)
# When Catch is installed system wide we need to include a different
# header, we need this define to use the correct one.
target_compile_definitions(${exercise} PRIVATE EXERCISM_TEST_SUITE)
else()
# Build executable from sources and headers
add_executable(${exercise} ${file}_test.cpp ${exercise_cpp} ${file}.h test/tests-main.cpp)
endif()

set_target_properties(${exercise} PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED OFF
CXX_EXTENSIONS OFF
)

set(CMAKE_BUILD_TYPE Debug)

if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(GNU|Clang)")
set_target_properties(${exercise} PROPERTIES
COMPILE_FLAGS "-Wall -Wextra -Wpedantic -Werror"
)
endif()

# Configure to run all the tests?
if(${EXERCISM_RUN_ALL_TESTS})
target_compile_definitions(${exercise} PRIVATE EXERCISM_RUN_ALL_TESTS)
endif()

# Tell MSVC not to warn us about unchecked iterators in debug builds
if(${MSVC})
set_target_properties(${exercise} PROPERTIES
COMPILE_DEFINITIONS_DEBUG _SCL_SECURE_NO_WARNINGS)
endif()

# Run the tests on every build
add_custom_target(test_${exercise} ALL DEPENDS ${exercise} COMMAND ${exercise})
5 changes: 5 additions & 0 deletions exercises/practice/minesweeper/minesweeper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "minesweeper.h"

namespace minesweeper {

} // namespace minesweeper
5 changes: 5 additions & 0 deletions exercises/practice/minesweeper/minesweeper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

namespace minesweeper {

} // namespace minesweeper
77 changes: 77 additions & 0 deletions exercises/practice/minesweeper/minesweeper_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include "minesweeper.h"
#ifdef EXERCISM_TEST_SUITE
#include <catch2/catch.hpp>
#else
#include "test/catch.hpp"
#endif

/*
The expected outputs are represented as arrays of strings to
improve readability in this JSON file.
Your track may choose whether to present the input as a single
string (concatenating all the lines) or as the list.
*/

TEST_CASE("no rows", "[0c5ec4bd-dea7-4138-8651-1203e1cb9f44]") {
const std::vector<std::string> expected{};
REQUIRE(expected == minesweeper::annotate({}));
}

#if defined(EXERCISM_RUN_ALL_TESTS)

TEST_CASE("no columns", "[650ac4c0-ad6b-4b41-acde-e4ea5852c3b8]") {
const std::vector<std::string> expected{""};
REQUIRE(expected == minesweeper::annotate({""}));
}

TEST_CASE("no mines", "[6fbf8f6d-a03b-42c9-9a58-b489e9235478]") {
const std::vector<std::string> expected{" ", " ", " "};
REQUIRE(expected == minesweeper::annotate({" ", " ", " "}));
}

TEST_CASE("minefield with only mines", "[61aff1c4-fb31-4078-acad-cd5f1e635655]") {
const std::vector<std::string> expected{"***", "***", "***"};
REQUIRE(expected == minesweeper::annotate({"***", "***", "***"}));
}

TEST_CASE("mine surrounded by spaces", "[84167147-c504-4896-85d7-246b01dea7c5]") {
const std::vector<std::string> expected{"111", "1*1", "111"};
REQUIRE(expected == minesweeper::annotate({" ", " * ", " "}));
}

TEST_CASE("space surrounded by mines", "[cb878f35-43e3-4c9d-93d9-139012cccc4a]") {
const std::vector<std::string> expected{"***", "*8*", "***"};
REQUIRE(expected == minesweeper::annotate({"***", "* *", "***"}));
}

TEST_CASE("horizontal line", "[7037f483-ddb4-4b35-b005-0d0f4ef4606f]") {
const std::vector<std::string> expected{"1*2*1"};
REQUIRE(expected == minesweeper::annotate({" * * "}));
}

TEST_CASE("horizontal line, mines at edges", "[e359820f-bb8b-4eda-8762-47b64dba30a6]") {
const std::vector<std::string> expected{"*1 1*"};
REQUIRE(expected == minesweeper::annotate({"* *"}));
}

TEST_CASE("vertical line", "[c5198b50-804f-47e9-ae02-c3b42f7ce3ab]") {
const std::vector<std::string> expected{"1", "*", "2", "*", "1"};
REQUIRE(expected == minesweeper::annotate({" ", "*", " ", "*", " "}));
}

TEST_CASE("vertical line, mines at edges", "[0c79a64d-703d-4660-9e90-5adfa5408939]") {
const std::vector<std::string> expected{"*", "1", " ", "1", "*"};
REQUIRE(expected == minesweeper::annotate({"*", " ", " ", " ", "*"}));
}

TEST_CASE("cross", "[4b098563-b7f3-401c-97c6-79dd1b708f34]") {
const std::vector<std::string> expected{" 2*2 ", "25*52", "*****", "25*52", " 2*2 "};
REQUIRE(expected == minesweeper::annotate({" * ", " * ", "*****", " * ", " * "}));
}

TEST_CASE("large minefield", "[04a260f1-b40a-4e89-839e-8dd8525abe0e]") {
const std::vector<std::string> expected{"1*22*1", "12*322", " 123*2", "112*4*", "1*22*2", "111111"};
REQUIRE(expected == minesweeper::annotate({" * * ", " * ", " * ", " * *", " * * ", " "}));
}

#endif
Loading

0 comments on commit e8662f9

Please sign in to comment.