Here's a piece of C++ code that will allow regular IOstreams output for a move class template. The default is that a move can be output in its native format, i.e. numerical notation for international draughts:
auto const p = Position<rules::International, board::International>::initial();
auto const moves = successor::generate(p);
std::cout << moves[0]; // print first move as "31-26"
Setting up a different game, e.g. Russian draughts, gives algebraic notation
auto const p = Position<rules::Russian, board::Checkers>::initial();
auto const moves = successor::generate(p);
std::cout << moves[0]; // print first move as "a3-b4"
Each individual game can have its default move formatting overridden by using so-called manipulators (similar to std::setprecision for formatting numbers in C++).
auto const p = Position<rules::International, board::International>::initial();
auto const moves = successor::generate(p);
std::cout << format::algebraic << moves[0]; // print first move as "b4-a5"
Here's the C++ code (using C++14 automatic return types, and the little-known feature of IOstreams that allows users to add "sticky" flags that control formatting of user-defined classes. These flags can be modified/accessed by the iword() library function with a pre-computed index obtained from the xalloc() library function):
namespace format {
enum { flag_native = 0, flag_numeric = 1, flag_algebraic = 2 };
template<class Move>
struct traits
:
std::integral_constant<int, flag_numeric>
{};
template<class Board>
struct traits<Move<rules::Russian, Board>>
:
std::integral_constant<int, flag_algebraic>
{};
template<class Board>
struct traits<Move<rules::Czech, Board>>
:
std::integral_constant<int, flag_algebraic>
{};
template<>
struct traits<Move<rules::International, board::Checkers>>
:
std::integral_constant<int, flag_algebraic>
{};
inline
auto index()
{
static auto const slot = std::ios_base::xalloc();
return slot;
}
template<class CharT, class Traits>
auto& numeric(std::basic_ostream<CharT, Traits>& ostr)
{
ostr.iword(index()) = flag_numeric;
return ostr;
}
template<class CharT, class Traits>
auto& algebraic(std::basic_ostream<CharT, Traits>& ostr)
{
ostr.iword(index()) = flag_algebraic;
return ostr;
}
template<class Move>
auto as_numeric(Move const& m)
{
using Board = typename Move::board_type;
std::stringstream sstr;
sstr << Board::numeric_from_bit(m.from());
sstr << (m.is_jump() ? 'x' : '-');
sstr << Board::numeric_from_bit(m.dest());
return sstr.str();
}
template<class Move>
auto as_algebraic(Move const& m)
{
using Board = typename Move::board_type;
std::stringstream sstr;
sstr << Board::algebraic_from_bit(m.from());
sstr << (m.is_jump() ? 'x' : '-');
sstr << Board::algebraic_from_bit(m.dest());
return sstr.str();
}
} // namespace format
template<class Rules, class Board>
auto& operator<<(std::ostream& ostr, Move<Rules, Board> const& m)
{
auto value = ostr.iword(format::index());
if (value == format::flag_native)
value = format::traits<Move<Rules, Board>>::value;
switch(value) {
case format::flag_numeric:
ostr << format::as_numeric(m);
break;
case format::flag_algebraic:
ostr << format::as_algebraic(m);
break;
default:
assert(false && !"Supplied move format not supported.");
break;
}
return ostr;
}
Many thanks to Wieger Wesselink for showing me his small interactive program that used the plain overloaded operator<< for moves. Being able to customized the move format without resorting to my old verbose notation::write<SomeFormatFlag>(m) was a nice exercise!