Skip to content

Commit

Permalink
Add the game of Go
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamStelmaszczyk committed Oct 15, 2021
1 parent 995249f commit d1f65da
Show file tree
Hide file tree
Showing 6 changed files with 415 additions and 20 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ Game examples
- Connect Four - [game rules](https://github.com/AdamStelmaszczyk/gtsa/blob/master/cpp/examples/connect_four.md), [code example](https://github.com/AdamStelmaszczyk/gtsa/blob/master/cpp/examples/connect_four.cpp).
<p><a href="https://github.com/AdamStelmaszczyk/gtsa/blob/master/cpp/examples/connect_four.md"><img src="https://github.com/AdamStelmaszczyk/gtsa/blob/master/cpp/examples/connect_four.gif"/></a></p>

- Go - [game rules](https://github.com/AdamStelmaszczyk/gtsa/blob/master/cpp/examples/go.md), [code example](https://github.com/AdamStelmaszczyk/gtsa/blob/master/cpp/examples/go.cpp).
<p><a href="https://github.com/AdamStelmaszczyk/gtsa/blob/master/cpp/examples/go.md"><img src="https://github.com/AdamStelmaszczyk/gtsa/blob/master/cpp/examples/go.gif"/></a></p>

Implemented algorithms
---

Expand Down
14 changes: 11 additions & 3 deletions cpp/Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
CC=g++
FLAGS=-std=c++11 -O2 -fprofile-arcs -ftest-coverage -Wreturn-type -D BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
FLAGS=-g -std=c++11 -O2 -fprofile-arcs -ftest-coverage -Wreturn-type -D BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS

all: tests/test_tic_tac_toe.o tests/test_isola.o tests/test_connect_four.o tests/play_isola.o
all: tests/test_tic_tac_toe.o tests/test_isola.o tests/test_connect_four.o tests.test_go.o tests/play_isola.o

test: tests/test_tic_tac_toe.o tests/test_isola.o tests/test_connect_four.o
tests/test_tic_tac_toe.o
tests/test_isola.o
tests/test_connect_four.o
tests/test_go.o

valgrind: tests/test_tic_tac_toe.o tests/test_isola.o tests/test_connect_four.o
valgrind: tests/test_tic_tac_toe.o tests/test_go.o tests/test_isola.o tests/test_connect_four.o
valgrind --error-exitcode=1 --leak-check=full tests/test_tic_tac_toe.o
valgrind --error-exitcode=1 --leak-check=full tests/test_isola.o
valgrind --error-exitcode=1 --leak-check=full tests/test_connect_four.o
valgrind --error-exitcode=1 --leak-check=full tests/test_go.o

test_tic_tac_toe: tests/test_tic_tac_toe.o
tests/test_tic_tac_toe.o
Expand All @@ -22,6 +24,9 @@ test_isola: tests/test_isola.o
test_connect_four: tests/test_connect_four.o
tests/test_connect_four.o

test_go: tests/test_go.o
tests/test_go.o

test_executable: tests/test_executable.o tests/marten.o
tests/test_executable.o

Expand All @@ -40,6 +45,9 @@ tests/test_isola.o: gtsa.hpp examples/isola.cpp tests/test_isola.cpp
tests/test_connect_four.o: gtsa.hpp examples/connect_four.cpp tests/test_connect_four.cpp
$(CC) $(FLAGS) tests/test_connect_four.cpp -o tests/test_connect_four.o

tests/test_go.o: gtsa.hpp examples/go.cpp tests/test_go.cpp
$(CC) $(FLAGS) tests/test_go.cpp -o tests/test_go.o

tests/test_executable.o: gtsa.hpp examples/isola.cpp tests/test_executable.cpp
$(CC) $(FLAGS) tests/test_executable.cpp -o tests/test_executable.o

Expand Down
277 changes: 277 additions & 0 deletions cpp/examples/go.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
#include "../gtsa.hpp"

using std::unordered_set;

const int SIDE = 5;
const char PLAYER_1 = '1';
const char PLAYER_2 = '2';
const char EMPTY = '_';

struct GoMove : public Move<GoMove> {
int x;
int y;

GoMove() { }

GoMove(int x, int y) : x(x), y(y) { }

void read(istream &stream = cin) override {
if (&stream == &cin) {
cout << "Enter space separated X and Y of your move (X = -1 is a pass): ";
}
stream >> x >> y;
}

ostream &to_stream(ostream &os) const override {
return os << x << " " << y;
}

bool operator==(const GoMove &rhs) const override {
return x == rhs.x && y == rhs.y;
}

size_t hash() const override {
using boost::hash_value;
using boost::hash_combine;
size_t seed = 0;
hash_combine(seed, hash_value(x));
hash_combine(seed, hash_value(y));
return seed;
}
};

struct cords {
int x;
int y;

friend ostream &operator<<(ostream &os, const cords &c) {
return os << c.x << " " << c.y;
}
};

struct ReachResult {
vector<cords> area;
bool closed;

friend ostream &operator<<(ostream &os, const ReachResult &r) {
os << "area:" << endl;
for (const auto a : r.area) {
os << a << endl;
}
os << "closed: " << r.closed;
return os;
}
};

struct GoState : public State<GoState, GoMove> {

vector<char> board;
vector<char> prev_board;
unordered_set<size_t> board_history;
vector<bool> pass;

GoState() : State({0, 1}) {
board_history.insert(hash());
}

GoState(const string &init_string) : State({0, 1}) {
const unsigned long length = init_string.length();
const unsigned long correct_length = SIDE * SIDE;
if (length != correct_length) {
throw invalid_argument("Initialization string length must be " + to_string(correct_length));
}
for (int i = 0; i < length; i++) {
const char c = init_string[i];
if (c != PLAYER_1 && c != PLAYER_2 && c != EMPTY) {
throw invalid_argument(string("Undefined symbol used: '") + c + "'");
}
}
board = vector<char>(init_string.begin(), init_string.end());
pass = vector<bool>(teams.size());
board_history.insert(hash());
}

GoState clone() const override {
GoState clone = GoState();
clone.board = board;
clone.prev_board = prev_board;
clone.pass = pass;
clone.board_history = board_history;
clone.player_to_move = player_to_move;
return clone;
}

int get_goodness() const override {
if (is_terminal()) {
if (is_winner(player_to_move)) {
return 10000;
}
if (is_winner(get_next_player(player_to_move))) {
return -10000;
}
return 10;
}
return 0;
}

vector<GoMove> get_legal_moves(int max_moves = INF) const override {
// A turn is either a pass; or a move that doesn't repeat an earlier grid coloring.
int available_moves = SIDE * SIDE + 1;
if (max_moves > available_moves) {
max_moves = available_moves;
}
vector<GoMove> moves(max_moves);
auto copy = clone();
int i = 0;
for (int y = 0; y < SIDE; ++y) {
for (int x = 0; x < SIDE; ++x) {
if (board[y * SIDE + x] == EMPTY) {
GoMove move = {x, y};
copy.make_move(move);
auto hash = copy.hash();
copy.undo_move(move);
if (board_history.find(hash) == board_history.end()) { // positional superko
moves[i++] = move;
if (i >= max_moves) {
return moves;
}
}
}
}
}
moves[i++] = GoMove(-1, 0); // pass
moves.resize(i);
return moves;
}

bool is_terminal() const override {
return all_of(pass.begin(), pass.end(), [](bool v) { return v; });
}

bool is_winner(int player) const override {
return get_score(player) > get_score(get_next_player(player));
}

int get_score(int player) const {
return get_stones(player) + get_area(player);
}

int get_stones(int player) const {
return count(board.begin(), board.end(), player_index_to_char(player));
}

int get_area(int player) const { // area = number of empty points that reach only player's stones
int area = 0;
vector<bool> seen(board.size());
const char player_char = player_index_to_char(player);
for (int x = 0; x < SIDE; ++x) {
for (int y = 0; y < SIDE; ++y) {
if (!seen[y * SIDE + x]) {
const auto result = reach(x, y, EMPTY, player_char, seen);
if (result.closed) {
area += result.area.size();
}
}
}
}
return area;
}

ReachResult reach(const int x, const int y, const char from, const char to, vector<bool> &seen) const {
if (x < 0 || y < 0 || x >= SIDE || y >= SIDE) {
return {{}, true};
}
const int i = y * SIDE + x;
if (seen[i] && board[i] == from) {
return {{}, true};
}
seen[i] = true;
if (board[i] == to) {
return {{}, true};
}
if (board[i] != from) {
return {{}, false};
}
const auto n = reach(x, y - 1, from, to, seen);
const auto e = reach(x + 1, y, from, to, seen);
const auto w = reach(x - 1, y, from, to, seen);
const auto s = reach(x, y + 1, from, to, seen);
vector<cords> area = {{x, y}};
area.insert(area.end(), n.area.begin(), n.area.end());
area.insert(area.end(), e.area.begin(), e.area.end());
area.insert(area.end(), w.area.begin(), w.area.end());
area.insert(area.end(), s.area.begin(), s.area.end());
return {area, n.closed && e.closed && w.closed && s.closed};
}

void clear(const int x, const int y, const int player) { // empty all player's stones that reach only enemy
vector<bool> seen(board.size());
auto result = reach(x, y, player_index_to_char(player), player_index_to_char(get_next_player(player)), seen);
if (result.closed) {
for (const cords c : result.area) {
board[c.y * SIDE + c.x] = EMPTY;
}
}
}

void make_move(const GoMove &move) override {
if (move.x == -1) {
pass[player_to_move] = true;
player_to_move = get_next_player(player_to_move);
return;
}
pass[player_to_move] = false;
prev_board = board;
board[move.y * SIDE + move.x] = player_index_to_char(player_to_move);
const int enemy = get_next_player(player_to_move);
clear(move.x, move.y - 1, enemy);
clear(move.x + 1, move.y, enemy);
clear(move.x - 1, move.y, enemy);
clear(move.x, move.y + 1, enemy);
clear(move.x, move.y, player_to_move); // suicide
player_to_move = get_next_player(player_to_move);
board_history.insert(hash());
}

void undo_move(const GoMove &move) override {
board_history.erase(hash());
player_to_move = get_prev_player(player_to_move);
board = prev_board;
pass[player_to_move] = false;
}

int player_char_to_index(char player) const override {
return (player == PLAYER_1) ? 0 : 1;
}

char player_index_to_char(int index) const override {
return (index == 0) ? PLAYER_1 : PLAYER_2;
}

ostream &to_stream(ostream &os) const override {
for (int y = 0; y < SIDE; ++y) {
for (int x = 0; x < SIDE; ++x) {
os << board[y * SIDE + x];
}
os << endl;
}
os << player_index_to_char(player_to_move) << endl;
for (auto p : pass) {
os << p << " ";
}
os << endl;
return os;
}

bool operator==(const GoState &other) const override {
return board == other.board;
}

size_t hash() const override {
using boost::hash_value;
using boost::hash_combine;
size_t seed = 0;
hash_combine(seed, hash_value(board));
return seed;
}
};
Binary file added cpp/examples/go.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 13 additions & 17 deletions cpp/examples/go.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Also known as: Weiqi, Baduk.

Go is a two-player board game. [Tromp-Taylor](http://tromp.github.io/go.html) rules:
Go is a two-player board game. [Tromp-Taylor](http://tromp.github.io/go.html) rules (similar to Chinese):

1. Go is played on a NxN square grid of points, by two players called Black and White.
2. Each point on the grid may be colored black, white or empty.
Expand All @@ -23,7 +23,7 @@ then clearing the opponent color, and then clearing one's own color.

Input
---
The input is a 7x7 matrix consisting only of `1`, `2` and `_`. Then another line follows with `1` or `2`, which is your player id.
The input is a 5x5 matrix consisting only of `1`, `2` and `_`. Then another line follows with `1` or `2`, which is your player id.

The cell marked `_` means it contains an empty square. The cell marked `1` means it contains player 1's point. The cell marked `2` means it contains player 2's point.

Expand All @@ -32,26 +32,22 @@ In the given matrix, top-left is (0, 0) and bottom-right is (6, 6). The x-coordi
First input
---
```
_______
_______
_______
_______
_______
_______
_______
_____
_____
_____
_____
_____
1
```

Sample input
---
```
_______
_22__2_
_11221_
___111_
______
_______
_______
_____
_22__
_1122
___11
_____
2
```

Expand All @@ -67,4 +63,4 @@ Sample output

Sample game
---
TODO: add GIF
<img src="https://github.com/AdamStelmaszczyk/gtsa/blob/master/cpp/examples/go.gif"/>

0 comments on commit d1f65da

Please sign in to comment.