diff --git a/CMakeLists.txt b/CMakeLists.txt index 81d2aa54..0a7f541e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif () # Source Files include_directories(src) add_library(puzzles_lib OBJECT - src/common/twobitstorage.cpp + src/common/file_reader.cpp src/cpic/data/easy.cpp src/cpic/data/trivial.cpp src/cpic/model/board.cpp @@ -46,6 +46,11 @@ add_library(puzzles_lib OBJECT src/cpic/solver/brute_force_board_solver.cpp src/cpic/solver/heuristic_board_solver.cpp src/cpic/view/board_logger.cpp + src/hangman/game.cpp + src/hangman/player/alpha_order_player.cpp + src/hangman/player/frequency_aware_player.cpp + src/hangman/player/random_player.cpp + src/hangman/word_repository.cpp src/shurikens/data/easy.cpp src/shurikens/data/real.cpp src/shurikens/data/trivial.cpp @@ -61,6 +66,9 @@ add_library(puzzles_lib OBJECT src/sudoku/view/board_logger.cpp ) +# Assets +file(COPY assets DESTINATION .) + # Executable add_executable(puzzles src/main.cpp $) @@ -76,8 +84,8 @@ add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}) add_subdirectory(libs/googletest) set(test_sources + tests/common/arbitrary_container_test.cpp tests/common/numbers_test.cpp - tests/common/twobitstorage_test.cpp tests/cpic/model/cpic_board_builder_test.cpp tests/cpic/model/cpic_board_solver_test.cpp tests/cpic/model/cpic_board_state_test.cpp diff --git a/Makefile b/Makefile index 2ef70495..8bd030f2 100644 --- a/Makefile +++ b/Makefile @@ -7,15 +7,13 @@ clean: check: build/debug/Makefile cmake --build build/debug --target check -- --no-print-directory +# Debug targets debug: build/debug/Makefile cmake --build build/debug --target puzzles -- --no-print-directory debug_all: build/debug/Makefile cmake --build build/debug -- --no-print-directory -release: build/release/Makefile - cmake --build build/release --target puzzles -- --no-print-directory - run: debug ./build/debug/puzzles @@ -23,7 +21,14 @@ run: debug run_full: debug ./build/debug/puzzles full -.PHONY: all clean check debug debug_all release run +# Release targets +release: build/release/Makefile + cmake --build build/release --target puzzles -- --no-print-directory + +run_release: release + ./build/release/puzzles + +.PHONY: all clean check debug debug_all release run run_release # Specific file targets build/debug/Makefile: CMakeLists.txt diff --git a/assets/.gitignore b/assets/.gitignore new file mode 100644 index 00000000..eecd76e5 --- /dev/null +++ b/assets/.gitignore @@ -0,0 +1,2 @@ +words.txt + diff --git a/src/common/arbitrary_container.h b/src/common/arbitrary_container.h new file mode 100644 index 00000000..ddb91242 --- /dev/null +++ b/src/common/arbitrary_container.h @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include + +namespace Puzzles { + +typedef size_t value_t; + +static constexpr inline size_t calculateValueBitLength(size_t value) { + assert(value > 0); + assert(value <= 0b11111111); // No real reason to stop there, I just haven't tested any further + + auto digits = 0; + size_t next = 1; + while (value > 0) { + digits++; + + if (next >= value) break; + + value = value - next; + next = next * 2; + } + + return digits; +} + +template struct ArbitraryContainer { + + const size_t valueBitLength = calculateValueBitLength(MAX_VALUE); + + // Constructors + ArbitraryContainer() = default; + ArbitraryContainer(const ArbitraryContainer&) = default; + explicit ArbitraryContainer(size_t count) : internal(count * valueBitLength, 0) { } + + ArbitraryContainer(std::initializer_list values) { + reserve(values.size()); + for (auto value : values) { + push(value); + } + } + + // Capacity + inline void reserve(size_t new_cap) { internal.reserve(new_cap * valueBitLength); } + inline void shrink_to_fit() { internal.shrink_to_fit(); } + + // Insertion + inline void push(value_t value) { + assert(value <= MAX_VALUE); + + std::vector results; + while (value > 0) { + results.push_back(value % 2); + value = value / 2; + } + + for (size_t i = 0; i < (valueBitLength - results.size()); ++i) { + internal.push_back(0); + } + + for (auto it = results.rbegin(); it != results.rend(); ++it) { + internal.push_back(*it); + } + } + + // Retrieval + inline value_t at(size_t index) const { + assert(index < size()); + + value_t result = 0; + auto start = index * valueBitLength; + + for (size_t i = 0; i < valueBitLength; ++i) { + if (internal[start + i]) { + result += std::pow(2, valueBitLength - 1 - i); + } + } + + return result; + } + inline value_t operator[](size_t i) const { return at(i); } + inline size_t size() const { return internal.size() / valueBitLength; } + + // Iterators + struct const_iterator { + + const_iterator(const ArbitraryContainer *container, size_t position) + : container(container), position(position) {} + + inline bool operator!=(const const_iterator &o) const { return container != o.container || position != o.position; } + + inline const_iterator *operator++() { + position++; + return this; + } + + inline value_t operator*() const { + assert(position < container->size()); + return container->at(position); + } + + private: + const ArbitraryContainer *container; + size_t position; + }; + + inline const_iterator begin() const { return const_iterator(this, 0); } + inline const_iterator end() const { return const_iterator(this, size()); } + + // Operators + inline ArbitraryContainer &operator=(const ArbitraryContainer &other) { + internal = other.internal; + return *this; + } + + inline bool operator<(const ArbitraryContainer &o) const { + if (size() != o.size()) { + return size() < o.size(); + } + + auto size = internal.size(); + for (size_t i = 0; i < size; ++i) { + if (internal[i] != o.internal[i]) { + return internal[i] < o.internal[i]; + } + } + return false; + } + +private: + std::vector internal; +}; +} diff --git a/src/common/collections.h b/src/common/collections.h new file mode 100644 index 00000000..4bc422f1 --- /dev/null +++ b/src/common/collections.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include + +namespace Puzzles { + +template +struct queue_inserter { + explicit queue_inserter(std::queue &queue) : queue(queue) {} + + queue_inserter operator++() { return *this; } + queue_inserter operator*() { return *this; } + queue_inserter &operator=(const char value) { + queue.push(value); + return *this; + } + +private: + std::queue &queue; +}; +} diff --git a/src/common/file_reader.cpp b/src/common/file_reader.cpp new file mode 100644 index 00000000..04d54e19 --- /dev/null +++ b/src/common/file_reader.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "file_reader.h" + +using namespace Puzzles; + +FileReader::FileReader(const std::string &filename) { + input.open(filename); +} + +FileReader::~FileReader() { + input.close(); +} + +std::istream *FileReader::istream() { + if (input) { + return &input; + } else { + return nullptr; + } +} diff --git a/src/common/file_reader.h b/src/common/file_reader.h new file mode 100644 index 00000000..9f3aa948 --- /dev/null +++ b/src/common/file_reader.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include +#include + +namespace Puzzles { + +struct FileReader { + explicit FileReader(const std::string &filename); + ~FileReader(); + + std::istream *istream(); + +private: + std::fstream input; +}; +} diff --git a/src/common/twobitstorage.cpp b/src/hangman/game.cpp similarity index 57% rename from src/common/twobitstorage.cpp rename to src/hangman/game.cpp index fefe7c7f..7b7b6d90 100644 --- a/src/common/twobitstorage.cpp +++ b/src/hangman/game.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Emanuel Machado da Silva + * Copyright (c) 2020 Emanuel Machado da Silva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,39 +20,40 @@ * SOFTWARE. */ -#include "twobitstorage.h" +#include "game.h" -#include +#include -using Puzzles::TwoBitStorage; +using namespace Hangman; -using std::vector; +inline uint8_t play(PlayerSession *player, std::string word) { + auto mistakes = 0; -TwoBitStorage::TwoBitStorage() : internal(0), _size(0) {} + while (word.empty() == false) { + auto letter = player->guess(); -void TwoBitStorage::push(u_int8_t value) { - assert(value <= 3); - assert(_size < MAX_SIZE); + if (std::find(word.begin(), word.end(), letter) == word.end()) { + mistakes++; + } else { + word.erase(std::remove(word.begin(), word.end(), letter), word.end()); + } + } - _size++; - auto shift = (MAX_SIZE - _size) * 2; - internal += static_cast(value) << shift; + return mistakes; } -u_int8_t TwoBitStorage::operator[](size_t i) const { - assert(i < _size); - auto shift = (MAX_SIZE - 1 - i) * 2; - return (internal & (static_cast(3) << shift)) >> shift; -} +GameResults Game::play(Player *player) { + uint64_t sum = 0; -TwoBitStorage::operator std::vector() const { - vector result; - result.reserve(_size); + std::vector words; + auto completeWords = wordRepository.words(); + for (auto i = 0; i < 10; ++i) { + words.push_back(completeWords[i]); + } - for (auto i = 0; i < _size; ++i) { - u_int8_t value = (*this)[i]; - result.push_back(value); + for (const auto &word : words) { + sum += ::play(player->session(), word); } - return result; + return {sum / words.size()}; } diff --git a/src/common/twobitstorage.h b/src/hangman/game.h similarity index 75% rename from src/common/twobitstorage.h rename to src/hangman/game.h index b90ee331..60396558 100644 --- a/src/common/twobitstorage.h +++ b/src/hangman/game.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Emanuel Machado da Silva + * Copyright (c) 2020 Emanuel Machado da Silva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,24 +22,22 @@ #pragma once -#include -#include - -namespace Puzzles { +#include "player/player.h" +#include "word_repository.h" -struct TwoBitStorage { - static const size_t MAX_SIZE = 32; +#include - TwoBitStorage(); +namespace Hangman { - inline size_t size() const { return _size; } +struct GameResults { + u_int64_t averageMistakes; +}; - void push(u_int8_t); - u_int8_t operator[](size_t i) const; - explicit operator std::vector() const; +struct Game { + GameResults play(Player *); private: - u_int64_t internal; - u_int8_t _size; + WordRepository wordRepository; }; + } diff --git a/src/hangman/player/alpha_order_player.cpp b/src/hangman/player/alpha_order_player.cpp new file mode 100644 index 00000000..08034cb7 --- /dev/null +++ b/src/hangman/player/alpha_order_player.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "alpha_order_player.h" + +#include + +using namespace Hangman; + +char AlphaOrderPlayerSession::guess() { + auto letters = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + for (auto letter : letters) { + if (std::find(guesses.begin(), guesses.end(), letter) == guesses.end()) { + guesses.push_back(letter); + return letter; + } + } + + throw 1; +} + +PlayerSession *AlphaOrderPlayer::session() { + currentSession = AlphaOrderPlayerSession(); + return ¤tSession; +} diff --git a/src/hangman/player/alpha_order_player.h b/src/hangman/player/alpha_order_player.h new file mode 100644 index 00000000..bec43104 --- /dev/null +++ b/src/hangman/player/alpha_order_player.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "player.h" + +namespace Hangman { + +struct AlphaOrderPlayerSession : public PlayerSession { + std::vector guesses; + + char guess() final; +}; + +struct AlphaOrderPlayer : public Player { + AlphaOrderPlayer() : Player("Alpha") {} + + [[nodiscard]] PlayerSession *session() final; + +private: + AlphaOrderPlayerSession currentSession; +}; + +} diff --git a/src/hangman/player/frequency_aware_player.cpp b/src/hangman/player/frequency_aware_player.cpp new file mode 100644 index 00000000..7d26e0f2 --- /dev/null +++ b/src/hangman/player/frequency_aware_player.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "frequency_aware_player.h" + +#include "common/collections.h" +#include "hangman/word_repository.h" + +#include +#include + +using namespace Hangman; +using namespace Puzzles; + +typedef uint32_t Frequency; +typedef std::map FrequencyMap; +typedef std::pair FrequencyPair; +typedef std::vector FrequencyVector; + +char FrequencyAwarePlayerSession::guess() { + assert(guesses.empty() == false); + + auto top = guesses.front(); + guesses.pop(); + return top; +} + +PlayerSession *FrequencyAwarePlayer::session() { + if (guessOrder.empty()) init(); + + currentSession = FrequencyAwarePlayerSession(guessOrder); + return ¤tSession; +} + +static inline FrequencyMap createFrequencyMap() { + WordRepository repository; + FrequencyMap frequency; + + for (auto letter = 'A'; letter <= 'Z'; letter++) { + frequency[letter] = 0; + } + + for (const auto &word : repository.words()) { + for (auto letter : word) { + assert(letter >= 'A' && letter <= 'Z'); + frequency[letter]++; + } + } + + return frequency; +} + +inline static FrequencyVector convertToPairs(FrequencyMap frequency) { + FrequencyVector result; + + std::transform(frequency.begin(), frequency.end(), std::back_inserter(result), + [](std::pair pair) { return std::pair(pair.first, pair.second); }); + + std::sort(result.begin(), result.end(), + [](FrequencyPair first, FrequencyPair second) { return first.second > second.second; }); + + return result; +} + +void FrequencyAwarePlayer::init() { + auto frequency = createFrequencyMap(); + auto pairs = convertToPairs(frequency); + + std::transform(pairs.begin(), pairs.end(), queue_inserter(guessOrder), [](auto value) { return value.first; }); +} diff --git a/tests/common/twobitstorage_test.cpp b/src/hangman/player/frequency_aware_player.h similarity index 60% rename from tests/common/twobitstorage_test.cpp rename to src/hangman/player/frequency_aware_player.h index bd3ca5e0..88fa73ee 100644 --- a/tests/common/twobitstorage_test.cpp +++ b/src/hangman/player/frequency_aware_player.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Emanuel Machado da Silva + * Copyright (c) 2020 Emanuel Machado da Silva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -20,35 +20,33 @@ * SOFTWARE. */ -#include "common/twobitstorage.h" +#pragma once -#include +#include "player.h" -using Puzzles::TwoBitStorage; +#include +#include -TEST(TwoBitStorage, ShouldStartAtZero) { - TwoBitStorage storage; +namespace Hangman { - EXPECT_EQ(storage.size(), 0); -} +struct FrequencyAwarePlayerSession : public PlayerSession { + FrequencyAwarePlayerSession(std::queue guesses) : guesses(guesses) {} + char guess() final; -TEST(TwoBitStorage, PushToEmpty) { - TwoBitStorage storage; +private: + std::queue guesses; +}; - storage.push(3); +struct FrequencyAwarePlayer : public Player { + FrequencyAwarePlayer() : Player("FrequencyAware"), currentSession({}) {} - EXPECT_EQ(storage.size(), 1); - EXPECT_EQ(storage[0], 3); -} + [[nodiscard]] PlayerSession *session() final; -TEST(TwoBitStorage, PushMultiple) { - const auto max = TwoBitStorage::MAX_SIZE; - TwoBitStorage storage; +private: + std::queue guessOrder; + FrequencyAwarePlayerSession currentSession; - for (size_t i = 0; i < max; ++i) - storage.push((max - i) % 4); + void init(); +}; - EXPECT_EQ(storage.size(), max); - for (size_t i = 0; i < max; ++i) - EXPECT_EQ(storage[i], (max - i) % 4) << "Comparison failed on position " << i; } diff --git a/src/hangman/player/player.h b/src/hangman/player/player.h new file mode 100644 index 00000000..8495f446 --- /dev/null +++ b/src/hangman/player/player.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include + +namespace Hangman { + +// TODO: Maybe this should be Player and the other PlayerFactory? Food for thought. +struct PlayerSession { + virtual ~PlayerSession() = default; + + virtual char guess() = 0; +}; + +struct Player { + Player(std::string name) : name(name) {}; + + const std::string name; + + [[nodiscard]] virtual PlayerSession *session() = 0; +}; + +} diff --git a/src/hangman/player/random_player.cpp b/src/hangman/player/random_player.cpp new file mode 100644 index 00000000..ae46f87c --- /dev/null +++ b/src/hangman/player/random_player.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "random_player.h" + +#include + +using namespace Hangman; + +RandomPlayerSession::RandomPlayerSession() + : guesses({'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}) {} + +char RandomPlayerSession::guess() { + assert(guesses.empty() == false); + + auto index = rand() % guesses.size(); + auto letter = guesses[index]; + guesses.erase(guesses.begin() + index); + + return letter; +} + +PlayerSession *RandomPlayer::session() { + currentSession = RandomPlayerSession(); + return ¤tSession; +} diff --git a/src/hangman/player/random_player.h b/src/hangman/player/random_player.h new file mode 100644 index 00000000..270340f9 --- /dev/null +++ b/src/hangman/player/random_player.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include "player.h" + +#include + +namespace Hangman { + +struct RandomPlayerSession : public PlayerSession { + RandomPlayerSession(); + + std::deque guesses; + + char guess() final; +}; + +struct RandomPlayer : public Player { + RandomPlayer() : Player("Random") {} + + [[nodiscard]] PlayerSession *session() final; + +private: + RandomPlayerSession currentSession; +}; + +} diff --git a/src/hangman/word_repository.cpp b/src/hangman/word_repository.cpp new file mode 100644 index 00000000..e2cd0d21 --- /dev/null +++ b/src/hangman/word_repository.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "word_repository.h" + +#include "common/file_reader.h" + +#include + +using namespace Hangman; +using namespace Puzzles; + +std::vector WordRepository::words() { + if (_words.empty()) { + FileReader reader("assets/words.txt"); + auto input = reader.istream(); + if (input) { + std::string buffer; + while (*input >> buffer) { + _words.push_back(buffer); + } + } + } + + return _words; +} diff --git a/src/hangman/word_repository.h b/src/hangman/word_repository.h new file mode 100644 index 00000000..93056bca --- /dev/null +++ b/src/hangman/word_repository.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include + +namespace Hangman { + +struct WordRepository { + std::vector words(); + +private: + std::vector _words; +}; + +} diff --git a/src/main.cpp b/src/main.cpp index ed39094e..eacbc7bd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,6 +24,10 @@ #include "cpic/solver/brute_force_board_solver.h" #include "cpic/solver/heuristic_board_solver.h" #include "cpic/view/board_logger.h" +#include "hangman/game.h" +#include "hangman/player/alpha_order_player.h" +#include "hangman/player/frequency_aware_player.h" +#include "hangman/player/random_player.h" #include "shurikens/data/data.h" #include "shurikens/logger.h" #include "shurikens/solver/breadth_search_solver.h" @@ -43,9 +47,10 @@ using std::chrono::duration_cast; using std::chrono::microseconds; using std::chrono::steady_clock; -inline bool solveCPic(); +inline bool solveCPic(bool fullRun); +inline bool solveHangman(); inline bool solveShurikens(bool fullRun); -inline bool solveSudoku(); +inline bool solveSudoku(bool fullRun); int main(int argc, char *argv[]) { // TODO Maybe creating a parsing unit? @@ -53,12 +58,13 @@ int main(int argc, char *argv[]) { auto fullRun = arg == "full"; auto start = steady_clock::now(); - if (!solveCPic() || !solveShurikens(fullRun) || !solveSudoku()) return 1; + if (!solveCPic(fullRun) || !solveHangman() || !solveShurikens(fullRun) || !solveSudoku(fullRun)) return 1; auto end = steady_clock::now(); cout << "All good, we took roughly " << duration_cast(end - start).count() << " microseconds!\n"; } -bool solveCPic() { +bool solveCPic(bool fullRun) { + if (!fullRun) return true; CPic::BruteForceBoardSolver bruteSolver; CPic::HeuristicBoardSolver heuristicSolver; CPic::BoardLogger logger; @@ -100,7 +106,25 @@ bool solveCPic() { return true; } +bool solveHangman() { + Hangman::AlphaOrderPlayer alphaOrderPlayer; + Hangman::FrequencyAwarePlayer frequencyAwarePlayer; + Hangman::RandomPlayer randomPlayer; + + Hangman::Player *players[] = {&alphaOrderPlayer, &frequencyAwarePlayer, &randomPlayer}; + Hangman::Game game; + + for (auto player: players) { + auto alphaOrderResults = game.play(player); + std::cout << "Hangman: " << player->name << " had " << alphaOrderResults.averageMistakes << " mistakes on average\n"; + } + + return true; +} + bool solveShurikens(bool fullRun) { + if (!fullRun) return true; + Shurikens::BreadthSearchSolver breadthSearchSolver; Shurikens::DepthSearchSolver depthSearchSolver; @@ -140,7 +164,9 @@ bool solveShurikens(bool fullRun) { return true; } -bool solveSudoku() { +bool solveSudoku(bool fullRun) { + if (!fullRun) return true; + Sudoku::BruteForceSolver bruteSolver; Sudoku::HeuristicBoardSolver heuristicSolver; Sudoku::BoardLogger logger; diff --git a/src/shurikens/data/data.h b/src/shurikens/data/data.h index b91ee3af..bf260d7c 100644 --- a/src/shurikens/data/data.h +++ b/src/shurikens/data/data.h @@ -28,29 +28,28 @@ #include #include #include -#include namespace Shurikens { struct ShurikenData { - ShurikenData(std::string name, Shuriken shuriken, const std::vector &solution) + ShurikenData(std::string name, Shuriken shuriken, const Shurikens::MoveContainer &solution) : name(std::move(name)), shuriken(std::move(shuriken)), solutions({solution}) {} - ShurikenData(std::string name, Shuriken shuriken, const std::vector &solution0, - const std::vector &solution1) + ShurikenData(std::string name, Shuriken shuriken, const Shurikens::MoveContainer &solution0, + const Shurikens::MoveContainer &solution1) : name(std::move(name)), shuriken(std::move(shuriken)), solutions({solution0, solution1}) { assert(solution0.size() == solution1.size()); } inline size_t solutionSize() const { return solutions.cbegin()->size(); } - inline bool isSolution(const std::vector &solution) const { + inline bool isSolution(const Shurikens::MoveContainer &solution) const { return solutions.find(solution) != solutions.end(); } const std::string name; const Shuriken shuriken; - const std::set> solutions; + const std::set solutions; }; std::vector createTrivialShurikens(); diff --git a/src/shurikens/data/real.cpp b/src/shurikens/data/real.cpp index 0bcfccf2..e06b10db 100644 --- a/src/shurikens/data/real.cpp +++ b/src/shurikens/data/real.cpp @@ -28,23 +28,21 @@ using std::vector; inline ShurikenData createReal0() { Shuriken shuriken({C, A, B, D, E, F, G, H, I, J, K, L}); - vector solution0 = {turn_a, swap_top, turn_b, swap_top, turn_a, turn_a, swap_top, turn_a, - swap_top, turn_a, turn_b, turn_b, turn_b, swap_top, turn_a, turn_a, - swap_top, turn_b, turn_b, swap_top, turn_b, turn_b}; - vector solution1 = {turn_a, turn_a, swap_top, turn_b, swap_top, turn_a, turn_a, swap_top, - turn_b, swap_top, turn_a, turn_a, swap_top, turn_b, swap_top, turn_a, - turn_a, swap_top, turn_b, swap_top, turn_a, turn_a}; + Shurikens::MoveContainer solution0 = {turn_a, swap_top, turn_b, reverse_a, swap_top, turn_a, swap_top, + reverse_b, swap_top, turn_b, swap_top, reverse_b, swap_top, reverse_a}; + Shurikens::MoveContainer solution1 = {reverse_a, swap_top, reverse_b, swap_top, turn_b, swap_top, reverse_b, + swap_top, turn_a, swap_top, reverse_a, turn_b, swap_top, turn_a}; return ShurikenData("real0", shuriken, solution0, solution1); } inline ShurikenData createReal1() { Shuriken shuriken({B, A, C, D, E, F, G, H, I, J, K, L}); - vector solution0 = {swap_top, turn_a, swap_top, turn_a, turn_a, swap_top, turn_a, turn_b, turn_b, - swap_top, turn_a, swap_top, turn_b, turn_b, swap_top, turn_a, swap_top, turn_a, - turn_b, swap_top, turn_a, turn_a, swap_top, turn_a, swap_top, turn_a, turn_a}; - vector solution1 = {turn_b, turn_a, swap_top, turn_b, turn_b, swap_top, turn_b, swap_top, turn_b, - turn_b, swap_top, turn_b, swap_top, turn_b, turn_b, swap_top, turn_b, turn_a, - turn_a, swap_top, turn_b, swap_top, turn_a, turn_a, swap_top, turn_b, swap_top}; + Shurikens::MoveContainer solution0 = {swap_top, turn_a, reverse_b, swap_top, turn_a, turn_a, swap_top, + turn_a, turn_a, swap_top, turn_a, swap_top, turn_a, turn_a, + swap_top, turn_b, swap_top, reverse_a, swap_top, turn_a, turn_a}; + Shurikens::MoveContainer solution1 = {reverse_b, reverse_a, swap_top, reverse_b, reverse_b, swap_top, reverse_a, + swap_top, reverse_b, reverse_b, swap_top, reverse_a, reverse_a, swap_top, + reverse_b, turn_a, swap_top, reverse_a, swap_top, turn_b, swap_top}; return ShurikenData("real1", shuriken, solution0, solution1); } diff --git a/src/shurikens/logger.cpp b/src/shurikens/logger.cpp index 503a42db..a7ec6af0 100644 --- a/src/shurikens/logger.cpp +++ b/src/shurikens/logger.cpp @@ -29,18 +29,27 @@ using namespace Shurikens; using std::cout; using std::vector; -void Logger::log(const vector &moves) const { +void Logger::log(const MoveContainer &moves) const { for (const auto &move : moves) { switch (move) { case swap_top: cout << "s"; break; + case swap_bottom: + cout << "S"; + break; case turn_a: cout << "a"; break; case turn_b: cout << "b"; break; + case reverse_a: + cout << "A"; + break; + case reverse_b: + cout << "B"; + break; } } cout << "\n"; diff --git a/src/shurikens/logger.h b/src/shurikens/logger.h index 85533934..6be5a4fc 100644 --- a/src/shurikens/logger.h +++ b/src/shurikens/logger.h @@ -24,11 +24,9 @@ #include "model.h" -#include - namespace Shurikens { class Logger { public: - void log(const std::vector &) const; + void log(const MoveContainer &) const; }; } diff --git a/src/shurikens/model.cpp b/src/shurikens/model.cpp index 120225d9..35a93225 100644 --- a/src/shurikens/model.cpp +++ b/src/shurikens/model.cpp @@ -27,57 +27,83 @@ using namespace Shurikens; using std::array; -using std::swap; -static inline array doSwapTop(array cells) { - swap(cells[0], cells[6]); - swap(cells[1], cells[7]); - swap(cells[2], cells[8]); +static inline array doSwapTop(const array &cells) { + array turned = cells; - return cells; + auto cellsBegin = cells.begin(); + auto turnedBegin = turned.begin(); + std::copy(cellsBegin, cellsBegin + 3, turnedBegin + 6); + std::copy(cellsBegin + 6, cellsBegin + 9, turnedBegin); + + return turned; } -static inline array doInvert(array cells) { - swap(cells[0], cells[6]); - swap(cells[1], cells[7]); - swap(cells[2], cells[8]); - swap(cells[3], cells[9]); - swap(cells[4], cells[10]); - swap(cells[5], cells[11]); +static inline array doSwapBottom(const array &cells) { + array turned = cells; + + auto cellsBegin = cells.begin(); + auto turnedBegin = turned.begin(); + std::copy(cellsBegin + 3, cellsBegin + 6, turnedBegin + 9); + std::copy(cellsBegin + 9, cellsBegin + 12, turnedBegin + 3); - return cells; + return turned; } static inline array doTurnA(const array &cells) { - array turned = {}; + array turned = cells; - for (int i = 0; i < 6; ++i) { - turned[(i + 1) % 6] = cells[i]; - turned[i + 6] = cells[i + 6]; - } + auto cellsBegin = cells.begin(); + std::copy(cellsBegin, cellsBegin + 5, turned.begin() + 1); + turned[0] = cells[5]; return turned; } static inline array doTurnB(const array &cells) { - array turned = {}; + array turned = cells; - for (int i = 0; i < 6; ++i) { - turned[i] = cells[i]; - turned[((i + 1) % 6) + 6] = cells[i + 6]; - } + auto cellsBegin = cells.begin(); + std::copy(cellsBegin + 6, cellsBegin + 11, turned.begin() + 7); + turned[6] = cells[11]; + + return turned; +} + +static inline array doReverseA(const array &cells) { + array turned = cells; + + auto cellsBegin = cells.begin(); + std::copy(cellsBegin + 1, cellsBegin + 6, turned.begin()); + turned[5] = cells[0]; + + return turned; +} + +static inline array doReverseB(const array &cells) { + array turned = cells; + + auto cellsBegin = cells.begin(); + std::copy(cellsBegin + 7, cellsBegin + 12, turned.begin() + 6); + turned[11] = cells[6]; return turned; } Shuriken Shuriken::apply(Shurikens::Move move) const { switch (move) { + case swap_top: + return Shuriken(doSwapTop(this->cells)); + case swap_bottom: + return Shuriken(doSwapBottom(this->cells)); case turn_a: return Shuriken(doTurnA(this->cells)); case turn_b: return Shuriken(doTurnB(this->cells)); - case swap_top: - return Shuriken(doSwapTop(this->cells)); + case reverse_a: + return Shuriken(doReverseA(this->cells)); + case reverse_b: + return Shuriken(doReverseB(this->cells)); default: assert(!"Tried to apply an invalid move!"); return *this; @@ -85,9 +111,17 @@ Shuriken Shuriken::apply(Shurikens::Move move) const { } bool Shuriken::operator==(const Shuriken &other) const { - // TODO There must be a better way of doing this check... if (cells == other.cells) return true; - if (cells == doInvert(other.cells)) return true; - return false; + auto us = cells.begin(); + auto them = other.cells.begin(); + + for (auto i = 0; i < 6; ++i) { + if (*us != *(them + 6)) return false; + if (*(us + 6) != *them) return false; + + ++us, ++them; + } + + return true; } diff --git a/src/shurikens/model.h b/src/shurikens/model.h index bbd8f310..4e5c5d7a 100644 --- a/src/shurikens/model.h +++ b/src/shurikens/model.h @@ -22,32 +22,18 @@ #pragma once +#include "common/arbitrary_container.h" + #include #include namespace Shurikens { -// Move and Cell should've been enums, but unfortunately this is more space-efficient -typedef uint8_t Move; -const Move swap_top = 0; -const Move turn_a = 1; -const Move turn_b = 2; - -typedef uint8_t Cell; -const Cell A = 0; -const Cell B = 1; -const Cell C = 2; -const Cell D = 3; -const Cell E = 4; -const Cell F = 5; -const Cell G = 6; -const Cell H = 7; -const Cell I = 8; -const Cell J = 9; -const Cell K = 10; -const Cell L = 11; +enum Move : uint8_t { swap_top, swap_bottom, turn_a, turn_b, reverse_a, reverse_b }; +enum Cell : uint8_t { A, B, C, D, E, F, G, H, I, J, K, L }; -const std::array allMoves = {swap_top, turn_a, turn_b}; +const std::array allMoves = {swap_top, swap_bottom, turn_a, turn_b, reverse_a, reverse_b}; +typedef Puzzles::ArbitraryContainer MoveContainer; class Shuriken { public: diff --git a/src/shurikens/solver/breadth_search_solver.cpp b/src/shurikens/solver/breadth_search_solver.cpp index 6ab85324..5d6c23a5 100644 --- a/src/shurikens/solver/breadth_search_solver.cpp +++ b/src/shurikens/solver/breadth_search_solver.cpp @@ -23,7 +23,6 @@ #include "breadth_search_solver.h" #include "common/numbers.h" -#include "common/twobitstorage.h" #include #include @@ -33,8 +32,6 @@ using namespace Puzzles; using namespace Shurikens; -using Puzzles::TwoBitStorage; - using std::move; using std::queue; using std::unordered_set; @@ -43,16 +40,18 @@ using std::vector; namespace { struct Node { const Shuriken shuriken; - const TwoBitStorage moves; + const MoveContainer moves; explicit Node(Shuriken shuriken) : shuriken(move(shuriken)), moves() {} - Node(Shuriken shuriken, const TwoBitStorage &moves) : shuriken(move(shuriken)), moves(moves) {} + Node(Shuriken shuriken, MoveContainer moves) : shuriken(move(shuriken)), moves(move(moves)) {} }; } -vector BreadthSearchSolver::solve(const Shuriken &shuriken, size_t knownUpperBound) const { - auto maxNodes = std::pow(allMoves.size(), knownUpperBound) + 1; - auto maxShurikens = static_cast(Numbers::factorial(12)) / 2; +MoveContainer BreadthSearchSolver::solve(const Shuriken &shuriken, size_t knownUpperBound) const { + if (shuriken.isSolved()) return {}; + + auto maxNodes = static_cast(std::pow(allMoves.size(), knownUpperBound) + 1); + auto maxShurikens = static_cast(std::ceil(Numbers::factorial(12) / 2)); unordered_set cache; cache.reserve(std::min(maxNodes, maxShurikens)); @@ -63,17 +62,19 @@ vector BreadthSearchSolver::solve(const Shuriken &shuriken, size_t knownUp do { auto next = nodes.front(); - if (next.shuriken.isSolved()) { - return static_cast>(next.moves); - } - nodes.pop(); for (auto &move : allMoves) { auto newShuriken = next.shuriken.apply(move); + if (cache.insert(newShuriken).second) { - TwoBitStorage newMoves(next.moves); + MoveContainer newMoves(next.moves); newMoves.push(move); + newMoves.shrink_to_fit(); + + if (newShuriken.isSolved()) { + return newMoves; + } nodes.emplace(newShuriken, newMoves); } diff --git a/src/shurikens/solver/breadth_search_solver.h b/src/shurikens/solver/breadth_search_solver.h index 27c66194..d247bceb 100644 --- a/src/shurikens/solver/breadth_search_solver.h +++ b/src/shurikens/solver/breadth_search_solver.h @@ -29,6 +29,6 @@ class BreadthSearchSolver : public Solver { public: BreadthSearchSolver() : Solver("Breadth", 10) {} - std::vector solve(const Shuriken &, size_t knownUpperBound) const override; + MoveContainer solve(const Shuriken &, size_t knownUpperBound) const override; }; } diff --git a/src/shurikens/solver/depth_search_solver.cpp b/src/shurikens/solver/depth_search_solver.cpp index eedf8793..067156e9 100644 --- a/src/shurikens/solver/depth_search_solver.cpp +++ b/src/shurikens/solver/depth_search_solver.cpp @@ -22,8 +22,6 @@ #include "depth_search_solver.h" -#include "common/twobitstorage.h" - #include #include #include @@ -31,8 +29,6 @@ using namespace Shurikens; -using Puzzles::TwoBitStorage; - using std::move; using std::stack; using std::unordered_map; @@ -41,25 +37,21 @@ using std::vector; namespace { struct Node { const Shuriken shuriken; - const TwoBitStorage moves; + const MoveContainer moves; explicit Node(Shuriken shuriken) : shuriken(move(shuriken)), moves() {} - Node(Shuriken shuriken, const TwoBitStorage &moves) : shuriken(move(shuriken)), moves(moves) {} + Node(Shuriken shuriken, MoveContainer moves) : shuriken(move(shuriken)), moves(move(moves)) {} }; } -vector DepthSearchSolver::solve(const Shuriken &shuriken, size_t knownUpperBound) const { +MoveContainer DepthSearchSolver::solve(const Shuriken &shuriken, size_t knownUpperBound) const { unordered_map cache; cache[shuriken] = 0; stack nodes; nodes.emplace(shuriken); - // TODO Find a better way of starting with knownUpperBound items - TwoBitStorage bestSolution; - for (size_t i = 0; i <= knownUpperBound; ++i) { - bestSolution.push(0); - } + MoveContainer bestSolution(knownUpperBound + 1); do { auto next = nodes.top(); @@ -79,8 +71,9 @@ vector DepthSearchSolver::solve(const Shuriken &shuriken, size_t knownUppe auto it = cache.find(newShuriken); if (it == cache.end() || it->second > (next.moves.size() + 1)) { - TwoBitStorage newMoves(next.moves); + MoveContainer newMoves(next.moves); newMoves.push(move); + newMoves.shrink_to_fit(); cache[newShuriken] = newMoves.size(); nodes.emplace(newShuriken, newMoves); @@ -88,5 +81,5 @@ vector DepthSearchSolver::solve(const Shuriken &shuriken, size_t knownUppe } } while (nodes.empty() == false); - return static_cast>(bestSolution); + return bestSolution; } diff --git a/src/shurikens/solver/depth_search_solver.h b/src/shurikens/solver/depth_search_solver.h index 869906d9..373b59c5 100644 --- a/src/shurikens/solver/depth_search_solver.h +++ b/src/shurikens/solver/depth_search_solver.h @@ -29,6 +29,6 @@ class DepthSearchSolver : public Solver { public: DepthSearchSolver() : Solver(" Depth", 10) {} - std::vector solve(const Shuriken &, size_t knownUpperBound) const override; + MoveContainer solve(const Shuriken &, size_t knownUpperBound) const override; }; } diff --git a/src/shurikens/solver/solver.h b/src/shurikens/solver/solver.h index 8b3eb783..195ca00a 100644 --- a/src/shurikens/solver/solver.h +++ b/src/shurikens/solver/solver.h @@ -27,17 +27,15 @@ #include #include #include -#include namespace Shurikens { -class Solver { -public: +struct Solver { Solver(std::string name, u_int8_t quickSolveLimit) : name(std::move(name)), quickSolveLimit(quickSolveLimit) {} virtual ~Solver() = default; const std::string name; const u_int8_t quickSolveLimit; - virtual std::vector solve(const Shuriken &, size_t knownUpperBound) const = 0; + virtual MoveContainer solve(const Shuriken &, size_t knownUpperBound) const = 0; }; } diff --git a/tests/common/arbitrary_container_test.cpp b/tests/common/arbitrary_container_test.cpp new file mode 100644 index 00000000..a40d7874 --- /dev/null +++ b/tests/common/arbitrary_container_test.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019 Emanuel Machado da Silva + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "common/arbitrary_container.h" + +#include + +using Puzzles::ArbitraryContainer; +using Puzzles::calculateValueBitLength; + +TEST(ArbitraryContainer, ValueBitLength) { + EXPECT_EQ(calculateValueBitLength(0b1), 1); + EXPECT_EQ(calculateValueBitLength(0b10), 2); + EXPECT_EQ(calculateValueBitLength(0b11), 2); + EXPECT_EQ(calculateValueBitLength(0b100), 3); + EXPECT_EQ(calculateValueBitLength(0b111), 3); + EXPECT_EQ(calculateValueBitLength(0b1000), 4); + EXPECT_EQ(calculateValueBitLength(0b1111), 4); + EXPECT_EQ(calculateValueBitLength(0b10000), 5); + EXPECT_EQ(calculateValueBitLength(0b11111), 5); + EXPECT_EQ(calculateValueBitLength(0b100000), 6); + EXPECT_EQ(calculateValueBitLength(0b111111), 6); + EXPECT_EQ(calculateValueBitLength(0b1000000), 7); + EXPECT_EQ(calculateValueBitLength(0b1111111), 7); + EXPECT_EQ(calculateValueBitLength(0b10000000), 8); + EXPECT_EQ(calculateValueBitLength(0b11111111), 8); +} + +TEST(ArbitraryContainer, ShouldStartAtZero) { + ArbitraryContainer<0b11> storage; + + EXPECT_EQ(storage.size(), 0); +} + +TEST(ArbitraryContainer, ConstructingWithInitialValues) { + ArbitraryContainer<0b11> storage({0, 1, 2}); + + EXPECT_EQ(storage.size(), 3); + EXPECT_EQ(storage[0], 0); + EXPECT_EQ(storage[1], 1); + EXPECT_EQ(storage[2], 2); +} + +TEST(ArbitraryContainer, PushToEmpty) { + ArbitraryContainer<0b11> storage; + + storage.push(3); + + EXPECT_EQ(storage.size(), 1); + EXPECT_EQ(storage[0], 3); +} + +TEST(ArbitraryContainer, PushMultiple_0b1) { + const auto maxSize = 256; + const auto maxValue = 0b1; + ArbitraryContainer storage; + + for (size_t i = 0; i < maxSize; ++i) { + storage.push((maxSize - 1) % (maxValue + 1)); + } + + EXPECT_EQ(storage.size(), maxSize); + for (size_t i = 0; i < maxSize; ++i) { + EXPECT_EQ(storage[i], (maxSize - 1) % (maxValue + 1)) << "Comparison failed on position " << i; + } +} + +TEST(ArbitraryContainer, PushMultiple_0b11) { + const auto maxSize = 256; + const auto maxValue = 0b11; + ArbitraryContainer storage; + + for (size_t i = 0; i < maxSize; ++i) { + storage.push((maxSize - i) % (maxValue + 1)); + } + + EXPECT_EQ(storage.size(), maxSize); + for (size_t i = 0; i < maxSize; ++i) + EXPECT_EQ(storage[i], (maxSize - i) % (maxValue + 1)) << "Comparison failed on position " << i; +} + +TEST(ArbitraryContainer, PushMultiple0b11111111) { + const auto maxSize = 256; + const auto maxValue = 0b11111111; + ArbitraryContainer storage; + + for (size_t i = 0; i < maxSize; ++i) { + storage.push((maxSize - 1) % (maxValue + 1)); + } + + EXPECT_EQ(storage.size(), maxSize); + for (size_t i = 0; i < maxSize; ++i) { + EXPECT_EQ(storage[i], (maxSize - 1) % (maxValue + 1)) << "Comparison failed on position " << i; + } +} + +TEST(ArbitraryContainer, LessThan_SameObjects) { + const ArbitraryContainer<0b11> storage0({0, 1}); + const ArbitraryContainer<0b11> storage1({0, 1}); + + EXPECT_FALSE(storage0 < storage1); + EXPECT_FALSE(storage1 < storage0); +} + +TEST(ArbitraryContainer, LessThan_DifferentSizes) { + const ArbitraryContainer<0b11> storage0({0, 1}); + const ArbitraryContainer<0b11> storage1({0, 1, 2}); + + EXPECT_TRUE(storage0 < storage1); + EXPECT_FALSE(storage1 < storage0); +} + +TEST(ArbitraryContainer, LessThan_DifferentValues) { + const ArbitraryContainer<0b11> storage0({0, 1, 1}); + const ArbitraryContainer<0b11> storage1({0, 1, 2}); + + EXPECT_TRUE(storage0 < storage1); + EXPECT_FALSE(storage1 < storage0); +} diff --git a/tests/shurikens/shurikens_model_test.cpp b/tests/shurikens/shurikens_model_test.cpp index 43af1b6a..584cd602 100644 --- a/tests/shurikens/shurikens_model_test.cpp +++ b/tests/shurikens/shurikens_model_test.cpp @@ -28,7 +28,7 @@ using namespace Shurikens; using std::array; -TEST(Shuriken, Swap) { +TEST(Shuriken, SwapTop) { auto shuriken = Shuriken({A, B, C, D, E, F, G, H, I, J, K, L}); auto swapped = shuriken.apply(swap_top); @@ -36,6 +36,14 @@ TEST(Shuriken, Swap) { EXPECT_EQ(swapped.cells, cells); } +TEST(Shuriken, SwapBottom) { + auto shuriken = Shuriken({A, B, C, D, E, F, G, H, I, J, K, L}); + auto swapped = shuriken.apply(swap_bottom); + + array cells = {A, B, C, J, K, L, G, H, I, D, E, F}; + EXPECT_EQ(swapped.cells, cells); +} + TEST(Shuriken, TurnSideA) { auto shuriken = Shuriken({A, B, C, D, E, F, G, H, I, J, K, L}); auto swapped = shuriken.apply(turn_a); @@ -52,6 +60,22 @@ TEST(Shuriken, TurnSideB) { EXPECT_EQ(swapped.cells, cells); } +TEST(Shuriken, ReverseSideA) { + auto shuriken = Shuriken({A, B, C, D, E, F, G, H, I, J, K, L}); + auto swapped = shuriken.apply(reverse_a); + + array cells = {B, C, D, E, F, A, G, H, I, J, K, L}; + EXPECT_EQ(swapped.cells, cells); +} + +TEST(Shuriken, ReverseSideB) { + auto shuriken = Shuriken({A, B, C, D, E, F, G, H, I, J, K, L}); + auto swapped = shuriken.apply(reverse_b); + + array cells = {A, B, C, D, E, F, H, I, J, K, L, G}; + EXPECT_EQ(swapped.cells, cells); +} + TEST(Shuriken, PerfectlySolvedShurikenShouldBeSolved) { auto shuriken = Shuriken({A, B, C, D, E, F, G, H, I, J, K, L});