Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include "StdAfx.h"
- #include "ConnectK.h"
- // constructor
- ConnectK::ConnectK()
- : M(0), N(0), K(0), G(FALSE)
- {
- board = 0;
- }
- // destructor
- ConnectK::~ConnectK()
- {
- for ( int i = 0; i < M; i++ )
- delete [] board[i];
- delete [] board;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- // newGame(int pM, int pN, int pK, bool pG, char pmark)
- //
- // Purpose: This function is to allocate memory and initialize structures necessary
- // to play the game defined by the parameters, M, N, K and G passed by the
- // GUI (or other driving program to this class). In this sample code, we are
- // using a two dimensional dynamic array of characters to represent the board
- // and the symbols X, O, and BLANK (defined in the ConnectK.h file) to keep
- // track of game progress.
- //
- // Parameters: pM number of rows in the game board
- // pN number of columns in the game board
- // pK number in a row to win the game
- // pG Gravity on/off. Determines whether or not game pieces fall to the
- // lowest available position in a column (as in Connect 4), or remain
- // in the position places (as in Tic-tac-toe).
- // pmark this is the mark that the computer player has been assigned.
- // it will be either 'X' or 'O'. It is not actually necessary to use
- // this variable, but you may find it useful to know which player will
- // move first, prior to nextMove() being called.
- //
- // Returns: nothing.
- //
- // NOTE: This function can be called at any time from the GUI. Make sure it
- // can re-allocate memory and re-initialize variables, game after game.
- //
- // The board layout used by the GUI has the origin located at the top left corner
- // of the board. For example:
- //
- // N columns
- //
- // 0 1 2..N-1
- // +-----------+
- // 0| | | | |
- // |--+--+--+--|
- // 1| | | | |
- // M rows |--+--+--+--|
- // 2| | | | |
- // ..|--+--+--+--|
- // M-1| | | | |
- // +-----------+
- //
- void ConnectK::newGame(int pM, int pN, int pK, bool pG, char pmark)
- {
- // re-initialize all variables
- // delete the previous game board if it exists
- if ( board != NULL )
- {
- for ( int i = 0; i < M; i++ )
- delete [] board[i];
- board = 0;
- delete [] board;
- }
- // p is for parameter
- M = pM;
- N = pN;
- K = pK;
- G = pG;
- computerMark = pmark;
- // initialize the board to all blank characters (see ConnectK.h for definitions)
- if ( M > 0 && N > 0 )
- {
- board = new CharArray[M];
- for (int i = 0; i < M; i++)
- {
- board[i] = new char[N];
- for (int j = 0; j < N; j++)
- {
- board[i][j] = BLANK;
- }
- }
- }
- else
- cerr << "Error: bad array size." << endl;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- // nextMove(int &row, int &col)
- //
- // Purpose: This function receives the move made by the human player (or other AI
- // player), records it, and returns a move based on an AI search routine.
- //
- // Parameters: &row this holds the row coordinate of the move made by the human player
- // &col this holds the column coordinate of the move made by the human player
- //
- // Returns: &row place the row value of the move made by your AI search in this variable
- // &col place the column value of the move made by your AI in this variable
- //
- // NOTE: Remember to do all initialization in newGame(). This function must be ready to
- // return a move when given one, and will determine the time taken by your program
- // in tournament competition. Make your search as efficient as possible.
- //
- void ConnectK::nextMove(int &row, int &col)
- {
- Node temp;
- PointItem tempItem;
- PointList* bestMove = new PointList();
- int alpha;
- int beta;
- int searchDepth = 1;
- CTime startTime;
- CTimeSpan timespan;
- char playerMark;
- bool searching = true;
- ConnectK::logfile.open("textlog.txt");
- if (computerMark == X)
- playerMark = O;
- else
- playerMark = X;
- // If x and y are not -1 then we need to record the move made by the human player
- // If x and y are -1, then this is the first move of the game, the AI moves first.
- if( ( row != -1 ) && ( col != -1 ) )
- {
- if(G)
- {
- row = 0;
- while(row < M - 1 && board[row+1][col] == BLANK)
- row++;
- }
- board[row][col] = playerMark;
- }
- ConnectK::logfile << "BEGIN" << endl;
- startTime = CTime::GetCurrentTime();
- alpha = MIN_HEURISTIC;
- beta = MAX_HEURISTIC;
- while(searching)
- {
- Killers = new PointItem[searchDepth+1];
- ConnectK::logfile << "Search level: " << searchDepth << endl << endl;
- tempItem = alphaBetaDeepening(searchDepth, true, alpha, beta);
- ConnectK::logfile << endl << "-----------------/ CUTOFF \\------------------ (" << tempItem.vals.x << ", " << tempItem.vals.y << ") :: " << tempItem.value << endl << endl;
- (*bestMove).Add(tempItem.vals);
- timespan = CTime::GetCurrentTime() - startTime;
- // check if we've broken the time limit
- if(timespan.GetSeconds() >= TIME_LIMIT)
- searching = false;
- searchDepth++;
- if(tempItem.value == MAX_HEURISTIC || tempItem.value == MIN_HEURISTIC) //search cutoff if we win here or lose no matter what we do
- searching = false;
- if(searchDepth > K+1) //arbitary cutoff to produce snappy response times in the final version
- searching = false;
- // an interesting note: at 5 plies this program will always produce a move in < 150 ms on
- // the standard setting. Humans cannot process anything generally faster than 200 ms and at
- // their best 150 ms. Anything that happens in 150 ms is percieved as instantaneous to a human
- delete[] Killers;
- }
- temp = (*bestMove).Get(0);
- #ifdef DEBUGGING
- ConnectK::logfile << "(" <<(*(*bestMove).Head).value << " " << (*(*bestMove).Tail).value << ")" << endl;
- #endif
- row = temp.x;
- col = temp.y;
- if(G)
- while(row < M - 1 && board[row+1][col] == BLANK)
- row++;
- board[row][col] = computerMark;
- ConnectK::logfile.close();
- delete bestMove;
- return;
- }
- /////////////////////////////////////////////////////////////////////////////////////////
- // Begin helper functions
- /////////////////////////////////////////////////////////////////////////////////////////
- PointItem ConnectK::alphaBetaDeepening(int depth, bool maxTurn, int alpha, int beta)
- {
- //PointList* heuristicList = new PointList();
- PointList* moves;
- PointList* searchList = new PointList();
- Node move;
- PointItem return_value;
- PointItem temp;
- char playerMark;
- return_value.value = 0;
- return_value.vals.x = 0;
- return_value.vals.y = 0;
- return_value.next = NULL;
- DWORD start,end;
- start = GetTickCount();
- #ifdef DEBUGGING
- ConnectK::logfile << "Depth: " << depth;
- for(int i = 0; i < (*searchList).Size(); i++)
- ConnectK::logfile << " " << (*(*searchList).Get(i)).Print();
- ConnectK::logfile << " ";
- #endif
- for(int i = 0; i < M; i++)
- {
- for(int j = 0; j < N; j++)
- {
- if(board[i][j] == BLANK && (!G || i == M-1 || board[i+1][j] != BLANK))
- {
- (*searchList).Add(i,j);
- }
- }
- }
- // rearrange our search list based on heuristic value
- searchListOrderingHeuristic(searchList, depth);
- if(computerMark == X)
- playerMark = O;
- else
- playerMark = X;
- if(depth <= 0 || (*searchList).Size() == 0)
- {
- return_value.value = evaluateBoard();
- #ifdef DEBUGGING
- ConnectK::logfile << "RETURN: " << (*(*moveStack).Get(0)).Print() << " " << return_value.value;
- end = GetTickCount();
- ConnectK::logfile << " t:" << end - start << " ms" << endl;
- #endif
- delete searchList;
- return return_value;
- }
- else
- {
- moves = new PointList();
- for(int i = 0; i < (*searchList).Size(); i++)
- {
- move.x = (*searchList).Get(i).x;
- move.y = (*searchList).Get(i).y;
- if(maxTurn)
- board[move.x][move.y] = computerMark;
- else
- board[move.x][move.y] = playerMark;
- temp = alphaBetaDeepening(depth-1, !maxTurn, alpha, beta);
- board[move.x][move.y] = BLANK;
- #ifdef DEBUGGING
- if(temp.value == MIN_HEURISTIC)
- ConnectK::logfile << " MIN_HEURISTIC ";
- else if(temp.value == MAX_HEURISTIC)
- ConnectK::logfile << " MAX_HEURISITC ";
- #endif
- if(maxTurn)
- {
- if(temp.value > alpha)
- alpha = temp.value;
- }
- else
- {
- if(temp.value < beta)
- beta = temp.value;
- }
- (*moves).Add(move, temp.value);
- if(beta <= alpha)
- break;
- }
- if(maxTurn)
- {
- if((*moves).Size() > 0)
- {
- return_value.vals = (*moves).Head->vals;
- }
- else
- {
- return_value.vals.x = -1;
- return_value.vals.y = -1;
- }
- return_value.value = alpha;
- if(Killers[depth].value < alpha)
- Killers[depth] = return_value;
- }
- else
- {
- if((*moves).Size() > 0)
- {
- return_value.vals = (*moves).Tail->vals;
- }
- else
- {
- return_value.vals.x = -1;
- return_value.vals.y = -1;
- }
- return_value.value = beta;
- if(Killers[depth].value > beta)
- Killers[depth] = return_value;
- }
- #ifdef DEBUGGING
- if(return_value.vals == NULL)
- ConnectK::logfile << "RETURN: (-1,-1)";
- else
- ConnectK::logfile << "RETURN: " << (*return_value.vals).Print();
- #endif
- end = GetTickCount();
- ConnectK::logfile << " alpha: " << alpha << " beta: " << beta << " t:" << end - start << " ms" << endl;
- delete moves;
- delete searchList;
- return return_value;
- }
- }
- // The idea behind this function is to reorder searchList into a temporary list
- // We order the items in searchList by the number of pieces in its immediate vicinity
- // This is O(b*K)
- // And then place our items back into searchList
- void ConnectK::searchListOrderingHeuristic(PointList* searchList, int depth)
- {
- PointList* tempList = new PointList();
- Node tempNode;
- int neighborCount, x, y;
- while((*searchList).Size() > 0)
- {
- neighborCount = 0;
- tempNode = (*searchList).Get(0);
- for(int j = 0; j < 8; j++)
- {
- for(int k = 0; k < K/2; k++)
- {
- switch(j)
- {
- case 0:
- x = tempNode.x+k;
- y = tempNode.y;
- break;
- case 1:
- x = tempNode.x+k;
- y = tempNode.y+k;
- break;
- case 2:
- x = tempNode.x;
- y = tempNode.y+k;
- break;
- case 3:
- x = tempNode.x-k;
- y = tempNode.y+k;
- break;
- case 4:
- x = tempNode.x-k;
- y = tempNode.y;
- break;
- case 5:
- x = tempNode.x-k;
- y = tempNode.y-k;
- break;
- case 6:
- x = tempNode.x;
- y = tempNode.y-k;
- break;
- case 7:
- x = tempNode.x+k;
- y = tempNode.y-k;
- break;
- }
- if(x < M && x >= 0 && y < N && y >= 0 && board[x][y] != BLANK)
- neighborCount++;
- }
- }
- (*tempList).Add(tempNode.x, tempNode.y, neighborCount);
- (*searchList).Remove(0);
- }
- //now searchList is empty and tempList has the ordered nodes, so we just put everything from tempList back in searchList
- while((*tempList).Size() > 0)
- {
- //now we check if we have any killer moves and pull those to the front
- if(Killers[depth].vals.x == (*tempList).Get(0).x && Killers[depth].vals.y == (*tempList).Get(0).y)
- (*(*tempList).Head).value = MAX_HEURISTIC;
- (*searchList).Add((*tempList).Get(0).x, (*tempList).Get(0).y, (*(*tempList).Head).value);
- (*tempList).Remove(0);
- }
- delete tempList;
- return;
- }
- int ConnectK::evaluateBoard()
- {
- int xtemppos = 0;
- int ytemppos = 0;
- int maxCount = 0;
- int minCount = 0;
- int retval = 0;
- bool maxWinFlag = false;
- bool minWinFlag = false;
- char playerMark = BLANK;
- if (computerMark == X)
- playerMark = O;
- else
- playerMark = X;
- #ifdef DEBUGGING
- ConnectK::logfile << endl;
- for(int i = 0; i < M; i++)
- {
- for(int j = 0; j < N; j++)
- {
- ConnectK::logfile << board[i][j] << "_";
- }
- ConnectK::logfile << endl;
- }
- #endif
- // now we need to provide an evaluation from the value of every position on our board
- // This will be O(n^2), but not tightly bounded. We need only look back at 4*K tiles at most
- // per square, so we're O(4*K*M*N)
- for(int x = 0; x < M; x++)
- {
- for(int y = 0; y < N; y++)
- {
- for(int i = 0; i < 4; i++)
- {
- maxCount = 0;
- minCount = 0;
- for(int k = 0; k < K; k++)
- {
- switch(i)
- {
- case 0:
- xtemppos = x;
- ytemppos = y - k;
- break;
- case 1:
- xtemppos = x - k;
- ytemppos = y - k;
- break;
- case 2:
- xtemppos = x - k;
- ytemppos = y;
- break;
- case 3:
- xtemppos = x - k;
- ytemppos = y + k;
- break;
- }
- if(xtemppos < M && xtemppos >= 0 && ytemppos < N && ytemppos >= 0)
- {
- if ( board[xtemppos][ytemppos] == computerMark )
- {
- maxCount++;
- if(minCount && maxCount)
- {
- maxCount = 0;
- minCount = 0;
- break; //both pieces, row has no value
- }
- }
- else if ( board[xtemppos][ytemppos] == playerMark )
- {
- minCount++;
- if(minCount && maxCount)
- {
- maxCount = 0;
- minCount = 0;
- break; //both pieces, row has no value
- }
- }
- else
- {
- if(G && xtemppos < M-1 && board[xtemppos+1][ytemppos] == BLANK)
- {
- maxCount = 0;
- minCount = 0;
- break;
- }
- }
- }
- else
- {
- maxCount = 0;
- minCount = 0;
- break; //less than K space, row has no value
- }
- }
- if(maxCount)
- {
- if(maxCount >= K)
- {
- maxWinFlag = true;
- }
- else
- retval++;
- }
- else if(minCount)
- {
- if(minCount >= K)
- {
- minWinFlag = true;
- }
- else
- retval--;
- }
- }
- }
- }
- if(maxWinFlag && minWinFlag)
- if(computerMark == O)
- retval = MIN_HEURISTIC;
- else
- retval = MAX_HEURISTIC;
- else if (maxWinFlag)
- retval = MAX_HEURISTIC;
- else if (minWinFlag)
- retval = MIN_HEURISTIC;
- return retval;
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement