Skip to content

Instantly share code, notes, and snippets.

@roberthoenig
Last active August 13, 2017 17:32
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 roberthoenig/64611d23490bdfaed572f28dec9339e0 to your computer and use it in GitHub Desktop.
Save roberthoenig/64611d23490bdfaed572f28dec9339e0 to your computer and use it in GitHub Desktop.
This is a text-based implementation of the game "Four in a row".
#include <iostream>
#include <stdexcept>
#include <vector>
enum State {
RUNNING,
DRAW,
VICTORY_P1,
VICTORY_P2
};
enum Id {
NONE,
P1,
P2
};
class Player {
Id _id;
public:
Player(Id);
int readMove();
int findMove(std::vector<std::vector<Id>>);
};
class FourInARow {
Id _pNow;
std::vector<std::vector<Id>> _field;
public:
FourInARow(Id);
State getGameState();
std::vector<std::vector<Id>> getField();
Id getPlayer();
bool isValidMove(int);
void makeMove(Id, int);
void printField();
void printResult();
const static int rows, columns;
Player p1, p2;
};
const int FourInARow::rows = 6;
const int FourInARow::columns = 7;
FourInARow::FourInARow(Id p_start)
: _pNow(p_start), p1(Player(P1)), p2(Player(P2)) {
_field = std::vector<std::vector<Id>>(rows, std::vector<Id>(columns, NONE));
}
State FourInARow::getGameState() {
bool is_full = true;
// Check for streaks
for (int r = 0; r < rows; ++r) {
for (int c = 0; c < columns; ++c) {
if (_field[r][c] == NONE) {
is_full = false;
continue;
}
bool horizontal = true,
vertical = true,
diagonal_up_right = true,
diagonal_down_right = true;
// Check for streak of 4 in each possible direction
for (int i = 1; i < 4; ++i) {
if (c+i >= columns || _field[r][c] != _field[r][c+i])
horizontal = false;
if (r+i >= rows || _field[r][c] != _field[r+i][c])
vertical = false;
if (c+i >= columns || r+i >= rows || _field[r][c] != _field[r+i][c+i])
diagonal_up_right = false;
if (c+i >= columns || r-i < 0 || _field[r][c] != _field[r-i][c+i])
diagonal_down_right = false;
}
if (horizontal || vertical || diagonal_up_right || diagonal_down_right) {
return (_field[r][c] == P1 ? VICTORY_P1 : VICTORY_P2);
}
}
}
return (is_full ? DRAW : RUNNING);
};
std::vector<std::vector<Id>> FourInARow::getField() {
return _field;
};
Id FourInARow::getPlayer() {
return _pNow;
};
bool FourInARow::isValidMove(int column) {
return (column > 0 && column <= columns && _field[rows-1][column-1] == NONE);
};
void FourInARow::makeMove(Id player, int column) {
if (!isValidMove(column)) {
throw std::invalid_argument("Trying to make a move with an invalid row value!");
}
else {
// 'Drop' a coin by filling in the first empty field in a column.
for (int i = 0; i < rows; ++i) {
if (_field[i][column-1] == NONE) {
_field[i][column-1] = player;
break;
}
}
}
_pNow = (_pNow == P1 ? P2 : P1);
};
void FourInARow::printField() {
for (int i = rows+1; i >= 0; --i) {
for (int j = 0; j <= columns+1; ++j) {
if (i == 1 || j == 0 || j == columns+1) {
std::cout << '#';
} else if (i == 0) {
std::cout << j;
} else {
switch(_field[i-2][j-1]) {
case P1: std::cout << 'X'; break;
case P2: std::cout << 'O'; break;
default: std::cout << ' '; break;
}
}
}
std::cout << std::endl;
}
};
void FourInARow::printResult() {
std::cout << "Game over!" << std::endl;
switch (getGameState()) {
case RUNNING: throw std::logic_error("Trying to print results while the game is running!"); break;
case DRAW: std::cout << "Draw!" << std::endl; break;
case VICTORY_P1: std::cout << "Player 1 won!" << std::endl; break;
case VICTORY_P2: std::cout << "Player 2 won!" << std::endl; break;
}
};
Player::Player(Id id)
: _id(id) {
};
int Player::readMove() {
int column = -1;
while (column < 1 || column > FourInARow::columns) {
std::cout << "Please enter a valid column where you want to dump your coin: ";
std::cin >> column;
}
return column;
};
int Player::findMove(std::vector<std::vector<Id>> field) {
// Simple, easy to beat strategy: If the opponent or I have already 3 coins in a row,
// place a fourth (no preference between either case, yet; only 3 consecutive coins are recognized).
// Otherwise, place a coin where it is first possible. Future improvement: Minimax
int last_empty_column = -1;
int rows = FourInARow::rows;
int columns = FourInARow::columns;
for (int r = 0; r < rows; ++r) {
for (int c = 0; c < columns; ++c) {
// Don't consider this field if it is not playable
if (field[r][c] != NONE || (r > 0 && field[r-1][c] == NONE)) {
continue;
}
last_empty_column = c;
bool horizontal_left = true,
horizontal_right = true,
vertical_down = true,
diagonal_up_right = true,
diagonal_down_right = true,
diagonal_up_left = true,
diagonal_down_left = true;
// Check for streak of 3 in each possible direction
for (int i = 1; i < 4; ++i) {
if (c-i < 0 || field[r][c-1] == NONE || field[r][c-1] != field[r][c-i])
horizontal_left = false;
if (c+i >= columns || field[r][c+1] == NONE || field[r][c+1] != field[r][c+i])
horizontal_right = false;
if (r-i < 0 || field[r-1][c] == NONE || field[r-1][c] != field[r-i][c])
vertical_down = false;
if (c+i >= columns || r+i >= rows || field[r+1][c+1] == NONE || field[r+1][c+1] != field[r+i][c+i])
diagonal_up_right = false;
if (c+i >= columns || r-i < 0 || field[r-1][c+1] == NONE || field[r-1][c+1] != field[r-i][c+i])
diagonal_down_right = false;
if (c-i < 0 || r+i >= rows || field[r+1][c-1] == NONE || field[r+1][c-1] != field[r+i][c-i])
diagonal_up_left = false;
if (c-i < 0 || r-i < 0 || field[r-1][c-1] == NONE || field[r-1][c-1] != field[r-i][c-i])
diagonal_down_left = false;
}
if (horizontal_left || horizontal_right || vertical_down || diagonal_up_right ||
diagonal_down_right || diagonal_up_left || diagonal_down_left) {
return c+1;
}
}
}
return last_empty_column+1;
}
int main() {
FourInARow game (P1);
while (game.getGameState() == RUNNING) {
std::cout << "Turn for player " << game.getPlayer() << std::endl;
game.printField();
switch (game.getPlayer()) {
case P1: {
int move = -1;
while (!game.isValidMove(move = game.p1.readMove()));
game.makeMove(P1, move);
break;
}
case P2: {
game.makeMove(P2, game.p2.findMove(game.getField()));
break;
}
}
}
game.printField();
game.printResult();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment