194 lines
4.7 KiB
C++
194 lines
4.7 KiB
C++
#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
|
|
}
|
|
|
|
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();
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Guessing
|
|
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();
|
|
}
|
|
|
|
// 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 && col_was_cleaned[guess[n]])
|
|
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};
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|