Skip to content

Instantly share code, notes, and snippets.

@oktal
Last active August 29, 2015 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oktal/e3390bb6ad2ffd7bae77 to your computer and use it in GitHub Desktop.
Save oktal/e3390bb6ad2ffd7bae77 to your computer and use it in GitHub Desktop.
Devils never cry
#include <iostream>
#include <vector>
#include <stdexcept>
#include <cassert>
#include <limits>
#include <algorithm>
class Cell {
public:
friend class Angel;
friend class Devil;
enum class State { Occupied, Broken, Free };
Cell() :
state_(State::Free)
{
}
State state() const {
return state_;
}
private:
State state_;
};
std::pair<size_t, size_t> input(size_t maxWidth, size_t maxHeight) {
std::cout << "Where to play next?\n";
size_t x, y;
auto ask_value = [=](size_t& out, size_t max, std::string message) {
for (;;) {
std::cout << message;
if (!(std::cin >> out)) {
std::cout << "Please enter a number between [1; " << max << "]\n";
std::cin.clear();
std::cin.ignore('\n', std::numeric_limits<std::streamsize>::max());
continue;
}
else if (out == 0 || out > max) {
std::cout << "Please enter a number between [1; " << max << "]\n";
continue;
}
break;
}
};
ask_value(x, maxWidth, "X: ");
ask_value(y, maxHeight, "Y: ");
return std::make_pair(x, y);
}
class Board {
public:
Board(size_t width, size_t height)
: width_(width)
, height_(height)
{
auto is_odd = [](size_t val) { return val % 2 > 0; };
if (width != height) {
throw std::invalid_argument("The board must be a square");
}
if (!is_odd(width) || !is_odd(height)) {
throw std::invalid_argument("Width and height must be odd");
}
cells.resize(width * height);
}
Board(const Board& other) = delete;
Board& operator=(const Board& other) = delete;
Cell& cellAt(size_t x, size_t y) {
return const_cast<Cell&>(static_cast<const Board&>(*this).cellAt(x, y));
}
const Cell& cellAt(size_t x, size_t y) const {
assert(x < width_);
assert(y < height_);
return cells[x * height_ + y];
}
void display(std::ostream& out) const {
for (size_t i = 0; i < width_; ++i) {
for (size_t j = 0; j < height_; ++j) {
const auto& cell = cellAt(i, j);
switch (cell.state()) {
case Cell::State::Free:
out << '.';
break;
case Cell::State::Occupied:
out << 'o';
break;
case Cell::State::Broken:
out << 'X';
break;
}
out << ' ';
}
out << '\n';
}
}
size_t width() const {
return width_;
}
size_t height() const {
return height_;
}
private:
size_t width_;
size_t height_;
std::vector<Cell> cells;
};
class Player {
public:
Player()
{
}
virtual void play(size_t x, size_t y, Board* board) = 0;
virtual bool canMoveAt(size_t x, size_t y, const Board& board) const = 0;
};
class Angel : public Player {
public:
Angel(Board* board) {
pos_x = board->width() / 2;
pos_y = board->height() / 2;
Cell& targetCell = board->cellAt(pos_x, pos_y);
targetCell.state_ = Cell::State::Occupied;
}
void play(size_t x, size_t y, Board* board) override {
Cell& targetCell = board->cellAt(x, y);
assert(targetCell.state() == Cell::State::Free);
Cell& currentCell = board->cellAt(pos_x, pos_y);
assert(currentCell.state() == Cell::State::Occupied);
currentCell.state_ = Cell::State::Free;
targetCell.state_ = Cell::State::Occupied;
pos_x = x;
pos_y = y;
}
bool canMoveAt(size_t x, size_t y, const Board& board) const override {
auto moves = validMoves(board);
auto it = std::find(
begin(moves), end(moves), std::make_pair(x, y));
return it != std::end(moves);
}
bool isJailed(const Board& board) const {
auto moves = validMoves(board);
return moves.empty();
}
private:
std::vector<std::pair<size_t, size_t>> validMoves(const Board& board) const {
std::vector<std::pair<size_t, size_t>> validPositions;
if (pos_x == 0) {
validPositions.push_back({pos_x + 1, pos_y }); /* Below */
if (pos_y == 0) {
validPositions.push_back({pos_x, pos_y + 1}); /* Right */
}
else if (pos_y == board.height() - 1) {
validPositions.push_back({pos_x, pos_y - 1}); /* Left */
}
else {
validPositions.push_back({pos_x, pos_y + 1}); /* Right */
validPositions.push_back({pos_x, pos_y - 1}); /* Left */
}
}
else if (pos_y == 0) {
validPositions.push_back({pos_x, pos_y + 1}); /* Right */
if (pos_x == board.width() - 1) {
validPositions.push_back({pos_x - 1, pos_y }); /* Above */
}
else {
validPositions.push_back({pos_x - 1, pos_y }); /* Above */
validPositions.push_back({pos_x + 1, pos_y }); /* Below */
}
}
else if (pos_x == board.width() - 1) {
assert(pos_y != 0);
validPositions.push_back({pos_x - 1, pos_y }); /* Above */
if (pos_y < board.height() - 1) {
validPositions.push_back({pos_x, pos_y + 1}); /* Right */
validPositions.push_back({pos_x, pos_y - 1}); /* Left */
}
else {
validPositions.push_back({pos_x, pos_y - 1}); /* Left */
}
}
else if (pos_y == board.height() - 1) {
assert(pos_x != 0);
assert(pos_x != board.width() - 1);
validPositions.push_back({pos_x, pos_y - 1}); /* Left */
validPositions.push_back({pos_x - 1, pos_y }); /* Above */
validPositions.push_back({pos_x + 1, pos_y }); /* Below */
}
else {
validPositions.push_back({pos_x, pos_y - 1}); /* Left */
validPositions.push_back({pos_x, pos_y + 1}); /* Right */
validPositions.push_back({pos_x - 1, pos_y }); /* Above */
validPositions.push_back({pos_x + 1, pos_y }); /* Below */
validPositions.push_back({pos_x - 1, pos_y - 1});
validPositions.push_back({pos_x - 1, pos_y + 1});
validPositions.push_back({pos_x + 1, pos_y - 1});
validPositions.push_back({pos_x + 1, pos_y + 1});
}
auto it = validPositions.begin();
while (it != validPositions.end()) {
const auto& pos = *it;
const auto& cell = board.cellAt(pos.first, pos.second);
/* The cell has been broken by the devil, the angel can't move here */
if (cell.state() == Cell::State::Broken) {
it = validPositions.erase(it);
}
else {
++it;
}
}
return validPositions;
}
size_t pos_x;
size_t pos_y;
};
class Devil : public Player {
public:
void play(size_t x, size_t y, Board* board) override {
Cell& targetCell = board->cellAt(x, y);
assert(targetCell.state() == Cell::State::Free);
targetCell.state_ = Cell::State::Broken;
}
bool canMoveAt(size_t x, size_t y, const Board& board) const override {
const auto& cell = board.cellAt(x, y);
return cell.state() == Cell::State::Free;
}
};
enum class PlayerTurn { Angel, Devil };
void play() {
enum {
Width = 9,
Height = 9
};
Board board(Width, Height);
Angel angel(&board);
board.display(std::cout);
Devil devil;
PlayerTurn turn = PlayerTurn::Angel;
bool finished = false;
while (!finished) {
bool validPosition = false;
std::pair<size_t, size_t> pos;
Player* player = nullptr;
while (!validPosition) {
if (turn == PlayerTurn::Angel) {
std::cout << "Your turn, Angel\n";
player = &angel;
}
else {
std::cout << "Your turn, Devil\n";
player = &devil;
}
pos = input(Width, Height);
if (!(validPosition = player->canMoveAt(pos.first - 1, pos.second - 1, board))) {
std::cout << "Invalid position" << std::endl;
}
}
size_t x = pos.first - 1;
size_t y = pos.second - 1;
player->play(x, y, &board);
board.display(std::cout);
std::cout << std::endl;
if (turn == PlayerTurn::Angel && (x == 0 || y == 0
|| x == board.width() - 1 || y == board.height() - 1)) {
std::cout << "Angel reached the side and won !\n";
finished = true;
}
else if (turn == PlayerTurn::Devil && angel.isJailed(board)) {
std::cout << "The Devil jailed the angel and won !\n";
finished = true;
}
turn = (turn == PlayerTurn::Angel) ? PlayerTurn::Devil : PlayerTurn::Angel;
}
}
int main() {
play();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment