How to store the first step of best value in a minimax tree? - minimax

I have a minimax tree and an evaluation function.The minimax function return only an integer(best value).How can i store the first move of the way of the founded best value ?
here's my code :
int Brain::MiniMax(GameBoard gb, int depth,int Turn,int lastcount) //0->Max 1->Min
{
if (depth == 5)
return Evaluation(lastcount, gb);
int bestval = 0;
if (Turn == 0)
{
bestval = -100;
vector<pair<int, pair<int, int>>> possibleFences = this->PossibleFences(gb);
for (int i = 0; i < possibleFences.size(); i++)//ForFences
{
int cnt = 0;
NextShortestPathMove(cnt, gb.OpponentPawn,gb);
if (gb.CanPutFence(possibleFences[i].first, possibleFences[i].second.first, possibleFences[i].second.second) == 0)
continue;
gb.PutFence(possibleFences[i].second.first, possibleFences[i].second.second, possibleFences[i].first);
int value = MiniMax(gb, depth + 1,1, cnt);
if (value > bestval)
{
bestval = value;
move = possibleFences[i];
}
}
return bestval;
}
else if (Turn == 1)
{
bestval = +100;
int** possibleMoves = this->PossibleMoves(gb.OpponentPawn.Row, gb.OpponentPawn.Column, gb.OpponentPawn,gb);
for (int i = 0; i < 6; i++)
{
if (possibleMoves[i][0] == -1)
continue;
int cnt = 0;
NextShortestPathMove(cnt, gb.OpponentPawn,gb);
gb.MoveOpponentPlayer(possibleMoves[i][0], possibleMoves[i][1]);
int value = MiniMax(gb, depth + 1, 0,cnt);
bestval = min(value, bestval);
}
return bestval;
}
}
for example at the end if the bestval = 10, i want the first move of the selection of this bestval. now i store the move in the 'move' variable but it doesn't work correctly.

In a practical minimax algorithm implementation, a hash table is used to enter evaluated scores and moves including hashkey, a value unique for each position of the players and pieces. This is also useful during implementation of "force move".
During move evaluation, hashkey, score and move position are recorded in a struct and the hash table as well. So, after successful search, the entire struct is returned to enable update of the graphics and game status.
A typical hash entry looks like so:
struct HashEntry {
int move;
int score;
int depth;
uint64_t posKey;
int flags;
};

Related

8 Tile Puzzle via BFS

I have searched the depths of the internet and I am yet to find a solution to my problem. I have implemented(I think) a BFS for the sliding tile game. However, It cannot solve a problem unless the state is a few steps away otherwise it just results in Out of Memory errors.
So my question to you, where am I going wrong? AFAIK my code follows the BFS pseudo code.
EDIT/NOTE: I have stepped through with a debugger and have yet to find anything out of the ordinary to my eye but I am merely a novice programmer comparatively.
#include <ctime>
#include <string>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <deque>
#include <vector>
using namespace std;
///////////////////////////////////////////////////////////////////////////////////////////
//
// Search Algorithm: Breadth-First Search
//
// Move Generator:
//
////////////////////////////////////////////////////////////////////////////////////////////
class State{
public:
int state[9];
State(){
for (int i = 0; i < 9; i++){
state[i] = i;
}
}
State(string st){
for (int i = 0; i < st.length(); i++){
state[i] = st.at(i) - '0';
}
}
State(const State &st){
for (int i = 0; i < 9; i++){
state[i] = st.state[i];
}
}
bool operator==(const State& other) {
for (int i = 0; i < 9; i++){
if (this->state[i] != other.state[i]){return false;}
}
return true;
}
bool operator!=(const State& other) {
return !(*this == other);
}
void swap(int x, int y){
// State b; // blank state
// for (int i = 0; i < 9; i++) // fill blank state with current state
// b.state[i] = state[i];
int t = this->state[x]; // saves value of the value in position x of the state
this->state[x] = this->state[y]; // swaps value of position x with position y in the state
this->state[y] = t; // swaps value of position y with saved position x in the state
}
int findBlank(){ // finds position in 3x3 of blank tile
for (int i=0; i<9; i++){
if (state[i]==0) return i;
}
}
vector<State> blankExpand(){
int pos = this->findBlank();
vector<State> vecStates;
if (pos != 0 && pos != 1 && pos != 2){ // swaps the tile above it
State newState = State(*this);
newState.swap(pos,pos - 3);
vecStates.push_back(newState);
}
if (pos != 6 && pos != 7 && pos != 8){ // swaps the tile above it
State newState = State(*this);
newState.swap(pos,pos + 3);
vecStates.push_back(newState);
}
if (pos != 0 && pos != 3 && pos != 6){ // swaps the tile above it
State newState = State(*this);
newState.swap(pos,pos - 1);
vecStates.push_back(newState);
}
if (pos != 2 && pos != 5 && pos != 8){ // swaps the tile above it
State newState = State(*this);
newState.swap(pos,pos + 1);
vecStates.push_back(newState);
}
return vecStates;
}
};
string breadthFirstSearch_with_VisitedList(string const initialState, string const goalState){
string path;
clock_t startTime;
startTime = clock();
deque<State> nodesToVisit;
vector<State> visitedList;
int maxQLength = 0;
//Init
State init(initialState);
State goal(goalState);
nodesToVisit.push_back(init);
int count = 0;
int numOfStateExpansions = 0 ;
//
while (!nodesToVisit.empty()){
if(maxQLength < nodesToVisit.size()){maxQLength = nodesToVisit.size();}
State cur = nodesToVisit.front();
nodesToVisit.pop_front();
//remove front
if (cur == goal){
//solution found
cout << "solved!";
break;
}
//Get children
vector<State> children = cur.blankExpand();
numOfStateExpansions += children.size();
//For each child
for (State& child : children) {
for (int i = 0 ; i < 9;i++){
cout << child.state[i];
}
cout << " child" << endl;
//If already visited ignore
if (std::find(visitedList.begin(), visitedList.end(), child) != visitedList.end()) {
// cout << "duplicate" << endl;
continue;
}
//If not in nodes to Visit
else if (std::find(nodesToVisit.begin(), nodesToVisit.end(), child) == nodesToVisit.end()) {
//Add child
nodesToVisit.push_back(child);
}
}
visitedList.push_back(cur);
}
//***********************************************************************************************************
clock_t actualRunningTime = ((float)(clock() - startTime)/CLOCKS_PER_SEC);
return path;
}
int main(){
breadthFirstSearch_with_VisitedList("042158367", "123804765");
//042158367
}
// 0 4 2
// 1 5 8
// 3 6 7
There are a number of inneficiencies in your code that are slowing it down. I'm actually surprised you had the patience to wait for it to reach an out-of-memory condition.
The main culprits are the searches:
std::find(visitedList.begin(), visitedList.end(), child) != visitedList.end()
//and
std::find(nodesToVisit.begin(), nodesToVisit.end(), child) == nodesToVisit.end()
Both of these execute in O(N), which sounds fine, but since you execute them on every node, that results in a O(N2).
You can fix this by using a std::unordered_set<> for the visitedList. Also, you could add nodes to the visited_list as soon as you queue them (instead of when you dequeue them). This way, you would only have a single lookup to do.
N.B. You will have to specialize std::hash<State> in order to use std::unordered_set.
One more hint: these cout << ... in your main loop really slow you down because they force a flush and sync with the OS by default, commenting these out will make your program run a lot faster.
There's actually quite a few more improvements that could be made in your code, but that's a topic for another day. Fixing the algorithmic complexity will bring it in the non-broken realm of things.

How to find local maximums in data set using C++?

