392 lines
7.8 KiB
C
392 lines
7.8 KiB
C
|
#pragma once
|
||
|
|
||
|
#include <cassert>
|
||
|
#include <cstdio>
|
||
|
#include <cstdlib>
|
||
|
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
typedef unsigned int idx_t;
|
||
|
|
||
|
/*
|
||
|
* Potential values for the pieces on the board.
|
||
|
*/
|
||
|
enum FieldType
|
||
|
{
|
||
|
EMPTY,
|
||
|
PLAYER_1,
|
||
|
PLAYER_2,
|
||
|
OUT_OF_BOUNDS
|
||
|
};
|
||
|
|
||
|
|
||
|
/*
|
||
|
* direction flags are used by the algorithms below to validate
|
||
|
* and perform moves.
|
||
|
*/
|
||
|
enum direction {
|
||
|
LEFT,
|
||
|
RIGHT,
|
||
|
UP,
|
||
|
DOWN,
|
||
|
UP_LEFT,
|
||
|
UP_RIGHT,
|
||
|
DOWN_LEFT,
|
||
|
DOWN_RIGHT
|
||
|
};
|
||
|
|
||
|
|
||
|
class OthelloBoard
|
||
|
{
|
||
|
protected:
|
||
|
idx_t _rows; // no. of rows
|
||
|
idx_t _cols; // no. of columns
|
||
|
|
||
|
bool _last_player_skipped; // remember if last player skipped
|
||
|
bool _both_skipped; // termination condition
|
||
|
|
||
|
FieldType *_field; // array storing the types of stones set on the board
|
||
|
|
||
|
FieldType _player; // whose turn is it?
|
||
|
|
||
|
|
||
|
/* make object non-assignable */
|
||
|
OthelloBoard& operator=(OthelloBoard&)
|
||
|
{ return *this; }
|
||
|
|
||
|
|
||
|
private:
|
||
|
/*
|
||
|
* Depending on a given direction, we need to setup up to four
|
||
|
* variables in different types of algorithms. This convenience
|
||
|
* function does so.
|
||
|
*/
|
||
|
void setup_directions(unsigned &row_idx, unsigned &col_idx,
|
||
|
int &inc_row, int &inc_col, direction const dir) const;
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Check if a position is reachable from a given direction.
|
||
|
*
|
||
|
* Reachability means, that from the target position given by
|
||
|
* (row, col) there are only non-empty stones of type != _player until
|
||
|
* we find a stone of type _player.
|
||
|
*/
|
||
|
bool position_reachable(unsigned row, unsigned col,
|
||
|
direction const dir) const;
|
||
|
|
||
|
/*
|
||
|
* Really make a move
|
||
|
*/
|
||
|
void make_move(unsigned row, unsigned col,
|
||
|
direction dir);
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Validate correctness of a move.
|
||
|
*
|
||
|
* \param row, col coordinates to move to
|
||
|
*
|
||
|
* \return true if move is valid
|
||
|
*/
|
||
|
bool validate(unsigned row, unsigned col, bool silent=false) const;
|
||
|
|
||
|
public:
|
||
|
|
||
|
/*
|
||
|
* Get the number of rows.
|
||
|
*/
|
||
|
unsigned rows() const { return _rows; }
|
||
|
|
||
|
/*
|
||
|
* Get the number of columns
|
||
|
*/
|
||
|
unsigned columns() const { return _cols; }
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Construct a default OthelloBoard
|
||
|
*/
|
||
|
OthelloBoard(unsigned r = 8,
|
||
|
unsigned c = 8)
|
||
|
: _rows(r),
|
||
|
_cols(c),
|
||
|
_last_player_skipped(false),
|
||
|
_both_skipped(false),
|
||
|
_field(0),
|
||
|
_player(PLAYER_1)
|
||
|
{
|
||
|
_field = new FieldType[rows()*columns()];
|
||
|
for (unsigned idx = 0; idx < rows() * columns(); ++idx)
|
||
|
_field[idx] = EMPTY;
|
||
|
|
||
|
set(rows()/2, columns()/2, PLAYER_1);
|
||
|
set(rows()/2+1, columns()/2+1, PLAYER_1);
|
||
|
|
||
|
set(rows()/2, columns()/2 + 1, PLAYER_2);
|
||
|
set(rows()/2+1, columns()/2, PLAYER_2);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Copy constructor.
|
||
|
*
|
||
|
* XXX: If you plan to use it, go and implement it!
|
||
|
*/
|
||
|
OthelloBoard(const OthelloBoard& copy);
|
||
|
|
||
|
|
||
|
/* Destructor */
|
||
|
virtual ~OthelloBoard() { delete [] _field; }
|
||
|
|
||
|
|
||
|
/* Get current player type */
|
||
|
FieldType player() const { return _player; }
|
||
|
|
||
|
/* Set current player */
|
||
|
void player(int no) {
|
||
|
switch(no) {
|
||
|
case 0: _player = PLAYER_1; break;
|
||
|
case 1: _player = PLAYER_2; break;
|
||
|
default: assert(false); break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Dump the current board state to a file
|
||
|
*/
|
||
|
bool dump_to_fd(int fd, bool fancy = false) const
|
||
|
{
|
||
|
FILE *file = fdopen(dup(fd), "w");
|
||
|
if (!file) return false;
|
||
|
|
||
|
// print head line
|
||
|
if (fancy)
|
||
|
::fprintf(file, " %c ", type_to_str(_player));
|
||
|
else
|
||
|
::fprintf(file, "%c", type_to_str(_player));
|
||
|
|
||
|
if (fancy) {
|
||
|
for (unsigned c = 0; c < columns(); ++c)
|
||
|
::fprintf(file, "%d ", c+1);
|
||
|
::fprintf(file,"\n");
|
||
|
}
|
||
|
|
||
|
for (unsigned r = 0; r < rows(); ++r) {
|
||
|
if (fancy) ::fprintf(file, "%2d ", r+1);
|
||
|
for (unsigned c = 0; c < columns(); ++c) {
|
||
|
if (fancy)
|
||
|
::fprintf(file, "%s ", type_to_str_fancy(get(r+1, c+1)));
|
||
|
else {
|
||
|
::fprintf(file, "%c", type_to_str(get(r+1,c+1)));
|
||
|
}
|
||
|
}
|
||
|
if (fancy) ::fprintf(file,"\n");
|
||
|
}
|
||
|
|
||
|
fclose(file);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* Special case: dump to stdout */
|
||
|
bool dump_to_stdio() const { return dump_to_fd(STDOUT_FILENO, true); }
|
||
|
|
||
|
/*
|
||
|
* Get element at location.
|
||
|
*
|
||
|
* Location coordinates are
|
||
|
* row with 0 < row <= rows()
|
||
|
* col with 0 < col <= columns()
|
||
|
*
|
||
|
* Otherwise, returns OUT_OF_BOUNDS field type.
|
||
|
*/
|
||
|
FieldType get(unsigned row, unsigned col) const
|
||
|
{
|
||
|
if ((row <= 0) ||
|
||
|
(col <= 0) ||
|
||
|
(row > rows()) ||
|
||
|
(col > columns()))
|
||
|
return OUT_OF_BOUNDS;
|
||
|
|
||
|
return _field[(row-1)*columns() + col - 1];
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Set element at certain location.
|
||
|
*
|
||
|
* Location coordinates are
|
||
|
* row with 0 < row <= rows()
|
||
|
* col with 0 < col <= columns()
|
||
|
*/
|
||
|
void set(unsigned row, unsigned col, FieldType type)
|
||
|
{
|
||
|
assert(row > 0);
|
||
|
assert(row <= rows());
|
||
|
assert(col > 0);
|
||
|
assert(col <= columns());
|
||
|
|
||
|
_field[(row-1)*columns() + col - 1] = type;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Perform a move.
|
||
|
*
|
||
|
* First, validates the move. Then performs all necessary
|
||
|
* field type conversions.
|
||
|
*/
|
||
|
bool move(unsigned row, unsigned col);
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Count the number of fields that contain a stone
|
||
|
* of the given type.
|
||
|
*/
|
||
|
unsigned count_of_type(FieldType t) const
|
||
|
{
|
||
|
unsigned res = 0;
|
||
|
for (unsigned i = 0; i < rows() * columns(); ++i)
|
||
|
if (_field[i] == t) res += 1;
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
|
||
|
void skipped()
|
||
|
{
|
||
|
if (_last_player_skipped)
|
||
|
_both_skipped = true;
|
||
|
else
|
||
|
_last_player_skipped = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Determine whether the game is done.
|
||
|
*/
|
||
|
bool finished() const
|
||
|
{
|
||
|
unsigned c1 = count_of_type(PLAYER_1);
|
||
|
unsigned c2 = count_of_type(PLAYER_2);
|
||
|
|
||
|
return ( _both_skipped ||
|
||
|
(c1 == 0) ||
|
||
|
(c2 == 0) ||
|
||
|
(c1 + c2 >= 64)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Transform field type into fancy print version (for stdio).
|
||
|
*/
|
||
|
char const *type_to_str_fancy(FieldType t) const
|
||
|
{
|
||
|
switch (t) {
|
||
|
case EMPTY: return "\033[34;1m.\033[0m";
|
||
|
case PLAYER_1: return "\033[31mO\033[0m";
|
||
|
case PLAYER_2: return "\033[33mX\033[0m";
|
||
|
case OUT_OF_BOUNDS: return "/";
|
||
|
default: __builtin_unreachable();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Transform field type into print version.
|
||
|
*/
|
||
|
char type_to_str(FieldType t) const
|
||
|
{
|
||
|
switch (t) {
|
||
|
case EMPTY: return '.';
|
||
|
case PLAYER_1: return 'O';
|
||
|
case PLAYER_2: return 'X';
|
||
|
case OUT_OF_BOUNDS: return '/';
|
||
|
default: __builtin_unreachable();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Check, whether the given player can make any move at the moment.
|
||
|
*/
|
||
|
bool can_move() const
|
||
|
{
|
||
|
for (unsigned r = 1; r <= rows(); ++r)
|
||
|
for (unsigned c = 1; c <= columns(); ++c)
|
||
|
if (validate(r, c, true))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Invalidate all stones belonging to a player.
|
||
|
*
|
||
|
* The engine uses this as a penalty for making more than 3
|
||
|
* illegal moves in a row.
|
||
|
*/
|
||
|
void invalidate_stones(FieldType t)
|
||
|
{
|
||
|
for (unsigned idx = 0; idx < rows() * columns(); ++idx)
|
||
|
if (_field[idx] == t)
|
||
|
_field[idx] = EMPTY;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
struct OthelloMove
|
||
|
{
|
||
|
idx_t row;
|
||
|
idx_t col;
|
||
|
|
||
|
OthelloMove(idx_t r=0, idx_t c=0)
|
||
|
: row(r), col(c)
|
||
|
{ }
|
||
|
};
|
||
|
|
||
|
|
||
|
/***************************************
|
||
|
* MCP adaptor definitions & functions *
|
||
|
***************************************/
|
||
|
|
||
|
typedef OthelloBoard game_state;
|
||
|
typedef OthelloMove game_move;
|
||
|
|
||
|
/**
|
||
|
* Game state is serialized into a human-readable string.
|
||
|
*/
|
||
|
bool serialize_state(int fd, const game_state *state);
|
||
|
|
||
|
/**
|
||
|
* Game state is parsed from a string written by serialize_state.
|
||
|
*/
|
||
|
bool deserialize_state(int fd, game_state *state);
|
||
|
|
||
|
|
||
|
bool serialize_move (int fd, const game_move *move);
|
||
|
bool deserialize_move(int fd, game_move *move);
|
||
|
|
||
|
/**
|
||
|
* Generate the initial board.
|
||
|
*/
|
||
|
void initialize_state(game_state *state, char const *init);
|
||
|
|
||
|
/**
|
||
|
* Returns true, if this is a final game state.
|
||
|
*/
|
||
|
bool is_final_state(game_state const *state);
|
||
|
|
||
|
/**
|
||
|
* Tries to apply a move to the given state. Returns false, if the
|
||
|
* move was invalid.
|
||
|
*/
|
||
|
bool apply_move(game_state *state,
|
||
|
const game_move *move);
|