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