C++ implementation of Monte Carlo Tree Search with Python wrapper

Multi tool use
Multi tool use

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
2
down vote

favorite
1












Important



Prebuilt Python Extensions built for Python 3.6



Includes Python f-strings (3.6+)



Complementary GitHub link



https://github.com/thejhonnyguy/CPyMCTS



Notable: Previous attempt with stock Python



Stock Monte Carlo Tree Search implementation to a simple connect 5 game in Python (Includes bugs)



C++ is the star of the show today with a custom MCTS implementation for the 5 in a row game. To build the pyd I used boost.python with Python 3.6 amd64 and x86 versions. (3.6.4 amd64, 3.6.5 x86)



C++



main.cpp



#define BOOST_PYTHON_STATIC_LIB
#include <string>
#include "boost/python.hpp"
#include "boost/python/def.hpp"
#include "boost/python/module.hpp"

#include "board.h"
#include "search.h"


BOOST_PYTHON_FUNCTION_OVERLOADS(search_overloads, search, 2, 3);


BOOST_PYTHON_MODULE(cpymcts)

using namespace boost::python;
class_<Board>("Board", init<>())
.def(init<int>())
.def("reset", &Board::reset)
.def("play", &Board::playMove)
.def("undo", &Board::undoMove)
.def("get", &Board::getBoardPos)
.def("check_win", &Board::checkWin)
.add_property("string", &Board::showString)
.add_property("size", &Board::getSize)
.add_property("move_count", &Board::getMoves)
;
def("search", search, search_overloads());



Below: Node class contained within node.h and node.cpp



node.h



#pragma once
#include "board.h"
#include <vector>
#include <string>
#include <memory>

// Node class

class Node

public:
Node *parentNode;
std::vector<std::unique_ptr<Node>> children;
unsigned long visits;
long long score;
unsigned long visit_offset;
int team_multiplier;
int move_to[2];
bool terminal;
int termscore;

// Constructors
// Default. Will assume top node
Node();
// Norm
Node(Node *parent, unsigned long offset, int mt_r, int mt_c, int term = 0);

//Methods
void Backtrack();
void CreateChild(unsigned long offset, int mt_r, int mt_c, int term = 0);
void BackPropagateScore(int s);
bool IsLeaf();
double ucb1();
double ucb1(double exploration);
Node* SelectBest(Board * board);
;


node.cpp



#include "node.h"
#include "board.h"
#include <string>
#include <iostream>
#include <limits>


// Node implementations

Node::Node()

// Default constuctor
// Node * parentNode will be left undefined
// It is the duty of the programmer to not do anything foolish
parentNode = nullptr;
visits = 0, score = 0, visit_offset = 0, team_multiplier = 1;


Node::Node(Node *parent, unsigned long offset, int mt_r, int mt_c, int term)

// Constructor used when the node is not the top node.
// int term default value 0
parentNode = parent;
move_to[0] = mt_r; move_to[1] = mt_c;
visits = 0, score = 0, visit_offset = offset, team_multiplier = -parent->team_multiplier;
if (term) terminal = true;
termscore = term;


void Node::Backtrack()

std::cout << "yeet" << std::endl;
if (parentNode == nullptr) return;
parentNode->Backtrack();


void Node::CreateChild(unsigned long offset, int mt_r, int mt_c, int term)

// Creates a child and appends it to Node<std::unique_ptr<Node>> children
// Nodes created here use the second constructor,
// and `this` is the parent of its children, hence the `this` argument
children.push_back(std::make_unique<Node>(this, offset, mt_r, mt_c, term));


void Node::BackPropagateScore(int s)

++visits;
score += s;
if (parentNode == nullptr) return;
parentNode->BackPropagateScore(s);


bool Node::IsLeaf()

return !children.size();


double Node::ucb1()

return ucb1(sqrt(2));


double Node::ucb1(double exploration)

if (!visits) return std::numeric_limits<double>::infinity();
double average = (double)score / visits;
return team_multiplier * average + exploration * sqrt(log(parentNode->visits) / visits);


Node* Node::SelectBest(Board * board)

if (parentNode != nullptr) board->playMove(move_to[0], move_to[1]);
// This will only have effect on the top node.
if (IsLeaf()) return this;
double max_value = -1.0;
int max_index = 0;
for (unsigned int i = 0; i < children.size(); i++)

double tmp = children[i]->ucb1();
if (tmp > max_value)

max_value = tmp;
max_index = i;


return children[max_index]->SelectBest(board);



Below: Board class contained within board.h and board.cpp



board.h



#pragma once
#include <vector>
#include <array>


// Class declaration and class method declarations go in the .h file here

class Board

private:
short board[23][23];
int lastPlaced[2];
int size;
int moves;
std::vector<std::array<int, 2>> playedPieces;

public:

Board(); // Default initialization to 19x19 board
Board(int); // Initialization to NxN board

std::string showString();
void print();
void reset();
void playMove(int, int);
void undoMove();
void replayMoves();
void manOverridePlayedPieces(std::vector<std::array<int, 2>> ov);
int checkWin();
int getMoves();
int getSize();
int getBoardPos(int r, int c);

std::vector<std::array<int, 2>> getObvMoves();
;


board.cpp



#include "board.h"
#include <string>
#include <iostream>
#include <vector>
#include <array>
#include <algorithm>


// Board class implementation goes here (the literal functions)

Board::Board()

size = 19;
reset();


Board::Board(const int s)

size = (s < 19) ? s : 19;
reset();


std::string Board::showString()

std::string str;
for (int r = 0; r < size; ++r)

for (int c = 0; c < size; ++c)

str += std::to_string(board[r][c]);
if (board[r][c] != -1) str += " ";

str += "n";

return str;


void Board::print()

for (int r = 0; r < size; ++r)

for (int c = 0; c < size; ++c)

std::cout << board[r][c] << "t";

std::cout << std::endl;



void Board::reset()

// Reset values
moves = 0;

// Reset board
for (int r = 0; r < 23; ++r)

for (int c = 0; c < 23; ++c)

board[r][c] = 0;



playedPieces.clear();



void Board::playMove(int r, int c)

if (!board[r][c])

board[r][c] = (moves % 2) * 2 - 1;

else return;
++moves;

lastPlaced[0] = r;
lastPlaced[1] = c;
std::array<int, 2> lp r,c ;
playedPieces.push_back(lp);


void Board::undoMove()

board[lastPlaced[0]][lastPlaced[1]] = 0;
--moves;
playedPieces.pop_back();
if (playedPieces.size())

lastPlaced[0] = playedPieces[playedPieces.size() - 1][0];
lastPlaced[1] = playedPieces[playedPieces.size() - 1][1];



void Board::replayMoves()

std::cout << "Replaying moves:" << std::endl;
for (unsigned int i = 0; i < playedPieces.size(); ++i)

std::cout << i << ": " << playedPieces[i][0] << ", " << playedPieces[i][1] << std::endl;



void Board::manOverridePlayedPieces(std::vector<std::array<int, 2>> ov)

playedPieces = ov;
moves = playedPieces.size();



int Board::checkWin()

if (!moves) return 0;
// Fast version: Check only the surroundings of the last placed piece
int locR lastPlaced[0] ;
int locC lastPlaced[1] ;

// Check horizontal of the area. Verified to work.
int startC (locC - 4 < 0) ? 0 : locC - 4 ;
int endC (size - locC >= 4) ? locC : size - 4 ; // Maths checks out
for (int sC = startC; sC <= endC; ++sC)

int sum 0 ;
for (int a = 0; a < 5; ++a)

sum += board[locR][sC + a];

if (sum == -5)

return -1;

if (sum == 5)

return 1;



// Check vertical of the area. Seems to work as well...
int startR (locR - 4 < 0) ? 0 : locR - 4 ;
int endR (size - locR >= 4) ? locR : size - 4 ;
for (int sR = startR; sR <= endR; ++sR)

int sum 0 ;
for (int a = 0; a < 5; ++a)

sum += board[sR + a][locC];

if (sum == -5)

return -1;

if (sum == 5)

return 1;



// Check the diagonal (top left to bottom right)
// startR and startC will be used from above code for streamlining
// delta-Row / delta-Col to be used in future
int dRow = locR - startR;
int dCol = locC - startC;
int offset (dRow < dCol) ? dRow : dCol ;
// Too lazy for now to program more to truncate the search if it goes out of the zone
// trust me I'm a programmer and it won't go out of index
for (int sD = -offset; sD <= 0; ++sD)

int sum 0 ;
for (int a = 0; a < 5; ++a)

sum += board[locR + sD + a][locC + sD + a];
//std::cout << "Access: " << locR + sD + a << " " << locC + sD + a << std::endl;

if (sum == -5)

return -1;

if (sum == 5)

return 1;



// Check the / diagonal (bottom left to top right) - holy shit I hope this works
// Calculate the offset to the left of which we start looking
// Reuse `dRow` from last time
offset = dCol; // I put a buffer so it won't go out of range
// Scenario: Top left of board. Program will go too far up. Going too far up is the only concern.
int truncate (dRow < 4) ? dRow - 4 : 0 ;
for (int sD = -offset; sD <= truncate; ++sD)

int sum 0 ;
for (int a = 0; a < 5; ++a)

sum += board[locR - sD - a][locC + sD + a];
//std::cout << "Access: " << (locR + sD + a) << " " << (locC + sD + 4 - a) << std::endl;
//std::cout << board[locR + sD + a][locC + sD + 4 - a] << " log" << std::endl;

if (sum == -5)

return -1;

if (sum == 5)

return 1;



// Draw
return 0;


int Board::getMoves()

return moves;


int Board::getSize()

return size;


int Board::getBoardPos(int r, int c)

return board[r][c];


std::vector<std::array<int, 2>> Board::getObvMoves() //TODO

std::vector<std::array<int, 2>> adjCells;

for (const std::array<int, 2>& piece : playedPieces)

int cpR = piece[0];
int cpC = piece[1];

std::vector<std::array<int, 2>> direcs;
// Above
direcs.push_back( cpR - 1, cpC - 1 );
direcs.push_back( cpR - 1, cpC );
direcs.push_back( cpR - 1, cpC + 1 );
// Beside
direcs.push_back( cpR, cpC - 1 );
direcs.push_back( cpR, cpC + 1 );
// Below
direcs.push_back( cpR + 1, cpC - 1 );
direcs.push_back( cpR + 1, cpC );
direcs.push_back( cpR + 1, cpC + 1 );
for (const std::array<int, 2>& direc : direcs)

if (std::find(adjCells.begin(), adjCells.end(), direc) != adjCells.end()) continue;
if (direc[0] < 0


return adjCells;



Below: The MCTS algorithm used contained within search.h and search.cpp



search.h



#pragma once
#define BOOST_PYTHON_STATIC_LIB
#include "boost/python/tuple.hpp"

#include "board.h"
#include "node.h"


boost::python::tuple search(Board inBoard, int team, unsigned long iterations = 10000);
void viewStats(Node* n, int t);
int simulation(Board board);
int simulation(Board board, bool rebuild);


search.cpp



#define BOOST_PYTHON_STATIC_LIB
#include "boost/python/tuple.hpp"

#include "search.h"
#include "node.h"
#include <iostream>
#include <vector>
#include <algorithm>


boost::python::tuple search(Board inBoard, int team, unsigned long iterations)

std::cout << "Iterations being run: " << iterations << std::endl;
if (!inBoard.getMoves())

std::cout << "Defaulting to middle of board for first move." << std::endl;
return boost::python::make_tuple(inBoard.getSize() / 2, inBoard.getSize() / 2);


Node topNode;
topNode.team_multiplier = -team;

int info_every(std::max(unsigned long(2000), iterations / 5));

/////////////////////////////// M C T S //////////////////////////////////
for (unsigned long iteration = 0; iteration < iterations; ++iteration)

if (0 == iteration % info_every)

std::cout << "Progress %: " << iteration * 100 / iterations << std::endl;
viewStats(&topNode, team);

Board tmp_board inBoard ;
Node* bestLeaf = topNode.SelectBest(&tmp_board);
if (bestLeaf->terminal)

bestLeaf->BackPropagateScore(bestLeaf->termscore);
continue;

if (!bestLeaf->visits)

int score = simulation(tmp_board, false);
bestLeaf->BackPropagateScore(score);
continue;

// Expand
std::vector<std::array<int, 2>> obvMoves = tmp_board.getObvMoves();
if (obvMoves.size() == 0)

bestLeaf->terminal = true;
bestLeaf->termscore = 0; // tie

for (const std::array<int, 2>&move : obvMoves)

tmp_board.playMove(move[0], move[1]);
int termscore = tmp_board.checkWin();
if (termscore) bestLeaf->CreateChild(topNode.visits, move[0], move[1], termscore);
bestLeaf->CreateChild(topNode.visits, move[0], move[1]);
// Undo the move
tmp_board.undoMove();



// Suggest move

int bestmove[2];
unsigned long maxvisits = 0;
for (const std::unique_ptr<Node>& move : topNode.children)

if (move->visits > maxvisits)

maxvisits = move->visits;
bestmove[0] = move->move_to[0];
bestmove[1] = move->move_to[1];


std::cout << "Suggest: " << bestmove[0] << "," << bestmove[1] << std::endl;
std::cout << "Win %: " << team * topNode.score * 100 / topNode.visits << std::endl;

return boost::python::make_tuple(bestmove[0], bestmove[1]);



void viewStats(Node * n, int t)
n->visits == 0) return;
std::cout << "Think: " << bestmove[0] << "," << bestmove[1] << std::endl;
std::cout << "Win %: " << t * n->score * 100 / n->visits << "n" << std::endl;



int simulation(Board board)

return simulation(board, true);


int simulation(Board board, bool rebuild)

if (rebuild)

/* Rebuild the board states to make sure it works properly
Will not guarantee proper working order initially, but
for the purpose of this function, it will suffice
*/
std::vector<std::array<int, 2>> tmp;
for (int r = 0; r < board.getSize(); ++r)

for (int c = 0; c < board.getSize(); ++c)

if (board.getBoardPos(r, c)) tmp.push_back(std::array<int, 2> r, c );


board.manOverridePlayedPieces(tmp);


while (board.getMoves() < pow(board.getSize(), 2))

// Make a random move
std::vector<std::array<int, 2>> choices = board.getObvMoves();
std::array<int, 2> randMove = choices[rand() % choices.size()];
board.playMove(randMove[0], randMove[1]);
if (board.checkWin()) return board.checkWin();

return 0;



Python



Below: the loader and module scripts



loader.py



import sys


if sys.maxsize == 2147483647: # 32 bit python install
from x86.cpymcts import Board, search

else: # 64 bit
from x64.cpymcts import Board, search


Reach.py (fancy naming)



"""
This script is designed to function as a higher level interface
to an end user & programmer. It also handles input checking and such
NO functionality is removed because of this
"""
from typing import Tuple
from loader import Board as Board_, search as search_


class Board:

def __init__(self, size: int = 19) -> None:
if size <= 0:
raise ValueError("Bad value for size.")
if size > 19:
raise ValueError("Size may not be larger than 19.")
self.board = Board_(size)

# Read only properties and (magic) methods
def __str__(self) -> str:
return self.board.string.replace('-1', 'X ').replace('1', 'O')

@property
def size(self) -> int:
"""Returns the size of the board. If 19x19 then will return 19"""
return self.board.size

@property
def move_count(self) -> int:
"""Moves that have been played since game start minus undone moves"""
return self.board.move_count

def check_win(self) -> int:
"""Returns -1 for P1 win, 0 for no win, 1 for P2 win"""
return self.board.check_win()

# The remainder of the methods to interface with Board_
def reset(self) -> None:
"""Resets to a new instance of `Board`"""
self.board.reset()

def search(self, team: int = None, iterations: int = 10000
) -> Tuple[int, int]:
"""
An alias for search(Board, int, int). Please see Reach.search
```python
>>> a = Board()
>>> a.search()
(9, 9)
>>> b = Board()
>>> search(b)
(9, 9)
```
"""
return search(self, team, iterations)

def get(self, row: int, col: int) -> int:
"""
Gets the piece (-1, 1 or 0) where -1 is a black stone (player 1)
and 1 is a white stone, and 0 is empty of the position.
```python
>>> a = Board()
>>> a.play(9, 9)
>>> a.get(9, 9)
-1
```

Args:
row: `int`
The row of the piece starting from 0

col: `int`
The column of the piece starting from 0

Returns:
piece: `int`
The piece at the position (row, col) of the board.

Raises:
`IndexError`
row or col are out of bounds.
"""
if not 0 <= row < self.size or not 0 <= col < self.size:
raise IndexError(f"Invalid coordinates (row, col): (row, col) "
f"exceeds dimentions of self.sizexself.size.")
return self.board.get(row, col)

def play(self, row: int, col: int) -> None:
"""
Places a piece at the location (row, col)
Args:
row `int`:
The row of which to place the piece
col `int`:
The column of which to place the piece

Raises:
`ValueError`
row or col are out of bounds.
"""
if not 0 <= row < self.size or not 0 <= col < self.size:
raise ValueError(f"Invalid coordinates (row, col): (row, col) "
f"exceeds dimentions of self.sizexself.size.")
self.board.play(row, col)

def undo(self) -> None:
"""Undoes the last move. May be called multiple times."""
self.board.undo()


def search(ref_board: Board, team: int = None, iterations: int = 10000
) -> Tuple[int, int]:
"""
Args:
ref_board: `Board`
The board to be examined.
team: `int`
The number of the piece that will be played this turn. First player is
-1
iterations: `int`
The amount of MCTS iterations to run.

Returns:
bestmove: `Tuple[int, int]`
A 2-tuple of the position to place the next piece for best perceived
outcome

Raises:
`ValueError`
when there is an object type mismatch

```python
>>> a = Board()
>>> a.search()
(9, 9)
>>> b = Board()
>>> search(b)
(9, 9)
```
"""
if team is not None:
print('You have chosen to manually overwrite the `team` parameter. '
'It is recommended to allow this to handle this automatically.')
else:
team = (ref_board.move_count % 2) * 2 - 1

if not isinstance(team, int):
raise ValueError(f"team should be of int and not "
f"type(team)")
if not isinstance(iterations, int):
raise ValueError(f"iterations should be of int and not "
f"type(iterations).")

if isinstance(ref_board, Board):
return search_(ref_board.board, team, iterations)
elif isinstance(ref_board, Board_):
return search_(ref_board, team, iterations)
else:
raise ValueError(f"ref_board should be of Board or Board_ and not"
f" type(ref_board).")


This is my first ever C++ work.

Not my first Python work, however, hence the overall higher quality. Now with...



✔ Python within 80 characters at all times

✔ Python functions well documented

✘ Lack of comments at some places

✘ This is your job to complain, not mine.



Give feedback on any of two languages.







