A new beginning of a new strategy
This commit is contained in:
parent
598d835142
commit
2ab571db24
5 changed files with 33 additions and 389 deletions
138
game.cpp
138
game.cpp
|
@ -1,138 +0,0 @@
|
|||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include "global.hpp"
|
||||
|
||||
Game::Game(int _N, int _M) : N(_N), M(_M) {
|
||||
possible = vector<vector<bool>>(N, vector<bool>(M, 1));
|
||||
empty_colors = vector<bool>(M, 0);
|
||||
final = vector<int>(N, -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<vector<int>> Game::get_positions_of_colors(vector<int> guess) {
|
||||
auto positions_of_colors = vector<vector<int>>(M, vector<int>(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) {
|
||||
if(final[n] > -1)
|
||||
return final[n];
|
||||
int final_col, count = 0;
|
||||
for(int col = 0; col < M; col++)
|
||||
if(possible[n][col]) {
|
||||
final_col = col;
|
||||
count++;
|
||||
}
|
||||
|
||||
if(count == 1) {
|
||||
final[n] = final_col;
|
||||
return final_col;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
bool Game::is_empty(int col) {
|
||||
if(empty_colors[col])
|
||||
return true;
|
||||
|
||||
for(int n = 0; n < N; n++)
|
||||
if(possible[n][col])
|
||||
return false;
|
||||
empty_colors[col] = true;
|
||||
return true;
|
||||
}
|
||||
vector<vector<int>> Game::list_all_possibilities() {
|
||||
auto r = vector<vector<int>>(N, vector<int>(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 << '\n';
|
||||
for(int i = 0; i < N; i++) {
|
||||
cout << i;
|
||||
for(auto col : possible[i])
|
||||
cout << col;
|
||||
cout << '\n';
|
||||
}
|
||||
cout << '\n';
|
||||
}
|
||||
|
||||
// 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;
|
||||
empty_colors[col] = true;
|
||||
}
|
||||
|
||||
// Specific reactions
|
||||
bool Game::if_not_here_then_nowhere(vector<int> guess) {
|
||||
auto positions_of_colors = get_positions_of_colors(guess);
|
||||
bool learned_something = false;
|
||||
|
||||
// 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 && !is_empty(col)) {
|
||||
empty_color(col);
|
||||
learned_something = true;
|
||||
}
|
||||
}
|
||||
|
||||
return learned_something;
|
||||
}
|
||||
void Game::here(vector<int> 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<int> guess) {
|
||||
for(int n = 0; n < N; n++)
|
||||
if(guess[n] > -1)
|
||||
cannot_be(n, guess[n]);
|
||||
}
|
||||
void Game::empty(vector<int> guess) {
|
||||
for(int col : guess)
|
||||
if(col > -1)
|
||||
empty_color(col);
|
||||
}
|
||||
void Game::all_are_here(vector<int> guess) {
|
||||
auto positions_of_colors = get_positions_of_colors(guess);
|
||||
|
||||
for(int col = 0; col < M; col++) {
|
||||
if(positions_of_colors[col].size() == 0)
|
||||
empty_color(col);
|
||||
}
|
||||
}
|
||||
|
||||
// For remembering guesses with their responses
|
||||
Historic_guess::Historic_guess(vector<int> _guess, Response _response) {
|
||||
guess = _guess;
|
||||
response = _response;
|
||||
}
|
45
global.hpp
45
global.hpp
|
@ -14,6 +14,12 @@ struct Response {
|
|||
int somewhere, correct;
|
||||
Response() {}
|
||||
Response(int _s, int _c) : somewhere(_s), correct(_c) {}
|
||||
bool operator==(Response second) {
|
||||
return somewhere == second.somewhere && correct == second.correct;
|
||||
}
|
||||
bool operator!=(Response second) {
|
||||
return !(operator==(second));
|
||||
}
|
||||
};
|
||||
|
||||
// Game generating
|
||||
|
@ -28,42 +34,3 @@ string format_lost_sequence(vector<int> sequence);
|
|||
|
||||
// Validating
|
||||
Response validate(vector<int> sequence, vector<int> guess);
|
||||
|
||||
// Game remembering
|
||||
struct Game {
|
||||
private:
|
||||
vector<vector<bool>> possible;
|
||||
vector<bool> empty_colors;
|
||||
vector<int> final;
|
||||
std::default_random_engine random_engine;
|
||||
public:
|
||||
int N, M;
|
||||
Game(int _N, int _M);
|
||||
|
||||
// Get known information
|
||||
bool can(int n, int col);
|
||||
int final_color(int n);
|
||||
bool is_empty(int col);
|
||||
void print();
|
||||
vector<vector<int>> list_all_possibilities();
|
||||
vector<vector<int>> get_positions_of_colors(vector<int> 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
|
||||
bool if_not_here_then_nowhere(vector<int> guess);
|
||||
void here(vector<int> guess);
|
||||
void not_here(vector<int> guess);
|
||||
void empty(vector<int> guess);
|
||||
void all_are_here(vector<int> guess);
|
||||
};
|
||||
|
||||
// Guess-response remembering
|
||||
struct Historic_guess {
|
||||
vector<int> guess;
|
||||
Response response;
|
||||
Historic_guess(vector<int> _guess, Response _response);
|
||||
};
|
||||
|
|
9
main.cpp
9
main.cpp
|
@ -18,7 +18,6 @@ int main(int argc, char* argv[]) {
|
|||
int N = stoi(get_input("-n", args, "Length of sequence", "5"));
|
||||
int M = stoi(get_input("-m", args, "Number of colors", "8"));
|
||||
string player = get_input("-p", args, string("Who plays [")+HUMAN+"/"+BOT+"]", "bot");
|
||||
bool learn = "y" == get_input("-l", args, "Do you want to know what bot learns [y/n]", "y");
|
||||
string gen = player == HUMAN ? RANDOM : get_input("-g", args, "Who generates the seque", RANDOM);
|
||||
bool human_player = player == HUMAN;
|
||||
|
||||
|
@ -62,23 +61,15 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
response = validate(sequence, guess);
|
||||
cout << format_response(response);
|
||||
|
||||
if(learn)
|
||||
bot.learn(guess, response);
|
||||
}
|
||||
|
||||
// Bot playing
|
||||
else {
|
||||
guess = bot.guess();
|
||||
|
||||
response = validate(sequence, guess);
|
||||
bot.learn(guess, response);
|
||||
if(learn)
|
||||
cout << "Guess " << history.size() << " : " << format_guess(guess) << format_response(response);
|
||||
}
|
||||
|
||||
if(learn)
|
||||
bot.print();
|
||||
history.push_back(guess);
|
||||
if(history.back() == sequence) break;
|
||||
}
|
||||
|
|
216
solver.cpp
216
solver.cpp
|
@ -1,202 +1,36 @@
|
|||
#include "solver.hpp"
|
||||
|
||||
|
||||
Solver::Solver(int _N, int _M) : N(_N), M(_M), known({_N, _M}) {}
|
||||
|
||||
// Check, if it could have been this
|
||||
bool Solver::all_are_consistent(vector<int> supposed_sequence) {
|
||||
for(auto hist : history) {
|
||||
auto response = validate(supposed_sequence, hist.guess);
|
||||
if(response.somewhere != hist.response.somewhere || response.correct != hist.response.correct)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int Solver::get_weight(vector<int> guess) {
|
||||
if(!all_are_consistent(guess))
|
||||
return -1;
|
||||
|
||||
// Get weight
|
||||
for(auto hist : history) {
|
||||
// TODO get worst-case weight
|
||||
// Possibly get how many sequences it eliminates
|
||||
void Solver::generate_set(vector<int> carry) {
|
||||
if(carry.size() == N) {
|
||||
possible.push_back(carry);
|
||||
return;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
// Now featuring: minimax pick
|
||||
Weighed_guess Solver::minimax(vector<vector<int>> *possibilities, vector<int> *chosen, int index) {
|
||||
// If complete guess, get weight and return
|
||||
if(index == N)
|
||||
return {get_weight(*chosen), *chosen};
|
||||
|
||||
// Get max-weighted children
|
||||
Weighed_guess r = {-2, {}};
|
||||
for(int col : (*possibilities)[index]) {
|
||||
chosen->push_back(col);
|
||||
auto r2 = minimax(possibilities, chosen, index+1);
|
||||
|
||||
if((r2.weight > r.weight || r.weight == -2) && r2.weight > -1)
|
||||
r = r2;
|
||||
chosen->pop_back();
|
||||
for(int col = 0; col < M; col++) {
|
||||
carry.push_back(col);
|
||||
generate_set(carry);
|
||||
carry.pop_back();
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Guessing
|
||||
Solver::Solver(int _N, int _M) : N(_N), M(_M) {
|
||||
generate_set({});
|
||||
}
|
||||
|
||||
// TODO
|
||||
vector<int> Solver::guess() {
|
||||
auto possibilities = known.list_all_possibilities();
|
||||
auto chosen = vector<int>(0);
|
||||
|
||||
return minimax(&possibilities, &chosen, 0).guess;
|
||||
}
|
||||
|
||||
// Prints what the solver deduced
|
||||
void Solver::print() {
|
||||
known.print();
|
||||
}
|
||||
void Solver::print_unknown() {
|
||||
for(auto hist : history) {
|
||||
auto cleaned = clean(hist);
|
||||
for(int pos : cleaned.guess)
|
||||
cout << pos << " ";
|
||||
cout << "[" << cleaned.response.somewhere << "/" << cleaned.response.correct << "]\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Clean guess and response from info we know
|
||||
Historic_guess Solver::clean(Historic_guess hist) {
|
||||
vector<bool> already_used_in_cleaning(N, false);
|
||||
|
||||
// Clean empty colors
|
||||
for(int n = 0; n < N; n++)
|
||||
if(known.is_empty(hist.guess[n]))
|
||||
hist.guess[n] = -1;
|
||||
|
||||
// The in-place colors we know
|
||||
for(int n = 0; n < N; n++) {
|
||||
if(hist.guess[n] <= -1)
|
||||
continue;
|
||||
if(known.final_color(n) == hist.guess[n]) {
|
||||
hist.guess[n] = -1;
|
||||
hist.response.correct -= 1;
|
||||
already_used_in_cleaning[n] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The out-of-place colors we know
|
||||
for(int n = 0; n < N; n++) {
|
||||
if(hist.guess[n] <= -1 || known.can(n, hist.guess[n]))
|
||||
continue;
|
||||
for(int i = 0; i < N; i++) {
|
||||
if(i == n || hist.guess[i] <= -1 || already_used_in_cleaning[i])
|
||||
continue;
|
||||
if(known.final_color(i) == hist.guess[n]) {
|
||||
hist.guess[n] = -1;
|
||||
hist.response.somewhere -= 1;
|
||||
already_used_in_cleaning[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hist;
|
||||
}
|
||||
|
||||
|
||||
// Here there be learning
|
||||
vector<bool> Solver::extract_info(Historic_guess hist) {
|
||||
bool something_to_learn = true, learned_something = false;
|
||||
|
||||
// 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++;
|
||||
|
||||
// The color isn't in the sequence, except for known info [0/0]
|
||||
if(response.somewhere == 0 && response.correct == 0) {
|
||||
|
||||
// Deduce what was cleaned
|
||||
vector<bool> col_was_cleaned(M, false);
|
||||
for(int n = 0; n < N; n++)
|
||||
if(guess[n] != hist.guess[n])
|
||||
col_was_cleaned[hist.guess[n]] = true;
|
||||
|
||||
for(int n = 0; n < N; n++)
|
||||
if(guess[n] > -1)
|
||||
for(int n2 = 0; n2 < N; n2++)
|
||||
if(known.final_color(n2) != guess[n])
|
||||
known.cannot_be(n2, guess[n]);
|
||||
|
||||
something_to_learn = false;
|
||||
learned_something = true;
|
||||
}
|
||||
|
||||
// None at the right spot [X/0]
|
||||
else if(response.correct == 0) {
|
||||
if(possible_count > 0) {
|
||||
known.not_here(guess);
|
||||
learned_something = true;
|
||||
}
|
||||
}
|
||||
|
||||
// At least only on the right spot [0/X]
|
||||
else if(response.somewhere == 0) {
|
||||
// Only colors that can be on these positions are left
|
||||
if(response.correct == possible_count) {
|
||||
known.here(guess);
|
||||
something_to_learn = false;
|
||||
learned_something = true;
|
||||
}
|
||||
else if(hist.response.somewhere == 0) {
|
||||
if(known.if_not_here_then_nowhere(hist.guess)) {
|
||||
learned_something = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The rest [X/X]
|
||||
else {
|
||||
// Only colors that can be on these positions are left
|
||||
if(response.correct == possible_count) {
|
||||
known.here(guess);
|
||||
learned_something = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {something_to_learn, learned_something};
|
||||
return {};
|
||||
}
|
||||
// TODO
|
||||
void Solver::learn(vector<int> guess, Response response) {
|
||||
// All guessed colors are in the sequence
|
||||
if(response.somewhere + response.correct == N)
|
||||
known.all_are_here(guess);
|
||||
|
||||
// Write to history
|
||||
history.push_back({guess, response});
|
||||
|
||||
// Repeat multiple times, if new information turned out
|
||||
bool learned_something = true;
|
||||
while(learned_something) {
|
||||
learned_something = false;
|
||||
|
||||
// Learn from previous guesses
|
||||
for(int i = 0; i < history.size(); i++) {
|
||||
auto info = extract_info(history[i]);
|
||||
|
||||
if(!info[0]) {
|
||||
// If there is nothing left to learn from the guess
|
||||
history.erase(history.begin()+i);
|
||||
i--;
|
||||
}
|
||||
if(info[1])
|
||||
learned_something = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TODO
|
||||
int Solver::get_weight(vector<int> guess) {
|
||||
return 0;
|
||||
}
|
||||
// TODO
|
||||
Weighed_guess Solver::minimax(vector<vector<int>> *possibilities, vector<int> *chosen, int index) {
|
||||
return {0, {}};
|
||||
}
|
||||
|
|
14
solver.hpp
14
solver.hpp
|
@ -1,8 +1,6 @@
|
|||
#pragma once
|
||||
#include "global.hpp"
|
||||
|
||||
|
||||
|
||||
// For deciding the best guess
|
||||
struct Weighed_guess {
|
||||
int weight;
|
||||
|
@ -13,24 +11,16 @@ struct Weighed_guess {
|
|||
|
||||
// Solving the game
|
||||
class Solver {
|
||||
Game known;
|
||||
int N, M;
|
||||
vector<Historic_guess> history = {};
|
||||
vector<vector<int>> possible = vector<vector<int>>(0);
|
||||
public:
|
||||
Solver(int N, int M);
|
||||
|
||||
vector<int> guess();
|
||||
void print();
|
||||
void print_unknown();
|
||||
void learn(vector<int> guess, Response response);
|
||||
|
||||
private:
|
||||
Historic_guess clean(Historic_guess hist);
|
||||
vector<bool> extract_info(Historic_guess hist);
|
||||
|
||||
bool all_are_consistent(vector<int> supposed_sequence);
|
||||
vector<int> brute_force(vector<vector<int>> *possibilities, vector<int> *chosen, int index);
|
||||
|
||||
void generate_set(vector<int> carry);
|
||||
int get_weight(vector<int> guess);
|
||||
Weighed_guess minimax(vector<vector<int>> *possibilities, vector<int> *chosen, int index);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue