diff --git a/format.hpp b/format.cpp similarity index 94% rename from format.hpp rename to format.cpp index f0500a8..7e5b255 100644 --- a/format.hpp +++ b/format.cpp @@ -1,9 +1,5 @@ -#pragma once -#include -#include +#include "global.hpp" -using std::vector; -using std::string; using std::to_string; #define YELLOW "\033[33m" diff --git a/gamestate.hpp b/gamestate.cpp similarity index 80% rename from gamestate.hpp rename to gamestate.cpp index a413bb3..fc48847 100644 --- a/gamestate.hpp +++ b/gamestate.cpp @@ -1,17 +1,21 @@ -#pragma once -#include -#include +#include "global.hpp" + +#include +#include +#include -using std::vector; -using std::cout; class Game { // Table of NxM saying which position can hold which color vector> possible; + std::default_random_engine random_engine; public: int N, M; Game(int p_N, int p_M) : N(p_N), M(p_M) { possible = vector>(N, vector(M, 1)); + + unsigned int seed = std::chrono::system_clock::now().time_since_epoch().count(); + random_engine = std::default_random_engine(seed); } // Learning functions @@ -64,7 +68,10 @@ public: for(int col = 0; col < M; col++) for(int n = 0; n < N; n++) if(possible[n][col]) - r[col].push_back(n); + r[n].push_back(col); + + for(int n = 0; n < N; n++) + std::shuffle(r[n].begin(), r[n].end(), random_engine); return r; } }; diff --git a/gen.hpp b/gen.cpp similarity index 79% rename from gen.hpp rename to gen.cpp index ec5080e..8f787f6 100644 --- a/gen.hpp +++ b/gen.cpp @@ -1,9 +1,7 @@ -#pragma once -#include +#include "global.hpp" #include #include -using std::vector; vector generate(int N, int M) { srand((unsigned)time(0)); diff --git a/global.hpp b/global.hpp new file mode 100644 index 0000000..d420651 --- /dev/null +++ b/global.hpp @@ -0,0 +1,22 @@ +#include +#include +#include + +using std::vector; +using std::string; +using std::cout; +using std::cin; + + +// Game generating +string get_input(string arg, vector args, string prompt_text, string default_arg); +vector generate(int N, int M); + +// Formatting +string format_response(vector response); +string format_guess(vector guess); +string format_guess_history(vector sequence, vector guess); +string format_lost_sequence(vector sequence); + +// Validating +vector validate(vector sequence, vector guess); diff --git a/input.hpp b/input.cpp similarity index 78% rename from input.hpp rename to input.cpp index 12c5216..764d85b 100644 --- a/input.hpp +++ b/input.cpp @@ -1,11 +1,5 @@ -#include -#include -#include +#include "global.hpp" -using std::vector; -using std::string; -using std::cout; -using std::cin; string get_param(string arg, vector args) { for(int i = 0; i < args.size(); i += 2) { diff --git a/main.cpp b/main.cpp index 65f0f6d..5b965fe 100644 --- a/main.cpp +++ b/main.cpp @@ -1,10 +1,6 @@ +#include "global.hpp" #include "solver.hpp" -#include "gen.hpp" -#include "validate.hpp" -#include "format.hpp" -#include "input.hpp" -using std::cin; using std::getline; // To avoid typos @@ -57,7 +53,7 @@ int main(int argc, char* argv[]) { vector guess(N), response; cout << "Guess " << history.size() << " : "; - // Bot playing + // Bot playin if(player == BOT) { guess = bot.guess(); cout << format_guess(guess); diff --git a/solver.cpp b/solver.cpp new file mode 100644 index 0000000..e967857 --- /dev/null +++ b/solver.cpp @@ -0,0 +1,258 @@ +#include "solver.hpp" + + +Game::Game(int p_N, int p_M) : N(p_N), M(p_M) { + possible = vector>(N, vector(M, 1)); + + unsigned int seed = std::chrono::system_clock::now().time_since_epoch().count(); + random_engine = std::default_random_engine(seed); +} + +// Getting known information +bool Game::can(int n, int col) { + return possible[n][col]; +} +vector> Game::get_positions_of_colors(vector guess) { + auto positions_of_colors = vector>(M, vector(0)); + for(int n = 0; n < N; n++) + if(guess[n] > -1) + positions_of_colors[guess[n]].push_back(n); + return positions_of_colors; +} +int Game::final_color(int n) { + int final_col, count = 0; + for(int col = 0; col < M; col++) + if(possible[n][col]) { + final_col = col; + count++; + } + + if(count == 1) + return final_col; + return -1; +} +vector> Game::list_all_possibilities() { + auto r = vector>(N, vector(0)); + for(int col = 0; col < M; col++) + for(int n = 0; n < N; n++) + if(possible[n][col]) + r[n].push_back(col); + + for(int n = 0; n < N; n++) + std::shuffle(r[n].begin(), r[n].end(), random_engine); + return r; +} +void Game::print() { + cout << " "; + for(int col = 0; col < M; col++) + cout << col; + cout << std::endl; + for(int i = 0; i < N; i++) { + cout << i; + for(auto col : possible[i]) + cout << col; + cout << std::endl;; + } +} + +// Learning functions +void Game::cannot_be(int n, int col) { + possible[n][col] = false; +} +void Game::must_be(int n, int must_col) { + for(int col = 0; col < M; col++) + if(col != must_col) + possible[n][col] = 0; +} +void Game::empty_color(int col) { + for(int n = 0; n < N; n++) + possible[n][col] = 0; +} + +// Specific reactions +void Game::if_not_here_then_nowhere(vector guess) { + auto positions_of_colors = get_positions_of_colors(guess); + + // If color isn't here, it can't be in the sequence + for(int col = 0; col < M; col++) { + int possible_count = 0; + for(int n : positions_of_colors[col]) + if(possible[n][col]) + possible_count++; + if(possible_count == 0 && positions_of_colors[col].size() > 0) + empty_color(col); + } +} +void Game::here(vector guess) { + for(int n = 0; n < N; n++) + if(guess[n] > -1 && possible[n][guess[n]]) + must_be(n, guess[n]); +} +void Game::not_here(vector guess) { + for(int n = 0; n < N; n++) + if(guess[n] > -1) + cannot_be(n, guess[n]); +} +void Game::empty(vector guess) { + for(int col : guess) + if(col > -1) + empty_color(col); +} +void Game::all_are_here(vector guess) { + auto positions_of_colors = get_positions_of_colors(guess); + + for(int col = 0; col < M; col++) { + if(!positions_of_colors[col].size()) + empty_color(col); + } +} + + +// For remembering guesses with their responses +Historic_guess::Historic_guess(vector p_guess, vector p_response) { + guess = p_guess; + response = p_response; +} + +// Solver + +Solver::Solver(int p_N, int p_M) : N(p_N), M(p_M), known({p_N, p_M}) {} + +// Check, if it could have been this +bool Solver::all_are_consistent(vector supposed_sequence) { + for(auto hist : history) { + auto response = validate(supposed_sequence, hist.guess); + if(response[0] != hist.response[0] || response[1] != hist.response[1]) + return false; + } + return true; +} + +// Try all remaining sequences +vector Solver::brute_force(vector> *possibilities, vector *chosen, int index) { + vector r = vector(N, -1); + if(index == N) { + if(all_are_consistent(*chosen)) + r = *chosen; + return r; + } + for(int col : (*possibilities)[index]) { + chosen->push_back(col); + r = brute_force(possibilities, chosen, index+1); + if(r[0] != -1) + return r; + chosen->pop_back(); + } + return r; +} + +// Guessing +vector Solver::guess() { + auto possibilities = known.list_all_possibilities(); + auto chosen = vector(0); + return brute_force(&possibilities, &chosen, 0); +} + +// Prints what the solver deduced +void Solver::print() { + known.print(); + auto possibilities = known.list_all_possibilities(); + auto chosen = vector(0); + auto v = brute_force(&possibilities, &chosen, 0); + cout << "Bruteforce chosen "; + for(int col : v) + cout << col << " "; + cout << std::endl; +} + +// Clean guess and response from info we know +Historic_guess Solver::clean(Historic_guess hist) { + // The in-place colors we know + for(int n = 0; n < N; n++) { + if(known.final_color(n) == hist.guess[n]) { + hist.guess[n] = -1; + hist.response[1] -= 1; + } + } + + // The out-of-place colors we know + for(int n = 0; n < N; n++) { + if(hist.guess[n] == -1) + continue; + for(int i = 0; i < N; i++) { + if(i == n) + continue; + if(known.final_color(i) == hist.guess[n]) { + hist.guess[n] = -1; + hist.response[0] -= 1; + } + } + } + + return hist; +} + + +// Here there be learning +bool Solver::extract_info(Historic_guess hist) { + bool something_to_learn = true; + + // A bit of cleaning + auto cleaned = clean(hist); + auto guess = cleaned.guess; + auto response = cleaned.response; + + // Get number of colors, that can be on their positions + int possible_count = 0; + for(int n = 0; n < N; n++) + if(guess[n] > -1 && known.can(n, guess[n])) + possible_count++; + + // None of these colors are there + if(response[0] == 0 && response[1] == 0) { + known.empty(guess); + something_to_learn = false; + } + + // None at the right spot + else if(response[1] == 0) + known.not_here(guess); + + // At least only on the right spot + else if(response[0] == 0) { + // Only colors that can be on these positions are left + if(response[1] == possible_count) { + known.here(guess); + something_to_learn = false; + } + else + known.if_not_here_then_nowhere(guess); + } + + // Nonzero / nonzero + else { + // Only colors that can be on these positions are left + if(response[1] == possible_count) + known.here(guess); + } + + // All guessed colors are in the sequence + if(response[0] + response[1] == N) + known.all_are_here(guess); + + return something_to_learn; +} +void Solver::learn(vector p_guess, vector p_response) { + // Write to history + history.push_back({p_guess, p_response}); + + // Repeat multiple times, if new information turned out + for(auto _ : history) + // Learn from previous guesses + for(int i = 0; i < history.size(); i++) + if(!extract_info(history[i])) { + // If there is nothing left to learn from the guess + history.erase(history.begin()+i); + i--; + } +} diff --git a/solver.hpp b/solver.hpp index 4bbfc52..555d151 100644 --- a/solver.hpp +++ b/solver.hpp @@ -1,195 +1,59 @@ -#pragma once -#include "gamestate.hpp" -#include "validate.hpp" +#include "global.hpp" +#include +#include +#include + + +struct Game { +private: + vector> possible; + std::default_random_engine random_engine; +public: + int N, M; + Game(int p_N, int p_M); + + // Get known information + bool can(int n, int col); + int final_color(int n); + void print(); + vector> list_all_possibilities(); + vector> get_positions_of_colors(vector guess); + + // Utility functions + void cannot_be(int n, int col); + void must_be(int n, int must_col); + void empty_color(int col); + + // Learning functions + void if_not_here_then_nowhere(vector guess); + void here(vector guess); + void not_here(vector guess); + void empty(vector guess); + void all_are_here(vector guess); + +}; + +// For remembering guesses with their responses +struct Historic_guess { + vector guess, response; + Historic_guess(vector p_guess, vector p_response); +}; class Solver { Game known; int N, M; vector history = {}; public: - Solver(int p_N, int p_M) : N(p_N), M(p_M), known({p_N, p_M}) {} + Solver(int N, int M); - // Guessing - vector guess() { - auto possibilities = known.list_all_possibilities(); - auto chosen = vector(0); - return brute_force(&possibilities, &chosen, 0); - } + vector guess(); + void print(); + void learn(vector guess, vector response); - // Utility functions - // Prints what the solver deduced - void print() { - known.print(); - auto possibilities = known.list_all_possibilities(); - auto chosen = vector(0); - auto v = brute_force(&possibilities, &chosen, 0); - cout << "Bruteforce chosen "; - for(int col : v) - cout << col << " "; - cout << std::endl; - } - // Clean guess and response from info we know - Historic_guess clean(Historic_guess hist) { - // The in-place colors we know - for(int n = 0; n < N; n++) { - if(known.final_color(n) == hist.guess[n]) { - hist.guess[n] = -1; - hist.response[1] -= 1; - } - } +private: + Historic_guess clean(Historic_guess hist); + bool extract_info(Historic_guess hist); - // The out-of-place colors we know - for(int n = 0; n < N; n++) { - if(hist.guess[n] == -1) - continue; - for(int i = 0; i < N; i++) { - if(i == n) - continue; - if(known.final_color(i) == hist.guess[n]) { - hist.guess[n] = -1; - hist.response[0] -= 1; - } - } - } - - return hist; - } - // For each color, get where it was guessed - vector> get_positions_of_colors(vector guess) { - auto positions_of_colors = vector>(M, vector(0)); - for(int n = 0; n < N; n++) - if(guess[n] > -1) - positions_of_colors[guess[n]].push_back(n); - return positions_of_colors; - } - // Check, if it could have been this - bool is_consistent(vector supposed_sequence, Historic_guess hist) { - auto response = validate(supposed_sequence, hist.guess); - return response[0] == hist.response[0] && - response[1] == hist.response[1]; - } - bool all_are_consistent(vector supposed_sequence) { - for(auto hist : history) - if(!is_consistent(supposed_sequence, hist)) - return false; - return true; - } - - // Specific reactions - void if_not_here_then_nowhere(vector guess) { - auto positions_of_colors = get_positions_of_colors(guess); - - // If color isn't here, it can't be in the sequence - for(int col = 0; col < M; col++) { - int possible_count = 0; - for(int n : positions_of_colors[col]) - if(known.can(n, col)) - possible_count++; - if(possible_count == 0 && positions_of_colors[col].size() > 0) - known.empty_color(col); - } - } - void here(vector guess) { - for(int n = 0; n < N; n++) - if(guess[n] > -1 && known.can(n, guess[n])) - known.must_be(n, guess[n]); - } - void not_here(vector guess) { - for(int n = 0; n < N; n++) - if(guess[n] > -1) - known.cannot_be(n, guess[n]); - } - void empty(vector guess) { - for(int col : guess) - if(col > -1) - known.empty_color(col); - } - void all_are_here(vector guess) { - auto positions_of_colors = get_positions_of_colors(guess); - - for(int col = 0; col < M; col++) { - if(!positions_of_colors[col].size()) - known.empty_color(col); - } - } - - // Try all remaining sequences - vector brute_force(vector> *possibilities, vector *chosen, int index) { - vector r = vector(N, -1); - if(index == N) { - if(all_are_consistent(*chosen)) - r = *chosen; - return r; - } - for(int col : (*possibilities)[index]) { - chosen->push_back(col); - r = brute_force(possibilities, chosen, index+1); - if(r[0] != -1) - return r; - chosen->pop_back(); - } - return r; - } - bool extract_info(Historic_guess hist) { - bool something_to_learn = true; - - // A bit of cleaning - auto cleaned = clean(hist); - auto guess = cleaned.guess; - auto response = cleaned.response; - - // Get number of colors, that can be on their positions - int possible_count = 0; - for(int n = 0; n < N; n++) - if(guess[n] > -1 && known.can(n, guess[n])) - possible_count++; - - // None of these colors are there - if(response[0] == 0 && response[1] == 0) { - empty(guess); - something_to_learn = false; - } - - // None at the right spot - else if(response[1] == 0) - not_here(guess); - - // At least only on the right spot - else if(response[0] == 0) { - // Only colors that can be on these positions are left - if(response[1] == possible_count) { - here(guess); - something_to_learn = false; - } - else - if_not_here_then_nowhere(guess); - } - - // Nonzero / nonzero - else { - // Only colors that can be on these positions are left - if(response[1] == possible_count) - here(guess); - } - - // All guessed colors are in the sequence - if(response[0] + response[1] == N) - all_are_here(guess); - - return something_to_learn; - } - void learn(vector p_guess, vector p_response) { - // Write to history - history.push_back({p_guess, p_response}); - - // Repeat multiple times, if new information turned out - for(auto _ : history) - // Learn from previous guesses - for(int i = 0; i < history.size(); i++) - if(!extract_info(history[i])) { - // If there is nothing left to learn from the guess - history.erase(history.begin()+i); - i--; - } - } + bool all_are_consistent(vector supposed_sequence); + vector brute_force(vector> *possibilities, vector *chosen, int index); }; diff --git a/validate.hpp b/validate.cpp similarity index 91% rename from validate.hpp rename to validate.cpp index e6fbf0c..47952a5 100644 --- a/validate.hpp +++ b/validate.cpp @@ -1,7 +1,5 @@ -#pragma once -#include +#include "global.hpp" -using std::vector; vector validate(vector sequence, vector guess) { int N = sequence.size();