Geoff Hill avatar Geoff Hill committed e8f3f00

finished Othello

Comments (0)

Files changed (15)

src/othello/Board.cpp

 #include "Board.hpp"
+#include <sstream>
+
+using std::stringstream;
 
 Board::Board() {
     for(int32_t i = 0; i < 16; i++) {
                 BoardLocs::iterator it;
                 for(it = changes.begin(); it != changes.end(); it++) {
                     BoardLoc loc = *it;
-                    next = next.extend(loc.get<0>(), loc.get<1>(), player);
+                    next = next.extend(loc, player);
                 }
             }
         }
     return next;
 }
 
-BoardLocs Board::validMoves(const BoardPlayerNum player) const {
+inline BoardLocs Board::validMoves(const BoardPlayerNum player) const {
     BoardLocs moves;
     for(int32_t i = 0; i < 8; i++) {
         for(int32_t j = 0; j < 8; j++) {
     return moves;
 }
 
-bool Board::validMove(const BoardIdx row, const BoardIdx col,
-                      const BoardPlayerNum player) const {
+inline bool Board::validMove(const BoardIdx row, const BoardIdx col,
+                             const BoardPlayerNum player) const {
     return (get(row, col) == UNPICKED) &&
            (sandwich(row, col, player, -1,  0) ||
             sandwich(row, col, player,  1,  0) ||
             sandwich(row, col, player,  1,  1));
 }
 
-bool Board::sandwich(const BoardIdx row, const BoardIdx col, const BoardPlayerNum player,
-                     const int8_t rowDir, const int8_t colDir) const {
+inline bool Board::sandwich(const BoardIdx row, const BoardIdx col, const BoardPlayerNum player,
+                            const int8_t rowDir, const int8_t colDir) const {
     if(valid(row+rowDir, col+colDir) && get(row+rowDir, col+colDir) == otherPlayer(player)) {
         for(int diff = 2; valid(row+rowDir*diff, col+colDir*diff); diff++) {
              BoardPlayerNum val = get(row+rowDir*diff, col+colDir*diff);
     return false;
 }
 
-BoardLocs Board::getSandwich(const BoardIdx row, const BoardIdx col, const BoardPlayerNum player,
-                             const int8_t rowDir, const int8_t colDir) const {
+inline BoardLocs Board::getSandwich(const BoardIdx row, const BoardIdx col, const BoardPlayerNum player,
+                                    const int8_t rowDir, const int8_t colDir) const {
     BoardPlayerNum other = otherPlayer(player);
     BoardLocs locs;
     for(int diff = 1; valid(row+rowDir*diff, col+colDir*diff); diff++) {
     return counter;
 }
 
+bool Board::complete() const {
+    return (score(UNPICKED) == 0) ||
+           ((validMoves(FST_PLAYER).size() == 0) &&
+            (validMoves(SND_PLAYER).size() == 0));
+           
+}
+
 BoardPlayerNum Board::winner() const {
     BoardScore counters[4] = {0, 0, 0, 0};
     for(int32_t i = 0; i < 8; i++) {
             ++(counters[get(i, j)]);
         }
     }
-    if((counters[UNPICKED] != 0) || (counters[FST_PLAYER] == counters[SND_PLAYER])) {
+    if(counters[FST_PLAYER] == counters[SND_PLAYER]) {
         return UNPICKED;
     }
     return (counters[FST_PLAYER] > counters[SND_PLAYER]) ? FST_PLAYER : SND_PLAYER;
 }
 
 string Board::toString() const {
-    char c[4] = {' ', 'B', 'W', 'I'};
+    char c[4] = {' ', 'B', '-', 'I'};
     string val;
     for(int32_t i = 0; i < 8; i++) {
         val += "+---+---+---+---+---+---+---+---+\n";
     return val;
 }
 
-Feature Board::featureCorners(BoardPlayerNum player) {
+bool Board::moveValid(const BoardIdx row, const BoardIdx col,
+                      const BoardLocs validMoves) {
+    BoardLocs::const_iterator it;
+    for(it = validMoves.begin(); it != validMoves.end(); it++) {
+        BoardLoc testMove = *it;
+        if((testMove.get<0>() == row) && (testMove.get<1>() == col)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+string Board::boardLocToString(const BoardLoc loc) {
+    const BoardIdx row = loc.get<0>();
+    const BoardIdx col = loc.get<1>();
+    stringstream s;
+    s << "(ROW " << ((int)row) << ", COL " << ((int)col) << ")";
+    return s.str();
+}
+
+string Board::boardLocsToString(const BoardLocs locs) {
+    string result = "[";
+    BoardLocs::const_iterator it;
+    for(it = locs.begin(); it != locs.end(); it++) {
+        if(it != locs.begin()) result += ", ";
+        result += boardLocToString(*it);
+    }
+    result += "]";
+    return result;
+}
+
+Feature Board::featureCorners(const BoardPlayerNum player) const {
     int32_t corners = 0;
     for(int32_t i = 0; i <= 7; i += 7) {
         for(int32_t j = 0; j <= 7; j += 7) {
     return ((double)corners) / 4.0;
 }
 
-Feature Board::featureEdges(BoardPlayerNum player) {
+Feature Board::featureEdges(const BoardPlayerNum player) const {
     int32_t edges = 0;
     for(int32_t i = 0; i < 8; i++) {
         for(int32_t j = 0; j < 8; j++) {

src/othello/Board.hpp

         }
     }
     BoardElem get(const BoardIdx row, const BoardIdx col) const;
+    BoardElem get(const BoardLoc loc) const
+        { return get(loc.get<0>(), loc.get<1>()); }
     Board extend(const BoardIdx row, const BoardIdx col,
                  const BoardPlayerNum player) const;
+    Board extend(const BoardLoc loc, const BoardPlayerNum player) const
+        { return extend(loc.get<0>(), loc.get<1>(), player); }
     Board makeMove(const BoardIdx row, const BoardIdx col,
                    const BoardPlayerNum player) const;
+    Board makeMove(const BoardLoc loc, const BoardPlayerNum player) const
+        { return makeMove(loc.get<0>(), loc.get<1>(), player); }
     BoardLocs validMoves(const BoardPlayerNum player) const;
     bool validMove(const BoardIdx row, const BoardIdx col,
                    const BoardPlayerNum player) const;
+    bool validMove(const BoardLoc loc, const BoardPlayerNum player) const
+        { return validMove(loc.get<0>(), loc.get<1>(), player); }
     BoardScore score(const BoardPlayerNum player) const;
-    bool complete() const
-        { return score(UNPICKED) == 0; }
+    bool complete() const;
     BoardPlayerNum winner() const;
     string toString() const;
     
-    Feature featureCorners(BoardPlayerNum player);
-    Feature featureEdges(BoardPlayerNum player);
-    Feature featureScore(BoardPlayerNum player)
+    static BoardPlayerNum otherPlayer(const BoardPlayerNum player) {
+        if (player == FST_PLAYER) return SND_PLAYER;
+        if (player == SND_PLAYER) return FST_PLAYER;
+        return UNPICKED;
+    }
+    static bool moveValid(const BoardIdx row, const BoardIdx col,
+                          const BoardLocs validMoves);
+    static bool moveValid(const BoardLoc loc, const BoardLocs validMoves)
+        { return moveValid(loc.get<0>(), loc.get<1>(), validMoves); }
+    static string boardLocToString(const BoardLoc loc);
+    static string boardLocsToString(const BoardLocs locs);
+    
+    Feature featureCorners(const BoardPlayerNum player) const;
+    Feature featureEdges(const BoardPlayerNum player) const;
+    Feature featureScore(const BoardPlayerNum player) const
         { return ((double)score(player)) / 64.0; }
-    Feature featureUnpicked(BoardPlayerNum player)
+    Feature featureUnpicked() const
         { return featureScore(UNPICKED); }
-    Feature featureMoves(BoardPlayerNum player)
+    Feature featureMoves(const BoardPlayerNum player) const
         { return ((double)validMoves(player).size()) / 32.0; }
 private:
     BoardElem positions[16];
-    BoardPlayerNum otherPlayer(const BoardPlayerNum player) const {
-        if (player == FST_PLAYER) return SND_PLAYER;
-        if (player == SND_PLAYER) return FST_PLAYER;
-        return UNPICKED;
-    }
     static bool valid(const BoardIdx row, const BoardIdx col) {
         return (row >= 0) && (row <= 7) && (col >= 0) && (col <= 7);
     }

src/othello/Game.cpp

+#include "Game.hpp"
+#include <iostream>
+
+using std::cout;
+using std::endl;
+
+void Game::play() const {
+    const string fstPlayerName = fst->toString() + " (black)";
+    const string sndPlayerName = snd->toString() + " (white)";
+    Board board;
+    BoardPlayerNum turn = FST_PLAYER;
+    while(!board.complete()) {
+        const BoardPlayerNum currentNum = turn;
+        const BoardPlayerNum otherNum = Board::otherPlayer(turn);
+        Player * const current = (turn == FST_PLAYER) ? fst : snd;
+        Player * const other = (turn == FST_PLAYER) ? snd : fst;
+        const BoardLocs validMoves = board.validMoves(currentNum);
+        BoardLoc move;
+        
+        cout << board.toString();
+        cout << "Current player: ";
+        cout << ((turn == FST_PLAYER) ? fstPlayerName : sndPlayerName);
+        cout << endl;
+        
+        if(validMoves.size() == 0) {
+            cout << "No moves available. Skipping turn." << endl;
+            turn = otherNum;
+            continue;
+        }
+        
+        while(true) {
+            move = current->chooseMove(board, validMoves);
+            if (Board::moveValid(move, validMoves)) break;
+            cout << "Invalid move " << Board::boardLocToString(move) << ". ";
+            cout << " Please try again." << endl;
+        }
+        board = board.makeMove(move, currentNum);
+        turn = otherNum;
+    }
+    
+    const BoardPlayerNum winnerNum = board.winner();
+    const BoardPlayerNum loserNum = Board::otherPlayer(winnerNum);
+    Player * const winner = (winnerNum == FST_PLAYER) ? fst : snd;
+    Player * const loser = (winnerNum == FST_PLAYER) ? snd : fst;
+    
+    cout << board.toString();
+    cout << endl << "Final Score" << endl;
+    cout << "===========" << endl;
+    cout << ((winnerNum == FST_PLAYER) ? fstPlayerName : sndPlayerName);
+    cout << ": " << (int)board.score(winnerNum) << endl;
+    cout << ((winnerNum == FST_PLAYER) ? sndPlayerName : fstPlayerName);
+    cout << ": " << (int)board.score(loserNum) << endl;
+}
+
+int main(int argc, char **argv) {
+    NaivePlayer fst(FST_PLAYER);
+    MinimaxPlayer snd(SND_PLAYER);
+    const Game game(&fst, &snd);
+    game.play();
+    return 0;
+}
+

src/othello/Game.hpp

+#ifndef AI_OTHELLO_GAME_H
+#define AI_OTHELLO_GAME_H
+
+#include "MinimaxPlayer.hpp"
+
+class Game {
+public:
+    Game(Player * const fstPlayer, Player * const sndPlayer) {
+        fst = fstPlayer;
+        snd = sndPlayer;
+    }
+    void play(void) const;
+private:
+    Player * fst;
+    Player * snd;
+};
+
+#endif

src/othello/Makefile

 CCX = g++ -L. -I. -I./gtest/include -lgtest -lstdc++ -lgtest
 
 all:
+	$(CCX) Board.cpp Player.cpp MinimaxPlayer.cpp Game.cpp -o Othello
+
+run: all
+	./Othello
+
+test:
 	$(CCX) Board.cpp BoardTest.cpp -o BoardTest
+	./BoardTest
 

src/othello/MinimaxPlayer.cpp

+#include "MinimaxPlayer.hpp"
+#include <limits>
+#include <cstdlib>
+#include <iostream>
+
+using std::cout;
+using std::endl;
+
+const double INF = std::numeric_limits<double>::max();
+const double realRand()
+    { return ((double)rand()/(double)RAND_MAX); }
+
+MinimaxPlayer::MinimaxPlayer(const BoardPlayerNum num) : Player(num) {
+    maxDepth = 3;
+    endTime = time(NULL);
+    
+    // Random parameter tweaking
+    srand((unsigned)(time(NULL) + num*31));
+    weightStartScore =    -0.04 * (0.5 + realRand());
+    weightStartCorners =   2.44 * (0.5 + realRand());
+    weightStartMoves =     3.89 * (0.5 + realRand());
+    weightEndScore =       1.05 * (0.5 + realRand());
+    weightEndCorners =     0.42 * (0.5 + realRand());
+    weightEndMoves =       0.48 * (0.5 + realRand());
+    cout << "Board " << (int)num << " parameters: " << endl;
+    cout << "    weightStartScore:    " << (double)weightStartScore << endl;
+    cout << "    weightStartCorners:  " << (double)weightStartCorners << endl;
+    cout << "    weightStartMoves:    " << (double)weightStartMoves << endl;
+    cout << "    weightEndScore:      " << (double)weightEndScore << endl;
+    cout << "    weightEndCorners:    " << (double)weightEndCorners << endl;
+    cout << "    weightEndMoves:      " << (double)weightEndMoves << endl;
+}
+
+const BoardLoc MinimaxPlayer::chooseMove(const Board state, const BoardLocs locs) {
+    // cout << "REM: " << state.featureUnpicked() << endl;
+    endTime = time(NULL) + 18;
+    BoardLoc bestMove = *(locs.begin());
+    Value bestValue = -INF;
+    BoardLocs::const_iterator it;
+    cout << "EVALUATION: " << evaluate(state, playerNum) << endl;
+    for(it = locs.begin(); it != locs.end(); it++) {
+        const Board nextState = state.extend(*it, playerNum);
+        const Value value = minValue(nextState, maxDepth, -INF, INF);
+        if(value > bestValue) {
+            bestMove = *it;
+            bestValue = value;
+        }
+        // cout << Board::boardLocToString(*it) << ": " << value << endl;
+    }
+    return bestMove;
+}
+
+Value MinimaxPlayer::maxValue(const Board state, const int16_t depth,
+                              const double alpha, const double beta) const {
+    BoardPlayerNum player = playerNum;
+    if(noAvailableMoves(state, player) || (depth == 0) || outOfTime()) {
+        return evaluate(state, player);
+    }
+    Value bestAlpha = alpha;
+    BoardLocs locs = state.validMoves(player);
+    BoardLocs::const_iterator it;
+    for(it = locs.begin(); it != locs.end(); it++) {
+        const Board nextState = state.extend(*it, player);
+        const Value value = minValue(nextState, depth-1, bestAlpha, beta);
+        if(value > bestAlpha) bestAlpha = value;
+        if(beta < bestAlpha) break;
+    }
+    return bestAlpha;
+}
+
+Value MinimaxPlayer::minValue(const Board state, const int16_t depth,
+                              const double alpha, const double beta) const {
+    BoardPlayerNum player = otherNum;
+    if(noAvailableMoves(state, player) || (depth == 0) || outOfTime()) {
+        return evaluate(state, player);
+    }
+    Value bestBeta = beta;
+    BoardLocs locs = state.validMoves(player);
+    BoardLocs::const_iterator it;
+    for(it = locs.begin(); it != locs.end(); it++) {
+        const Board nextState = state.extend(*it, player);
+        const Value value = maxValue(nextState, depth-1, alpha, bestBeta);
+        if(value < bestBeta) bestBeta = value;
+        if(bestBeta < alpha) break;
+    }
+    return bestBeta;
+}
+
+inline Value MinimaxPlayer::evaluate(const Board state, const BoardPlayerNum player) const {
+    const BoardPlayerNum other = Board::otherPlayer(player);
+    return evaluatePlayer(state, player) - evaluatePlayer(state, other);
+}
+
+inline Value MinimaxPlayer::evaluatePlayer(const Board state, const BoardPlayerNum player) const {
+    if(state.complete()) {
+        return 1000.0 * state.featureScore(player);
+    }
+    const Feature rem = state.featureUnpicked();
+    const Feature featureScore = state.featureScore(player);
+    const Feature featureCorners = state.featureCorners(player);
+    const Feature featureMoves = state.featureMoves(player);
+    Value value = 0.0;
+    value += rem * weightStartScore * featureScore;
+    value += rem * weightStartCorners * featureCorners;
+    value += rem * weightStartMoves * featureMoves;
+    value += (1.0 - rem) * weightEndScore * featureScore;
+    value += (1.0 - rem) * weightEndCorners * featureCorners;
+    value += (1.0 - rem) * weightEndMoves * featureMoves;
+    return value;
+}
+
+inline bool MinimaxPlayer::outOfTime() const {
+    return (time(NULL) >= endTime);
+}
+

src/othello/MinimaxPlayer.hpp

+#ifndef AI_OTHELLO_MINIMAX_PLAYER_H
+#define AI_OTHELLO_MINIMAX_PLAYER_H
+
+#include "Player.hpp"
+#include <ctime>
+
+typedef double Value;
+
+class MinimaxPlayer : public Player {
+public:
+    MinimaxPlayer(const BoardPlayerNum num);
+    const BoardLoc chooseMove(const Board board, const BoardLocs locs);
+    string toString(void) const
+        { return "MINIMAX_PLAYER"; }
+private:
+    int16_t maxDepth;
+    time_t endTime;
+    Value weightStartScore;
+    Value weightStartCorners;
+    Value weightStartMoves;
+    Value weightEndScore;
+    Value weightEndCorners;
+    Value weightEndMoves;
+    Value maxValue(const Board state, const int16_t depth,
+                   const double alpha, const double beta) const;
+    Value minValue(const Board state, const int16_t depth,
+                   const double alpha, const double beta) const;
+    Value evaluate(const Board state, const BoardPlayerNum player) const;
+    Value evaluatePlayer(const Board state, const BoardPlayerNum player) const;
+    static bool noAvailableMoves(const Board state, const BoardPlayerNum player)
+        { return (state.validMoves(player).size() == 0); }
+    bool outOfTime() const;
+};
+
+
+#endif

src/othello/Player.cpp

 #include "Player.hpp"
+#include <iostream>
+#include <cstdlib>
+#include <iterator>
 
+using std::cout;
+using std::cin;
+using std::endl;
+using std::advance;
+
+Player::~Player() {}
+
+const BoardLoc HumanPlayer::chooseMove(const Board board, const BoardLocs locs) {
+    string rowStr, colStr;
+    int row, col;
+    cout << "Pick a row (0-7): " << endl;
+    cin >> row;
+    cout << "Pick a col (0-7): " << endl;
+    cin >> col;
+    return BoardLoc((int8_t)row, (int8_t)col);
+}
+
+const BoardLoc NaivePlayer::chooseMove(const Board board, const BoardLocs locs) {
+    int num = rand() % locs.size();
+    BoardLocs::const_iterator it = locs.begin();
+    advance(it, num);
+    return *it;
+}
 

src/othello/Player.hpp

 #ifndef AI_OTHELLO_PLAYER_H
 #define AI_OTHELLO_PLAYER_H
 
-#include <boost/smart_ptr/shared_ptr.hpp>
+#include "Board.hpp"
 
 class Player {
+public:
+    Player(const BoardPlayerNum num) {
+        playerNum = num;
+        otherNum = Board::otherPlayer(num);
+    }
     virtual ~Player(void) = 0;
+    virtual const BoardLoc chooseMove(const Board board,
+                                      const BoardLocs locs) = 0;
+    virtual string toString(void) const
+        { return "PLAYER"; }
+protected:
+    BoardPlayerNum playerNum;
+    BoardPlayerNum otherNum;
 };
 
 class HumanPlayer : public Player {
-    
+public:
+    HumanPlayer(const BoardPlayerNum num) : Player(num) {}
+    const BoardLoc chooseMove(const Board board, const BoardLocs locs);
+    string toString(void) const
+        { return "HUMAN_PLAYER"; }
 };
 
-class MinimaxPlayer : public Player {
-    
+class NaivePlayer : public Player {
+public:
+    NaivePlayer(const BoardPlayerNum num) : Player(num) {}
+    const BoardLoc chooseMove(const Board board, const BoardLocs locs);
+    string toString(void) const
+        { return "NAIVE_PLAYER"; }
 };
 
+
 #endif

src/othello/advBlack_01.txt

++---+---+---+---+---+---+---+---+
+|   |   |   |   |   | B | B | B |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   | - |   |   |
++---+---+---+---+---+---+---+---+
+|   |   | - | - | - | - | B |   |
++---+---+---+---+---+---+---+---+
+|   |   | B | - | B | B |   | B |
++---+---+---+---+---+---+---+---+
+|   |   | - | B | B | B | B | B |
++---+---+---+---+---+---+---+---+
+|   | - | - | - | - | - |   |   |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   |   | - |   |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   |   |   |   |
++---+---+---+---+---+---+---+---+
+EVALUATION: 0.736417

src/othello/advBlack_02.txt

++---+---+---+---+---+---+---+---+
+|   |   |   |   |   | B | B | B |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   | - |   | B |
++---+---+---+---+---+---+---+---+
+|   |   | - | - | - | - | - | B |
++---+---+---+---+---+---+---+---+
+|   | - | - | - | B | B | B | B |
++---+---+---+---+---+---+---+---+
+|   |   | - | - | - | B | B | B |
++---+---+---+---+---+---+---+---+
+|   | - | - | - | - | B |   |   |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   | - | - | - |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   |   | - | B |
++---+---+---+---+---+---+---+---+
+EVALUATION: 1.23663

src/othello/advBlack_03.txt

++---+---+---+---+---+---+---+---+
+| B | B | B | B | B | B | B | B |
++---+---+---+---+---+---+---+---+
+| - | - | - | - | B | B | B | B |
++---+---+---+---+---+---+---+---+
+|   | - | B | B | B | B | B | B |
++---+---+---+---+---+---+---+---+
+|   | - | - | B | - | - | B | - |
++---+---+---+---+---+---+---+---+
+|   |   | B | - | - | - | - | - |
++---+---+---+---+---+---+---+---+
+| - | - | - | - | - | - |   |   |
++---+---+---+---+---+---+---+---+
+|   |   | - |   | - |   |   |   |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   |   |   |   |
++---+---+---+---+---+---+---+---+
+EVALUATION: 0.974657

src/othello/advWhite_01.txt

++---+---+---+---+---+---+---+---+
+|   |   |   |   |   |   |   |   |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   | B |   |   |
++---+---+---+---+---+---+---+---+
+| - |   | - | - | B |   |   |   |
++---+---+---+---+---+---+---+---+
+| - | - | - | B | - |   |   |   |
++---+---+---+---+---+---+---+---+
+| - | B | - | B | - |   |   |   |
++---+---+---+---+---+---+---+---+
+| - | B | - | - | - | - |   |   |
++---+---+---+---+---+---+---+---+
+| - | - | - | - | - | B | - | B |
++---+---+---+---+---+---+---+---+
+| - | B | B | B | B |   | B | - |
++---+---+---+---+---+---+---+---+
+EVALUATION: -0.298973

src/othello/advWhite_02.txt

++---+---+---+---+---+---+---+---+
+| - | - | - | B | - | - | - | - |
++---+---+---+---+---+---+---+---+
+| B | - | - | - | - | - | - | - |
++---+---+---+---+---+---+---+---+
+| - | B | - | - | B | B | - | - |
++---+---+---+---+---+---+---+---+
+| - | - | B | - | - | - | - | - |
++---+---+---+---+---+---+---+---+
+|   |   | B | B | B | - | B | B |
++---+---+---+---+---+---+---+---+
+|   |   |   | B | B | B | B |   |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   | - | B | - |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   | - |   |   |
++---+---+---+---+---+---+---+---+
+EVALUATION: 0.632257

src/othello/advWhite_03.txt

++---+---+---+---+---+---+---+---+
+| - | - | - |   | - |   |   |   |
++---+---+---+---+---+---+---+---+
+|   |   | B |   | - |   |   |   |
++---+---+---+---+---+---+---+---+
+|   |   | B | B | - | B |   |   |
++---+---+---+---+---+---+---+---+
+|   |   | B | B | B |   | B |   |
++---+---+---+---+---+---+---+---+
+|   |   |   | B | B | B |   |   |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   |   |   |   |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   |   |   |   |
++---+---+---+---+---+---+---+---+
+|   |   |   |   |   |   |   |   |
++---+---+---+---+---+---+---+---+
+EVALUATION: 0.501289
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.