Last active
August 29, 2015 14:15
-
-
Save oktal/e3390bb6ad2ffd7bae77 to your computer and use it in GitHub Desktop.
Devils never cry
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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