share|improve this question

























    up vote
    2
    down vote

    favorite
    1












    Important



    Prebuilt Python Extensions built for Python 3.6



    Includes Python f-strings (3.6+)



    Complementary GitHub link



    https://github.com/thejhonnyguy/CPyMCTS



    Notable: Previous attempt with stock Python



    Stock Monte Carlo Tree Search implementation to a simple connect 5 game in Python (Includes bugs)



    C++ is the star of the show today with a custom MCTS implementation for the 5 in a row game. To build the pyd I used boost.python with Python 3.6 amd64 and x86 versions. (3.6.4 amd64, 3.6.5 x86)



    C++



    main.cpp



    #define BOOST_PYTHON_STATIC_LIB
    #include <string>
    #include "boost/python.hpp"
    #include "boost/python/def.hpp"
    #include "boost/python/module.hpp"

    #include "board.h"
    #include "search.h"


    BOOST_PYTHON_FUNCTION_OVERLOADS(search_overloads, search, 2, 3);


    BOOST_PYTHON_MODULE(cpymcts)

    using namespace boost::python;
    class_<Board>("Board", init<>())
    .def(init<int>())
    .def("reset", &Board::reset)
    .def("play", &Board::playMove)
    .def("undo", &Board::undoMove)
    .def("get", &Board::getBoardPos)
    .def("check_win", &Board::checkWin)
    .add_property("string", &Board::showString)
    .add_property("size", &Board::getSize)
    .add_property("move_count", &Board::getMoves)
    ;
    def("search", search, search_overloads());



    Below: Node class contained within node.h and node.cpp



    node.h



    #pragma once
    #include "board.h"
    #include <vector>
    #include <string>
    #include <memory>

    // Node class

    class Node

    public:
    Node *parentNode;
    std::vector<std::unique_ptr<Node>> children;
    unsigned long visits;
    long long score;
    unsigned long visit_offset;
    int team_multiplier;
    int move_to[2];
    bool terminal;
    int termscore;

    // Constructors
    // Default. Will assume top node
    Node();
    // Norm
    Node(Node *parent, unsigned long offset, int mt_r, int mt_c, int term = 0);

    //Methods
    void Backtrack();
    void CreateChild(unsigned long offset, int mt_r, int mt_c, int term = 0);
    void BackPropagateScore(int s);
    bool IsLeaf();
    double ucb1();
    double ucb1(double exploration);
    Node* SelectBest(Board * board);
    ;


    node.cpp



    #include "node.h"
    #include "board.h"
    #include <string>
    #include <iostream>
    #include <limits>


    // Node implementations

    Node::Node()

    // Default constuctor
    // Node * parentNode will be left undefined
    // It is the duty of the programmer to not do anything foolish
    parentNode = nullptr;
    visits = 0, score = 0, visit_offset = 0, team_multiplier = 1;


    Node::Node(Node *parent, unsigned long offset, int mt_r, int mt_c, int term)

    // Constructor used when the node is not the top node.
    // int term default value 0
    parentNode = parent;
    move_to[0] = mt_r; move_to[1] = mt_c;
    visits = 0, score = 0, visit_offset = offset, team_multiplier = -parent->team_multiplier;
    if (term) terminal = true;
    termscore = term;


    void Node::Backtrack()

    std::cout << "yeet" << std::endl;
    if (parentNode == nullptr) return;
    parentNode->Backtrack();


    void Node::CreateChild(unsigned long offset, int mt_r, int mt_c, int term)

    // Creates a child and appends it to Node<std::unique_ptr<Node>> children
    // Nodes created here use the second constructor,
    // and `this` is the parent of its children, hence the `this` argument
    children.push_back(std::make_unique<Node>(this, offset, mt_r, mt_c, term));


    void Node::BackPropagateScore(int s)

    ++visits;
    score += s;
    if (parentNode == nullptr) return;
    parentNode->BackPropagateScore(s);


    bool Node::IsLeaf()

    return !children.size();


    double Node::ucb1()

    return ucb1(sqrt(2));


    double Node::ucb1(double exploration)

    if (!visits) return std::numeric_limits<double>::infinity();
    double average = (double)score / visits;
    return team_multiplier * average + exploration * sqrt(log(parentNode->visits) / visits);


    Node* Node::SelectBest(Board * board)

    if (parentNode != nullptr) board->playMove(move_to[0], move_to[1]);
    // This will only have effect on the top node.
    if (IsLeaf()) return this;
    double max_value = -1.0;
    int max_index = 0;
    for (unsigned int i = 0; i < children.size(); i++)

    double tmp = children[i]->ucb1();
    if (tmp > max_value)

    max_value = tmp;
    max_index = i;


    return children[max_index]->SelectBest(board);



    Below: Board class contained within board.h and board.cpp



    board.h



    #pragma once
    #include <vector>
    #include <array>


    // Class declaration and class method declarations go in the .h file here

    class Board

    private:
    short board[23][23];
    int lastPlaced[2];
    int size;
    int moves;
    std::vector<std::array<int, 2>> playedPieces;

    public:

    Board(); // Default initialization to 19x19 board
    Board(int); // Initialization to NxN board

    std::string showString();
    void print();
    void reset();
    void playMove(int, int);
    void undoMove();
    void replayMoves();
    void manOverridePlayedPieces(std::vector<std::array<int, 2>> ov);
    int checkWin();
    int getMoves();
    int getSize();
    int getBoardPos(int r, int c);

    std::vector<std::array<int, 2>> getObvMoves();
    ;


    board.cpp



    #include "board.h"
    #include <string>
    #include <iostream>
    #include <vector>
    #include <array>
    #include <algorithm>


    // Board class implementation goes here (the literal functions)

    Board::Board()

    size = 19;
    reset();


    Board::Board(const int s)

    size = (s < 19) ? s : 19;
    reset();


    std::string Board::showString()

    std::string str;
    for (int r = 0; r < size; ++r)

    for (int c = 0; c < size; ++c)

    str += std::to_string(board[r][c]);
    if (board[r][c] != -1) str += " ";

    str += "n";

    return str;


    void Board::print()

    for (int r = 0; r < size; ++r)

    for (int c = 0; c < size; ++c)

    std::cout << board[r][c] << "t";

    std::cout << std::endl;



    void Board::reset()

    // Reset values
    moves = 0;

    // Reset board
    for (int r = 0; r < 23; ++r)

    for (int c = 0; c < 23; ++c)

    board[r][c] = 0;



    playedPieces.clear();



    void Board::playMove(int r, int c)

    if (!board[r][c])

    board[r][c] = (moves % 2) * 2 - 1;

    else return;
    ++moves;

    lastPlaced[0] = r;
    lastPlaced[1] = c;
    std::array<int, 2> lp r,c ;
    playedPieces.push_back(lp);


    void Board::undoMove()

    board[lastPlaced[0]][lastPlaced[1]] = 0;
    --moves;
    playedPieces.pop_back();
    if (playedPieces.size())

    lastPlaced[0] = playedPieces[playedPieces.size() - 1][0];
    lastPlaced[1] = playedPieces[playedPieces.size() - 1][1];



    void Board::replayMoves()

    std::cout << "Replaying moves:" << std::endl;
    for (unsigned int i = 0; i < playedPieces.size(); ++i)

    std::cout << i << ": " << playedPieces[i][0] << ", " << playedPieces[i][1] << std::endl;



    void Board::manOverridePlayedPieces(std::vector<std::array<int, 2>> ov)

    playedPieces = ov;
    moves = playedPieces.size();



    int Board::checkWin()

    if (!moves) return 0;
    // Fast version: Check only the surroundings of the last placed piece
    int locR lastPlaced[0] ;
    int locC lastPlaced[1] ;

    // Check horizontal of the area. Verified to work.
    int startC (locC - 4 < 0) ? 0 : locC - 4 ;
    int endC (size - locC >= 4) ? locC : size - 4 ; // Maths checks out
    for (int sC = startC; sC <= endC; ++sC)

    int sum 0 ;
    for (int a = 0; a < 5; ++a)

    sum += board[locR][sC + a];

    if (sum == -5)

    return -1;

    if (sum == 5)

    return 1;



    // Check vertical of the area. Seems to work as well...
    int startR (locR - 4 < 0) ? 0 : locR - 4 ;
    int endR (size - locR >= 4) ? locR : size - 4 ;
    for (int sR = startR; sR <= endR; ++sR)

    int sum 0 ;
    for (int a = 0; a < 5; ++a)

    sum += board[sR + a][locC];

    if (sum == -5)

    return -1;

    if (sum == 5)

    return 1;



    // Check the diagonal (top left to bottom right)
    // startR and startC will be used from above code for streamlining
    // delta-Row / delta-Col to be used in future
    int dRow = locR - startR;
    int dCol = locC - startC;
    int offset (dRow < dCol) ? dRow : dCol ;
    // Too lazy for now to program more to truncate the search if it goes out of the zone
    // trust me I'm a programmer and it won't go out of index
    for (int sD = -offset; sD <= 0; ++sD)

    int sum 0 ;
    for (int a = 0; a < 5; ++a)

    sum += board[locR + sD + a][locC + sD + a];
    //std::cout << "Access: " << locR + sD + a << " " << locC + sD + a << std::endl;

    if (sum == -5)

    return -1;

    if (sum == 5)

    return 1;



    // Check the / diagonal (bottom left to top right) - holy shit I hope this works
    // Calculate the offset to the left of which we start looking
    // Reuse `dRow` from last time
    offset = dCol; // I put a buffer so it won't go out of range
    // Scenario: Top left of board. Program will go too far up. Going too far up is the only concern.
    int truncate (dRow < 4) ? dRow - 4 : 0 ;
    for (int sD = -offset; sD <= truncate; ++sD)

    int sum 0 ;
    for (int a = 0; a < 5; ++a)

    sum += board[locR - sD - a][locC + sD + a];
    //std::cout << "Access: " << (locR + sD + a) << " " << (locC + sD + 4 - a) << std::endl;
    //std::cout << board[locR + sD + a][locC + sD + 4 - a] << " log" << std::endl;

    if (sum == -5)

    return -1;

    if (sum == 5)

    return 1;



    // Draw
    return 0;


    int Board::getMoves()

    return moves;


    int Board::getSize()

    return size;


    int Board::getBoardPos(int r, int c)

    return board[r][c];


    std::vector<std::array<int, 2>> Board::getObvMoves() //TODO

    std::vector<std::array<int, 2>> adjCells;

    for (const std::array<int, 2>& piece : playedPieces)

    int cpR = piece[0];
    int cpC = piece[1];

    std::vector<std::array<int, 2>> direcs;
    // Above
    direcs.push_back( cpR - 1, cpC - 1 );
    direcs.push_back( cpR - 1, cpC );
    direcs.push_back( cpR - 1, cpC + 1 );
    // Beside
    direcs.push_back( cpR, cpC - 1 );
    direcs.push_back( cpR, cpC + 1 );
    // Below
    direcs.push_back( cpR + 1, cpC - 1 );
    direcs.push_back( cpR + 1, cpC );
    direcs.push_back( cpR + 1, cpC + 1 );
    for (const std::array<int, 2>& direc : direcs)

    if (std::find(adjCells.begin(), adjCells.end(), direc) != adjCells.end()) continue;
    if (direc[0] < 0


    return adjCells;



    Below: The MCTS algorithm used contained within search.h and search.cpp



    search.h



    #pragma once
    #define BOOST_PYTHON_STATIC_LIB
    #include "boost/python/tuple.hpp"

    #include "board.h"
    #include "node.h"


    boost::python::tuple search(Board inBoard, int team, unsigned long iterations = 10000);
    void viewStats(Node* n, int t);
    int simulation(Board board);
    int simulation(Board board, bool rebuild);


    search.cpp



    #define BOOST_PYTHON_STATIC_LIB
    #include "boost/python/tuple.hpp"

    #include "search.h"
    #include "node.h"
    #include <iostream>
    #include <vector>
    #include <algorithm>


    boost::python::tuple search(Board inBoard, int team, unsigned long iterations)

    std::cout << "Iterations being run: " << iterations << std::endl;
    if (!inBoard.getMoves())

    std::cout << "Defaulting to middle of board for first move." << std::endl;
    return boost::python::make_tuple(inBoard.getSize() / 2, inBoard.getSize() / 2);


    Node topNode;
    topNode.team_multiplier = -team;

    int info_every(std::max(unsigned long(2000), iterations / 5));

    /////////////////////////////// M C T S //////////////////////////////////
    for (unsigned long iteration = 0; iteration < iterations; ++iteration)

    if (0 == iteration % info_every)

    std::cout << "Progress %: " << iteration * 100 / iterations << std::endl;
    viewStats(&topNode, team);

    Board tmp_board inBoard ;
    Node* bestLeaf = topNode.SelectBest(&tmp_board);
    if (bestLeaf->terminal)

    bestLeaf->BackPropagateScore(bestLeaf->termscore);
    continue;

    if (!bestLeaf->visits)

    int score = simulation(tmp_board, false);
    bestLeaf->BackPropagateScore(score);
    continue;

    // Expand
    std::vector<std::array<int, 2>> obvMoves = tmp_board.getObvMoves();
    if (obvMoves.size() == 0)

    bestLeaf->terminal = true;
    bestLeaf->termscore = 0; // tie

    for (const std::array<int, 2>&move : obvMoves)

    tmp_board.playMove(move[0], move[1]);
    int termscore = tmp_board.checkWin();
    if (termscore) bestLeaf->CreateChild(topNode.visits, move[0], move[1], termscore);
    bestLeaf->CreateChild(topNode.visits, move[0], move[1]);
    // Undo the move
    tmp_board.undoMove();



    // Suggest move

    int bestmove[2];
    unsigned long maxvisits = 0;
    for (const std::unique_ptr<Node>& move : topNode.children)

    if (move->visits > maxvisits)

    maxvisits = move->visits;
    bestmove[0] = move->move_to[0];
    bestmove[1] = move->move_to[1];


    std::cout << "Suggest: " << bestmove[0] << "," << bestmove[1] << std::endl;
    std::cout << "Win %: " << team * topNode.score * 100 / topNode.visits << std::endl;

    return boost::python::make_tuple(bestmove[0], bestmove[1]);



    void viewStats(Node * n, int t)
    n->visits == 0) return;
    std::cout << "Think: " << bestmove[0] << "," << bestmove[1] << std::endl;
    std::cout << "Win %: " << t * n->score * 100 / n->visits << "n" << std::endl;



    int simulation(Board board)

    return simulation(board, true);


    int simulation(Board board, bool rebuild)

    if (rebuild)

    /* Rebuild the board states to make sure it works properly
    Will not guarantee proper working order initially, but
    for the purpose of this function, it will suffice
    */
    std::vector<std::array<int, 2>> tmp;
    for (int r = 0; r < board.getSize(); ++r)

    for (int c = 0; c < board.getSize(); ++c)

    if (board.getBoardPos(r, c)) tmp.push_back(std::array<int, 2> r, c );


    board.manOverridePlayedPieces(tmp);


    while (board.getMoves() < pow(board.getSize(), 2))

    // Make a random move
    std::vector<std::array<int, 2>> choices = board.getObvMoves();
    std::array<int, 2> randMove = choices[rand() % choices.size()];
    board.playMove(randMove[0], randMove[1]);
    if (board.checkWin()) return board.checkWin();

    return 0;



    Python



    Below: the loader and module scripts



    loader.py



    import sys


    if sys.maxsize == 2147483647: # 32 bit python install
    from x86.cpymcts import Board, search

    else: # 64 bit
    from x64.cpymcts import Board, search


    Reach.py (fancy naming)



    """
    This script is designed to function as a higher level interface
    to an end user & programmer. It also handles input checking and such
    NO functionality is removed because of this
    """
    from typing import Tuple
    from loader import Board as Board_, search as search_


    class Board:

    def __init__(self, size: int = 19) -> None:
    if size <= 0:
    raise ValueError("Bad value for size.")
    if size > 19:
    raise ValueError("Size may not be larger than 19.")
    self.board = Board_(size)

    # Read only properties and (magic) methods
    def __str__(self) -> str:
    return self.board.string.replace('-1', 'X ').replace('1', 'O')

    @property
    def size(self) -> int:
    """Returns the size of the board. If 19x19 then will return 19"""
    return self.board.size

    @property
    def move_count(self) -> int:
    """Moves that have been played since game start minus undone moves"""
    return self.board.move_count

    def check_win(self) -> int:
    """Returns -1 for P1 win, 0 for no win, 1 for P2 win"""
    return self.board.check_win()

    # The remainder of the methods to interface with Board_
    def reset(self) -> None:
    """Resets to a new instance of `Board`"""
    self.board.reset()

    def search(self, team: int = None, iterations: int = 10000
    ) -> Tuple[int, int]:
    """
    An alias for search(Board, int, int). Please see Reach.search
    ```python
    >>> a = Board()
    >>> a.search()
    (9, 9)
    >>> b = Board()
    >>> search(b)
    (9, 9)
    ```
    """
    return search(self, team, iterations)

    def get(self, row: int, col: int) -> int:
    """
    Gets the piece (-1, 1 or 0) where -1 is a black stone (player 1)
    and 1 is a white stone, and 0 is empty of the position.
    ```python
    >>> a = Board()
    >>> a.play(9, 9)
    >>> a.get(9, 9)
    -1
    ```

    Args:
    row: `int`
    The row of the piece starting from 0

    col: `int`
    The column of the piece starting from 0

    Returns:
    piece: `int`
    The piece at the position (row, col) of the board.

    Raises:
    `IndexError`
    row or col are out of bounds.
    """
    if not 0 <= row < self.size or not 0 <= col < self.size:
    raise IndexError(f"Invalid coordinates (row, col): (row, col) "
    f"exceeds dimentions of self.sizexself.size.")
    return self.board.get(row, col)

    def play(self, row: int, col: int) -> None:
    """
    Places a piece at the location (row, col)
    Args:
    row `int`:
    The row of which to place the piece
    col `int`:
    The column of which to place the piece

    Raises:
    `ValueError`
    row or col are out of bounds.
    """
    if not 0 <= row < self.size or not 0 <= col < self.size:
    raise ValueError(f"Invalid coordinates (row, col): (row, col) "
    f"exceeds dimentions of self.sizexself.size.")
    self.board.play(row, col)

    def undo(self) -> None:
    """Undoes the last move. May be called multiple times."""
    self.board.undo()


    def search(ref_board: Board, team: int = None, iterations: int = 10000
    ) -> Tuple[int, int]:
    """
    Args:
    ref_board: `Board`
    The board to be examined.
    team: `int`
    The number of the piece that will be played this turn. First player is
    -1
    iterations: `int`
    The amount of MCTS iterations to run.

    Returns:
    bestmove: `Tuple[int, int]`
    A 2-tuple of the position to place the next piece for best perceived
    outcome

    Raises:
    `ValueError`
    when there is an object type mismatch

    ```python
    >>> a = Board()
    >>> a.search()
    (9, 9)
    >>> b = Board()
    >>> search(b)
    (9, 9)
    ```
    """
    if team is not None:
    print('You have chosen to manually overwrite the `team` parameter. '
    'It is recommended to allow this to handle this automatically.')
    else:
    team = (ref_board.move_count % 2) * 2 - 1

    if not isinstance(team, int):
    raise ValueError(f"team should be of int and not "
    f"type(team)")
    if not isinstance(iterations, int):
    raise ValueError(f"iterations should be of int and not "
    f"type(iterations).")

    if isinstance(ref_board, Board):
    return search_(ref_board.board, team, iterations)
    elif isinstance(ref_board, Board_):
    return search_(ref_board, team, iterations)
    else:
    raise ValueError(f"ref_board should be of Board or Board_ and not"
    f" type(ref_board).")


    This is my first ever C++ work.

    Not my first Python work, however, hence the overall higher quality. Now with...



    ✔ Python within 80 characters at all times

    ✔ Python functions well documented

    ✘ Lack of comments at some places

    ✘ This is your job to complain, not mine.



    Give feedback on any of two languages.







    share|improve this question





















      up vote
      2
      down vote

      favorite
      1









      up vote
      2
      down vote

      favorite
      1






      1





      Important



      Prebuilt Python Extensions built for Python 3.6



      Includes Python f-strings (3.6+)



      Complementary GitHub link



      https://github.com/thejhonnyguy/CPyMCTS



      Notable: Previous attempt with stock Python



      Stock Monte Carlo Tree Search implementation to a simple connect 5 game in Python (Includes bugs)



      C++ is the star of the show today with a custom MCTS implementation for the 5 in a row game. To build the pyd I used boost.python with Python 3.6 amd64 and x86 versions. (3.6.4 amd64, 3.6.5 x86)



      C++



      main.cpp



      #define BOOST_PYTHON_STATIC_LIB
      #include <string>
      #include "boost/python.hpp"
      #include "boost/python/def.hpp"
      #include "boost/python/module.hpp"

      #include "board.h"
      #include "search.h"


      BOOST_PYTHON_FUNCTION_OVERLOADS(search_overloads, search, 2, 3);


      BOOST_PYTHON_MODULE(cpymcts)

      using namespace boost::python;
      class_<Board>("Board", init<>())
      .def(init<int>())
      .def("reset", &Board::reset)
      .def("play", &Board::playMove)
      .def("undo", &Board::undoMove)
      .def("get", &Board::getBoardPos)
      .def("check_win", &Board::checkWin)
      .add_property("string", &Board::showString)
      .add_property("size", &Board::getSize)
      .add_property("move_count", &Board::getMoves)
      ;
      def("search", search, search_overloads());



      Below: Node class contained within node.h and node.cpp



      node.h



      #pragma once
      #include "board.h"
      #include <vector>
      #include <string>
      #include <memory>

      // Node class

      class Node

      public:
      Node *parentNode;
      std::vector<std::unique_ptr<Node>> children;
      unsigned long visits;
      long long score;
      unsigned long visit_offset;
      int team_multiplier;
      int move_to[2];
      bool terminal;
      int termscore;

      // Constructors
      // Default. Will assume top node
      Node();
      // Norm
      Node(Node *parent, unsigned long offset, int mt_r, int mt_c, int term = 0);

      //Methods
      void Backtrack();
      void CreateChild(unsigned long offset, int mt_r, int mt_c, int term = 0);
      void BackPropagateScore(int s);
      bool IsLeaf();
      double ucb1();
      double ucb1(double exploration);
      Node* SelectBest(Board * board);
      ;


      node.cpp



      #include "node.h"
      #include "board.h"
      #include <string>
      #include <iostream>
      #include <limits>


      // Node implementations

      Node::Node()

      // Default constuctor
      // Node * parentNode will be left undefined
      // It is the duty of the programmer to not do anything foolish
      parentNode = nullptr;
      visits = 0, score = 0, visit_offset = 0, team_multiplier = 1;


      Node::Node(Node *parent, unsigned long offset, int mt_r, int mt_c, int term)

      // Constructor used when the node is not the top node.
      // int term default value 0
      parentNode = parent;
      move_to[0] = mt_r; move_to[1] = mt_c;
      visits = 0, score = 0, visit_offset = offset, team_multiplier = -parent->team_multiplier;
      if (term) terminal = true;
      termscore = term;


      void Node::Backtrack()

      std::cout << "yeet" << std::endl;
      if (parentNode == nullptr) return;
      parentNode->Backtrack();


      void Node::CreateChild(unsigned long offset, int mt_r, int mt_c, int term)

      // Creates a child and appends it to Node<std::unique_ptr<Node>> children
      // Nodes created here use the second constructor,
      // and `this` is the parent of its children, hence the `this` argument
      children.push_back(std::make_unique<Node>(this, offset, mt_r, mt_c, term));


      void Node::BackPropagateScore(int s)

      ++visits;
      score += s;
      if (parentNode == nullptr) return;
      parentNode->BackPropagateScore(s);


      bool Node::IsLeaf()

      return !children.size();


      double Node::ucb1()

      return ucb1(sqrt(2));


      double Node::ucb1(double exploration)

      if (!visits) return std::numeric_limits<double>::infinity();
      double average = (double)score / visits;
      return team_multiplier * average + exploration * sqrt(log(parentNode->visits) / visits);


      Node* Node::SelectBest(Board * board)

      if (parentNode != nullptr) board->playMove(move_to[0], move_to[1]);
      // This will only have effect on the top node.
      if (IsLeaf()) return this;
      double max_value = -1.0;
      int max_index = 0;
      for (unsigned int i = 0; i < children.size(); i++)

      double tmp = children[i]->ucb1();
      if (tmp > max_value)

      max_value = tmp;
      max_index = i;


      return children[max_index]->SelectBest(board);



      Below: Board class contained within board.h and board.cpp



      board.h



      #pragma once
      #include <vector>
      #include <array>


      // Class declaration and class method declarations go in the .h file here

      class Board

      private:
      short board[23][23];
      int lastPlaced[2];
      int size;
      int moves;
      std::vector<std::array<int, 2>> playedPieces;

      public:

      Board(); // Default initialization to 19x19 board
      Board(int); // Initialization to NxN board

      std::string showString();
      void print();
      void reset();
      void playMove(int, int);
      void undoMove();
      void replayMoves();
      void manOverridePlayedPieces(std::vector<std::array<int, 2>> ov);
      int checkWin();
      int getMoves();
      int getSize();
      int getBoardPos(int r, int c);

      std::vector<std::array<int, 2>> getObvMoves();
      ;


      board.cpp



      #include "board.h"
      #include <string>
      #include <iostream>
      #include <vector>
      #include <array>
      #include <algorithm>


      // Board class implementation goes here (the literal functions)

      Board::Board()

      size = 19;
      reset();


      Board::Board(const int s)

      size = (s < 19) ? s : 19;
      reset();


      std::string Board::showString()

      std::string str;
      for (int r = 0; r < size; ++r)

      for (int c = 0; c < size; ++c)

      str += std::to_string(board[r][c]);
      if (board[r][c] != -1) str += " ";

      str += "n";

      return str;


      void Board::print()

      for (int r = 0; r < size; ++r)

      for (int c = 0; c < size; ++c)

      std::cout << board[r][c] << "t";

      std::cout << std::endl;



      void Board::reset()

      // Reset values
      moves = 0;

      // Reset board
      for (int r = 0; r < 23; ++r)

      for (int c = 0; c < 23; ++c)

      board[r][c] = 0;



      playedPieces.clear();



      void Board::playMove(int r, int c)

      if (!board[r][c])

      board[r][c] = (moves % 2) * 2 - 1;

      else return;
      ++moves;

      lastPlaced[0] = r;
      lastPlaced[1] = c;
      std::array<int, 2> lp r,c ;
      playedPieces.push_back(lp);


      void Board::undoMove()

      board[lastPlaced[0]][lastPlaced[1]] = 0;
      --moves;
      playedPieces.pop_back();
      if (playedPieces.size())

      lastPlaced[0] = playedPieces[playedPieces.size() - 1][0];
      lastPlaced[1] = playedPieces[playedPieces.size() - 1][1];



      void Board::replayMoves()

      std::cout << "Replaying moves:" << std::endl;
      for (unsigned int i = 0; i < playedPieces.size(); ++i)

      std::cout << i << ": " << playedPieces[i][0] << ", " << playedPieces[i][1] << std::endl;



      void Board::manOverridePlayedPieces(std::vector<std::array<int, 2>> ov)

      playedPieces = ov;
      moves = playedPieces.size();



      int Board::checkWin()

      if (!moves) return 0;
      // Fast version: Check only the surroundings of the last placed piece
      int locR lastPlaced[0] ;
      int locC lastPlaced[1] ;

      // Check horizontal of the area. Verified to work.
      int startC (locC - 4 < 0) ? 0 : locC - 4 ;
      int endC (size - locC >= 4) ? locC : size - 4 ; // Maths checks out
      for (int sC = startC; sC <= endC; ++sC)

      int sum 0 ;
      for (int a = 0; a < 5; ++a)

      sum += board[locR][sC + a];

      if (sum == -5)

      return -1;

      if (sum == 5)

      return 1;



      // Check vertical of the area. Seems to work as well...
      int startR (locR - 4 < 0) ? 0 : locR - 4 ;
      int endR (size - locR >= 4) ? locR : size - 4 ;
      for (int sR = startR; sR <= endR; ++sR)

      int sum 0 ;
      for (int a = 0; a < 5; ++a)

      sum += board[sR + a][locC];

      if (sum == -5)

      return -1;

      if (sum == 5)

      return 1;



      // Check the diagonal (top left to bottom right)
      // startR and startC will be used from above code for streamlining
      // delta-Row / delta-Col to be used in future
      int dRow = locR - startR;
      int dCol = locC - startC;
      int offset (dRow < dCol) ? dRow : dCol ;
      // Too lazy for now to program more to truncate the search if it goes out of the zone
      // trust me I'm a programmer and it won't go out of index
      for (int sD = -offset; sD <= 0; ++sD)

      int sum 0 ;
      for (int a = 0; a < 5; ++a)

      sum += board[locR + sD + a][locC + sD + a];
      //std::cout << "Access: " << locR + sD + a << " " << locC + sD + a << std::endl;

      if (sum == -5)

      return -1;

      if (sum == 5)

      return 1;



      // Check the / diagonal (bottom left to top right) - holy shit I hope this works
      // Calculate the offset to the left of which we start looking
      // Reuse `dRow` from last time
      offset = dCol; // I put a buffer so it won't go out of range
      // Scenario: Top left of board. Program will go too far up. Going too far up is the only concern.
      int truncate (dRow < 4) ? dRow - 4 : 0 ;
      for (int sD = -offset; sD <= truncate; ++sD)

      int sum 0 ;
      for (int a = 0; a < 5; ++a)

      sum += board[locR - sD - a][locC + sD + a];
      //std::cout << "Access: " << (locR + sD + a) << " " << (locC + sD + 4 - a) << std::endl;
      //std::cout << board[locR + sD + a][locC + sD + 4 - a] << " log" << std::endl;

      if (sum == -5)

      return -1;

      if (sum == 5)

      return 1;



      // Draw
      return 0;


      int Board::getMoves()

      return moves;


      int Board::getSize()

      return size;


      int Board::getBoardPos(int r, int c)

      return board[r][c];


      std::vector<std::array<int, 2>> Board::getObvMoves() //TODO

      std::vector<std::array<int, 2>> adjCells;

      for (const std::array<int, 2>& piece : playedPieces)

      int cpR = piece[0];
      int cpC = piece[1];

      std::vector<std::array<int, 2>> direcs;
      // Above
      direcs.push_back( cpR - 1, cpC - 1 );
      direcs.push_back( cpR - 1, cpC );
      direcs.push_back( cpR - 1, cpC + 1 );
      // Beside
      direcs.push_back( cpR, cpC - 1 );
      direcs.push_back( cpR, cpC + 1 );
      // Below
      direcs.push_back( cpR + 1, cpC - 1 );
      direcs.push_back( cpR + 1, cpC );
      direcs.push_back( cpR + 1, cpC + 1 );
      for (const std::array<int, 2>& direc : direcs)

      if (std::find(adjCells.begin(), adjCells.end(), direc) != adjCells.end()) continue;
      if (direc[0] < 0


      return adjCells;



      Below: The MCTS algorithm used contained within search.h and search.cpp



      search.h



      #pragma once
      #define BOOST_PYTHON_STATIC_LIB
      #include "boost/python/tuple.hpp"

      #include "board.h"
      #include "node.h"


      boost::python::tuple search(Board inBoard, int team, unsigned long iterations = 10000);
      void viewStats(Node* n, int t);
      int simulation(Board board);
      int simulation(Board board, bool rebuild);


      search.cpp



      #define BOOST_PYTHON_STATIC_LIB
      #include "boost/python/tuple.hpp"

      #include "search.h"
      #include "node.h"
      #include <iostream>
      #include <vector>
      #include <algorithm>


      boost::python::tuple search(Board inBoard, int team, unsigned long iterations)

      std::cout << "Iterations being run: " << iterations << std::endl;
      if (!inBoard.getMoves())

      std::cout << "Defaulting to middle of board for first move." << std::endl;
      return boost::python::make_tuple(inBoard.getSize() / 2, inBoard.getSize() / 2);


      Node topNode;
      topNode.team_multiplier = -team;

      int info_every(std::max(unsigned long(2000), iterations / 5));

      /////////////////////////////// M C T S //////////////////////////////////
      for (unsigned long iteration = 0; iteration < iterations; ++iteration)

      if (0 == iteration % info_every)

      std::cout << "Progress %: " << iteration * 100 / iterations << std::endl;
      viewStats(&topNode, team);

      Board tmp_board inBoard ;
      Node* bestLeaf = topNode.SelectBest(&tmp_board);
      if (bestLeaf->terminal)

      bestLeaf->BackPropagateScore(bestLeaf->termscore);
      continue;

      if (!bestLeaf->visits)

      int score = simulation(tmp_board, false);
      bestLeaf->BackPropagateScore(score);
      continue;

      // Expand
      std::vector<std::array<int, 2>> obvMoves = tmp_board.getObvMoves();
      if (obvMoves.size() == 0)

      bestLeaf->terminal = true;
      bestLeaf->termscore = 0; // tie

      for (const std::array<int, 2>&move : obvMoves)

      tmp_board.playMove(move[0], move[1]);
      int termscore = tmp_board.checkWin();
      if (termscore) bestLeaf->CreateChild(topNode.visits, move[0], move[1], termscore);
      bestLeaf->CreateChild(topNode.visits, move[0], move[1]);
      // Undo the move
      tmp_board.undoMove();



      // Suggest move

      int bestmove[2];
      unsigned long maxvisits = 0;
      for (const std::unique_ptr<Node>& move : topNode.children)

      if (move->visits > maxvisits)

      maxvisits = move->visits;
      bestmove[0] = move->move_to[0];
      bestmove[1] = move->move_to[1];


      std::cout << "Suggest: " << bestmove[0] << "," << bestmove[1] << std::endl;
      std::cout << "Win %: " << team * topNode.score * 100 / topNode.visits << std::endl;

      return boost::python::make_tuple(bestmove[0], bestmove[1]);



      void viewStats(Node * n, int t)
      n->visits == 0) return;
      std::cout << "Think: " << bestmove[0] << "," << bestmove[1] << std::endl;
      std::cout << "Win %: " << t * n->score * 100 / n->visits << "n" << std::endl;



      int simulation(Board board)

      return simulation(board, true);


      int simulation(Board board, bool rebuild)

      if (rebuild)

      /* Rebuild the board states to make sure it works properly
      Will not guarantee proper working order initially, but
      for the purpose of this function, it will suffice
      */
      std::vector<std::array<int, 2>> tmp;
      for (int r = 0; r < board.getSize(); ++r)

      for (int c = 0; c < board.getSize(); ++c)

      if (board.getBoardPos(r, c)) tmp.push_back(std::array<int, 2> r, c );


      board.manOverridePlayedPieces(tmp);


      while (board.getMoves() < pow(board.getSize(), 2))

      // Make a random move
      std::vector<std::array<int, 2>> choices = board.getObvMoves();
      std::array<int, 2> randMove = choices[rand() % choices.size()];
      board.playMove(randMove[0], randMove[1]);
      if (board.checkWin()) return board.checkWin();

      return 0;



      Python



      Below: the loader and module scripts



      loader.py



      import sys


      if sys.maxsize == 2147483647: # 32 bit python install
      from x86.cpymcts import Board, search

      else: # 64 bit
      from x64.cpymcts import Board, search


      Reach.py (fancy naming)



      """
      This script is designed to function as a higher level interface
      to an end user & programmer. It also handles input checking and such
      NO functionality is removed because of this
      """
      from typing import Tuple
      from loader import Board as Board_, search as search_


      class Board:

      def __init__(self, size: int = 19) -> None:
      if size <= 0:
      raise ValueError("Bad value for size.")
      if size > 19:
      raise ValueError("Size may not be larger than 19.")
      self.board = Board_(size)

      # Read only properties and (magic) methods
      def __str__(self) -> str:
      return self.board.string.replace('-1', 'X ').replace('1', 'O')

      @property
      def size(self) -> int:
      """Returns the size of the board. If 19x19 then will return 19"""
      return self.board.size

      @property
      def move_count(self) -> int:
      """Moves that have been played since game start minus undone moves"""
      return self.board.move_count

      def check_win(self) -> int:
      """Returns -1 for P1 win, 0 for no win, 1 for P2 win"""
      return self.board.check_win()

      # The remainder of the methods to interface with Board_
      def reset(self) -> None:
      """Resets to a new instance of `Board`"""
      self.board.reset()

      def search(self, team: int = None, iterations: int = 10000
      ) -> Tuple[int, int]:
      """
      An alias for search(Board, int, int). Please see Reach.search
      ```python
      >>> a = Board()
      >>> a.search()
      (9, 9)
      >>> b = Board()
      >>> search(b)
      (9, 9)
      ```
      """
      return search(self, team, iterations)

      def get(self, row: int, col: int) -> int:
      """
      Gets the piece (-1, 1 or 0) where -1 is a black stone (player 1)
      and 1 is a white stone, and 0 is empty of the position.
      ```python
      >>> a = Board()
      >>> a.play(9, 9)
      >>> a.get(9, 9)
      -1
      ```

      Args:
      row: `int`
      The row of the piece starting from 0

      col: `int`
      The column of the piece starting from 0

      Returns:
      piece: `int`
      The piece at the position (row, col) of the board.

      Raises:
      `IndexError`
      row or col are out of bounds.
      """
      if not 0 <= row < self.size or not 0 <= col < self.size:
      raise IndexError(f"Invalid coordinates (row, col): (row, col) "
      f"exceeds dimentions of self.sizexself.size.")
      return self.board.get(row, col)

      def play(self, row: int, col: int) -> None:
      """
      Places a piece at the location (row, col)
      Args:
      row `int`:
      The row of which to place the piece
      col `int`:
      The column of which to place the piece

      Raises:
      `ValueError`
      row or col are out of bounds.
      """
      if not 0 <= row < self.size or not 0 <= col < self.size:
      raise ValueError(f"Invalid coordinates (row, col): (row, col) "
      f"exceeds dimentions of self.sizexself.size.")
      self.board.play(row, col)

      def undo(self) -> None:
      """Undoes the last move. May be called multiple times."""
      self.board.undo()


      def search(ref_board: Board, team: int = None, iterations: int = 10000
      ) -> Tuple[int, int]:
      """
      Args:
      ref_board: `Board`
      The board to be examined.
      team: `int`
      The number of the piece that will be played this turn. First player is
      -1
      iterations: `int`
      The amount of MCTS iterations to run.

      Returns:
      bestmove: `Tuple[int, int]`
      A 2-tuple of the position to place the next piece for best perceived
      outcome

      Raises:
      `ValueError`
      when there is an object type mismatch

      ```python
      >>> a = Board()
      >>> a.search()
      (9, 9)
      >>> b = Board()
      >>> search(b)
      (9, 9)
      ```
      """
      if team is not None:
      print('You have chosen to manually overwrite the `team` parameter. '
      'It is recommended to allow this to handle this automatically.')
      else:
      team = (ref_board.move_count % 2) * 2 - 1

      if not isinstance(team, int):
      raise ValueError(f"team should be of int and not "
      f"type(team)")
      if not isinstance(iterations, int):
      raise ValueError(f"iterations should be of int and not "
      f"type(iterations).")

      if isinstance(ref_board, Board):
      return search_(ref_board.board, team, iterations)
      elif isinstance(ref_board, Board_):
      return search_(ref_board, team, iterations)
      else:
      raise ValueError(f"ref_board should be of Board or Board_ and not"
      f" type(ref_board).")


      This is my first ever C++ work.

      Not my first Python work, however, hence the overall higher quality. Now with...



      ✔ Python within 80 characters at all times

      ✔ Python functions well documented

      ✘ Lack of comments at some places

      ✘ This is your job to complain, not mine.



      Give feedback on any of two languages.







      share|improve this question











      Important



      Prebuilt Python Extensions built for Python 3.6



      Includes Python f-strings (3.6+)



      Complementary GitHub link



      https://github.com/thejhonnyguy/CPyMCTS



      Notable: Previous attempt with stock Python



      Stock Monte Carlo Tree Search implementation to a simple connect 5 game in Python (Includes bugs)



      C++ is the star of the show today with a custom MCTS implementation for the 5 in a row game. To build the pyd I used boost.python with Python 3.6 amd64 and x86 versions. (3.6.4 amd64, 3.6.5 x86)



      C++



      main.cpp



      #define BOOST_PYTHON_STATIC_LIB
      #include <string>
      #include "boost/python.hpp"
      #include "boost/python/def.hpp"
      #include "boost/python/module.hpp"

      #include "board.h"
      #include "search.h"


      BOOST_PYTHON_FUNCTION_OVERLOADS(search_overloads, search, 2, 3);


      BOOST_PYTHON_MODULE(cpymcts)

      using namespace boost::python;
      class_<Board>("Board", init<>())
      .def(init<int>())
      .def("reset", &Board::reset)
      .def("play", &Board::playMove)
      .def("undo", &Board::undoMove)
      .def("get", &Board::getBoardPos)
      .def("check_win", &Board::checkWin)
      .add_property("string", &Board::showString)
      .add_property("size", &Board::getSize)
      .add_property("move_count", &Board::getMoves)
      ;
      def("search", search, search_overloads());



      Below: Node class contained within node.h and node.cpp



      node.h



      #pragma once
      #include "board.h"
      #include <vector>
      #include <string>
      #include <memory>

      // Node class

      class Node

      public:
      Node *parentNode;
      std::vector<std::unique_ptr<Node>> children;
      unsigned long visits;
      long long score;
      unsigned long visit_offset;
      int team_multiplier;
      int move_to[2];
      bool terminal;
      int termscore;

      // Constructors
      // Default. Will assume top node
      Node();
      // Norm
      Node(Node *parent, unsigned long offset, int mt_r, int mt_c, int term = 0);

      //Methods
      void Backtrack();
      void CreateChild(unsigned long offset, int mt_r, int mt_c, int term = 0);
      void BackPropagateScore(int s);
      bool IsLeaf();
      double ucb1();
      double ucb1(double exploration);
      Node* SelectBest(Board * board);
      ;


      node.cpp



      #include "node.h"
      #include "board.h"
      #include <string>
      #include <iostream>
      #include <limits>


      // Node implementations

      Node::Node()

      // Default constuctor
      // Node * parentNode will be left undefined
      // It is the duty of the programmer to not do anything foolish
      parentNode = nullptr;
      visits = 0, score = 0, visit_offset = 0, team_multiplier = 1;


      Node::Node(Node *parent, unsigned long offset, int mt_r, int mt_c, int term)

      // Constructor used when the node is not the top node.
      // int term default value 0
      parentNode = parent;
      move_to[0] = mt_r; move_to[1] = mt_c;
      visits = 0, score = 0, visit_offset = offset, team_multiplier = -parent->team_multiplier;
      if (term) terminal = true;
      termscore = term;


      void Node::Backtrack()

      std::cout << "yeet" << std::endl;
      if (parentNode == nullptr) return;
      parentNode->Backtrack();


      void Node::CreateChild(unsigned long offset, int mt_r, int mt_c, int term)

      // Creates a child and appends it to Node<std::unique_ptr<Node>> children
      // Nodes created here use the second constructor,
      // and `this` is the parent of its children, hence the `this` argument
      children.push_back(std::make_unique<Node>(this, offset, mt_r, mt_c, term));


      void Node::BackPropagateScore(int s)

      ++visits;
      score += s;
      if (parentNode == nullptr) return;
      parentNode->BackPropagateScore(s);


      bool Node::IsLeaf()

      return !children.size();


      double Node::ucb1()

      return ucb1(sqrt(2));


      double Node::ucb1(double exploration)

      if (!visits) return std::numeric_limits<double>::infinity();
      double average = (double)score / visits;
      return team_multiplier * average + exploration * sqrt(log(parentNode->visits) / visits);


      Node* Node::SelectBest(Board * board)

      if (parentNode != nullptr) board->playMove(move_to[0], move_to[1]);
      // This will only have effect on the top node.
      if (IsLeaf()) return this;
      double max_value = -1.0;
      int max_index = 0;
      for (unsigned int i = 0; i < children.size(); i++)

      double tmp = children[i]->ucb1();
      if (tmp > max_value)

      max_value = tmp;
      max_index = i;


      return children[max_index]->SelectBest(board);



      Below: Board class contained within board.h and board.cpp



      board.h



      #pragma once
      #include <vector>
      #include <array>


      // Class declaration and class method declarations go in the .h file here

      class Board

      private:
      short board[23][23];
      int lastPlaced[2];
      int size;
      int moves;
      std::vector<std::array<int, 2>> playedPieces;

      public:

      Board(); // Default initialization to 19x19 board
      Board(int); // Initialization to NxN board

      std::string showString();
      void print();
      void reset();
      void playMove(int, int);
      void undoMove();
      void replayMoves();
      void manOverridePlayedPieces(std::vector<std::array<int, 2>> ov);
      int checkWin();
      int getMoves();
      int getSize();
      int getBoardPos(int r, int c);

      std::vector<std::array<int, 2>> getObvMoves();
      ;


      board.cpp



      #include "board.h"
      #include <string>
      #include <iostream>
      #include <vector>
      #include <array>
      #include <algorithm>


      // Board class implementation goes here (the literal functions)

      Board::Board()

      size = 19;
      reset();


      Board::Board(const int s)

      size = (s < 19) ? s : 19;
      reset();


      std::string Board::showString()

      std::string str;
      for (int r = 0; r < size; ++r)

      for (int c = 0; c < size; ++c)

      str += std::to_string(board[r][c]);
      if (board[r][c] != -1) str += " ";

      str += "n";

      return str;


      void Board::print()

      for (int r = 0; r < size; ++r)

      for (int c = 0; c < size; ++c)

      std::cout << board[r][c] << "t";

      std::cout << std::endl;



      void Board::reset()

      // Reset values
      moves = 0;

      // Reset board
      for (int r = 0; r < 23; ++r)

      for (int c = 0; c < 23; ++c)

      board[r][c] = 0;



      playedPieces.clear();



      void Board::playMove(int r, int c)

      if (!board[r][c])

      board[r][c] = (moves % 2) * 2 - 1;

      else return;
      ++moves;

      lastPlaced[0] = r;
      lastPlaced[1] = c;
      std::array<int, 2> lp r,c ;
      playedPieces.push_back(lp);


      void Board::undoMove()

      board[lastPlaced[0]][lastPlaced[1]] = 0;
      --moves;
      playedPieces.pop_back();
      if (playedPieces.size())

      lastPlaced[0] = playedPieces[playedPieces.size() - 1][0];
      lastPlaced[1] = playedPieces[playedPieces.size() - 1][1];



      void Board::replayMoves()

      std::cout << "Replaying moves:" << std::endl;
      for (unsigned int i = 0; i < playedPieces.size(); ++i)

      std::cout << i << ": " << playedPieces[i][0] << ", " << playedPieces[i][1] << std::endl;



      void Board::manOverridePlayedPieces(std::vector<std::array<int, 2>> ov)

      playedPieces = ov;
      moves = playedPieces.size();



      int Board::checkWin()

      if (!moves) return 0;
      // Fast version: Check only the surroundings of the last placed piece
      int locR lastPlaced[0] ;
      int locC lastPlaced[1] ;

      // Check horizontal of the area. Verified to work.
      int startC (locC - 4 < 0) ? 0 : locC - 4 ;
      int endC (size - locC >= 4) ? locC : size - 4 ; // Maths checks out
      for (int sC = startC; sC <= endC; ++sC)

      int sum 0 ;
      for (int a = 0; a < 5; ++a)

      sum += board[locR][sC + a];

      if (sum == -5)

      return -1;

      if (sum == 5)

      return 1;



      // Check vertical of the area. Seems to work as well...
      int startR (locR - 4 < 0) ? 0 : locR - 4 ;
      int endR (size - locR >= 4) ? locR : size - 4 ;
      for (int sR = startR; sR <= endR; ++sR)

      int sum 0 ;
      for (int a = 0; a < 5; ++a)

      sum += board[sR + a][locC];

      if (sum == -5)

      return -1;

      if (sum == 5)

      return 1;



      // Check the diagonal (top left to bottom right)
      // startR and startC will be used from above code for streamlining
      // delta-Row / delta-Col to be used in future
      int dRow = locR - startR;
      int dCol = locC - startC;
      int offset (dRow < dCol) ? dRow : dCol ;
      // Too lazy for now to program more to truncate the search if it goes out of the zone
      // trust me I'm a programmer and it won't go out of index
      for (int sD = -offset; sD <= 0; ++sD)

      int sum 0 ;
      for (int a = 0; a < 5; ++a)

      sum += board[locR + sD + a][locC + sD + a];
      //std::cout << "Access: " << locR + sD + a << " " << locC + sD + a << std::endl;

      if (sum == -5)

      return -1;

      if (sum == 5)

      return 1;



      // Check the / diagonal (bottom left to top right) - holy shit I hope this works
      // Calculate the offset to the left of which we start looking
      // Reuse `dRow` from last time
      offset = dCol; // I put a buffer so it won't go out of range
      // Scenario: Top left of board. Program will go too far up. Going too far up is the only concern.
      int truncate (dRow < 4) ? dRow - 4 : 0 ;
      for (int sD = -offset; sD <= truncate; ++sD)

      int sum 0 ;
      for (int a = 0; a < 5; ++a)

      sum += board[locR - sD - a][locC + sD + a];
      //std::cout << "Access: " << (locR + sD + a) << " " << (locC + sD + 4 - a) << std::endl;
      //std::cout << board[locR + sD + a][locC + sD + 4 - a] << " log" << std::endl;

      if (sum == -5)

      return -1;

      if (sum == 5)

      return 1;



      // Draw
      return 0;


      int Board::getMoves()

      return moves;


      int Board::getSize()

      return size;


      int Board::getBoardPos(int r, int c)

      return board[r][c];


      std::vector<std::array<int, 2>> Board::getObvMoves() //TODO

      std::vector<std::array<int, 2>> adjCells;

      for (const std::array<int, 2>& piece : playedPieces)

      int cpR = piece[0];
      int cpC = piece[1];

      std::vector<std::array<int, 2>> direcs;
      // Above
      direcs.push_back( cpR - 1, cpC - 1 );
      direcs.push_back( cpR - 1, cpC );
      direcs.push_back( cpR - 1, cpC + 1 );
      // Beside
      direcs.push_back( cpR, cpC - 1 );
      direcs.push_back( cpR, cpC + 1 );
      // Below
      direcs.push_back( cpR + 1, cpC - 1 );
      direcs.push_back( cpR + 1, cpC );
      direcs.push_back( cpR + 1, cpC + 1 );
      for (const std::array<int, 2>& direc : direcs)

      if (std::find(adjCells.begin(), adjCells.end(), direc) != adjCells.end()) continue;
      if (direc[0] < 0


      return adjCells;



      Below: The MCTS algorithm used contained within search.h and search.cpp



      search.h



      #pragma once
      #define BOOST_PYTHON_STATIC_LIB
      #include "boost/python/tuple.hpp"

      #include "board.h"
      #include "node.h"


      boost::python::tuple search(Board inBoard, int team, unsigned long iterations = 10000);
      void viewStats(Node* n, int t);
      int simulation(Board board);
      int simulation(Board board, bool rebuild);


      search.cpp



      #define BOOST_PYTHON_STATIC_LIB
      #include "boost/python/tuple.hpp"

      #include "search.h"
      #include "node.h"
      #include <iostream>
      #include <vector>
      #include <algorithm>


      boost::python::tuple search(Board inBoard, int team, unsigned long iterations)

      std::cout << "Iterations being run: " << iterations << std::endl;
      if (!inBoard.getMoves())

      std::cout << "Defaulting to middle of board for first move." << std::endl;
      return boost::python::make_tuple(inBoard.getSize() / 2, inBoard.getSize() / 2);


      Node topNode;
      topNode.team_multiplier = -team;

      int info_every(std::max(unsigned long(2000), iterations / 5));

      /////////////////////////////// M C T S //////////////////////////////////
      for (unsigned long iteration = 0; iteration < iterations; ++iteration)

      if (0 == iteration % info_every)

      std::cout << "Progress %: " << iteration * 100 / iterations << std::endl;
      viewStats(&topNode, team);

      Board tmp_board inBoard ;
      Node* bestLeaf = topNode.SelectBest(&tmp_board);
      if (bestLeaf->terminal)

      bestLeaf->BackPropagateScore(bestLeaf->termscore);
      continue;

      if (!bestLeaf->visits)

      int score = simulation(tmp_board, false);
      bestLeaf->BackPropagateScore(score);
      continue;

      // Expand
      std::vector<std::array<int, 2>> obvMoves = tmp_board.getObvMoves();
      if (obvMoves.size() == 0)

      bestLeaf->terminal = true;
      bestLeaf->termscore = 0; // tie

      for (const std::array<int, 2>&move : obvMoves)

      tmp_board.playMove(move[0], move[1]);
      int termscore = tmp_board.checkWin();
      if (termscore) bestLeaf->CreateChild(topNode.visits, move[0], move[1], termscore);
      bestLeaf->CreateChild(topNode.visits, move[0], move[1]);
      // Undo the move
      tmp_board.undoMove();



      // Suggest move

      int bestmove[2];
      unsigned long maxvisits = 0;
      for (const std::unique_ptr<Node>& move : topNode.children)

      if (move->visits > maxvisits)

      maxvisits = move->visits;
      bestmove[0] = move->move_to[0];
      bestmove[1] = move->move_to[1];


      std::cout << "Suggest: " << bestmove[0] << "," << bestmove[1] << std::endl;
      std::cout << "Win %: " << team * topNode.score * 100 / topNode.visits << std::endl;

      return boost::python::make_tuple(bestmove[0], bestmove[1]);



      void viewStats(Node * n, int t)
      n->visits == 0) return;
      std::cout << "Think: " << bestmove[0] << "," << bestmove[1] << std::endl;
      std::cout << "Win %: " << t * n->score * 100 / n->visits << "n" << std::endl;



      int simulation(Board board)

      return simulation(board, true);


      int simulation(Board board, bool rebuild)

      if (rebuild)

      /* Rebuild the board states to make sure it works properly
      Will not guarantee proper working order initially, but
      for the purpose of this function, it will suffice
      */
      std::vector<std::array<int, 2>> tmp;
      for (int r = 0; r < board.getSize(); ++r)

      for (int c = 0; c < board.getSize(); ++c)

      if (board.getBoardPos(r, c)) tmp.push_back(std::array<int, 2> r, c );


      board.manOverridePlayedPieces(tmp);


      while (board.getMoves() < pow(board.getSize(), 2))

      // Make a random move
      std::vector<std::array<int, 2>> choices = board.getObvMoves();
      std::array<int, 2> randMove = choices[rand() % choices.size()];
      board.playMove(randMove[0], randMove[1]);
      if (board.checkWin()) return board.checkWin();

      return 0;



      Python



      Below: the loader and module scripts



      loader.py



      import sys


      if sys.maxsize == 2147483647: # 32 bit python install
      from x86.cpymcts import Board, search

      else: # 64 bit
      from x64.cpymcts import Board, search


      Reach.py (fancy naming)



      """
      This script is designed to function as a higher level interface
      to an end user & programmer. It also handles input checking and such
      NO functionality is removed because of this
      """
      from typing import Tuple
      from loader import Board as Board_, search as search_


      class Board:

      def __init__(self, size: int = 19) -> None:
      if size <= 0:
      raise ValueError("Bad value for size.")
      if size > 19:
      raise ValueError("Size may not be larger than 19.")
      self.board = Board_(size)

      # Read only properties and (magic) methods
      def __str__(self) -> str:
      return self.board.string.replace('-1', 'X ').replace('1', 'O')

      @property
      def size(self) -> int:
      """Returns the size of the board. If 19x19 then will return 19"""
      return self.board.size

      @property
      def move_count(self) -> int:
      """Moves that have been played since game start minus undone moves"""
      return self.board.move_count

      def check_win(self) -> int:
      """Returns -1 for P1 win, 0 for no win, 1 for P2 win"""
      return self.board.check_win()

      # The remainder of the methods to interface with Board_
      def reset(self) -> None:
      """Resets to a new instance of `Board`"""
      self.board.reset()

      def search(self, team: int = None, iterations: int = 10000
      ) -> Tuple[int, int]:
      """
      An alias for search(Board, int, int). Please see Reach.search
      ```python
      >>> a = Board()
      >>> a.search()
      (9, 9)
      >>> b = Board()
      >>> search(b)
      (9, 9)
      ```
      """
      return search(self, team, iterations)

      def get(self, row: int, col: int) -> int:
      """
      Gets the piece (-1, 1 or 0) where -1 is a black stone (player 1)
      and 1 is a white stone, and 0 is empty of the position.
      ```python
      >>> a = Board()
      >>> a.play(9, 9)
      >>> a.get(9, 9)
      -1
      ```

      Args:
      row: `int`
      The row of the piece starting from 0

      col: `int`
      The column of the piece starting from 0

      Returns:
      piece: `int`
      The piece at the position (row, col) of the board.

      Raises:
      `IndexError`
      row or col are out of bounds.
      """
      if not 0 <= row < self.size or not 0 <= col < self.size:
      raise IndexError(f"Invalid coordinates (row, col): (row, col) "
      f"exceeds dimentions of self.sizexself.size.")
      return self.board.get(row, col)

      def play(self, row: int, col: int) -> None:
      """
      Places a piece at the location (row, col)
      Args:
      row `int`:
      The row of which to place the piece
      col `int`:
      The column of which to place the piece

      Raises:
      `ValueError`
      row or col are out of bounds.
      """
      if not 0 <= row < self.size or not 0 <= col < self.size:
      raise ValueError(f"Invalid coordinates (row, col): (row, col) "
      f"exceeds dimentions of self.sizexself.size.")
      self.board.play(row, col)

      def undo(self) -> None:
      """Undoes the last move. May be called multiple times."""
      self.board.undo()


      def search(ref_board: Board, team: int = None, iterations: int = 10000
      ) -> Tuple[int, int]:
      """
      Args:
      ref_board: `Board`
      The board to be examined.
      team: `int`
      The number of the piece that will be played this turn. First player is
      -1
      iterations: `int`
      The amount of MCTS iterations to run.

      Returns:
      bestmove: `Tuple[int, int]`
      A 2-tuple of the position to place the next piece for best perceived
      outcome

      Raises:
      `ValueError`
      when there is an object type mismatch

      ```python
      >>> a = Board()
      >>> a.search()
      (9, 9)
      >>> b = Board()
      >>> search(b)
      (9, 9)
      ```
      """
      if team is not None:
      print('You have chosen to manually overwrite the `team` parameter. '
      'It is recommended to allow this to handle this automatically.')
      else:
      team = (ref_board.move_count % 2) * 2 - 1

      if not isinstance(team, int):
      raise ValueError(f"team should be of int and not "
      f"type(team)")
      if not isinstance(iterations, int):
      raise ValueError(f"iterations should be of int and not "
      f"type(iterations).")

      if isinstance(ref_board, Board):
      return search_(ref_board.board, team, iterations)
      elif isinstance(ref_board, Board_):
      return search_(ref_board, team, iterations)
      else:
      raise ValueError(f"ref_board should be of Board or Board_ and not"
      f" type(ref_board).")


      This is my first ever C++ work.

      Not my first Python work, however, hence the overall higher quality. Now with...



      ✔ Python within 80 characters at all times

      ✔ Python functions well documented

      ✘ Lack of comments at some places

      ✘ This is your job to complain, not mine.



      Give feedback on any of two languages.









      share|improve this question










      share|improve this question




      share|improve this question









      asked Jun 6 at 13:12









      Luke

      284210




      284210

























          active

          oldest

          votes











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195958%2fc-implementation-of-monte-carlo-tree-search-with-python-wrapper%23new-answer', 'question_page');

          );

          Post as a guest



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195958%2fc-implementation-of-monte-carlo-tree-search-with-python-wrapper%23new-answer', 'question_page');

          );

          Post as a guest













































































          BrEZ,YzM,XzNXEgsjTcew jpCu,N,Keizi,KZUD tsHVgL0dXkV0,edmOrDpodqfxgtawpe4,0DyGiXKdFRTLznh5KlevZbTxhV
          zx1I9ZxpDTlT bkcopKwZYa5ZDFtR66PQBTbHACZs6WziTiFk3,m r0CRL

          Popular posts from this blog

          Chat program with C++ and SFML

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          Read an image with ADNS2610 optical sensor and Arduino Uno