-
Notifications
You must be signed in to change notification settings - Fork 0
/
hexboard.cpp
204 lines (166 loc) · 8.28 KB
/
hexboard.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#include <iostream>
#include <iomanip>
#include "hexboard.h"
/* Create a hex board of size width x height. Throws an std::invalid_argument exception if player markers (p1/p2)
are a reserved value (HexBoaard::EMPTY/HexBoard::OUT_OF_BOUNDS) */
HexBoard::HexBoard(int width, int height, char p1, char p2, const std::string& p1Name, const std::string& p2Name) :
BoardState(width * height, HexBoard::EMPTY), Width{width}, Height{height}, P1{p1}, P2{p2},
P1Name{p1Name}, P2Name{p2Name}, VisitedCells(width * height, false)
{
if (P1 == HexBoard::EMPTY || P2 == HexBoard::EMPTY || P1 == HexBoard::OUT_OF_BOUNDS || P2 == HexBoard::OUT_OF_BOUNDS)
throw std::invalid_argument{"Player markers can't have reserved values HexBoard::EMPTY or HexBoard::OUT_OF_BOUNDS"};
}
// Create a deep copy of the specified board
HexBoard::HexBoard(const HexBoard& hexBoard) :
BoardState(hexBoard.BoardState), Width{hexBoard.Width}, Height{hexBoard.Height}, P1{hexBoard.P1}, P2{hexBoard.P2},
P1Name{hexBoard.P1Name}, P2Name{hexBoard.P2Name}, VisitedCells(hexBoard.Width * hexBoard.Height, false) {}
/* Create a board using the configuration specified in settings. Throws an std::invalid_argument exception if player markers
are a reserved value (HexBoaard::EMPTY/HexBoard::OUT_OF_BOUNDS) */
HexBoard::HexBoard(const Settings& settings) :
BoardState(settings.BoardSize.first * settings.BoardSize.first, HexBoard::EMPTY), Width{settings.BoardSize.first},
Height{settings.BoardSize.second}, P1{settings.PlayerMarkers.first}, P2{settings.PlayerMarkers.second},
P1Name{settings.PlayerNames.first}, P2Name{settings.PlayerNames.second}, VisitedCells(settings.BoardSize.first * settings.BoardSize.first, false)
{
if (P1 == HexBoard::EMPTY || P2 == HexBoard::EMPTY || P1 == HexBoard::OUT_OF_BOUNDS || P2 == HexBoard::OUT_OF_BOUNDS)
throw std::invalid_argument{"Player markers can't have reserved values HexBoard::EMPTY or HexBoard::OUT_OF_BOUNDS"};
}
// Return the state of the cell (x, y) if (x, y) is in the bounds of the board. Otherwise return HexBoard::OUT_OF_BOUNDS
char HexBoard::GetCell(int x, int y) const
{
if (x < 0 || x >= Width || y < 0 || y >= Height)
return HexBoard::OUT_OF_BOUNDS;
else
return BoardState[x + y * Width];
}
/* Change the state of the cell (x, y) to player. If (x, y) is occupied (i.e. not HexBoard::EMPTY) or out of bounds, display
an error instead. Return true if a cell's state was changed, false otherwise */
bool HexBoard::MarkCell(int x, int y, char player)
{
switch (GetCell(x, y))
{
case HexBoard::EMPTY:
BoardState[x + y * Width] = player;
return true;
case HexBoard::OUT_OF_BOUNDS:
std::cerr << '(' << x << ',' << y << ") is out of bounds\n\n";
return false;
default:
std::cerr << '(' << x << ',' << y << ") is occupied\n\n";
return false;
}
}
/* Swaps the board's players such that player 1 plays vertically and player 2 plays horizontally. The board state is
also inverted such that player 1 marks become player 2 marks, and vice versa */
void HexBoard::SwapPlayers()
{
std::swap(P1, P2);
std::swap(P1Name, P2Name);
for (int y = 0; y < Height; ++y)
{
for (int x = 0; x < Width; ++x)
{
if (BoardState[x + y * Width] == P1) BoardState[x + y * Width] = P2;
else if (BoardState[x + y * Width] == P2) BoardState[x + y * Width] = P1;
}
}
}
/* Find a winning path starting from cell (x, y). If player is player 1, a winning path is a path that reaches the right
side of the board, otherwise it is a path that reaches the bottom side of the board. Return true if a winning path is
found, otherwise return false */
bool HexBoard::TraversePathsFromCell(int x, int y, char player)
{
// Check if (x, y) is at the right or bottom edge
if (player == P1 && x == Width - 1) return true;
else if (player == P2 && y == Height - 1) return true;
VisitedCells[x + y * Width] = true;
// Check cell (x, y)'s hexagonal neighbours
if (GetCell(x - 1, y) == player && VisitedCells[(x - 1) + y * Width] == false)
if (TraversePathsFromCell(x - 1, y, player) == true) return true;
if (GetCell(x - 1, y + 1) == player && VisitedCells[(x - 1) + (y + 1) * Width] == false)
if (TraversePathsFromCell(x - 1, y + 1, player) == true) return true;
if (GetCell(x, y - 1) == player && VisitedCells[x + (y - 1) * Width] == false)
if (TraversePathsFromCell(x, y - 1, player) == true) return true;
if (GetCell(x, y + 1) == player && VisitedCells[x + (y + 1) * Width] == false)
if (TraversePathsFromCell(x, y + 1, player) == true) return true;
if (GetCell(x + 1, y - 1) == player && VisitedCells[(x + 1) + (y - 1) * Width] == false)
if (TraversePathsFromCell(x + 1, y - 1, player) == true) return true;
if (GetCell(x + 1, y) == player && VisitedCells[(x + 1) + y * Width] == false)
if (TraversePathsFromCell(x + 1, y, player) == true) return true;
return false;
}
/* Check if player has a winning path. A winning path is horizontal if player is player 1, vertical otherwise.
Return true if a winning path is found, false otherwise */
bool HexBoard::HasPlayerWon(char player)
{
bool isGameWon = false;
int y = 0, x = 0;
int end = (player == P1 ? Height : Width);
VisitedCells.assign(Width * Height, false);
for (int i = 0; i < end; ++i) // Look for a path from the first column (P1) or y (P2) to the opposite side
{
if (GetCell(x, y) == player && TraversePathsFromCell(x, y, player) == true)
{
isGameWon = true;
break;
}
player == P1 ? ++y : ++x;
}
return isGameWon;
}
// Return the number of digits in n
inline int numberOfDigits(int n) {return (n == 0) ? 1 : floor(log10(n)) + 1;}
// Create a deep copy of the specified board
HexBoard& HexBoard::operator=(const HexBoard& hexBoard)
{
Width = hexBoard.Width;
Height = hexBoard.Height;
P1 = hexBoard.P1;
P2 = hexBoard.P2;
P1Name = hexBoard.P1Name;
P2Name = hexBoard.P2Name;
BoardState = hexBoard.BoardState;
return *this;
}
// Print the board, player names, and headers for each row and column
std::ostream& operator<<(std::ostream& out, const HexBoard& hexboard)
{
int colHeaderWidth = numberOfDigits(hexboard.Width - 1);
int rowHeaderWidth = numberOfDigits(hexboard.Height - 1);
int rowPadding = rowHeaderWidth + hexboard.P1Name.size() + strlen(" ");
// Print player 2's header
std::cout << std::setw(rowPadding + ((hexboard.Width - 1) * 4) / 2 - hexboard.P2Name.size() / 2) << "" << hexboard.P2Name << '\n';
// Print top column headers
for (int col = 0; col < hexboard.Width; ++col)
{
if (col == 0) std::cout << std::setw(rowPadding) << "";
std::cout << std::setw(colHeaderWidth) << std::left << col << \
std::setw(strlen(" ") - colHeaderWidth) << "";
}
std::cout << '\n';
for (int row = 0; row < hexboard.Height; ++row)
{
if (row == hexboard.Height / 2) // Print player 1's header and left row header
std::cout << std::setw(rowPadding - rowHeaderWidth - strlen(" ")) << std::right << hexboard.P1Name << ' ' << std::setw(rowHeaderWidth) << row << ' ';
else // Print left row header only
std::cout << std::setw(rowPadding) << std::right << row << ' ';
// Print row and horizontal links
for (int col = 0; col < hexboard.Width - 1; ++col)
std::cout << hexboard.GetCell(col, row) << " - ";
// Print row's last cell and right row header
std::cout << hexboard.GetCell(hexboard.Width - 1, row) << ' ' << std::left << row << '\n';
// Print links to cells below
if (row < hexboard.Height - 1)
{
std::cout << std::setw(rowPadding += 2) << "";
for (int col = 0; col < hexboard.Width - 1; ++col)
std::cout << "\\ / ";
std::cout << "\\ \n";
}
}
// Print bottom column headers
std::cout << std::setw(rowPadding + 2) << "" << std::left;
for (int col = 0; col < hexboard.Width; ++col)
std::cout << std::setw(colHeaderWidth) << col << std::setw(strlen(" ") - colHeaderWidth) << "";
std::cout << "\n\n";
return out;
}