patterncppMinor
Machine learning tic-tac-toe implementation
Viewed 0 times
toeticlearningtacmachineimplementation
Problem
```
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const int initWeight = 100;
const int precCoeff = 50;
const double stepCoeff = 0.65;
const int learningStep = 20;
char gameField[ 9 ];
enum OUTCOME { XS, OS, DRAW, UNFINISHED };
OUTCOME
gameStatus()
{
static int V[ 8 ][ 3 ] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 0, 3, 6 },
{ 1, 4, 7 }, { 2, 5, 8 }, { 0, 4, 8 }, { 2, 4, 6 } };
for( int i = 0; i DataBaseType;
class Player
{
public:
Player( char _symbol ) : symbol( _symbol ) {}
virtual void makeMove() = 0;
virtual void learn( int ) { }
protected:
const char symbol;
};
class HumanPlayer : public Player
{
public:
HumanPlayer( char _symbol ) : Player( _symbol ) {}
virtual void makeMove()
{
while(true)
{
int cell;
std::cin >> cell;
if(cell 8 || gameField[cell] != ' ')
{
std::cout v;
for( int i = 0; i second.weight, s -> second.weight + 9, 0 );
if( sum == 0 )
return std::find( gameField, gameField + 9, ' ' ) - gameField;
std::vector coords;
for( int i = 0; i second.weight[ i ] / sum, i );
return coords[ rand() % coords.size()];
}
};
template
class SmartPlayer : public Player,
public LearningPolciy
{
private:
struct HistoryElement
{
DataBaseType::iterator situation;
int move;
HistoryElement(DataBaseType::iterator s, int m )
: situation( s ), move( m ) { }
};
typedef std::stack HistoryType;
public:
DataBaseType::iterator getSituation();
public:
SmartPlayer ( char _symbol ) : Player( _symbol ) { }
virtual void makeMove();
void learn( int step );
private:
HistoryType history;
DataBaseType database;
};
template
DataBaseType::iterator
SmartPlayer::getSituation()
{
DataBaseType::it
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
const int initWeight = 100;
const int precCoeff = 50;
const double stepCoeff = 0.65;
const int learningStep = 20;
char gameField[ 9 ];
enum OUTCOME { XS, OS, DRAW, UNFINISHED };
OUTCOME
gameStatus()
{
static int V[ 8 ][ 3 ] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 }, { 0, 3, 6 },
{ 1, 4, 7 }, { 2, 5, 8 }, { 0, 4, 8 }, { 2, 4, 6 } };
for( int i = 0; i DataBaseType;
class Player
{
public:
Player( char _symbol ) : symbol( _symbol ) {}
virtual void makeMove() = 0;
virtual void learn( int ) { }
protected:
const char symbol;
};
class HumanPlayer : public Player
{
public:
HumanPlayer( char _symbol ) : Player( _symbol ) {}
virtual void makeMove()
{
while(true)
{
int cell;
std::cin >> cell;
if(cell 8 || gameField[cell] != ' ')
{
std::cout v;
for( int i = 0; i second.weight, s -> second.weight + 9, 0 );
if( sum == 0 )
return std::find( gameField, gameField + 9, ' ' ) - gameField;
std::vector coords;
for( int i = 0; i second.weight[ i ] / sum, i );
return coords[ rand() % coords.size()];
}
};
template
class SmartPlayer : public Player,
public LearningPolciy
{
private:
struct HistoryElement
{
DataBaseType::iterator situation;
int move;
HistoryElement(DataBaseType::iterator s, int m )
: situation( s ), move( m ) { }
};
typedef std::stack HistoryType;
public:
DataBaseType::iterator getSituation();
public:
SmartPlayer ( char _symbol ) : Player( _symbol ) { }
virtual void makeMove();
void learn( int step );
private:
HistoryType history;
DataBaseType database;
};
template
DataBaseType::iterator
SmartPlayer::getSituation()
{
DataBaseType::it
Solution
I see a number of things that may help you improve your program.
Fix the bug
The code marks squares with uppercase letters, but the
Eliminate "magic numbers"
Instead of using
Don't use
Since C++11,
write this:
Note that this uses the C++11 initialization syntax. If your compiler doesn't support that, my first advice would be to update the compiler. Only if that's not possible for some very compelling reason should you limit yourself to C++03 syntax.
Consider using a better random number generator
You are currently using
There are two problems with this approach. One is that the low order bits of the random number generator are not particularly random, so neither is your result. On my machine, there's a slight but measurable bias toward 0 with that. The second problem is that unless
Think about the cost of objects
The current code includes this object:
The ultimate effect of this is to create a potentially huge vector just to select one entry and throw the rest away. This is a very inefficient way to do things, in addition to the problem with the random number mentioned earlier. The goal, as I understand it, is to select a move with a probability proportional to its weight. There's a very direct and efficient way to do that in C++11 using
Note that in my version,
Eliminate global variables where practical
The code declares and uses several global variables. Global variables obfuscate the actual dependencies within code and make maintainance and understanding of the code that much more difficult. It also makes the code harder to reuse. For all of these reasons, it's generally far preferable to eliminate global variables and to instead pass pointers to them. That way the linkage is explicit and may be altered more easily if needed. The constants are not so bad, but
Use objects
The
Naturally, you'll have to also adjust places within the code which assume access to a global variable.
Use better variable names
The variable name
Think about the user
It's not immediately obvious to the human player that the machine is expecting input, nor is it obvious that it numbers the squares starting with zero. A little explanatory message would be nice for the user.
Fix spelling errors
The code has
Fix the bug
The code marks squares with uppercase letters, but the
gameStatus code looks for a lowercase x. The result is that the computer incorrectly concludes that O always wins, which obviously throws off the training a bit. That could have been avoided by following the next suggestion.Eliminate "magic numbers"
Instead of using
'X' and ' ' and 'O' everywhere, create and use three named constants. Similarly, the number 9 occurs many places and could also be a named constant instead.Don't use
auto_ptrSince C++11,
std::auto_ptr is deprecated and it will completely disappear in C++17. Anyway, in this particular case, it adds nothing but complication. Instead of this: std::auto_ptr r(new RandomPlayer( 'O' ) );
std::auto_ptr h(new HumanPlayer( 'O' ) );
std::auto_ptr s(new CompPlayer( 'X' ) );write this:
RandomPlayer r{'O'};
HumanPlayer h{'O'};
CompPlayer s{'X'};Note that this uses the C++11 initialization syntax. If your compiler doesn't support that, my first advice would be to update the compiler. Only if that's not possible for some very compelling reason should you limit yourself to C++03 syntax.
Consider using a better random number generator
You are currently using
gameField[ v[ rand() % v.size() ] ] = symbol;There are two problems with this approach. One is that the low order bits of the random number generator are not particularly random, so neither is your result. On my machine, there's a slight but measurable bias toward 0 with that. The second problem is that unless
RAND_MAX happens to be an integral multiple of v.size(), this will not result in a uniform distribution. A better solution, if your compiler and library supports it, would be to use the C++11 std::uniform_int_distribution. Think about the cost of objects
The current code includes this object:
struct WRandmomPolciy
{
static int getMove(DataBaseType::iterator s)
{
const int sum = std::accumulate( s -> second.weight, s -> second.weight + 9, 0 );
if( sum == 0 )
return std::find( gameField, gameField + 9, ' ' ) - gameField;
std::vector coords;
for( int i = 0; i second.weight[ i ] / sum, i );
return coords[ rand() % coords.size()];
}
};The ultimate effect of this is to create a potentially huge vector just to select one entry and throw the rest away. This is a very inefficient way to do things, in addition to the problem with the random number mentioned earlier. The goal, as I understand it, is to select a move with a probability proportional to its weight. There's a very direct and efficient way to do that in C++11 using
std::discrete_distributionclass SmartPolicy
{
public:
int getMove(DataBaseType::iterator s)
{
const auto &w = s->second.weight;
std::discrete_distribution<> dist(w.begin(), w.end());
return dist(gen);
}
private:
std::mt19937 gen;
}Note that in my version,
TWeight is now a std::array.Eliminate global variables where practical
The code declares and uses several global variables. Global variables obfuscate the actual dependencies within code and make maintainance and understanding of the code that much more difficult. It also makes the code harder to reuse. For all of these reasons, it's generally far preferable to eliminate global variables and to instead pass pointers to them. That way the linkage is explicit and may be altered more easily if needed. The constants are not so bad, but
gameField really should be inside of main rather than global. Use objects
The
gameField has both data and logic associated with it. This strongly suggests an object. I'd propose something like this:class GameField {
public:
enum OUTCOME { XS, OS, DRAW, UNFINISHED };
GameField::OUTCOME gameStatus() const;
void print() const;
void play(Player& xp, Player& op, bool verbose);
bool move(int location, char symbol);
bool occupied(int location) const;
int firstFree() const;
friend TField;
private:
char gameField[ 9 ];
};Naturally, you'll have to also adjust places within the code which assume access to a global variable.
Use better variable names
The variable name
gameField is good, but the name s is not. The first name explains something about what the variable means within the context of the code, but the latter is too short to convey much of anything.Think about the user
It's not immediately obvious to the human player that the machine is expecting input, nor is it obvious that it numbers the squares starting with zero. A little explanatory message would be nice for the user.
Fix spelling errors
The code has
WRandmomPolciy instead of RandomPolicy and LearningPolciy instead of LearningPolicy. These kinds of typos don't bother the compiler at all, but they will bother human readers of tCode Snippets
std::auto_ptr<RandomPlayer> r(new RandomPlayer( 'O' ) );
std::auto_ptr<HumanPlayer> h(new HumanPlayer( 'O' ) );
std::auto_ptr<CompPlayer> s(new CompPlayer( 'X' ) );RandomPlayer r{'O'};
HumanPlayer h{'O'};
CompPlayer s{'X'};gameField[ v[ rand() % v.size() ] ] = symbol;struct WRandmomPolciy
{
static int getMove(DataBaseType::iterator s)
{
const int sum = std::accumulate( s -> second.weight, s -> second.weight + 9, 0 );
if( sum == 0 )
return std::find( gameField, gameField + 9, ' ' ) - gameField;
std::vector < int > coords;
for( int i = 0; i < 9; ++i )
std::fill_n( std::back_inserter( coords ), precCoeff * s ->second.weight[ i ] / sum, i );
return coords[ rand() % coords.size()];
}
};class SmartPolicy
{
public:
int getMove(DataBaseType::iterator s)
{
const auto &w = s->second.weight;
std::discrete_distribution<> dist(w.begin(), w.end());
return dist(gen);
}
private:
std::mt19937 gen;
}Context
StackExchange Code Review Q#156295, answer score: 5
Revisions (0)
No revisions yet.