Last active
August 13, 2017 17:32
-
-
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".
This file contains 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 <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