From 1a9cc0b5a57bbf6649776a3ece71c6007c5ef55f Mon Sep 17 00:00:00 2001 From: Matthew Daiter Date: Tue, 30 Apr 2019 14:11:15 -0500 Subject: [PATCH 1/2] [mdaiter]: add CMake file for easier build --- CMakeLists.txt | 27 +++++++++++++++++++++++++++ README.md | 6 ++++++ generator.cpp | 11 ++++++----- 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c55776d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(CONNECT4) +SET(CONNECT4_VERSION_MAJOR 0) +SET(CONNECT4_VERSION_MINOR 9) + +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11 -W -Wall -O3 -DNDEBUG") + +ADD_LIBRARY(c4solver_dependencies STATIC + Solver.cpp + Solver.hpp + Position.hpp + TranspositionTable.hpp + OpeningBook.hpp + MoveSorter.hpp) + +ADD_EXECUTABLE(c4solver + main.cpp) + +ADD_EXECUTABLE(c4generator + generator.cpp) + +TARGET_LINK_LIBRARIES(c4solver c4solver_dependencies) +TARGET_LINK_LIBRARIES(c4generator c4solver_dependencies) + +INSTALL(TARGETS c4solver_dependencies DESTINATION lib) +INSTALL(TARGETS c4generator DESTINATION bin) +INSTALL(TARGETS c4solver DESTINATION bin) diff --git a/README.md b/README.md index d7f5d28..690dcbd 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,9 @@ This C++ source code is published under AGPL v3 license. Read the associated [step by step tutorial to build a perfect Connect 4 AI](http://blog.gamesolver.org) for explanations. + +## Quick build +``` +mkdir build && cd build +cmake ../ +``` diff --git a/generator.cpp b/generator.cpp index bd4f0b8..747a1ba 100644 --- a/generator.cpp +++ b/generator.cpp @@ -13,17 +13,17 @@ std::unordered_set visited; * Explore and print all possible position under a given depth. * symetric positions are printed only once. */ -void explore(const Position &P, char* pos_str, const int depth) { +void explore(const Position &P, char* const pos_str, const int depth) { uint64_t key = P.key3(); if(visited.count(key)) return; // already explored position visited.insert(key); // flag new position as visited - int nb_moves = P.nbMoves(); + const int nb_moves = P.nbMoves(); if(nb_moves <= depth) std::cout << pos_str << std::endl; if(nb_moves >= depth) return; // do not explore at further depth - for(int i = 0; i < Position::WIDTH; i++) // explore all possible moves + for(int i = 0; i < Position::WIDTH; ++i) // explore all possible moves if(P.canPlay(i) && !P.isWinningMove(i)) { Position P2(P); P2.playCol(i); @@ -71,8 +71,9 @@ void generate_opening_book() { */ int main(int argc, char** argv) { if(argc > 1) { - int depth = atoi(argv[1]); - char pos_str[depth + 1] = {0}; + const int depth = atoi(argv[1]); + char pos_str[depth + 1]; + memset(pos_str, 0, (depth + 1) * sizeof(int)); explore(Position(), pos_str, depth); } else generate_opening_book(); } From c1ff40c218b0abf39ee3f5aebd28865e139c5e3d Mon Sep 17 00:00:00 2001 From: Matthew Daiter Date: Thu, 2 May 2019 16:07:23 -0500 Subject: [PATCH 2/2] [mdaiter]: optimize using consts, inlines, builtins --- MoveSorter.hpp | 9 +++------ OpeningBook.hpp | 43 +++++++++++++++++++++++++----------------- Position.hpp | 22 ++++++++++++--------- Solver.cpp | 4 ++-- Solver.hpp | 4 ++-- TranspositionTable.hpp | 5 +++-- 6 files changed, 49 insertions(+), 38 deletions(-) diff --git a/MoveSorter.hpp b/MoveSorter.hpp index 73137e7..5f155e5 100644 --- a/MoveSorter.hpp +++ b/MoveSorter.hpp @@ -42,7 +42,7 @@ class MoveSorter { * Add a move in the container with its score. * You cannot add more than Position::WIDTH moves */ - void add(Position::position_t move, int score) { + void add(const Position::position_t move, const int score) { int pos = size++; for(; pos && entries[pos - 1].score > score; --pos) entries[pos] = entries[pos - 1]; entries[pos].move = move; @@ -54,11 +54,8 @@ class MoveSorter { * @return next remaining move with max score and remove it from the container. * If no more move is available return 0 */ - Position::position_t getNext() { - if(size) - return entries[--size].move; - else - return 0; + inline Position::position_t getNext() __attribute__((always_inline)) { + return size ? entries[--size].move : 0; } /** diff --git a/OpeningBook.hpp b/OpeningBook.hpp index a15ad47..b8c1eec 100644 --- a/OpeningBook.hpp +++ b/OpeningBook.hpp @@ -24,6 +24,9 @@ #include "Position.hpp" #include "TranspositionTable.hpp" +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + namespace GameSolver { namespace Connect4 { @@ -34,23 +37,29 @@ class OpeningBook { int depth; template - TableGetter* initTranspositionTable(int log_size) { - switch(log_size) { - case 21: - return new TranspositionTable(); - case 22: - return new TranspositionTable(); - case 23: - return new TranspositionTable(); - case 24: - return new TranspositionTable(); - case 25: - return new TranspositionTable(); - case 26: - return new TranspositionTable(); - case 27: - return new TranspositionTable(); - default: + TableGetter* initTranspositionTable(const int log_size) { + constexpr auto LOG_SIZE_LOWER_BOUND = 20; + constexpr auto LOG_SIZE_UPPER_BOUND = 28; + if (likely(log_size > LOG_SIZE_LOWER_BOUND && log_size < LOG_SIZE_UPPER_BOUND)) { + switch(log_size) { + case 21: + return new TranspositionTable(); + case 22: + return new TranspositionTable(); + case 23: + return new TranspositionTable(); + case 24: + return new TranspositionTable(); + case 25: + return new TranspositionTable(); + case 26: + return new TranspositionTable(); + case 27: + return new TranspositionTable(); + default: + return 0; + } + } else { std::cerr << "Unimplemented OpeningBook size: " << log_size << std::endl; return 0; } diff --git a/Position.hpp b/Position.hpp index 95e96b6..c1068ac 100644 --- a/Position.hpp +++ b/Position.hpp @@ -83,16 +83,16 @@ namespace Connect4 { class Position { public: - static const int WIDTH = 7; // width of the board - static const int HEIGHT = 6; // height of the board + static constexpr int WIDTH = 7; // width of the board + static constexpr int HEIGHT = 6; // height of the board // Board size is 64bits or 128 bits depending on WIDTH and HEIGHT using position_t = typename std::conditional < WIDTH * (HEIGHT + 1) <= 64, uint64_t, __int128>::type; // __int128 is a g++ non portable type. Use the following line limited to 64bits board for C++ compatibility // using position_t = uint64_t - static const int MIN_SCORE = -(WIDTH*HEIGHT) / 2 + 3; - static const int MAX_SCORE = (WIDTH * HEIGHT + 1) / 2 - 3; + static constexpr int MIN_SCORE = -(WIDTH*HEIGHT) / 2 + 3; + static constexpr int MAX_SCORE = (WIDTH * HEIGHT + 1) / 2 - 3; static_assert(WIDTH < 10, "Board's width must be less than 10"); static_assert(WIDTH * (HEIGHT + 1) <= sizeof(position_t)*8, "Board does not fit into position_t bitmask"); @@ -121,8 +121,8 @@ class Position { * Caller can check if the move sequence was valid by comparing the number of * processed moves to the length of the sequence. */ - unsigned int play(std::string seq) { - for(unsigned int i = 0; i < seq.size(); i++) { + unsigned int play(const std::string& seq) { + for(unsigned int i = 0; i < seq.size(); ++i) { int col = seq[i] - '1'; if(col < 0 || col >= Position::WIDTH || !canPlay(col) || isWinningMove(col)) return i; // invalid move playCol(col); @@ -133,7 +133,7 @@ class Position { /** * return true if current player can win next move */ - bool canWinNext() const { + inline bool canWinNext() const { return winning_position() & possible(); } @@ -141,14 +141,14 @@ class Position { /** * @return number of moves played from the beginning of the game. */ - int nbMoves() const { + inline int nbMoves() const { return moves; } /** * @return a compact representation of a position on WIDTH*(HEIGHT+1) bits. */ - position_t key() const { + inline position_t key() const { return current_position + mask; } @@ -286,9 +286,13 @@ class Position { * counts number of bit set to one in a 64bits integer */ static unsigned int popcount(position_t m) { + #if defined(__GNUC__) || defined(__GNUG__) + return __builtin_popcount(m); + #else unsigned int c = 0; for(c = 0; m; c++) m &= m - 1; return c; + #endif } /** diff --git a/Solver.cpp b/Solver.cpp index b4686d4..390c7ff 100644 --- a/Solver.cpp +++ b/Solver.cpp @@ -127,8 +127,8 @@ int Solver::solve(const Position &P, bool weak) { // Constructor Solver::Solver() : nodeCount{0} { - for(int i = 0; i < Position::WIDTH; i++) // initialize the column exploration order, starting with center columns - columnOrder[i] = Position::WIDTH / 2 + (1 - 2 * (i % 2)) * (i + 1) / 2; // example for WIDTH=7: columnOrder = {3, 4, 2, 5, 1, 6, 0} + for(int i = 0; i < Position::WIDTH; ++i) // initialize the column exploration order, starting with center columns + columnOrder[i] = Position::WIDTH / 2 + (1 - 2 * (i & 1)) * (i + 1) / 2; // example for WIDTH=7: columnOrder = {3, 4, 2, 5, 1, 6, 0} } } // namespace Connect4 diff --git a/Solver.hpp b/Solver.hpp index 18adfeb..64503f9 100644 --- a/Solver.hpp +++ b/Solver.hpp @@ -28,7 +28,7 @@ namespace Connect4 { class Solver { private: - static const int TABLE_SIZE = 24; // store 2^TABLE_SIZE elements in the transpositiontbale + static constexpr int TABLE_SIZE = 24; // store 2^TABLE_SIZE elements in the transpositiontbale TranspositionTable < uint_t < Position::WIDTH*(Position::HEIGHT + 1) - TABLE_SIZE >, Position::position_t, uint8_t, TABLE_SIZE > transTable; OpeningBook book{Position::WIDTH, Position::HEIGHT}; // opening book unsigned long long nodeCount; // counter of explored nodes. @@ -51,7 +51,7 @@ class Solver { int solve(const Position &P, bool weak = false); - unsigned long long getNodeCount() { + unsigned long long getNodeCount() const { return nodeCount; } diff --git a/TranspositionTable.hpp b/TranspositionTable.hpp index f3ba041..1fe5479 100644 --- a/TranspositionTable.hpp +++ b/TranspositionTable.hpp @@ -103,8 +103,9 @@ class TranspositionTable : public TableGetter { int getKeySize() override {return sizeof(partial_key_t);} int getValueSize() override {return sizeof(value_t);} - size_t index(key_t key) const { - return key % size; + inline size_t index(const key_t key) const { + // Because size is a power of 2, key % size == key & (size - 1) + return key & (size - 1); } public: