-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathBoard.cpp
More file actions
419 lines (356 loc) · 15.7 KB
/
Board.cpp
File metadata and controls
419 lines (356 loc) · 15.7 KB
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
/**
* \Author: Chance Penner
* \Author: Markus Becerra
* \Author: Sarah Scott
* \Author: Thomas Gardner
* \Author: Haonan Hu
* \File: Board.cpp
* \Date: 09/19/2019
* \Brief: File is cpp file
* \copyright: Group "Big SegFault Energy" All rights reserved
*/
#include "Board.h"
#include <sstream>
#include <limits>
Board::Board(int shipnum)
{
numberOfShips = shipnum; //the number of ships per player.
blueTilde = "\033[1;36m~\033[0m"; //sets the color of ~ to blue
redHit = "\033[1;31mX\033[0m"; //sets the color of X to red
whiteMiss = "\033[1;37mO\033[0m"; //sets the color of O to white
ship = "\033[1;32m∆\033[0m"; //sets the color of ship (∆) to green
for (int i=0; i<8; i++) //initializes myBoard and shotBoard to a grid of just blueTildes (aka water)
{
for(int j=0; j<8; j++)
{
myBoard[i][j] = blueTilde;
shotBoard[i][j] = blueTilde;
}
}
}
Board::~Board() //destructor to delete m_ship
{
delete[] m_ship;
}
void Board::printShotBoard() //prints the current player's view of their opponents side. Aka what they are shooting at
{
std::cout << "\n\t\t\tYour opponent's board\n";
std::cout << '\t';
for(int i=0;i<8;i++)
{
std::cout << m_rowNames[i] << "\t"; //prints the column names (A-H)
}
std::cout << "\n";
for(int i=0;i<8;i++)
{
std::cout << i+1; //prints the row names (1-8)
for(int j=0;j<8;j++)
{
std::cout << "\t" << shotBoard[i][j]; //prints the elements of the board
}
if(i != 7)
{
std::cout << "\n\n\n"; //prints spaces between rows. Not needed at end to conserve space
}
else
{
std::cout << "\n\n"; //prints only two newlines instead of three to conserve space
}
}
}
void Board::printMyBoard() //prints the current player's board
{
std::cout << "\t\t\tYour board\n";
std::cout << "\t";
for(int i=0;i<8;i++)
{
std::cout << m_rowNames[i] << "\t"; //prints the column names (A-H)
}
std::cout << "\n";
for(int i=0;i<8;i++)
{
std::cout << i+1; //prints the row names (1-8)
for(int j=0;j<8;j++)
{
std::cout << "\t" << myBoard[i][j]; //prints the elements of the board
}
if(i<8) //used to reduce whitespace between end of board and next game message
{
std::cout << "\n\n\n";
}
else
{
std::cout << "\n\n";
}
}
}
void Board::printIntermission() //prints the intermission screen so player's can swap turns
{
for(int i=0;i<40;i++)
{
std::cout << "\n\n\n\n\n\n"; //prints a lot of newlines to add blank space so that player's can swap turns without seeing each other's boards
}
std::cout << "When ready, please press Enter: ";
std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); //takes in input of the user, basically ignoring any input so that the user can type anything
}
bool Board::updateMyBoard(std::string userGuess) //updates the current player's board
{
guessConversion(userGuess); //updates m_rowIndex and m_columnIndex based on userGuess
std::string location = myBoard[m_rowIndex][m_columnIndex]; //sets the location to the current element in the index that the player got shot at
if(location == blueTilde) //if the location is a blueTilde (aka water), then it becomes a miss
{
myBoard[m_rowIndex][m_columnIndex] = whiteMiss;
}
else if(location == ship) //if the location is a ship, then we set that element to a redHit
{
myBoard[m_rowIndex][m_columnIndex] = redHit;
for(int i = 0; i < numberOfShips; i++) //searches through each ship, at the length of each ship, until it finds the correct index holding the userGuess location
{
for(int j = 0; j < m_ship[i].getLength(); j++)
{
if(m_ship[i].getCoordinate(j) == userGuess)
{
m_ship[i].addDamage(); //add damage counter to that ship
if(m_ship[i].isSunk()) //then, we check if it has an amount of damage counters equal to its length, meaning it has been sunk
{
std::cout << "BATTLESHIP SUNK!!!\n"; //prints that the ship has been sunk
}
break; //we can break since we found the indices of the userGuess location ship
}
}
}
return true; //return true because a ship was hit
}
else if(location == redHit || location == whiteMiss) //if the user guesses a location already shot at before, we prompt them to try again
{
throw(std::runtime_error("You already shot at this location! Try again."));
}
return false; //if there were no hits, then this runs and we return false because it was a miss
}
void Board::updateShotBoard(std::string userGuess, bool wasHit) //takes in the userGuess and a bool wasHit
{
guessConversion(userGuess); //calls guess conversion to get back the correct indices based on userGuess
if(wasHit) //if that location was hit, then we changed it to a redHit
{
shotBoard[m_rowIndex][m_columnIndex] = redHit;
}
else //if that location was a miss, we change it to whiteMiss
{
shotBoard[m_rowIndex][m_columnIndex] = whiteMiss;
}
}
//assumes userGuess is within boundary since that is checked first
void Board::guessConversion(std::string userGuess) //converts userGuess to two indices and updates member variables m_rowIndex and m_columnIndex with those indices
{
if(userGuess.length() != 2) //if userGuess is not a string of length 2, we instantly return because it cannot be a valid input
{
return;
}
else
{
for(unsigned int i=0;i<m_rowNames.length();i++) //had to make i an unsigned int since m_rowNames.length() returns an unsigned in as well
{
if(userGuess.at(0) == m_rowNames.at(i) ||userGuess.at(0) == (tolower(m_rowNames.at(i)))) //compares the first char in userGuess to every element in m_rowNames,
//which is "ABCDEFGH" until it matches one, and returns that index.
//it also compares it to the tolower version, so "abcdefgh"
{
m_columnIndex = i; //sets m_columnIndex to the correct index, and then breaks out since we do not need to scan through m_rowNames anymore
break;
}
else
{
m_columnIndex = 9; //sets m_rowIndex to a number outside of the range so that it's not just the value it held previously.
//if the letter the user typed is withing "ABCDEFGH", then the correct index is set and we break out
//of this for loop and m_rowIndex does not become 9
}
}
}
int temp = userGuess.at(1) - '0'; //sets temp to the index the user typed. We subtract '0' to convert it from the ASCII value to the proper decimal value. In works cited
m_rowIndex = temp - 1; //sets it to the column the user wants, but subtracts 1 to get the proper index
}
bool Board::withinBoundary(std::string userGuess) //returns true if userGuess is within bounds of the board, false if not
{
if(userGuess.length() != 2) //checks that the userGuess was length 2. If not, returns false immediately
{
return false;
}
else
{
guessConversion(userGuess); //takes in userGuess and sets the indices based on that guess
if((0 <= m_rowIndex && m_rowIndex <= 7) && (0 <= m_columnIndex && m_columnIndex <= 7))
{
return true; //if the indices are within the bounds of our board, we return true
}
else
{
return false; //otherwise, we return false
}
}
}
bool Board::noHorizontalCollision(std::string userGuess, int shipLength) //returns true if the userGuess location will not cause any horizontal ship collision or out of bounds errors
{
guessConversion(userGuess); //updates m_rowIndex and m_columnIndex based on userGuess
for(int i = 0; i < shipLength; i++)
{
if((0 <= m_rowIndex && m_rowIndex <= 7) && (0 <= m_columnIndex + i && m_columnIndex + i <= 7)) //checks that all the next right indices are within the bounds
{
if(myBoard[m_rowIndex][m_columnIndex + i] != blueTilde) //returns false if at any time the next right indices are not blueTildes
{
return false;
}
}
else
{
return false; //returns false if any of the right indices are out of bounds
}
}
return true; //returns true since all the false checks were not hit, so there is no collision that would happen
}
bool Board::noVerticalCollision(std::string userGuess, int shipLength) //returns true if the userGuess location will not cause any vertical ship collison or out of bounds errors
{
guessConversion(userGuess); //updates m_rowIndex and m_columnIndex based on userGuess
for(int i = 0; i < shipLength; i++)
{
if((0 <= m_rowIndex + i && m_rowIndex + i <= 7) && (0 <= m_columnIndex && m_columnIndex <= 7)) //checks that all the next below indices are within bounds
{
if(myBoard[m_rowIndex + i][m_columnIndex] != blueTilde) //returns false if at any time the next below indices are not blueTildes
{
return false;
}
}
else
{
return false; //returns false if any of the below indices are out of bounds
}
}
return true; //returns true since all the false checks were not hit, so there is no collision that would happen
}
void Board::setupBoard() //sets up the board
{
std::string userGuess; //used to take in the user's location
std::string userDirection; //("H" or "h" or "V" or "v") horizontal or vertical ship placement
bool validLocation = false; //used to keep asking for valid location if still false
std::string temp; //used for ascii conversion
bool HorV = false; //gets set to true if the user types "H" or "h" or "V" or "v"
m_ship = new Ship[numberOfShips]; //creates an array of Ship objects, the amount is the number of ships
for(int i = 0; i < numberOfShips; i++)
{
m_ship[i].createShip(i+1); //creates a ship at each index, of which the size corresponds to the index +1 (eg. index 0 houses ship of length 1, index 1 houses ship of length 2)
if(m_ship[i].getLength() == 1) //if the ship is length 1, we do not need to do horizontal or vertical collision checks, so we just ask where to place it
{
userGuess = " "; //reinitialize userGuess
do {
printMyBoard(); //prints the user's board s they can see where to place the ship
std::cout<<"Where would you like to place this ship of size 1? Enter your coordinate: ";
std::getline(std::cin, userGuess); //takes in the user's input
std::transform(userGuess.begin(), userGuess.end(),userGuess.begin(), ::toupper); //converts guess to uppercase so that it is always passed in consistently
if(!withinBoundary(userGuess)) //if that location is not within the boundary, we tell them to try again
{
std::cout << "Invalid coordinate! Try again.\n";
}
} while(!withinBoundary(userGuess)); //runs until the user's guess is within the boundary
myBoard[m_rowIndex][m_columnIndex] = ship; //sets the user's guess location to a ship
m_ship[i].setCoordinate(userGuess, 0); //sets the element in the m_ship array to the location string (eg. "A1", "B4"), and passes in the index as 0
printMyBoard(); //prints the newly updated board
}
else
{
std::cout<<"HORIZONTAL(H/h) OR VERTICAL(V/v) orientation for this ship of size " <<i+1 <<": ";
std::getline(std::cin, userDirection); //takes in the user input of horizontal or vertical
do
{
HorV = false; //need to reinitialize to false so that each run through, this loop correclty runs
if(userDirection == "H" || userDirection == "h")
{
validLocation = false; //reinitializes to false since if they do H twice in a row, it could have been set to true from before
std::cout<<"Where would you like the left most coordinate of this ship to be? ";
std::getline(std::cin, userGuess);
std::transform(userGuess.begin(), userGuess.end(),userGuess.begin(), ::toupper); //converts guess to uppercase
while(validLocation == false) //runs until the location is valid
{
if(noHorizontalCollision(userGuess,i+1)) //checks for no horizontal collion, passing in the location and current ship length
{
guessConversion(userGuess); //pushing two int indexes back to orignal spot of user guess
temp = userGuess; //used to store and manipulate userGuess without changing userGuess
for(int j = 0; j < m_ship[i].getLength(); j++ )
{
myBoard[m_rowIndex][m_columnIndex+j] = ship; //sets the horizontal locations to a ship
m_ship[i].setCoordinate(temp, j);
temp[0] = temp.at(0) + 1; //sets the first value of temp (which is a copy of userGuess) to the next consecutive alphabetical value, using ASCII math
//for example, if temp is "A1" then temp[0] = 'A', and temp.at(0) = 'A'. temp.at(0) + 1 is 'A' + 1, which returns 'B'
//so, when then override temp[0] to B, hence traversing the columns
}
printMyBoard(); //prints the updated board
validLocation = true; //sets valid location to true to help break out of loop
HorV = true; //true to kick out of while loop
}
else //if the input was not "H" or "h"
{
printMyBoard(); //prints the board again and asks the user to try again
std::cout << "Invalid location. Try again!\n";
std::cout<<"Where would you like the left most coordinate of this ship to be? ";
std::getline(std::cin, userGuess);
std::transform(userGuess.begin(), userGuess.end(),userGuess.begin(), ::toupper); //converts guess to uppercase
}
}
}
else if(userDirection == "V" || userDirection == "v")
{
validLocation = false; //reinitializes to false since if they do H twice in a row, it could have been set to true from before
std::cout<<"Where would you like the top most coordinate of this ship to be? ";
std::getline(std::cin, userGuess);
std::transform(userGuess.begin(), userGuess.end(),userGuess.begin(), ::toupper); //converts guess to uppercase
while(validLocation == false)
{
if(noVerticalCollision(userGuess,i+1))
{
guessConversion(userGuess); //pushing two int indexes back to orignal spot of user guess
temp = userGuess; //used to store and manipulate userGuess without changing userGuess
for(int j = 0; j < m_ship[i].getLength(); j++ )
{
myBoard[m_rowIndex+j][m_columnIndex] = ship;
m_ship[i].setCoordinate(temp, j);
temp[1] = temp.at(1) + 1; //sets the second value of temp (which is a copy of userGuess) to the next consecutive numberical value
//for example, if temp is "A1" then temp[1] = '1', and temp.at(1) = '1'. temp.at(1) + 1 is '1' + 1, which returns '2'
//so, when then override temp[1] to 2, hence traversing the rows
}
printMyBoard(); //prints the updated board
validLocation = true; //sets valid location to true to help break out of loop
HorV = true; //true to kick out of while loop
}
else
{
printMyBoard(); //prints the updated board again and asks the user to try again
std::cout << "Invalid location. Try again!\n";
std::cout<<"Where would you like the top most coordinate of this ship to be? ";
std::getline(std::cin, userGuess);
std::transform(userGuess.begin(), userGuess.end(),userGuess.begin(), ::toupper); //converts guess to uppercase
}
}
}
else //if the input was not "V" or "v"
{
std::cout << "Invalid Direction. Try again!\n";
printMyBoard(); //prints the board again so that the user can try again
std::cout<<"HORIZONTAL(H/h) OR VERTICAL(V/v) orientation for this ship of size " <<i+1 <<": ";
std::getline(std::cin, userDirection);
}
}while(!HorV); //runs until the user has inputed "H" or "h" or "V" or "v". Also, there are checks to insure that the location was valid first as well, before getting to this point
}
}
std::cout << "Press Enter to go to the next Player's turn: ";
std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n'); //basically lets the user type in anything, ignoring their input
printIntermission(); //prints the intermission screen
}
void Board::setNumberofShips(int shipnum) //sets the number of ships
{
numberOfShips = shipnum;
}
int Board::getNumberofShips() const //returns the number of ships
{
return numberOfShips;
}
Ship* Board::getShip() const //returns m_ship
{
return m_ship;
}