diff --git a/.gitignore b/.gitignore index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e660fd93d3196215552065b1e63bf6a2f393ed86 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +bin/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..210e18914485675f6b481d93864139d90fc37ecc --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +CC=g++-4.9 +BIN=bin +INCLUDE=-I src/game -I src/util -I src/monte_carlo -I src/mcts -I src/minmax +CFLAGS=-g -O3 -ffast-math -fopenmp -c -Wall -std=c++11 $(INCLUDE) +LDFLAGS=-fopenmp -std=c++11 #-lprofiler -Wl,-no_pie +SOURCES=omp_util.cpp fast_log.cpp display_node.cpp connect4.cpp test_connect4.cpp monte_carlo.cpp test_monte_carlo.cpp test_fast_log.cpp\ +statistics.cpp node.cpp allocator.cpp test_allocator.cpp openings.cpp mcts_two_players.cpp test_mcts_two_players.cpp test_minmax.cpp\ +bits.cpp test_bits.cpp main.cpp +OBJECTS=$(addprefix $(BIN)/, $(SOURCES:.cpp=.o)) +EXECUTABLE=$(BIN)/theturk +vpath %.cpp src/game:src/main:src/util:src/monte_carlo:src/mcts:src/gdl:src/minmax + +all: $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(LDFLAGS) $(OBJECTS) -o $@ + +-include $(BIN)/$(OBJECTS:.o=.d) + +$(BIN)/%.o: %.cpp + $(CC) -c $(CFLAGS) $< -o $(BIN)/$*.o + $(CC) -MM $(CFLAGS) $< > $(BIN)/$*.d + @mv -f $(BIN)/$*.d $(BIN)/$*.d.tmp + @sed -e 's|.*:|$(BIN)/$*.o:|' < $(BIN)/$*.d.tmp > $(BIN)/$*.d + @sed -e 's/.*://' -e 's/\\$$//' < $(BIN)/$*.d.tmp | fmt -1 | \ + sed -e 's/^ *//' -e 's/$$/:/' >> $(BIN)/$*.d + @rm -f $(BIN)/$*.d.tmp + +clean: + rm -f $(BIN)/*.o $(BIN)/*.d $(EXECUTABLE) diff --git a/src/game/connect4.cpp b/src/game/connect4.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c7d4a713ee93bf8e6ed6d5350696906a2c7b0f05 --- /dev/null +++ b/src/game/connect4.cpp @@ -0,0 +1,363 @@ +#include "connect4.hpp" +#include <sstream> + +using namespace std; + +namespace game +{ + static vector<vector<uint64_t>> create_hash_values() + { + default_random_engine generator; + uniform_int_distribution<uint64_t> distribution; + vector<vector<uint64_t>> res(7, vector<uint64_t>(6, 0)); + for (int i = 0; i < 7; ++i) + { + for (int j = 0; j < 6; ++j) + { + res[i][j] = distribution(generator); + } + } + return res; + } + + vector<vector<uint64_t>> connect4::cross_hash_values = create_hash_values(); + vector<vector<uint64_t>> connect4::circle_hash_values = create_hash_values(); + + connect4::connect4() + { + } + + shared_ptr<game<connect4_state>> connect4::do_copy() const + { + return shared_ptr<connect4>(new connect4(*this)); + } + + connect4_state connect4::get_state() + { + return state; + } + + void connect4::set_state(const connect4_state& s) + { + state = s; + } + + bool connect4::end_of_game() const + { + return state.first_player_win || state.second_player_win || state.total_moves == 42; + } + + bool connect4::won(std::uint8_t player) const + { + if (player == 0) return state.first_player_win; + return state.second_player_win; + } + + bool connect4::lost(std::uint8_t player) const + { + if (player == 0) return state.second_player_win; + return state.first_player_win; + } + + bool connect4::draw(std::uint8_t player) const + { + if (state.first_player_win || state.second_player_win) return false; + return state.total_moves == 42; + } + + uint8_t connect4::current_player() const + { + return state.total_moves & 1 ? CIRCLE : CROSS; + } + + // uint8_t connect4::current_player_representation() const + // { + // return state.total_moves & 1 ? CIRCLE_REPRESENTATION : CROSS_REPRESENTATION; + // } + + // uint8_t connect4::player_representation_to_player(uint8_t player) const + // { + // return player == CIRCLE_REPRESENTATION ? CIRCLE : (player == CROSS_REPRESENTATION ? CROSS : -1); + // } + + int connect4::value(uint8_t player) const + { + if (player == CROSS) + { + return state.first_player_win ? 1 : (state.second_player_win ? -1 : 0); + } + else if (player == CIRCLE) + { + return state.second_player_win ? 1 : (state.first_player_win ? -1 : 0); + } + return 0; + } + + uint16_t connect4::number_of_moves() const + { + return state.nb_moves; + } + + bool connect4::get(uint64_t bitboard, uint8_t col, uint8_t row) const + { + return bitboard & (1LL << ((col << 3) + row)); + //return (state.board[i] >> (j << 1)) & 3; + } + +#define set(bitboard, col, row) (bitboard |= (1LL << (((col) << 3) + (row)))) + + // bool connect4::vertical(uint8_t position, uint8_t free, uint8_t player) const + // { + // return free >= 3 && get(position, free - 1) == player && get(position, free - 2) == player && get(position, free - 3) == player; + // } + + // bool connect4::horizontal(uint8_t position, uint8_t free, uint8_t player) const + // { + // uint8_t sum = 0; + // if (position >= 1 && get(position - 1, free) == player) + // { + // ++sum; + // if (position >= 2 && get(position - 2, free) == player) + // { + // ++sum; + // if (position >= 3 && get(position - 3, free) == player) ++sum; + // } + // } + // if (position <= 5 && get(position + 1, free) == player) + // { + // ++sum; + // if (position <= 4 && get(position + 2, free) == player) + // { + // ++sum; + // if (position <= 3 && get(position + 3, free) == player) ++sum; + // } + // } + // return sum >= 3; + // } + + // bool connect4::diagonal(uint8_t position, uint8_t free, uint8_t player) const + // { + // uint8_t sum = 0; + // if (position >= 1 && free <= 4 && get(position - 1, free + 1) == player) + // { + // ++sum; + // if (position >= 2 && free <= 3 && get(position - 2, free + 2) == player) + // { + // ++sum; + // if (position >= 3 && free <= 2 && get(position - 3, free + 3) == player) ++sum; + // } + // } + // if (position <= 5 && free >= 1 && get(position + 1, free - 1) == player) + // { + // ++sum; + // if (position <= 4 && free >= 2 && get(position + 2, free - 2) == player) + // { + // ++sum; + // if (position <= 3 && free >= 3 && get(position + 3, free - 3) == player) ++sum; + // } + // } + // return sum >= 3; + // } + + // bool connect4::other_diagonal(uint8_t position, uint8_t free, uint8_t player) const + // { + // uint8_t sum = 0; + // if (position >= 1 && free >= 1 && get(position - 1, free - 1) == player) + // { + // ++sum; + // if (position >= 2 && free >= 2 && get(position - 2, free - 2) == player) + // { + // ++sum; + // if (position >= 3 && free >= 3 && get(position - 3, free - 3) == player) ++sum; + // } + // } + // if (position <= 5 && free <= 4 && get(position + 1, free + 1) == player) + // { + // ++sum; + // if (position <= 4 && free <= 3 && get(position + 2, free + 2) == player) + // { + // ++sum; + // if (position <= 3 && free <= 2 && get(position + 3, free + 3) == player) ++sum; + // } + // } + // return sum >= 3; + // } + + // void connect4::update_win(uint8_t position, uint8_t free) + // { + // uint8_t player = current_player_representation(); + // bool win = + // vertical(position, free, player) + // || + // horizontal(position, free, player) + // || + // diagonal(position, free, player) + // || + // other_diagonal(position, free, player); + // if (win) + // { + // if (player == CROSS_REPRESENTATION) state.first_player_win = true; + // else state.second_player_win = true; + // } + // } + + void connect4::update_win() + { + if (has_won(state.cross_bitboard)) state.first_player_win = true; + else if (has_won(state.circle_bitboard)) state.second_player_win = true; + } + + bool connect4::has_won(uint64_t bitboard) + { + int64_t y = bitboard & (bitboard >> 7); + if (y & (y >> 2 * 7)) // check \ diagonal + return true; + y = bitboard & (bitboard >> 8); + if (y & (y >> 2 * 8)) // check horizontal - + return true; + y = bitboard & (bitboard >> 9); + if (y & (y >> 2 * 9)) // check / diagonal + return true; + y = bitboard & (bitboard >> 1); + if (y & (y >> 2)) // check vertical | + return true; + return false; + } + + void connect4::update_moves(uint16_t move) + { + --state.nb_moves; + uint32_t prefix = state.moves >> ((move + 1) * 3); + uint8_t shift = 32 - move * 3; + state.moves = (uint32_t)((uint64_t)state.moves << shift) >> shift; + state.moves |= prefix << (move * 3); + } + + void connect4::play(uint16_t m) + { + uint8_t position = ((state.moves >> (m * 3)) & 7); + uint8_t p = position * 3; + uint8_t f = (state.free >> p) & 7; + // state.hash_value ^= current_player() == CIRCLE ? circle_hash_values[position][f] : cross_hash_values[position][f]; + // state.board[position] |= current_player_representation() << (f << 1); + if (current_player() == CROSS) + { + set(state.cross_bitboard, position, f); + } + else + { + set(state.circle_bitboard, position, f); + } + update_win(); + ++f; + state.free = (state.free & ~(((uint32_t)7) << p)) | (f << p); + if (f == 6) + { + update_moves(m); + } + ++state.total_moves; + } + + string connect4::player_to_string(uint8_t player) const + { + return player == CROSS ? "X" : (player == CIRCLE ? "O" : " "); + } + + string connect4::move_to_string(uint16_t m) const + { + uint8_t position = ((state.moves >> (m * 3)) & 7); + return std::to_string(position); + } + + set<int> connect4::to_input_vector() const + { + set<int> res; + int k = 0; + if (current_player()) res.insert(k++); + for (int col = 0; col < 7; ++col) + { + for (int row = 0; row < 6; ++row) + { + if (get(state.cross_bitboard, col, row)) res.insert(k++); + } + } + for (int col = 0; col < 7; ++col) + { + for (int row = 0; row < 6; ++row) + { + if (get(state.circle_bitboard, col, row)) res.insert(k++); + } + } + return res; + } + + void connect4::from_input_vector(const std::set<int>& input) + { + state = connect4_state(); + for (int index : input) + { + if (index == 1) continue; + if (index <= 43) + { + index -= 2; + state.cross_bitboard |= 1LL << (index + (index / 6) * 2); + } + else + { + index -= 44; + state.circle_bitboard |= 1LL << (index + (index / 6) * 2); + } + state.total_moves += 1; + } + } + + string connect4::to_string() const + { + stringbuf buffer; + ostream os(&buffer); + for (int y = 5; y >= 0; --y) + { + for (int k = 0; k < 7; ++k) os << "+-"; + os << "+" << endl; + for (int x = 0; x < 7; ++x) + { + os << "|" << (get(state.cross_bitboard, x, y) ? player_to_string(CROSS) : (get(state.circle_bitboard, x, y) ? player_to_string(CIRCLE) : " ")); + } + os << "|" << endl; + } + for (int k = 0; k < 7; ++k) os << "+-"; + os << "+" << endl; + for (int k = 0; k < 7; ++k) os << " " << k; + os << endl; + return buffer.str(); + } + + void connect4::playout(mt19937& engine, int max_depth) + { + while (!end_of_game()) + { + uniform_int_distribution<uint16_t> distribution(0, number_of_moves() - 1); + uint16_t move = distribution(engine); + play(move); + } + } + + std::uint64_t connect4::hash() const + { + return state.hash_value; + } + + std::uint64_t connect4::hash(std::uint16_t m) const + { + uint8_t position = ((state.moves >> (m * 3)) & 7); + uint8_t p = position * 3; + uint8_t f = (state.free >> p) & 7; + return state.hash_value ^ (current_player() == CIRCLE ? circle_hash_values[position][f] : cross_hash_values[position][f]); + } + + ostream& operator<<(ostream& os, const connect4& c4) + { + os << c4.to_string() << endl; + return os; + } +} diff --git a/src/game/connect4.hpp b/src/game/connect4.hpp new file mode 100644 index 0000000000000000000000000000000000000000..369bd8ecdd43c73ba7a4ca1da790885c023dde40 --- /dev/null +++ b/src/game/connect4.hpp @@ -0,0 +1,79 @@ +#ifndef __CONNECT4_HPP__ +#define __CONNECT4_HPP__ + +#include "game.hpp" +#include <random> +#include <array> +#include <iostream> +#include <memory> + +namespace game +{ + + struct connect4_state + { + uint64_t cross_bitboard = 0; + uint64_t circle_bitboard = 0; + // std::array<uint16_t, 7> board{}; + std::uint32_t moves = 0x1AC688; + std::uint32_t free = 0; + uint64_t hash_value = 0; + std::uint16_t nb_moves = 7; + uint8_t total_moves = 0; + bool first_player_win = false; + bool second_player_win = false; + }; + + class connect4 : public game<connect4_state> + { + public: + connect4(); + connect4(const connect4& c4) = default; + connect4& operator=(const connect4& c4) = default; + bool end_of_game() const; + int value(std::uint8_t player) const; + bool won(std::uint8_t player) const; + bool lost(std::uint8_t player) const; + bool draw(std::uint8_t player) const; + uint8_t current_player() const; + std::uint16_t number_of_moves() const; + void play(std::uint16_t m); + void undo(std::uint16_t m) {} + std::string player_to_string(std::uint8_t player) const; + std::string move_to_string(std::uint16_t m) const; + std::string to_string() const; + void playout(std::mt19937& engine, int max_depth = -1); + std::set<int> to_input_vector() const; + void from_input_vector(const std::set<int>& input); + connect4_state get_state(); + void set_state(const connect4_state& state); + std::shared_ptr<game<connect4_state>> do_copy() const; + std::uint64_t hash(std::uint16_t m) const; + std::uint64_t hash() const; + + private: + inline void update_win(); + inline bool has_won(uint64_t bitboard); + inline void update_moves(uint16_t move); + inline bool get(uint64_t bitboard, uint8_t i, uint8_t j) const; + // inline bool vertical(uint8_t position, uint8_t free, uint8_t player) const; + // inline bool horizontal(uint8_t position, uint8_t free, uint8_t player) const; + // inline bool diagonal(uint8_t position, uint8_t free, uint8_t player) const; + // inline bool other_diagonal(uint8_t position, uint8_t free, uint8_t player) const; + // inline uint8_t current_player_representation() const; + // inline uint8_t player_representation_to_player(uint8_t player) const; + + const uint8_t CROSS = 0; + const uint8_t CIRCLE = 1; + // const uint8_t CROSS_REPRESENTATION = 1; + // const uint8_t CIRCLE_REPRESENTATION = 2; + + connect4_state state; + + static std::vector<std::vector<uint64_t>> cross_hash_values; + static std::vector<std::vector<uint64_t>> circle_hash_values; + }; + std::ostream& operator<<(std::ostream& os, const connect4& c4); +} + +#endif diff --git a/src/game/game.hpp b/src/game/game.hpp new file mode 100644 index 0000000000000000000000000000000000000000..20f5e2f1938ca0d3a1eb50e4c97736e851189ae0 --- /dev/null +++ b/src/game/game.hpp @@ -0,0 +1,48 @@ +#ifndef __GAME_HPP__ +#define __GAME_HPP__ + +#include <string> +#include <cstdint> +#include <random> +#include <memory> +#include <set> + +namespace game +{ + template <typename State> + struct game + { + virtual bool end_of_game() const = 0; + virtual int value(std::uint8_t player) const = 0; + int value_for_current_player() const; + virtual bool won(std::uint8_t player) const = 0; + virtual bool lost(std::uint8_t player) const = 0; + virtual bool draw(std::uint8_t player) const = 0; + virtual uint8_t current_player() const = 0; + virtual std::uint16_t number_of_moves() const = 0; + virtual void play(std::uint16_t m) = 0; + virtual void undo(std::uint16_t m) = 0; + virtual std::string player_to_string(std::uint8_t player) const = 0; + virtual std::string move_to_string(std::uint16_t m) const = 0; + virtual std::string to_string() const = 0; + virtual void playout(std::mt19937& engine, int max_depth = -1) = 0; + virtual std::set<int> to_input_vector() const = 0; + virtual void from_input_vector(const std::set<int>& input) = 0; + virtual State get_state() = 0; + virtual void set_state(const State& state) = 0; + virtual std::shared_ptr<game<State>> do_copy() const = 0; + virtual std::uint64_t hash(std::uint16_t m) const = 0; + virtual std::uint64_t hash() const = 0; + virtual ~game() {} + }; + template<typename Game> std::shared_ptr<Game> copy(const Game& g) + { + return std::dynamic_pointer_cast<Game>(g.do_copy()); + } + template<typename State> int game<State>::value_for_current_player() const + { + return value(current_player()); + } +} + +#endif diff --git a/src/game/lines_of_action.cpp b/src/game/lines_of_action.cpp new file mode 100644 index 0000000000000000000000000000000000000000..48120d0708552a0f959b7e42cd0c65d5c3e50657 --- /dev/null +++ b/src/game/lines_of_action.cpp @@ -0,0 +1,214 @@ +#include "lines_of_action.hpp" +#include <sstream> + +using namespace std; + +namespace game +{ + static vector<vector<uint64_t>> create_hash_values() + { + default_random_engine generator; + uniform_int_distribution<uint64_t> distribution; + vector<vector<uint64_t>> res(8, vector<uint64_t>(8, 0)); + for (int i = 0; i < 8; ++i) + { + for (int j = 0; j < 8; ++j) + { + res[i][j] = distribution(generator); + } + } + return res; + } + + vector<vector<uint64_t>> lines_of_action::black_hash_values = create_hash_values(); + vector<vector<uint64_t>> lines_of_action::white_hash_values = create_hash_values(); + + shared_ptr<game<lines_of_action_state>> lines_of_action::do_copy() const + { + return shared_ptr<lines_of_action>(new lines_of_action(*this)); + } + + lines_of_action_state lines_of_action::get_state() + { + return state; + } + + void lines_of_action::set_state(const lines_of_action_state& s) + { + state = s; + } + + bool lines_of_action::end_of_game() const + { + return state.first_player_win || state.second_player_win; + } + + bool lines_of_action::won(std::uint8_t player) const + { + if (player == 0) return state.first_player_win; + return state.second_player_win; + } + + bool lines_of_action::lost(std::uint8_t player) const + { + if (player == 0) return state.second_player_win; + return state.first_player_win; + } + + bool lines_of_action::draw(std::uint8_t player) const + { + return false; + } + + uint8_t lines_of_action::current_player() const + { + return state.depth & 1 ? WHITE : BLACK; + } + + int lines_of_action::value(uint8_t player) const + { + if (player == BLACK) + { + return state.first_player_win ? 1 : (state.second_player_win ? -1 : 0); + } + else if (player == WHITE) + { + return state.second_player_win ? 1 : (state.first_player_win ? -1 : 0); + } + return 0; + } + + uint16_t lines_of_action::number_of_moves() const + { + return state.nb_moves; + } + +#define coord2bit(i, j) (1LL << (((i)<<3) + (j))) + + uint8_t lines_of_action::get(uint8_t i, uint8_t j) const + { + if (state.black_bitboard & coord2bit(i, j)) return BLACK; + if (state.white_bitboard & coord2bit(i, j)) return WHITE; + return EMPTY; + } + +#define is_set(bitboard, i, j) (bitboard & coord2bit(i, j)) + + bool lines_of_action::is_valid_row_right(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const + { + uint8_t dest_j = from_j + n; + if (dest_j > 7) return false; + if (is_set(bitboard1, from_i, dest_j)) return false; + for (int j = from_j + 1; j < dest_j; ++j) + { + if (is_set(bitboard2, from_i, j)) return false; + } + return true; + } + + bool lines_of_action::is_valid_row_left(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const + { + if (n > from_j) return false; + uint8_t dest_j = from_j - n; + if (is_set(bitboard1, from_i, dest_j)) return false; + for (int j = from_j - 1; j > dest_j; --j) + { + if (is_set(bitboard2, from_i, j)) return false; + } + return true; + } + + bool lines_of_action::is_valid_column_up(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const + { + if (n > from_i) return false; + uint8_t dest_i = from_i - n; + if (is_set(bitboard1, dest_i, from_j)) return false; + for (int i = from_i - 1; i > dest_i; --i) + { + if (is_set(bitboard2, i, from_j)) return false; + } + return true; + } + + bool lines_of_action::is_valid_column_down(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const + { + uint8_t dest_i = from_i + n; + if (dest_i > 7) return false; + if (is_set(bitboard1, dest_i, from_j)) return false; + for (int i = from_i + 1; i < dest_i; ++i) + { + if (is_set(bitboard2, i, from_j)) return false; + } + return true; + } + + void lines_of_action::update_state(const move& m) + { + + } + + void lines_of_action::play(uint16_t m) + { + update_state(move_index_to_move(m)); + } + + string lines_of_action::player_to_string(uint8_t player) const + { + return player == BLACK ? "@" : (player == WHITE ? "O" : " "); + } + + string lines_of_action::move_to_string(uint16_t m) const + { + return move_index_to_move(m).to_string(); + } + + string lines_of_action::to_string() const + { + stringbuf buffer; + ostream os(&buffer); + os << " a b c d e f g h" << endl; + for (int i = 0; i < 8; ++i) + { + os << " "; + for (int k = 0; k < 8; ++k) os << "+-"; + os << "+" << endl; + os << 8 - i << "|"; + for (int j = 0; j < 8; ++j) + { + os << player_to_string(get(i, j)) << "|"; + } + os << 8 - i << endl; + } + os << " "; + for (int k = 0; k < 8; ++k) os << "+-"; + os << "+" << endl; + os << " a b c d e f g h" << endl; + return buffer.str(); + } + + void lines_of_action::playout(mt19937& engine, int max_depth) + { + while (state.depth <= max_depth && !end_of_game()) + { + uniform_int_distribution<uint16_t> distribution(0, number_of_moves() - 1); + uint16_t move = distribution(engine); + play(move); + } + } + + std::uint64_t lines_of_action::hash() const + { + return state.hash_value; + } + + std::uint64_t lines_of_action::hash(std::uint16_t m) const + { + return 0; + } + + ostream& operator<<(ostream& os, const lines_of_action& loa) + { + os << loa.to_string() << endl; + return os; + } +} diff --git a/src/game/lines_of_action.hpp b/src/game/lines_of_action.hpp new file mode 100644 index 0000000000000000000000000000000000000000..5138f44a97fcf2ac12c509b90c05c488c0f1ac31 --- /dev/null +++ b/src/game/lines_of_action.hpp @@ -0,0 +1,88 @@ +#ifndef __LINES_OF_ACTION_HPP__ +#define __LINES_OF_ACTION_HPP__ + +#include "game.hpp" +#include <random> +#include <array> +#include <vector> +#include <iostream> +#include <memory> + +namespace game +{ + + struct lines_of_action_state + { + uint64_t black_bitboard = 0x7E0000000000007E; + uint64_t white_bitboard = 0x0081818181818100; + uint32_t row_nb_pieces = 0x62222226; + uint32_t column_nb_pieces = 0x62222226; + uint64_t left_to_right_diagonal_nb_pieces = 0x0022222202222220; + uint64_t right_to_left_diagonal_nb_pieces = 0x0022222202222220; + uint64_t hash_value = 0; + uint16_t nb_moves = 48; + uint8_t black_nb_pieces = 12; + uint8_t white_nb_pieces = 12; + uint8_t depth = 0; + bool first_player_win = false; + bool second_player_win = false; + }; + + class lines_of_action : public game<lines_of_action_state> + { + public: + lines_of_action() = default; + lines_of_action(const lines_of_action& loa) = default; + lines_of_action& operator=(const lines_of_action& loa) = default; + bool end_of_game() const; + int value(std::uint8_t player) const; + bool won(std::uint8_t player) const; + bool lost(std::uint8_t player) const; + bool draw(std::uint8_t player) const; + uint8_t current_player() const; + std::uint16_t number_of_moves() const; + void play(std::uint16_t m); + void undo(std::uint16_t m) {} + std::string player_to_string(std::uint8_t player) const; + std::string move_to_string(std::uint16_t m) const; + std::string to_string() const; + void playout(std::mt19937& engine, int max_depth = -1); + lines_of_action_state get_state(); + void set_state(const lines_of_action_state& state); + std::shared_ptr<game<lines_of_action_state>> do_copy() const; + std::uint64_t hash(std::uint16_t m) const; + std::uint64_t hash() const; + private: + const uint8_t BLACK = 0; + const uint8_t WHITE = 1; + const uint8_t EMPTY = 2; + struct move + { + uint8_t from_i, from_j; + uint8_t to_i, to_j; + move(uint8_t fi, uint8_t fj, uint8_t ti, uint8_t tj) : from_i(fi), from_j(fj), to_i(ti), to_j(tj) {} + std::string to_string() const + { + return char('a' + from_j) + std::to_string(8 - from_i) + "-" + + char('a' + to_j) + std::to_string(8 - to_i); + } + }; + lines_of_action_state state; + inline uint8_t get(uint8_t i, uint8_t j) const; + inline bool is_valid_row_right(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const; + inline bool is_valid_row_left(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const; + inline bool is_valid_column_up(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const; + inline bool is_valid_column_down(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const; + inline bool is_valid_left_to_right_diagonal_up(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const; + inline bool is_valid_left_to_right_diagonal_down(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const; + inline bool is_valid_right_to_left_diagonal_up(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const; + inline bool is_valid_right_to_left_diagonal_down(uint64_t bitboard1, uint64_t bitboard2, uint8_t from_i, uint8_t from_j, uint8_t n) const; + move move_index_to_move(uint16_t m) const; + void update_state(const move& m); + static std::vector<std::vector<uint64_t>> black_hash_values; + static std::vector<std::vector<uint64_t>> white_hash_values; + }; + std::ostream& operator<<(std::ostream& os, const lines_of_action& loa); +} + +#endif diff --git a/src/game/test_connect4.cpp b/src/game/test_connect4.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4269f324270374010b23011dd3450e2c899f543b --- /dev/null +++ b/src/game/test_connect4.cpp @@ -0,0 +1,58 @@ +#include "connect4.hpp" +#include "test_connect4.hpp" +#include <iostream> +#include <map> + +using namespace std; + +namespace game +{ + test_connect4::test_connect4() + { + playout(); + play(); + } + + void test_connect4::playout() + { + mt19937 mt; + connect4 c4; + cout << "playout" << endl; + for (int i = 0; i < 100; ++i) + { + uint8_t player = c4.current_player(); + auto state = c4.get_state(); + c4.playout(mt); + cout << "value: " << c4.value(player) << endl << c4 << endl; + c4.set_state(state); + string wait; + getline(cin, wait); + } + } + + void test_connect4::play() + { + connect4 c4; + int player = 0; + cout << "play one game" << endl; + while (!c4.end_of_game()) + { + cout << c4 << endl; + cout << c4.player_to_string(player) << " move: "; + map<string, int> m; + for (int i = 0; i < c4.number_of_moves(); ++i) + { + m[c4.move_to_string(i)] = i; + } + string move; + cin >> move; + c4.play(m[move]); + player = 1 - player; + } + cout << c4 << endl; + if (c4.value(0) == 1) cout << c4.player_to_string(0) << " won"; + else if (c4.value(1) == 1) cout << c4.player_to_string(1) << " won"; + else cout << "draw"; + cout << endl; + } +} diff --git a/src/game/test_connect4.hpp b/src/game/test_connect4.hpp new file mode 100644 index 0000000000000000000000000000000000000000..1ecee241bf2950e5217d808856eb76ac1689601a --- /dev/null +++ b/src/game/test_connect4.hpp @@ -0,0 +1,14 @@ +#ifndef __TEST_CONNECT4_HPP__ +#define __TEST_CONNECT4_HPP__ + +namespace game +{ + class test_connect4 + { + void playout(); + void play(); + public: + test_connect4(); + }; +} +#endif diff --git a/src/game/test_lines_of_action.cpp b/src/game/test_lines_of_action.cpp new file mode 100644 index 0000000000000000000000000000000000000000..70347d1099b3f6b3a60fd8e25318c1db139c3688 --- /dev/null +++ b/src/game/test_lines_of_action.cpp @@ -0,0 +1,38 @@ +#include "test_lines_of_action.hpp" +#include <iostream> +#include <map> + +using namespace std; + +namespace game +{ + test_lines_of_action::test_lines_of_action() + { + play(); + } + + void test_lines_of_action::play() + { + lines_of_action loa; + int player = 0; + cout << "play one game" << endl; + while (!loa.end_of_game()) + { + cout << loa << endl; + cout << loa.player_to_string(player) << " move: "; + map<string, int> m; + for (int i = 0; i < loa.number_of_moves(); ++i) + { + m[loa.move_to_string(i)] = i; + } + string move; + cin >> move; + loa.play(m[move]); + player = 1 - player; + } + cout << loa << endl; + if (loa.value(0) == 1) cout << loa.player_to_string(0) << " won"; + cout << loa.player_to_string(1) << " won"; + cout << endl; + } +} diff --git a/src/game/test_lines_of_action.hpp b/src/game/test_lines_of_action.hpp new file mode 100644 index 0000000000000000000000000000000000000000..28f4be6106fd232dc9ad0e3cf4ea4f01ad731553 --- /dev/null +++ b/src/game/test_lines_of_action.hpp @@ -0,0 +1,15 @@ +#ifndef __TEST_LINES_OF_ACTION_HPP__ +#define __TEST_LINES_OF_ACTION_HPP__ + +#include "lines_of_action.hpp" + +namespace game +{ + class test_lines_of_action + { + void play(); + public: + test_lines_of_action(); + }; +} +#endif diff --git a/src/main/main.cpp b/src/main/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5451eeb95b87ee841a20052a3f86ec3b07615e54 --- /dev/null +++ b/src/main/main.cpp @@ -0,0 +1,26 @@ +#include "test_connect4.hpp" +#include "test_monte_carlo.hpp" +#include "test_fast_log.hpp" +#include "test_allocator.hpp" +#include "test_mcts_two_players.hpp" +#include "test_minmax.hpp" +#include "test_bits.hpp" +#include "learning.hpp" + +#include <omp.h> + +using namespace std; + +int main(int argc, char *argv[]) +{ + // game::test_connect4(); + // util::test_fast_log(100000000); + // mcts::test_allocator(10, 2); + // omp_set_num_threads(8); + mcts::test_mcts_two_players(); + // minmax::test_minmax(); + //util::test_bits(200000000); + //game::connect4 c4; + // util::learning::display_file(c4, "learning_examples.txt"); + return 0; +} diff --git a/src/mcts/allocator.cpp b/src/mcts/allocator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3dc230918912a87494e93c83bfaf910be9bb0593 --- /dev/null +++ b/src/mcts/allocator.cpp @@ -0,0 +1,84 @@ +#include "allocator.hpp" +#include <iostream> +#include <cstring> + +namespace mcts +{ + allocator::allocator(unsigned int size) + { + std::cout << "allocator initialization..." << std::endl; + node_arena = new node[size]; + std::cout << "allocator initialization done" << std::endl; + limit = node_arena + size; + free_pointer = node_arena; + } + + allocator::~allocator() + { + delete[] node_arena; + } + + node* allocator::allocate(unsigned int size) + { + node* n; +#pragma GCC diagnostic ignored "-Wunused-value" +#pragma omp atomic capture + { + n = free_pointer; + free_pointer += size; + } + memset(n, 0, sizeof(node) * size); + if (n + size >= limit) throw "allocate_children: not enough memory"; + return n; + } + + unsigned int allocator::size() const + { + return limit - node_arena; + } + + unsigned int allocator::free_space() const + { + return limit - free_pointer; + } + + void allocator::clear() + { + free_pointer = node_arena; + } + + node* allocator::allocate_unsafe(unsigned int size) + { + node* n = free_pointer; + free_pointer += size; + memset(n, 0, sizeof(node) * size); + if (n + size > limit) throw "allocate_children: not enough memory"; + return n; + } + + void allocator::copy(node* n1, node* n2, unsigned int prunning) + { + if (n1->get_statistics().count < prunning) return; + n2->set_statistics(n1->get_statistics()); + unsigned int nb_children = n1->get_number_of_children(); + n2->set_number_of_children(nb_children); + if (nb_children == 0) return; + n2->set_children(allocate_unsafe(nb_children)); + node* children1 = n1->get_children(); + node* children2 = n2->get_children(); + for (unsigned int i = 0; i < nb_children; ++i) + { + copy(children1 + i, children2 + i, prunning); + } + } + + node* allocator::move(node* root, unsigned int prunning) + { + node* r = allocate_unsafe(1); + copy(root, r, prunning); + free_pointer = node_arena; + node* res = allocate_unsafe(1); + copy(r, res); + return res; + } +} diff --git a/src/mcts/allocator.hpp b/src/mcts/allocator.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9ee278779ec061547a6383404507d7e29bb0a1ec --- /dev/null +++ b/src/mcts/allocator.hpp @@ -0,0 +1,28 @@ +#ifndef __ALLOCATOR_HPP__ +#define __ALLOCATOR_HPP__ + +#include "node.hpp" + +namespace mcts +{ + class allocator + { + node* node_arena; + node* limit; + node* free_pointer; + + node* allocate_unsafe(unsigned int size); + void copy(node* n1, node* n2, unsigned int prunning = 0); + + public: + allocator(unsigned int size = 20000000U); + ~allocator(); + node* allocate(unsigned int size); + void clear(); + node* move(node* root, unsigned int prunning = 0); + unsigned int size() const; + unsigned int free_space() const; + }; +} + +#endif diff --git a/src/mcts/allocator.hpp~ b/src/mcts/allocator.hpp~ new file mode 100644 index 0000000000000000000000000000000000000000..150a2ffc1d220711051c77e21fe5d33779f145fa --- /dev/null +++ b/src/mcts/allocator.hpp~ @@ -0,0 +1,28 @@ +#ifndef __ALLOCATOR_HPP__ +#define __ALLOCATOR_HPP__ + +#include "node.hpp" + +namespace mcts +{ + class allocator + { + node* node_arena; + node* limit; + node* free_pointer; + + node* allocate_unsafe(unsigned int size); + void copy(node* n1, node* n2, unsigned int prunning = 0); + + public: + allocator(unsigned int size = 1000000U); + ~allocator(); + node* allocate(unsigned int size); + void clear(); + node* move(node* root, unsigned int prunning = 0); + unsigned int size() const; + unsigned int free_space() const; + }; +} + +#endif diff --git a/src/mcts/mcts.hpp b/src/mcts/mcts.hpp new file mode 100644 index 0000000000000000000000000000000000000000..66865d168a11e22a6471d3d106c1d9c7aec4b41c --- /dev/null +++ b/src/mcts/mcts.hpp @@ -0,0 +1,31 @@ +#ifndef __MCTS_HPP__ +#define __MCTS_HPP__ + +#include <random> +#include <chrono> +#include <vector> +#include "game.hpp" +#include "node.hpp" + +namespace mcts +{ + template <typename Game> + class mcts + { + protected: + Game& game; + std::chrono::milliseconds milliseconds; + std::vector<std::mt19937> generators; + node* root; + + public: + mcts(Game& game, uint32_t milliseconds) : game(game), milliseconds(milliseconds) {} + + virtual void reset() = 0; + virtual uint16_t select_move() = 0; + virtual void last_moves(uint16_t m1, uint16_t m2) {} + virtual void last_moves(const std::vector<uint16_t>& moves) {} + }; +} + +#endif diff --git a/src/mcts/mcts_two_players.cpp b/src/mcts/mcts_two_players.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f3e31c058085b8ece508e7cdf9eecad97621137a --- /dev/null +++ b/src/mcts/mcts_two_players.cpp @@ -0,0 +1,7 @@ +#include "mcts_two_players.hpp" + +using namespace std; + +namespace mcts +{ +} diff --git a/src/mcts/mcts_two_players.hpp b/src/mcts/mcts_two_players.hpp new file mode 100644 index 0000000000000000000000000000000000000000..29e4e96abf3daa5f696c514780f86da7919d9652 --- /dev/null +++ b/src/mcts/mcts_two_players.hpp @@ -0,0 +1,274 @@ +#ifndef __MCTS_TWO_PLAYERS_HPP__ +#define __MCTS_TWO_PLAYERS_HPP__ + +#include "mcts.hpp" +#include "allocator.hpp" +#include "fast_log.hpp" +#include "omp_util.hpp" +#include "display_node.hpp" +#include <fstream> +#include <vector> +#include "openings.hpp" + +namespace mcts +{ + template <typename Game> + class mcts_two_players : public mcts<Game> + { + allocator alloc_; + const util::fast_log fast_log_; + const float C_; + const unsigned int nb_visits_before_expansion_; + const bool new_version_; + + inline node* select(const std::shared_ptr<Game>& game, std::mt19937& generator, node* parent); + inline void expand(const std::shared_ptr<Game>& game, node* n); + void think(const std::shared_ptr<Game>& game); + public: + mcts_two_players(Game& game, uint32_t milliseconds, float C, unsigned int nb_visits_before_expansion = 8, bool new_version = true); + void reset(); + void init_with_openings(const openings& o); + inline uint16_t select_move(); + void last_move(uint16_t move); + void last_moves(uint16_t computer, uint16_t other); + }; + + template <typename Game> + mcts_two_players<Game> make_mcts_two_players(Game& game, uint32_t milliseconds, float C, unsigned int nb_visits_before_expansion = 8, bool new_version = true) + { + return mcts_two_players<Game>(game, milliseconds, C, nb_visits_before_expansion, new_version); + } + + template <typename Game> + mcts_two_players<Game>::mcts_two_players(Game& game, uint32_t milliseconds, float C, unsigned int nb_visits_before_expansion, bool new_version) + : mcts<Game>(game, milliseconds), C_(C), nb_visits_before_expansion_(nb_visits_before_expansion), new_version_(new_version) + { + this->generators.assign(util::omp_util::get_num_threads(), std::mt19937()); + this->root = alloc_.allocate(1); + } + + template <typename Game> + void mcts_two_players<Game>::reset() + { + alloc_.clear(); + this->root = alloc_.allocate(1); + } + + template <typename Game> + void mcts_two_players<Game>::init_with_openings(const openings& o) + { + o.copy_to(this->root, alloc_); + } + + template <typename Game> + node* mcts_two_players<Game>::select(const std::shared_ptr<Game>& game, std::mt19937& generator, node* parent) + { + using namespace std; + const unsigned int N = parent->get_statistics().count; + const float log_of_N = fast_log_.log(N); + const uint16_t nb_children = parent->get_number_of_children(); + uniform_int_distribution<uint16_t> d(0, nb_children - 1); + uint16_t k = d(generator); + double best_value_so_far = numeric_limits<double>::lowest(); + uint16_t best_move_so_far = k; + node* const children = parent->get_children(); + node* best_child_so_far = children + k; + unsigned int count; + float v; + for (uint16_t i = 0; i < nb_children; ++i) + { + node* child = children + k; + count = child->get_statistics().count; + v = -child->get_statistics().value + C_ * sqrt(log_of_N / count); + if (v > best_value_so_far) + { + best_value_so_far = v; + best_child_so_far = child; + best_move_so_far = k; + } + ++k; + if (k == nb_children) k = 0; + } + if (best_child_so_far->is_proven()) + { + if (best_child_so_far->is_lost()) parent->set_won(); + else + { + bool all_won = true; + for (uint16_t i = 0; i < nb_children; ++i) + { + node* child = children + i; + if (!child->is_won()) + { + all_won = false; + break; + } + } + if (all_won) parent->set_lost(); + } + } + game->play(best_move_so_far); + return best_child_so_far; + } + + template <typename Game> + void mcts_two_players<Game>::expand(const std::shared_ptr<Game>& game, node* n) + { + unsigned int count = n->get_statistics().count; + if (count >= nb_visits_before_expansion_ && !n->test_and_set()) + { + unsigned int nb_children = game->number_of_moves(); + node* children = alloc_.allocate(nb_children); + for (unsigned int i = 0; i < nb_children; ++i) + { + node* child = children + i; + child->get_statistics_ref().count = 1; + child->get_statistics_ref().value = 0; + } + n->set_children(children); + n->set_number_of_children(nb_children); + } + } + + template <typename Game> + void mcts_two_players<Game>::think(const std::shared_ptr<Game>& game) + { + using namespace std; + const chrono::steady_clock::time_point start = chrono::steady_clock::now(); + chrono::steady_clock::time_point now; + mt19937& generator = mcts<Game>::generators[util::omp_util::get_thread_num()]; + auto state = game->get_state(); + vector<node*> visited(200); + vector<uint16_t> moves(200); + unsigned int nb_iter = 0; + do + { + int size = 1; + node* current = this->root; + visited[0] = current; + while (!game->end_of_game() && !current->is_leaf() && !current->is_proven()) + { + current = select(game, generator, current); + visited[size++] = current; + } + int game_value = 0; + if (current->is_proven()) + { + if (current->is_won()) game_value = 1; + else + { + game_value = -1; + } + } + else if (game->end_of_game()) + { + int v = game->value_for_current_player(); + if (v > 0) + { + game_value = 1; + if (new_version_) current->set_won(); + } + else if (v < 0) + { + game_value = -1; + if (new_version_) + { + current->set_lost(); + if (size > 1) visited[size - 2]->set_won(); + } + } + } + else + { + uint8_t player = game->current_player(); + expand(game, current); + game->playout(generator); + int v = game->value(player); + if (v > 0) game_value = 1; + else if (v < 0) game_value = -1; + } + for (int i = size - 1; i >= 0; --i) + { + visited[i]->update(game_value); + game_value = -game_value; + } + game->set_state(state); + ++nb_iter; + if ((nb_iter & 0x3F) == 0) now = chrono::steady_clock::now(); + } + while ((nb_iter & 0x3F) != 0 || now < start + this->milliseconds); + } + + template <typename Game> + uint16_t mcts_two_players<Game>::select_move() + { + using namespace std; + if (!this->root->is_proven()) + { +#pragma omp parallel + think(game::copy(this->game)); + } + // std::ofstream ofs ("graph.gv", ofstream::out); + // util::display_node::node_to_dot(ofs, this->root, 1000, 50); + util::display_node::node_to_ascii(cout, this->root, 2); + // std::cout << "finished " << new_version_ << std::endl; + // string _; + // getline(cin, _); + unsigned int best_count_so_far = 0; + uint16_t nb_children = this->root->get_number_of_children(); + uniform_int_distribution<uint16_t> d(0, nb_children - 1); + uint16_t k = d(this->generators[0]); + uint16_t best_move_so_far = k; + node* children = this->root->get_children(); + unsigned int c; + for (uint16_t i = 0; i < nb_children; ++i) + { + node *child = children + k; + if (child->is_lost()) + { + best_move_so_far = k; + break; + } + c = children[k].get_statistics().count; + if (c > best_count_so_far) + { + best_count_so_far = c; + best_move_so_far = k; + } + ++k; + if (k == nb_children) k = 0; + } + return best_move_so_far; + } + + template <typename Game> + void mcts_two_players<Game>::last_moves(uint16_t computer, uint16_t other) + { + if (this->root->is_leaf() || this->root->get_children()[computer].is_leaf()) + { + alloc_.clear(); + this->root = alloc_.allocate(1); + } + else + { + this->root = alloc_.move(&this->root->get_children()[computer].get_children()[other]); + } + } + + template <typename Game> + void mcts_two_players<Game>::last_move(uint16_t move) + { + if (this->root->is_leaf()) + { + alloc_.clear(); + this->root = alloc_.allocate(1); + } + else + { + this->root = alloc_.move(&this->root->get_children()[move]); + } + } + +} + +#endif diff --git a/src/mcts/node.cpp b/src/mcts/node.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bc40789a0f24a01cf8960a9d3247a8069b4fa81f --- /dev/null +++ b/src/mcts/node.cpp @@ -0,0 +1,24 @@ +#include "node.hpp" +#include <sstream> +#include "display_node.hpp" + +using namespace std; + +namespace mcts +{ + string node::to_string() const + { + stringbuf buffer; + ostream os(&buffer); + util::display_node::node_to_ascii(os, this); + os << endl; + return buffer.str(); + } + + ostream& operator<<(ostream& os, const node& n) + { + util::display_node::node_to_ascii(os, &n); + os << endl; + return os; + } +} diff --git a/src/mcts/node.hpp b/src/mcts/node.hpp new file mode 100644 index 0000000000000000000000000000000000000000..591e6dfd4dfc41ad77b2e44d9bd8df25bb5e7c1d --- /dev/null +++ b/src/mcts/node.hpp @@ -0,0 +1,138 @@ +#ifndef __NODE_HPP__ +#define __NODE_HPP__ + +#include "statistics.hpp" +#include <string> +#include <iostream> +#include <limits> + +#define NODE_WON_VALUE 1e15 +#define NODE_LOST_VALUE -1e15 + +namespace mcts +{ + class node + { + statistics stats; + bool flag = false; + uint16_t number_of_children = 0; + node* children = nullptr; + + public: + inline uint16_t get_winning_index() const; + inline bool is_leaf() const; + inline uint16_t get_number_of_children() const; + inline node* get_children() const; + inline void set_number_of_children(uint16_t n); + inline void set_children(node* n); + inline void set_won(); + inline void set_lost(); + inline bool is_proven() const; + inline bool is_won() const; + inline bool is_lost() const; + inline const statistics& get_statistics() const; + inline statistics& get_statistics_ref(); + inline void set_statistics(const statistics& s); + inline bool test_and_set(); + inline void update(int value); + inline void update_count(); + std::string to_string() const; + friend std::ostream& operator<<(std::ostream& os, const node& n); + }; + + bool node::is_proven() const + { + return is_won() || is_lost(); + } + + void node::set_won() + { + stats.value = NODE_WON_VALUE; + } + + void node::set_lost() + { + stats.value = NODE_LOST_VALUE; + } + + bool node::is_won() const + { + return stats.value > 1.1; + } + + bool node::is_lost() const + { + return stats.value < -1.1; + } + + bool node::is_leaf() const + { + return get_number_of_children() == 0 || get_children() == nullptr; + } + + uint16_t node::get_number_of_children() const + { + uint16_t res; +#pragma omp atomic read + res = number_of_children; + return res; + } + node* node::get_children() const + { + node* res; +#pragma omp atomic read + res = children; + return res; + } + void node::set_number_of_children(uint16_t n) + { +#pragma omp atomic write + number_of_children = n; + } + void node::set_children(node* n) + { +#pragma omp atomic write + children = n; + } + const statistics& node::get_statistics() const + { + return stats; + } + statistics& node::get_statistics_ref() + { + return stats; + } + void node::set_statistics(const statistics& s) + { + stats = s; + } + bool node::test_and_set() + { + bool res; +#pragma GCC diagnostic ignored "-Wunused-value" +#pragma omp atomic capture + { + res = flag; + flag = true; + } + return res; + } + + void node::update_count() + { + ++stats.count; + } + + void node::update(int v) + { + unsigned int count = stats.count; + double value = stats.value; + ++count; + value += (v - value) / count; + stats.value = value; + stats.count = count; + } + +} + +#endif diff --git a/src/mcts/openings.cpp b/src/mcts/openings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e139c7d19db6a3639c45a2c523c1dadc50d5ecf2 --- /dev/null +++ b/src/mcts/openings.cpp @@ -0,0 +1,30 @@ +#include "openings.hpp" + +namespace mcts +{ + std::ostream& operator<<(std::ostream& os, const openings& op) + { + util::display_node::node_to_ascii(os, op.root_, 3); + return os; + } + + void openings::copy_to(node* root, allocator& alloc) const + { + copy(root_, root, alloc); + } + + void openings::copy(node* src, node* dst, allocator& alloc) const + { + dst->set_statistics(src->get_statistics()); + const unsigned int nb_children = src->get_number_of_children(); + dst->set_number_of_children(nb_children); + if (nb_children == 0) return; + dst->set_children(alloc.allocate(nb_children)); + node* const src_children = src->get_children(); + node* const dst_children = dst->get_children(); + for (unsigned int i = 0; i < nb_children; ++i) + { + copy(src_children + i, dst_children + i, alloc); + } + } +} diff --git a/src/mcts/openings.hpp b/src/mcts/openings.hpp new file mode 100644 index 0000000000000000000000000000000000000000..94e0c791e7670e520bdb9efbc74372ec4237e88e --- /dev/null +++ b/src/mcts/openings.hpp @@ -0,0 +1,115 @@ +#ifndef __OPENINGS_HPP__ +#define __OPENINGS_HPP__ + +#include "allocator.hpp" +#include <vector> +#include "display_node.hpp" + +namespace mcts +{ + class openings + { + allocator alloc_; + node* root_; + const unsigned int nb_visits_before_expansion_; + + void copy(node* src, node* dst, allocator& alloc) const; + template <typename Game> + void expand(Game& game, node* n, uint16_t move, int value); + public: + template <typename Game> + openings(const Game& game, unsigned int nb_visits_before_expansion = 2); + void copy_to(node* root, allocator& alloc) const; + template <typename Game> + void update(Game& game, const std::vector<uint16_t>& moves, int value); + friend std::ostream& operator<<(std::ostream& os, const openings& op); + }; + + template <typename Game> + openings::openings(const Game& game, unsigned int nb_visits_before_expansion) : nb_visits_before_expansion_(nb_visits_before_expansion) + { + root_ = alloc_.allocate(1); + unsigned int nb_children = game.number_of_moves(); + node* children = alloc_.allocate(nb_children); + for (unsigned int i = 0; i < nb_children; ++i) + { + node* child = children + i; + child->get_statistics_ref().count = 1; + child->get_statistics_ref().value = 0; + } + } + + template <typename Game> + void openings::expand(Game& game, node* n, uint16_t move, int value) + { + unsigned int count = n->get_statistics().count; + if (count >= nb_visits_before_expansion_) + { + unsigned int nb_children = game.number_of_moves(); + node* children = alloc_.allocate(nb_children); + for (unsigned int i = 0; i < nb_children; ++i) + { + node* child = children + i; + child->get_statistics_ref().count = 1; + child->get_statistics_ref().value = 0; + } + n->set_children(children); + n->set_number_of_children(nb_children); + children[move].update(value); + } + } + + template <typename Game> + void openings::update(Game& game, const std::vector<uint16_t>& moves, int value) + { + node* pred = nullptr; + node* current = root_; + int k = 0; + while (true) + { + current->update(value); + value = -value; + if (current->is_leaf()) break; + uint16_t m = moves[k++]; + game.play(m); + pred = current; + current = current->get_children() + m; + if (current->is_proven()) + { + if (current->is_lost()) pred->set_won(); + else + { + const uint16_t nb_children = pred->get_number_of_children(); + node* const children = pred->get_children(); + bool all_won = true; + for (uint16_t i = 0; i < nb_children; ++i) + { + node* child = children + i; + if (!child->is_won()) + { + all_won = false; + break; + } + } + if (all_won) pred->set_lost(); + } + return; + } + } + if (!game.end_of_game()) + { + expand(game, current, moves[k], value); + } + else + { + if (value == 1) current->set_won(); + else if (value == -1) + { + current->set_lost(); + if (pred != nullptr) pred->set_won(); + } + } + } +} + +#endif diff --git a/src/mcts/statistics.cpp b/src/mcts/statistics.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cafa2adac339ebe8362b206de8426ad4bde63a7f --- /dev/null +++ b/src/mcts/statistics.cpp @@ -0,0 +1,16 @@ +#include "statistics.hpp" +#include <sstream> +#include <iomanip> + +using namespace std; + +namespace mcts +{ + std::string statistics::to_string() const + { + stringbuf buffer; + ostream os(&buffer); + os << "(count: " << count << ", value: " << setprecision(2) << value << ")"; + return buffer.str(); + } +} diff --git a/src/mcts/statistics.hpp b/src/mcts/statistics.hpp new file mode 100644 index 0000000000000000000000000000000000000000..78dfa693946e7f46f184ad358c6cd85a4acaa0c8 --- /dev/null +++ b/src/mcts/statistics.hpp @@ -0,0 +1,16 @@ +#ifndef __STATISTICS_HPP__ +#define __STATISTICS_HPP__ + +#include <string> + +namespace mcts +{ + struct statistics + { + unsigned int count = 0; + float value = 0; + std::string to_string() const; + }; +} + +#endif diff --git a/src/mcts/test_allocator.cpp b/src/mcts/test_allocator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..98f70cd70841541e44b796bf4005ec89416ae72f --- /dev/null +++ b/src/mcts/test_allocator.cpp @@ -0,0 +1,109 @@ +#include "test_allocator.hpp" +#include <algorithm> +#include "pow.hpp" +#include <iostream> +//#include <thread> +#include <random> +#include "display_node.hpp" + +using namespace std; + +namespace mcts +{ + test_allocator::test_allocator(unsigned int depth, unsigned int nb_children) + { + allocate(depth, nb_children); + allocate_multi_threaded(depth, nb_children); + alloc.clear(); + allocate(depth, nb_children); + allocate_multi_threaded(depth, nb_children); + alloc.clear(); + node* root = alloc.allocate(1); + build(depth, nb_children, root); + cout << "size: " << alloc.size() << endl; + cout << "free space: " << alloc.free_space() << endl; + cout << root << endl << *root << endl; + util::display_node::node_to_dot(cout, root); + node* new_root = alloc.move(root); + cout << "free space: " << alloc.free_space() << endl; + cout << new_root << endl << *new_root << endl; + } + + void test_allocator::build(unsigned int depth, unsigned int nb_children, node* n) + { + uniform_int_distribution<int> dint(1, nb_children); + statistics& stats = n->get_statistics_ref(); + stats.count = dint(generator); + uniform_real_distribution<float> dfloat(-1, 1); + stats.value = dfloat(generator); + if (depth == 0) return; + node* children = alloc.allocate(nb_children); + n->set_children(children); + n->set_number_of_children(nb_children); + for (unsigned int i = 0; i < nb_children; ++i) + { + build(depth - 1, nb_children, children + i); + } + } + + void test_allocator::collect(vector<node*>& nodes, node* n) + { + if (n == nullptr) return; + nodes.push_back(n); + uint16_t nb_children = n->get_number_of_children(); + node* children = n->get_children(); + for (unsigned int i = 0; i < nb_children; ++i) + { + collect(nodes, children + i); + } + } + + bool test_allocator::check(vector<node*> nodes, unsigned int depth, unsigned int nb_children) + { + if ((int)nodes.size() != 1 + (util::pow_l(nb_children, depth + 1) - nb_children) / (nb_children - 1)) return false; + sort(nodes.begin(), nodes.end()); + for (unsigned int i = 1; i < nodes.size(); ++i) + { + if (nodes[i-1] + 1 != nodes[i]) return false; + } + return true; + } + + void test_allocator::test(node* root, unsigned int depth, unsigned int nb_children, const string& msg) + { + vector<node*> nodes; + collect(nodes, root); + if (check(nodes, depth, nb_children)) std::cout << msg + " succeeded" << std::endl; + else std::cout << msg + " failed" << std::endl; + } + + void test_allocator::allocate(unsigned int depth, unsigned int nb_children) + { + node* root = alloc.allocate(1); + build(depth, nb_children, root); + test(root, depth, nb_children, "allocate"); + } + + void test_allocator::allocate_multi_threaded(unsigned int depth, unsigned int nb_children) + { + node* root = alloc.allocate(1); + if (depth != 0) + { + root->set_number_of_children(nb_children); + node* children = alloc.allocate(nb_children); + root->set_children(children); + // vector<thread> threads; + // for (unsigned int i = 0; i < nb_children; ++i) + // { + // threads.push_back(thread(&test_allocator::build, this, depth - 1, nb_children, children + i)); + // } + // for_each(threads.begin(), threads.end(), mem_fn(&thread::join)); +#pragma omp parallel for + for (unsigned int i = 0; i < nb_children; ++i) + { + build(depth - 1, nb_children, children + i); + } + } + test(root, depth, nb_children, "allocate multi threaded"); + } +} diff --git a/src/mcts/test_allocator.hpp b/src/mcts/test_allocator.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3e141c29d3947c4056b6963eb3fbcceda4eb37d9 --- /dev/null +++ b/src/mcts/test_allocator.hpp @@ -0,0 +1,29 @@ +#ifndef __TEST_ALLOCATOR_HPP__ +#define __TEST_ALLOCATOR_HPP__ + +#include "allocator.hpp" +#include <vector> +#include <string> +#include <random> + +namespace mcts +{ + class test_allocator + { + allocator alloc; + std::default_random_engine generator; + + void allocate(unsigned int depth, unsigned int nb_children); + void allocate_multi_threaded(unsigned int depth, unsigned int nb_children); + void build(unsigned int depth, unsigned int nb_children, node* n); + void collect(std::vector<node*>& nodes, node* n); + bool check(std::vector<node*> nodes, unsigned int depth, unsigned int nb_children); + void test(node* root, unsigned int depth, unsigned int nb_children, const std::string& msg); + + public: + test_allocator(unsigned int depth = 3, unsigned int nb_children = 2); + std::string to_string() const; + }; +} + +#endif diff --git a/src/mcts/test_mcts_two_players.cpp b/src/mcts/test_mcts_two_players.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dbe95ec611fc4ca617c6064ab1d6307747e8187e --- /dev/null +++ b/src/mcts/test_mcts_two_players.cpp @@ -0,0 +1,280 @@ +#include "mcts_two_players.hpp" +#include "test_mcts_two_players.hpp" +#include "connect4.hpp" +#include <iostream> +#include <iomanip> +#include <map> + +//#include <gperftools/profiler.h> + +using namespace std; +using namespace game; + +namespace mcts +{ + test_mcts_two_players::test_mcts_two_players() : openings_(connect4()) + { + //self_play(1000); + play(); + //test_openings(10000, 1000); + } + + void test_mcts_two_players::test_openings(int nb_learning, int nb_testing) + { + // self_play(nb_testing); + self_play_learn_openings(nb_learning); + self_play(nb_testing, true); + } + + template <typename Game> + int test_mcts_two_players::select_move(Game& game) + { + cout << game.player_to_string(game.current_player()) << " move: "; + map<string, int> m; + for (int i = 0; i < game.number_of_moves(); ++i) + { + m[game.move_to_string(i)] = i; + } + string move; + getline(cin, move); + game.play(m[move]); + return m[move]; + } + + void test_mcts_two_players::play() + { + // ProfilerStart("theturk.prof"); + connect4 c4; + auto the_turk = make_mcts_two_players(c4, 5000, 0.3, 4); + cout << "play one game" << endl; + cout << "who's first? (h)uman/(c)omputer "; + string ans; + getline(cin, ans); + cout << c4 << endl; + int human_last_move = -1, computer_last_move = -1; + while (!c4.end_of_game()) + { + if ((ans == "h" && c4.current_player() == 0) || (ans == "c" && c4.current_player() == 1)) + { + human_last_move = select_move(c4); + } + else + { + if (human_last_move != -1 && computer_last_move != -1) + { + the_turk.last_moves(computer_last_move, human_last_move); + } + uint16_t move = the_turk.select_move(); + computer_last_move = move; + cout << c4.player_to_string(c4.current_player()) << " move: " << c4.move_to_string(move) << endl; + c4.play(move); + } + cout << c4 << endl; + } + if (c4.value(0) == 1) cout << c4.player_to_string(0) << " won"; + else if (c4.value(1) == 1) cout << c4.player_to_string(1) << " won"; + else cout << "draw"; + cout << endl; + // ProfilerStop(); + } + + void test_mcts_two_players::self_play_learn_openings(int n) + { + connect4 c4; + vector<uint16_t> moves(200); + auto state = c4.get_state(); + auto the_turk_1 = make_mcts_two_players(c4, 1000, 0.6, 2); + auto the_turk_2 = make_mcts_two_players(c4, 1000, 0.6, 2); + map<set<int>, pair<uint32_t, double>> learning_examples; + for (int i = 0; i < n; ++i) + { + cout << i << endl; + cout << openings_ << endl << endl; + moves.clear(); + c4.set_state(state); + the_turk_1.reset(); + the_turk_1.init_with_openings(openings_); + the_turk_2.reset(); + the_turk_2.init_with_openings(openings_); + int the_turk_1_last_move = -1, the_turk_2_last_move = -1; + int k = 0; + while (!c4.end_of_game()) + { + if (k == 1) the_turk_2.last_move(the_turk_1_last_move); + if (k % 2 == 0) + { + if (the_turk_2_last_move != -1) + { + the_turk_1.last_moves(the_turk_1_last_move, the_turk_2_last_move); + } + uint16_t move = the_turk_1.select_move(); + moves.push_back(move); + the_turk_1_last_move = move; + c4.play(move); + } + else + { + if (the_turk_2_last_move != -1) + { + the_turk_2.last_moves(the_turk_2_last_move, the_turk_1_last_move); + } + uint16_t move = the_turk_2.select_move(); + moves.push_back(move); + the_turk_2_last_move = move; + c4.play(move); + } + ++k; + } + std::cout << c4 << std::endl; + int v = c4.value(0); + cout << "value for first player " << v << endl; + c4.set_state(state); + openings_.update(c4, moves, v); + c4.set_state(state); + for (uint16_t m : moves) + { + c4.play(m); + v = -v; + set<int> input_vector = std::move(c4.to_input_vector()); + auto it = learning_examples.find(input_vector); + if (it == learning_examples.end()) + { + learning_examples[input_vector] = make_pair(1, v); + } + else + { + it->second.second = (it->second.second * it->second.first + v) / (it->second.first + 1); + it->second.first += 1; + } + } + } + cout << "number of learning examples: " << learning_examples.size() << endl; + ofstream output("learning_examples.txt"); + for (const auto& example : learning_examples) + { + output << example.second.second; + for (int index : example.first) + { + output << " " << index << ":" << 1; + } + output << endl; + } + output.close(); + } + + void test_mcts_two_players::self_play(int n, bool with_openings) + { + connect4 c4; + auto state = c4.get_state(); + auto the_turk_v1 = make_mcts_two_players(c4, 1000, 0.6, 2); + auto the_turk_v2 = make_mcts_two_players(c4, 1000, 0.6, 2); + int nb_win_v1 = 0, nb_win_v2 = 0, nb_draw = 0; + for (int i = 0; i < n; ++i) + { + cout << i << endl; + c4.set_state(state); + the_turk_v1.reset(); + the_turk_v2.reset(); + if (with_openings) the_turk_v2.init_with_openings(openings_); + int the_turk_v1_last_move = -1, the_turk_v2_last_move = -1; + int k = 0; + while (!c4.end_of_game()) + { + if (with_openings && k == 1 && i % 2 == 0) the_turk_v2.last_move(the_turk_v1_last_move); + ++k; + // cout << c4 << endl; + if ((i % 2 == 0 && c4.current_player() == 0) || (i % 2 == 1 && c4.current_player() == 1)) + { + if (the_turk_v1_last_move != -1 && the_turk_v2_last_move != -1) + { + the_turk_v1.last_moves(the_turk_v1_last_move, the_turk_v2_last_move); + } + uint16_t move = the_turk_v1.select_move(); + the_turk_v1_last_move = move; + c4.play(move); + } + else + { + if (the_turk_v1_last_move != -1 && the_turk_v2_last_move != -1) + { + the_turk_v2.last_moves(the_turk_v2_last_move, the_turk_v1_last_move); + } + uint16_t move = the_turk_v2.select_move(); + the_turk_v2_last_move = move; + c4.play(move); + } + } + if (c4.value(0) == 1) + { + if (i % 2 == 0) + { + /*cout << "v1 won" << endl;*/ ++nb_win_v1; + } + else + { + /*cout << "v2 won" << endl;*/ ++nb_win_v2; + } + } + else if (c4.value(1) == 1) + { + if (i % 2 == 0) + { + /*cout << "v2 won" << endl;*/ ++nb_win_v2; + } + else + { + /*cout << "v1 won" << endl;*/ ++nb_win_v1; + } + } + else + { + /*cout << "draw" << endl;*/ ++nb_draw; + } + cout << setw(10) << "v1 nb wins: " << nb_win_v1 << " v2 nb wins: " << nb_win_v2 << " nb draws: " << nb_draw << endl; + } + } + + void test_mcts_two_players::self_play() + { + connect4 c4; + auto the_turk_v1 = make_mcts_two_players(c4, 1000, 1.2, 2); + auto the_turk_v2 = make_mcts_two_players(c4, 1000, 1.2, 2); + cout << "play one game" << endl; + cout << "who's first? the_turk_(v1)/the_turk_(v2) "; + string ans; + getline(cin, ans); + cout << c4 << endl; + int the_turk_v1_last_move = -1, the_turk_v2_last_move = -1; + while (!c4.end_of_game()) + { + if ((ans == "v1" && c4.current_player() == 0) || (ans == "v2" && c4.current_player() == 1)) + { + if (the_turk_v1_last_move != -1 && the_turk_v2_last_move != -1) + { + the_turk_v1.last_moves(the_turk_v1_last_move, the_turk_v2_last_move); + } + uint16_t move = the_turk_v1.select_move(); + the_turk_v1_last_move = move; + cout << c4.player_to_string(c4.current_player()) << " move: " << c4.move_to_string(move) << endl; + c4.play(move); + } + else + { + if (the_turk_v1_last_move != -1 && the_turk_v2_last_move != -1) + { + the_turk_v2.last_moves(the_turk_v2_last_move, the_turk_v1_last_move); + } + uint16_t move = the_turk_v2.select_move(); + the_turk_v2_last_move = move; + cout << c4.player_to_string(c4.current_player()) << " move: " << c4.move_to_string(move) << endl; + c4.play(move); + } + cout << c4 << endl; + } + if (c4.value(0) == 1) cout << c4.player_to_string(0) << " won"; + else if (c4.value(1) == 1) cout << c4.player_to_string(1) << " won"; + else cout << "draw"; + cout << endl; + } + +} diff --git a/src/mcts/test_mcts_two_players.hpp b/src/mcts/test_mcts_two_players.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c04f71f676f0036ac61d1194924437e02baa757c --- /dev/null +++ b/src/mcts/test_mcts_two_players.hpp @@ -0,0 +1,25 @@ +#ifndef __TEST_MCTS_TWO_PLAYERS_HPP__ +#define __TEST_MCTS_TWO_PLAYERS_HPP__ + +#include "openings.hpp" + +namespace mcts +{ + class test_mcts_two_players + { + openings openings_; + + void play(); + void self_play(); + void self_play(int n, bool with_openings = false); + void self_play_learn_openings(int n); + void test_openings(int nb_learning, int nb_testing); + + template <typename Game> + int select_move(Game& game); + public: + test_mcts_two_players(); + }; +} + +#endif diff --git a/src/minmax/connect4_heuristic.cpp b/src/minmax/connect4_heuristic.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25ad3a68164d9a37cce728826ae3aaa922529eac --- /dev/null +++ b/src/minmax/connect4_heuristic.cpp @@ -0,0 +1,11 @@ +#include "connect4_heuristic.hpp" + +namespace minmax +{ + double connect4_heuristic::value(const connect4& c4) const + { + const connect4_state& state = c4.get_state(); + return 0; + } + +} diff --git a/src/minmax/connect4_heuristic.hpp b/src/minmax/connect4_heuristic.hpp new file mode 100644 index 0000000000000000000000000000000000000000..050d176de3d5f29fd37804e054d3e98556a7a0c1 --- /dev/null +++ b/src/minmax/connect4_heuristic.hpp @@ -0,0 +1,14 @@ +#ifndef __CONNECT4_HEURISTIC_HPP__ +#define __CONNECT4_HEURISTIC_HPP__ + +#include <connect4.hpp> + +namespace minmax +{ + class connect4_heuristic : public heuristic<connect4> + { + double value(const connect4& c4) const; + }; +} + +#endif diff --git a/src/minmax/heuristic.hpp b/src/minmax/heuristic.hpp new file mode 100644 index 0000000000000000000000000000000000000000..09803f07736b0e5534696be18bde3c9cc446208d --- /dev/null +++ b/src/minmax/heuristic.hpp @@ -0,0 +1,26 @@ +#ifndef __HEURISTIC_HPP__ +#define __HEURISTIC_HPP__ + +namespace minmax +{ + template <typename Game> + class heuristic + { + public: + virtual double value(const Game& game) const = 0; + }; + + template <typename Game> + class null_heuristic : public heuristic<Game> + { + double value(const Game& game) const; + }; + + template <typename Game> + double null_heuristic<Game>::value(const Game& game) const + { + return 0; + } +} + +#endif diff --git a/src/minmax/minmax.hpp b/src/minmax/minmax.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6a77f7cc0d2ed11ffc0e9db2b3cdb1cb1962e9e4 --- /dev/null +++ b/src/minmax/minmax.hpp @@ -0,0 +1,135 @@ +#ifndef __MINMAX_HPP__ +#define __MINMAX_HPP__ + +#include <chrono> +#include <vector> +#include "game.hpp" +#include "heuristic.hpp" +#include <iostream> +#include <limits> + +namespace minmax +{ + struct cannot_prove : std::exception + { + const char* what() const noexcept { return "cannot prove the game theoritical value"; } + }; + + template <typename Game> + class minmax + { + protected: + Game& game; + std::chrono::milliseconds milliseconds; + const heuristic<Game>& h; + std::vector<uint16_t> principal_variation; + std::chrono::steady_clock::time_point start; + + public: + minmax(Game& game, uint32_t milliseconds, const heuristic<Game>& h = null_heuristic<Game>()) + : game(game), milliseconds(milliseconds), h(h) + { + } + + int prove(); + double solve(); + uint16_t select_move(); + + private: + long prove(int depth); + double solve(int depth); + }; + + template <typename Game> + minmax<Game> make_minmax(Game& game, uint32_t milliseconds, const heuristic<Game>& h = null_heuristic<Game>()) + { + return minmax<Game>(game, milliseconds, h); + } + + template <typename Game> + double minmax<Game>::solve() + { + return 0; + } + + template <typename Game> + double minmax<Game>::solve(int depth) + { + return 0; + } + + template <typename Game> + uint16_t minmax<Game>::select_move() + { + return 0; + } + + const long UNKNOWN = std::numeric_limits<long>::max(); + + template <typename Game> + int minmax<Game>::prove() + { + start = std::chrono::steady_clock::now(); + uint16_t nb_moves = game.number_of_moves(); + long value[nb_moves]; + std::fill_n(value, nb_moves, UNKNOWN); + int depth = 3; + auto state = game.get_state(); + try + { + int nb_proved_moves = 0, nb_draws = 0; + while (true) + { + for (uint16_t move = 0; move < nb_moves; ++move) + { + if (value[move] != UNKNOWN) continue; + game.play(move); + value[move] = prove(depth); + game.set_state(state); + if (value[move] == -1) return 1; + if (value[move] == 0) ++nb_draws; + if (value[move] != UNKNOWN) ++nb_proved_moves; + } + if (nb_proved_moves == nb_moves) + { + if (nb_draws != 0) return 0; + return -1; + } + ++depth; + } + } + catch (cannot_prove fail) + { + game.set_state(state); + } + throw cannot_prove(); + } + + template <typename Game> + long minmax<Game>::prove(int depth) + { + if (game.end_of_game()) return game.value(game.current_player()); + if (depth == 0) return UNKNOWN; + if ((depth & 3) && std::chrono::steady_clock::now() >= start + milliseconds) throw cannot_prove(); + uint16_t nb_moves = game.number_of_moves(); + auto state = game.get_state(); + int nb_proved_moves = 0, nb_draws = 0; + for (uint16_t move = 0; move < nb_moves; ++move) + { + game.play(move); + long v = prove(depth - 1); + game.set_state(state); + if (v == -1) return 1; + if (v == 0) ++nb_draws; + if (v != UNKNOWN) ++nb_proved_moves; + } + if (nb_proved_moves == nb_moves) + { + if (nb_draws != 0) return 0; + return -1; + } + return UNKNOWN; + } +} + +#endif diff --git a/src/minmax/test_minmax.cpp b/src/minmax/test_minmax.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7d5aa9c583ebb0e5f0a5e79f4789baeacbce6fd5 --- /dev/null +++ b/src/minmax/test_minmax.cpp @@ -0,0 +1,67 @@ +#include "minmax.hpp" +#include "test_minmax.hpp" +#include "connect4.hpp" +#include <iostream> +#include <iomanip> +#include <map> + +using namespace std; +using namespace game; + +namespace minmax +{ + test_minmax::test_minmax() + { + play(); + } + + template <typename Game> + int test_minmax::select_move(Game& game) + { + cout << game.player_to_string(game.current_player()) << " move: "; + map<string, int> m; + for (int i = 0; i < game.number_of_moves(); ++i) + { + m[game.move_to_string(i)] = i; + } + string move; + getline(cin, move); + game.play(m[move]); + return m[move]; + } + + void test_minmax::play() + { + connect4 c4; + auto minimax = make_minmax(c4, 2000); + cout << "play one game" << endl; + cout << c4 << endl; + while (!c4.end_of_game()) + { + cout << "try to prove? (y/n)" << endl; + string ans; + getline(cin, ans); + if (ans == "y") + { + try + { + int v = minimax.prove(); + if (v == 1) cout << "won position" << endl; + if (v == -1) cout << "lost position" << endl; + if (v == 0) cout << "drawn position" << endl; + } + catch (exception fail) + { + cout << "unable to prove" << endl; + } + } + select_move(c4); + cout << c4 << endl; + } + if (c4.value(0) == 1) cout << c4.player_to_string(0) << " won"; + else if (c4.value(1) == 1) cout << c4.player_to_string(1) << " won"; + else cout << "draw"; + cout << endl; + } + +} diff --git a/src/minmax/test_minmax.hpp b/src/minmax/test_minmax.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7d8fe99d111c3e634d4a8ede551a3b8f80f2fe26 --- /dev/null +++ b/src/minmax/test_minmax.hpp @@ -0,0 +1,16 @@ +#ifndef __TEST_MINMAX_HPP__ +#define __TEST_MINMAX_HPP__ + +namespace minmax +{ + class test_minmax + { + void play(); + template <typename Game> + int select_move(Game& game); + public: + test_minmax(); + }; +} + +#endif diff --git a/src/monte_carlo/monte_carlo.cpp b/src/monte_carlo/monte_carlo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eee0301eb069a92a6a50985d21b6685c4f27e176 --- /dev/null +++ b/src/monte_carlo/monte_carlo.cpp @@ -0,0 +1,12 @@ +#include "monte_carlo.hpp" +#include "omp_util.hpp" +#include <chrono> +#include <limits> +#include <iostream> +#include <algorithm> + +using namespace std; + +namespace monte_carlo +{ +} diff --git a/src/monte_carlo/monte_carlo.hpp b/src/monte_carlo/monte_carlo.hpp new file mode 100644 index 0000000000000000000000000000000000000000..299d2fd56df3cabf71712c5843aebf95ae4d30ab --- /dev/null +++ b/src/monte_carlo/monte_carlo.hpp @@ -0,0 +1,95 @@ +#ifndef __MONTE_CARLO_HPP__ +#define __MONTE_CARLO_HPP__ + +#include "game.hpp" +#include <random> +#include <chrono> +#include <vector> +#include "omp_util.hpp" +#include <iostream> +#include <algorithm> + +namespace monte_carlo +{ + template <typename Game> + class monte_carlo + { + Game& game; + uint32_t batch_size; + std::chrono::milliseconds milliseconds; + std::vector<std::mt19937> generators; + + public: + monte_carlo(Game& game, uint32_t batch_size, uint32_t milliseconds); + uint16_t select_move(); + }; + + template <typename Game> + uint16_t monte_carlo<Game>::select_move() + { + using namespace std; + uint8_t current_player = game.current_player(); + uint16_t nb_moves = game.number_of_moves(); + vector<int> values(game.number_of_moves(), 0); + const chrono::steady_clock::time_point start = chrono::steady_clock::now(); + chrono::steady_clock::time_point now; + vector<shared_ptr<Game>> games(util::omp_util::get_num_threads(), nullptr); + transform(games.begin(), games.end(), games.begin(), [this](const shared_ptr<Game>& _){ return game::copy(game); }); + do + { +#pragma omp parallel for + for (uint16_t move = 0; move < nb_moves; ++move) + { + int thread_num = util::omp_util::get_thread_num(); + mt19937& generator = generators[thread_num]; + shared_ptr<Game> g = games[thread_num]; + for (uint32_t i = 0; i < batch_size; ++i) + { + auto state = g->get_state(); + g->play(move); + g->playout(generator); + values[move] += g->value(current_player); + g->set_state(state); + } + } + now = chrono::steady_clock::now(); + // cout << chrono::duration_cast<chrono::milliseconds>(now - start).count() << endl; + } + while (now < start + milliseconds); + + int best_value_so_far = numeric_limits<int>::min(); + uint16_t best_move_so_far = -1; + for (uint16_t move = 0; move < nb_moves; ++move) + { + cout << "(" << game.move_to_string(move) << ", " << values[move] << ") "; + if (values[move] > best_value_so_far) + { + best_value_so_far = values[move]; + best_move_so_far = move; + } + } + cout << endl; + return best_move_so_far; + } + + template <typename Game> + monte_carlo<Game> make_monte_carlo(Game& game, uint32_t batch_size, uint32_t milliseconds) + { + return monte_carlo<Game>(game, batch_size, milliseconds); + } + + template <typename Game> + monte_carlo<Game>::monte_carlo(Game& game, uint32_t batch_size, uint32_t milliseconds) + : game(game), batch_size(batch_size), milliseconds(milliseconds) + { + using namespace std; + int num_threads = util::omp_util::get_num_threads(); + for (int i = 0; i < num_threads; ++i) + { + generators.push_back(mt19937(chrono::system_clock::now().time_since_epoch().count())); + } + } + +} + +#endif diff --git a/src/monte_carlo/test_monte_carlo.cpp b/src/monte_carlo/test_monte_carlo.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1ad7267efdbe9fbc211b5f0fecd54ee337f53de2 --- /dev/null +++ b/src/monte_carlo/test_monte_carlo.cpp @@ -0,0 +1,58 @@ +#include "monte_carlo.hpp" +#include "test_monte_carlo.hpp" +#include <iostream> +#include <map> + +using namespace std; +using namespace game; + +namespace monte_carlo +{ + test_monte_carlo::test_monte_carlo() + { + play(); + } + + template <typename Game> + void test_monte_carlo::select_move(Game& game) + { + cout << game.player_to_string(game.current_player()) << " move: "; + map<string, int> m; + for (int i = 0; i < game.number_of_moves(); ++i) + { + m[game.move_to_string(i)] = i; + } + string move; + cin >> move; + game.play(m[move]); + } + + void test_monte_carlo::play() + { + connect4 c4; + auto mc = make_monte_carlo(c4, 80000, 2000); + cout << "play one game" << endl; + cout << "who's first? (h)uman/(c)omputer "; + string ans; + cin >> ans; + cout << c4 << endl; + while (!c4.end_of_game()) + { + if ((ans == "h" && c4.current_player() == 0) || (ans == "c" && c4.current_player() == 1)) + { + select_move(c4); + } + else + { + uint16_t move = mc.select_move(); + cout << c4.player_to_string(c4.current_player()) << " move: " << c4.move_to_string(move) << endl; + c4.play(move); + } + cout << c4 << endl; + } + if (c4.value(0) == 1) cout << c4.player_to_string(0) << " won"; + else if (c4.value(1) == 1) cout << c4.player_to_string(1) << " won"; + else cout << "draw"; + cout << endl; + } +} diff --git a/src/monte_carlo/test_monte_carlo.hpp b/src/monte_carlo/test_monte_carlo.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e1fceb21ff9621ce602355e28fb2ab5ba4691472 --- /dev/null +++ b/src/monte_carlo/test_monte_carlo.hpp @@ -0,0 +1,18 @@ +#ifndef __TEST_MONTE_CARLO_HPP__ +#define __TEST_MONTE_CARLO_HPP__ + +#include "connect4.hpp" + +namespace monte_carlo +{ + class test_monte_carlo + { + void play(); + template <typename Game> + void select_move(Game& game); + public: + test_monte_carlo(); + }; +} + +#endif diff --git a/src/util/bits.cpp b/src/util/bits.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bad4a45e229a0d242a19c867ba5d8d37493403ba --- /dev/null +++ b/src/util/bits.cpp @@ -0,0 +1,16 @@ +#include "bits.hpp" + +namespace util +{ + uint8_t bits::index64[64] = + { + 0, 47, 1, 56, 48, 27, 2, 60, + 57, 49, 41, 37, 28, 16, 3, 61, + 54, 58, 35, 52, 50, 42, 21, 44, + 38, 32, 29, 23, 17, 11, 4, 62, + 46, 55, 26, 59, 40, 36, 15, 53, + 34, 51, 20, 43, 31, 22, 10, 45, + 25, 39, 14, 33, 19, 30, 9, 24, + 13, 18, 8, 12, 7, 6, 5, 63 + }; +} diff --git a/src/util/bits.hpp b/src/util/bits.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4aed89454e7d80308dc04be59442d7b073d0f1f8 --- /dev/null +++ b/src/util/bits.hpp @@ -0,0 +1,27 @@ +#ifndef __BITS_HPP__ +#define __BITS_HPP__ + +#include <cstdint> + +namespace util +{ + class bits + { + static uint8_t index64[64]; + + public: + /** + * bit_scan_forward + * @author Kim Walisch (2012) + * @param bb bitboard to scan + * @precondition bb != 0 + * @return index (0..63) of least significant one bit + */ + static inline int bit_scan_forward(uint64_t bb) + { + const uint64_t debruijn64 = uint64_t(0x03f79d71b4cb0a89); + return index64[((bb ^ (bb-1)) * debruijn64) >> 58]; + } + }; +} +#endif diff --git a/src/util/bits.hpp.gch b/src/util/bits.hpp.gch new file mode 100644 index 0000000000000000000000000000000000000000..dddb27cdd57ad8a4ede839476dc90e518d6747ff Binary files /dev/null and b/src/util/bits.hpp.gch differ diff --git a/src/util/display_node.cpp b/src/util/display_node.cpp new file mode 100644 index 0000000000000000000000000000000000000000..71cfd06187fd839417a79fa3dae9cacb3d4abfdf --- /dev/null +++ b/src/util/display_node.cpp @@ -0,0 +1,82 @@ +#include "display_node.hpp" +#include <string> +#include <iomanip> +#include <sstream> + +using namespace std; + +namespace util +{ + void display_node::node_to_ascii(std::ostream& os, const mcts::node* n, unsigned int depth, unsigned int prunning) + { + node_to_ascii(os, "", n, depth, prunning); + os << endl; + } + + void display_node::node_to_dot(std::ostream& os, const mcts::node* n, unsigned int depth, unsigned int prunning) + { + os << "digraph {" << endl; + node_to_dot(os, 0, n, depth, prunning); + os << "}" << endl; + } + + int display_node::node_to_dot(ostream& os, int id, const mcts::node* n, unsigned int depth, unsigned int prunning) + { + stringbuf buffer; + ostream o (&buffer); + if (n->is_won()) + o << "\"won\""; + else if (n->is_lost()) + o << "\"lost\""; + else + o << "\"" << n->get_statistics().count << "\\n" << setprecision(2) << n->get_statistics().value << "\""; + string label = buffer.str(); + os << id << "[label=" << label << "]" << endl; + if (depth == 0 || n->get_statistics().count < prunning || n->get_number_of_children() == 0) return id + 1; + int cpt = id + 1; + for (int i = 0; i < n->get_number_of_children(); ++i) + { + os << id << "->" << cpt << endl; + cpt = node_to_dot(os, cpt, n->get_children() + i, depth - 1, prunning); + } + return cpt + 1; + } + + void display_node::node_to_ascii(ostream& os, string prefix, const mcts::node* n, unsigned int depth, unsigned int prunning) + { + string s; + s = n->get_statistics().to_string(); + if (n->is_won()) s += "won"; + else if (n->is_lost()) s += "lost"; + // else s = n->get_statistics().to_string(); + os << s; + unsigned int w = s.size(); + string new_prefix = prefix + string(w + 1, ' '); + if (depth == 0 || n->get_statistics().count < prunning || n->get_number_of_children() == 0) return; + if (n->get_number_of_children() == 1) + { + os << "---"; + node_to_ascii(os, new_prefix + " ", n->get_children(), depth - 1, prunning); + return; + } + os << "-"; + children_to_ascii(os, new_prefix, n->get_number_of_children(), n->get_children(), depth, prunning); + } + + void display_node::children_to_ascii(ostream& os, string prefix, unsigned int nb_children, const mcts::node* children, unsigned int depth, unsigned int prunning) + { + os << "+-"; + node_to_ascii(os, prefix + "| ", children, depth - 1, prunning); + os << endl; + os << prefix; + for (unsigned int i = 1; i < nb_children - 1; ++i) + { + os << "|-"; + node_to_ascii(os, prefix + "| ", children + i, depth - 1, prunning); + os << endl; + os << prefix; + } + os << "`-"; + node_to_ascii(os, prefix + " ", children + nb_children - 1, depth - 1, prunning); + } +} diff --git a/src/util/display_node.hpp b/src/util/display_node.hpp new file mode 100644 index 0000000000000000000000000000000000000000..86837fe8629e48580c13852d91b9b0c94bc19579 --- /dev/null +++ b/src/util/display_node.hpp @@ -0,0 +1,22 @@ +#ifndef __DISPLAY_NODE_HPP__ +#define __DISPLAY_NODE_HPP__ + +#include "node.hpp" +#include <iostream> +#include <limits> + +namespace util +{ + class display_node + { + public: + static void node_to_ascii(std::ostream& os, const mcts::node* n, unsigned int depth = std::numeric_limits<unsigned int>::max(), unsigned int prunning = 0); + static void node_to_dot(std::ostream& os, const mcts::node* n, unsigned int depth = std::numeric_limits<int>::max(), unsigned int prunning = 0); + private: + static void node_to_ascii(std::ostream& os, std::string prefix, const mcts::node* n, unsigned int depth, unsigned int prunning); + static void children_to_ascii(std::ostream& os, std::string prefix, unsigned int nb_children, const mcts::node* children, unsigned int depth, unsigned int prunning); + static int node_to_dot(std::ostream& os, int id, const mcts::node* n, unsigned int depth, unsigned int prunning); + }; +} + +#endif diff --git a/src/util/fast_log.cpp b/src/util/fast_log.cpp new file mode 100644 index 0000000000000000000000000000000000000000..70ec031f8826adb229b48c868c73d870c3174303 --- /dev/null +++ b/src/util/fast_log.cpp @@ -0,0 +1,29 @@ +#include "fast_log.hpp" +#include <cassert> +#include <limits> + +namespace util +{ + fast_log::fast_log(int mantissa_nb_bits) + : mantissa_shift(MAX_MANTISSA_NB_BITS - mantissa_nb_bits) + { + assert(sizeof(int) == 4); + assert(std::numeric_limits<float>::is_iec559); + lookup_table = new float[1 << mantissa_nb_bits]; + int_float x; + x._int = 0x3F800000; + int incr = (1 << mantissa_shift); + int p = (1 << mantissa_nb_bits); + float inv_log_two = 1.0f / log(2.0f); + for (int i = 0; i < p; ++i) + { + lookup_table[i] = log(x._float) * inv_log_two; + x._int += incr; + } + } + + fast_log::~fast_log() + { + delete[] lookup_table; + } +} diff --git a/src/util/fast_log.hpp b/src/util/fast_log.hpp new file mode 100644 index 0000000000000000000000000000000000000000..482512d1f890172b28dea67edc1c735a990ceeb1 --- /dev/null +++ b/src/util/fast_log.hpp @@ -0,0 +1,34 @@ +#ifndef __FAST_LOG_HPP__ +#define __FAST_LOG_HPP__ + +namespace util +{ + class fast_log + { + public: + fast_log(int mantissa_nb_bits = 11); + ~fast_log(); + inline float log(float v) const; + private: + union int_float + { + int _int; + float _float; + }; + float* lookup_table; + const int mantissa_shift; + const int MAX_MANTISSA_NB_BITS = 23; + }; + + float fast_log::log(float val) const + { + int_float x; + x._float = val; + int exponent = ((x._int >> MAX_MANTISSA_NB_BITS) & 255) - 127; + x._int &= 0x7FFFFF; + x._int >>= mantissa_shift; + return (lookup_table[x._int] + float(exponent)) * 0.69314718f; + } +} + +#endif diff --git a/src/util/learning.hpp b/src/util/learning.hpp new file mode 100644 index 0000000000000000000000000000000000000000..da69bb8c9e655507b3be51528143ff9cf09f4034 --- /dev/null +++ b/src/util/learning.hpp @@ -0,0 +1,46 @@ +#ifndef __LEARNING_HPP__ +#define __LEARNING_HPP__ + +#include <iostream> +#include <fstream> +#include <sstream> +#include <string> +#include <set> + +namespace util +{ + struct learning + { + template <typename Game> + static void display_file(Game& game, const std::string& filename); + }; + template <typename Game> + void learning::display_file(Game& game, const std::string& filename) + { + using namespace std; + ifstream file(filename); + string line; + while (getline(file, line)) + { + cout << line << endl; + double value; + set<int> input_vector; + int index, v; + char c; + stringstream ss(line); + ss >> value; + cout << "game value: " << value << endl; + while (ss >> index >> c >> v) + { + input_vector.insert(index); + } + game.from_input_vector(input_vector); + cout << "player to move: " << game.player_to_string(game.current_player()) << endl; + cout << game << endl; + getline(cin, line); + } + file.close(); + } +} + +#endif diff --git a/src/util/omp_util.cpp b/src/util/omp_util.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0f8493b8fd320f2ae78a0affd1c5209b415ad0cb --- /dev/null +++ b/src/util/omp_util.cpp @@ -0,0 +1,23 @@ +#include "omp_util.hpp" + +namespace util +{ + int omp_util::get_num_threads() + { + int num_threads = 1; +#if defined(_OPENMP) +#pragma omp parallel + num_threads = omp_get_num_threads(); +#endif + return num_threads; + } + + int omp_util::get_thread_num() + { + int thread_num = 0; +#if defined(_OPENMP) + thread_num = omp_get_thread_num(); +#endif + return thread_num; + } +} diff --git a/src/util/omp_util.hpp b/src/util/omp_util.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7f7d55f6ad1a680621828e14eb60a143c6a4afdf --- /dev/null +++ b/src/util/omp_util.hpp @@ -0,0 +1,15 @@ +#ifndef __OMP_UTIL_HPP__ +#define __OMP_UTIL_HPP__ + +#include <omp.h> + +namespace util +{ + struct omp_util + { + static int get_num_threads(); + static int get_thread_num(); + }; +} + +#endif diff --git a/src/util/pow.hpp b/src/util/pow.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a166551f3c6aef235718423a0d6151ad68521643 --- /dev/null +++ b/src/util/pow.hpp @@ -0,0 +1,25 @@ +#ifndef __POW_HPP_ +#define __POW_HPP_ + +namespace util +{ + long pow_l(long x, long n) + { + long res = 1; + while (n != 0) + { + if (n & 1) + { + n--; + res = res * x; + } + else + { + n = n / 2; + x = x * x; + } + } + return res; + } +} +#endif diff --git a/src/util/test_bits.cpp b/src/util/test_bits.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25be74159e0e43d14513a331104e7187391e7fd8 --- /dev/null +++ b/src/util/test_bits.cpp @@ -0,0 +1,37 @@ +#include "test_bits.hpp" +#include <string.h> +#include <chrono> +#include <iostream> +#include <random> + +using namespace std; + +namespace util +{ + test_bits::test_bits(unsigned int nb_samples) + { + volatile int res = 0; + default_random_engine generator(11); + uniform_int_distribution<uint64_t> distribution(0, uint64_t(0xFFFFFFFFFFFFFFFF)); + + chrono::steady_clock::time_point start = chrono::steady_clock::now(); + for (unsigned int i = 1; i <= nb_samples; ++i) + { + uint64_t v = distribution(generator); + res += bits::bit_scan_forward(v); + } + cout << "res for debrujin: " << res << endl; + cout << "time for debrujin (ms): " << chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - start).count() << endl; + + generator = default_random_engine(11); + res = 0; + start = chrono::steady_clock::now(); + for (unsigned int i = 1; i <= nb_samples; ++i) + { + uint64_t v = distribution(generator); + res += ffsll(v) - 1; + } + cout << "res for ffsll: " << res << endl; + cout << "time for ffsll (ms): " << chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - start).count() << endl; + } +} diff --git a/src/util/test_bits.hpp b/src/util/test_bits.hpp new file mode 100644 index 0000000000000000000000000000000000000000..48daee46b9aaeb46c1eaa5de6762f7d997ef52be --- /dev/null +++ b/src/util/test_bits.hpp @@ -0,0 +1,14 @@ +#ifndef __TEST_BITS_HPP__ +#define __TEST_BITS_HPP__ + +#include "bits.hpp" + +namespace util +{ + struct test_bits + { + test_bits(unsigned int nb_samples); + }; +} + +#endif diff --git a/src/util/test_fast_log.cpp b/src/util/test_fast_log.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ba7a0c44bfd059ee60a97b37c303f73c0a21870f --- /dev/null +++ b/src/util/test_fast_log.cpp @@ -0,0 +1,50 @@ +#include "test_fast_log.hpp" +#include <cmath> +#include <chrono> +#include <iostream> +#include <random> + +using namespace std; + +namespace util +{ + test_fast_log::test_fast_log(unsigned int nb_samples, unsigned int mantissa_nb_bits) + : nb_samples(nb_samples), flog(mantissa_nb_bits) + { + precision(); + time(); + } + + void test_fast_log::precision() + { + default_random_engine generator; + uniform_real_distribution<float> distribution(0.01,1000000.0); + double res = 0; + for (unsigned int i = 0; i < nb_samples; ++i) + { + float v = distribution(generator); + res += abs(log(v) - flog.log(v)) / (double)v; + } + cout << "precision: " << res / nb_samples << endl; + } + + void test_fast_log::time() + { + volatile double res = 0; + chrono::steady_clock::time_point start = chrono::steady_clock::now(); + for (unsigned int i = 1; i <= nb_samples; ++i) + { + res += log(i); + } + cout << "time for log (ms): " << chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - start).count() << endl; + + res = 0; + start = chrono::steady_clock::now(); + for (unsigned int i = 1; i <= nb_samples; ++i) + { + res += flog.log(i); + } + cout << "time for fast log (ms): " << chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - start).count() << endl; + + } +} diff --git a/src/util/test_fast_log.hpp b/src/util/test_fast_log.hpp new file mode 100644 index 0000000000000000000000000000000000000000..bbfd2c7ca612fc0fbe3c67959d7fb56c6baf79a0 --- /dev/null +++ b/src/util/test_fast_log.hpp @@ -0,0 +1,19 @@ +#ifndef __TEST_FAST_LOG_HPP__ +#define __TEST_FAST_LOG_HPP__ + +#include "fast_log.hpp" + +namespace util +{ + class test_fast_log + { + unsigned int nb_samples; + fast_log flog; + + void precision(); + void time(); + public: + test_fast_log(unsigned int nb_samples = 100000000U, unsigned int mantissa_nb_bits = 11); + }; +} +#endif