I am using an arduino to read a sensor which stores 256 values into an array. I am trying to find local max's but some values being stored have repeating values to the left and right of itself causing the value to print multiple times. Is there a way to take all true values meaning they are a max value and store them in another array to process and reduce the repeated values to just 1 value...
OR is there a way to send the max values to another array where the repeated values get reduced to just 1? OR
IE:
Array1[] = {1,2,3,4,4,4,3,2,7,8,9,10}
max = 4 at index 3
max = 4 at index 4
max = 4 at index 5
since 4 is a peak point but repeats how can I reduce it so that the array looks like
Array2[] = {1,2,3,4,3,2,7,8,9,10}
max = 4 at index 3
I need the most basic breakdown if possible nothing on an expert level, thanks.
Code from Arduino:
int inp[20] = {24,100,13,155,154,157,156,140,14,175,158,102,169,160,190,100,200,164,143,20};
void setup()
{
Serial.begin(9600); // for debugging
}
void loop()
{
int i;
int count = 0;
for (i = 0; i < 20; i++)
{
Serial.println((String)inp[i]+" index at - "+i);
delay(100);
};
int N = 5; // loc max neighborhood size
for (int i = N-1; i < 19-N; i++)
{
bool loc = false;
for (int j = 1; j < N; j++) // look N-1 back and N-1 ahead
{
if (inp[i] > inp[i-j] && inp[i] > inp[i+j]) loc = true;
}
if (loc == true)
{
Serial.println((String)"max = "inp[i]+" at index "+i);
}
}
Serial.println("----------------------------------");
}
You can detect "local maxima" or peaks in a single loop without the need of copying something into another array. You just have to ignore repeating values, and you just have to keep track if the values considered are currently increasing or decreasing. Each value after which this status switches from increasing to decreasing is then a peak:
int main() {
int Array1[] = {1,2,3,4,4,4,3,2,7,8,9,10};
int prevVal = INT_MIN;
enum {
Ascending,
Descending
} direction = Ascending;
for (int i=0; i<sizeof(Array1)/sizeof(*Array1); i++) {
int curVal = Array1[i];
if (prevVal < curVal) { // (still) ascending?
direction = Ascending;
}
else if (prevVal > curVal) { // (still) descending?
if (direction != Descending) { // starts descending?
cout << "peak at index " << i-1 << ": " << prevVal << endl;
direction = Descending;
}
}
// prevVal == curVal is simply ignored...
prevVal = curVal;
}
}

Connect Four - Negamax AI evaluation function issue

I'm trying to implement NegaMax ai for Connect 4. The algorithm works well some of the time, and the ai can win. However, sometimes it completely fails to block opponent 3 in a rows, or doesn't take a winning shot when it has three in a row.
The evaluation function iterates through the grid (horizontally, vertically, diagonally up, diagonally down), and takes every set of four squares. It then checks within each of these sets and evaluates based on this.
I've based the function on the evaluation code provided here: http://blogs.skicelab.com/maurizio/connect-four.html
My function is as follows:
//All sets of four tiles are evaluated before this
//and values for the following variables are set.
if (redFoursInARow != 0)
{
redScore = INT_MAX;
}
else
{
redScore = (redThreesInARow * threeWeight) + (redTwosInARow * twoWeight);
}
int yellowScore = 0;
if (yellowFoursInARow != 0)
{
yellowScore = INT_MAX;
}
else
{
yellowScore = (yellowThreesInARow * threeWeight) + (yellowTwosInARow * twoWeight);
}
int finalScore = yellowScore - redScore;
return turn ? finalScore : -finalScore; //If this is an ai turn, return finalScore. Else return -finalScore.
My negamax function looks like this:
inline int NegaMax(char g[6][7], int depth, int &bestMove, int row, int col, bool aiTurn)
{
{
char c = CheckForWinner(g);
if ('E' != c || 0 == depth)
{
return EvaluatePosition(g, aiTurn);
}
}
int bestScore = INT_MIN;
for (int i = 0; i < 7; ++i)
{
if (CanMakeMove(g, i)) //If column i is not full...
{
{
//...then make a move in that column.
//Grid is a 2d char array.
//'E' = empty tile, 'Y' = yellow, 'R' = red.
char newPos[6][7];
memcpy(newPos, g, sizeof(char) * 6 * 7);
int newRow = GetNextEmptyInCol(g, i);
if (aiTurn)
{
UpdateGrid(newPos, i, 'Y');
}
else
{
UpdateGrid(newPos, i, 'R');
}
int newScore = 0; int newMove = 0;
newScore = NegaMax(newPos, depth - 1, newMove, newRow, i, !aiTurn);
newScore = -newScore;
if (newScore > bestScore)
{
bestMove = i;
bestScore = newScore;
}
}
}
}
return bestScore;
}
I'm aware that connect four has been solved are that there are definitely better ways to go about this, but any help or suggestions with fixing/improving this will be greatly appreciated. Thanks!

minimax c++ implementation for tic tac toe

void generate_moves(int gameBoard[9], list<int> &moves)
{
for (int i = 0; i < 9; i++)
{
if (gameBoard[i] == 0){
moves.push_back(i);
}
}
}
int evaluate_position(int gameBoard[9], int playerTurn)
{
state currentGameState = checkWin(gameBoard);
if (currentGameState != PLAYING)
{
if ((playerTurn == 1 && currentGameState == XWIN) || (playerTurn == -1 && currentGameState == OWIN))
return +infinity;
else if ((playerTurn == -1 && currentGameState == XWIN) || (playerTurn == 1 && currentGameState == OWIN))
return -infinity;
else if (currentGameState == DRAW)
return 0;
}
return -1;
}
int MinMove(int gameBoard[9], int playerTurn)
{
//if (checkWin(gameBoard) != PLAYING) { return evaluate_board(gameBoard); };
int pos_val = evaluate_position(gameBoard, playerTurn);
if (pos_val != -1) return pos_val;
int bestScore = +infinity;
list<int> movesList;
generate_moves(gameBoard, movesList);
while (!movesList.empty())
{
gameBoard[movesList.front()] = playerTurn;
int score = MaxMove(gameBoard, playerTurn*-1);
if (score < bestScore)
{
bestScore = score;
}
gameBoard[movesList.front()] = 0;
movesList.pop_front();
}
return bestScore;
}
int MaxMove(int gameBoard[9], int playerTurn)
{
//if (checkWin(gameBoard) != PLAYING) { return evaluate_board(gameBoard); };
int pos_val = evaluate_position(gameBoard, playerTurn);
if (pos_val != -1) return pos_val;
int bestScore = -infinity;
list<int> movesList;
generate_moves(gameBoard, movesList);
while (!movesList.empty())
{
gameBoard[movesList.front()] = playerTurn;
int score = MinMove(gameBoard, playerTurn*-1);
if (score > bestScore)
{
bestScore = score;
}
gameBoard[movesList.front()] = 0;
movesList.pop_front();
}
return bestScore;
}
int MiniMax(int gameBoard[9], int playerTurn)
{
int bestScore = -infinity;
int index = 0;
list<int> movesList;
vector<int> bestMoves;
generate_moves(gameBoard, movesList);
while (!movesList.empty())
{
gameBoard[movesList.front()] = playerTurn;
int score = MinMove(gameBoard, playerTurn);
if (score > bestScore)
{
bestScore = score;
bestMoves.clear();
bestMoves.push_back(movesList.front());
}
else if (score == bestScore)
{
bestMoves.push_back(movesList.front());
}
gameBoard[movesList.front()] = 0;
movesList.pop_front();
}
index = bestMoves.size();
if (index > 0) {
time_t secs;
time(&secs);
srand((uint32_t)secs);
index = rand() % index;
}
return bestMoves[index];
}
I created a tic tac toe program in C++ and tried to implement a MiniMax algorithm with exhaustive search tree.
These are the functions I have written using wiki and with the help of some websites. But the AI just doesn't work right and at times doesn't play its turn at all.
Could someone have a look and please point out if there is anything wrong with the logic?
This is how I think it works:
Minimax : This function starts with very large -ve number as best score and goal is to maximize that number. It calls minMove function. If new score > best score, then best score = new score...
MinMove : This function evaluates game board. If game over then it returns -infinity or +infinity depending on who won. If game is going on this function starts with max +infinity value as best score and goal is to minimize it as much possible. It calls MaxMove with opponent player's turn. (since players alternate turns).
If score < best score then best score = score. ...
MaxMove : This function evaluates game board. If game over then it returns -infinity or +infinity depending on who won. If game is going on this function starts with least -infinity value as best score and goal is to maximize it as much possible. It calls MinMove with opponent player's turn. (since players alternate turns).
If score > best score then best score = score. ...
Minmove and MaxMove call each other mutually recursively, MaxMove maximizing the value and MinMove minimizing it. Finally it returns the best possible moves list.
If there are more than 1 best moves, then a random of them is picked as the computer's move.
In MiniMax, MinMove(gameBoard, playerTurn) should be MinMove(gameBoard, -playerTurn) as you do in MaxMove.
As you use MinMove and MaxMove, your evaluation function should be absolute. I mean +infinity for XWIN
and -infinity for OWIN. And so MinMove can only be use when player == -1 and MaxMove when player == 1, thus the parameter become useless. And so MiniMax can only be used by player == 1.
I have done some changes in your code and it works (https://ideone.com/Ihy1SR).

weighted RNG speed problem in C++

Edit: to clarify, the problem is with the second algorithm.
I have a bit of C++ code that samples cards from a 52 card deck, which works just fine:
void sample_allcards(int table[5], int holes[], int players) {
int temp[5 + 2 * players];
bool try_again;
int c, n, i;
for (i = 0; i < 5 + 2 * players; i++) {
try_again = true;
while (try_again == true) {
try_again = false;
c = fast_rand52();
// reject collisions
for (n = 0; n < i + 1; n++) {
try_again = (temp[n] == c) || try_again;
}
temp[i] = c;
}
}
copy_cards(table, temp, 5);
copy_cards(holes, temp + 5, 2 * players);
}
I am implementing code to sample the hole cards according to a known distribution (stored as a 2d table). My code for this looks like:
void sample_allcards_weighted(double weights[][HOLE_CARDS], int table[5], int holes[], int players) {
// weights are distribution over hole cards
int temp[5 + 2 * players];
int n, i;
// table cards
for (i = 0; i < 5; i++) {
bool try_again = true;
while (try_again == true) {
try_again = false;
int c = fast_rand52();
// reject collisions
for (n = 0; n < i + 1; n++) {
try_again = (temp[n] == c) || try_again;
}
temp[i] = c;
}
}
for (int player = 0; player < players; player++) {
// hole cards according to distribution
i = 5 + 2 * player;
bool try_again = true;
while (try_again == true) {
try_again = false;
// weighted-sample c1 and c2 at once
// h is a number < 1325
int h = weighted_randi(&weights[player][0], HOLE_CARDS);
// i2h uses h and sets temp[i] to the 2 cards implied by h
i2h(&temp[i], h);
// reject collisions
for (n = 0; n < i; n++) {
try_again = (temp[n] == temp[i]) || (temp[n] == temp[i+1]) || try_again;
}
}
}
copy_cards(table, temp, 5);
copy_cards(holes, temp + 5, 2 * players);
}
My problem? The weighted sampling algorithm is a factor of 10 slower. Speed is very important for my application.
Is there a way to improve the speed of my algorithm to something more reasonable? Am I doing something wrong in my implementation?
Thanks.
edit: I was asked about this function, which I should have posted, since it is key
inline int weighted_randi(double *w, int num_choices) {
double r = fast_randd();
double threshold = 0;
int n;
for (n = 0; n < num_choices; n++) {
threshold += *w;
if (r <= threshold) return n;
w++;
}
// shouldn't get this far
cerr << n << "\t" << threshold << "\t" << r << endl;
assert(n < num_choices);
return -1;
}
...and i2h() is basically just an array lookup.
Your reject collisions are turning an O(n) algorithm into (I think) an O(n^2) operation.
There are two ways to select cards from a deck: shuffle and pop, or pick sets until the elements of the set are unique; you are doing the latter which requires a considerable amount of backtracking.
I didn't look at the details of the code, just a quick scan.
you could gain some speed by replacing the all the loops that check if a card is taken with a bit mask, eg for a pool of 52 cards, we prevent collisions like so:
DWORD dwMask[2] = {0}; //64 bits
//...
int nCard;
while(true)
{
nCard = rand_52();
if(!(dwMask[nCard >> 5] & 1 << (nCard & 31)))
{
dwMask[nCard >> 5] |= 1 << (nCard & 31);
break;
}
}
//...
My guess would be the memcpy(1326*sizeof(double)) within the retry-loop. It doesn't seem to change, so should it be copied each time?
Rather than tell you what the problem is, let me suggest how you can find it. Either 1) single-step it in the IDE, or 2) randomly halt it to see what it's doing.
That said, sampling by rejection, as you are doing, can take an unreasonably long time if you are rejecting most samples.
Your inner "try_again" for loop should stop as soon as it sets try_again to true - there's no point in doing more work after you know you need to try again.
for (n = 0; n < i && !try_again; n++) {
try_again = (temp[n] == temp[i]) || (temp[n] == temp[i+1]);
}
Answering the second question about picking from a weighted set also has an algorithmic replacement that should be less time complex. This is based on the principle of that which is pre-computed does not need to be re-computed.
In an ordinary selection, you have an integral number of bins which makes picking a bin an O(1) operation. Your weighted_randi function has bins of real length, thus selection in your current version operates in O(n) time. Since you don't say (but do imply) that the vector of weights w is constant, I'll assume that it is.
You aren't interested in the width of the bins, per se, you are interested in the locations of their edges that you re-compute on every call to weighted_randi using the variable threshold. If the constancy of w is true, pre-computing a list of edges (that is, the value of threshold for all *w) is your O(n) step which need only be done once. If you put the results in a (naturally) ordered list, a binary search on all future calls yields an O(log n) time complexity with an increase in space needed of only sizeof w / sizeof w[0].