Question - C++ Battleships Game

Hi All,

I have a question on one of my functions for a code I am writing in C++ that should hopefully resolve the issues with the other portions since the logic is the same.

My Battleship implementation in C++ is almost fully functional aside from some logic errors I am having. I have functions that store and display on the board the ship placement for both the user and CPU. The ships come in sizes of 5,4,3 (for two of the ships), and 2 for the last. I am seeing my ship's placement if near a boundary either being cut off completely or being pushed over to the next row or column. I believe it may be an issue with my for loop and conditional statements. My approach is below:

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
  /*******************
Place Player Ships
*******************/
//Places the ships for the user's board - allows the user to confirm before play begins
//
//Preconditions: User has selected to place their ships by hand/choice (through input to console) as opposed
// to allowing the game engine itself to randomly place their ships; ships placement must be valid as well
// 
// Postconditions: Displays the user's board for their ship placement after they have decided to place their ships. 
void placePlayerShips(char shipBoard[][MAX_ROWS])
{
	//variables for board
	//for the character corresponding to column
	char xChar;
	//x and y coordinates for gameplay
	int x;
	int y;
	//direction for vertical or horizontal placement
	int dir;
	//ship number for the loop
	int shipNumber = 0;
	//check if user wants to enter their ship placement manually or randomly
	int manRand = 0;
	//names of ships
	string names[] = { "Aircraft Carrier", "Battleship", "Submarine", "Cruiser", "Destroyer" };
	//representation of thoe ships by letter
	char shipLetter[] = { 'A', 'B', 'S', 'C', 'D' };
	//ships lengths
	int lengths[] = { 5, 4, 3, 3, 2 };
	//booleans to determine if user placement is done and whether or not ships overlap
	bool done = false;
	bool isOverlap = true;
	//ask user if they want to place ships manually or randomized by CPU
	cout << "Would you like to place your ships manually, or at random?\n";
	cout << "1.) Manual\n";
	cout << "2.) Random\n";
	cout << "Enter choice (1/2): ";
	//input for manual implementation - user needs to select choice 1 to place, otherwise, choice 2 will place their ships
	cin >> manRand;
	if (manRand == 2) {
		done = true;
		placeEnemyShips(shipBoard); //function call also used to place user ships
	}
	//while user placement of ships are not done
	while (!done)
	{
		//user defined clear function - helpful with clearing board and displaying new board as ships are set
		clear();
		displayPlayerBoard(shipBoard);
		//tell user what ships they are placing
		cout << "\nYou are placing the " << names[shipNumber] << ".\n";
		cout << "Its length is " << lengths[shipNumber] << ".\n\n";
		//where should the ship be placed by letters
		cout << "Where should the front of the ship be? (A-O): ";
		cin >> xChar;
		//do double duty here and inspect if the user's ship placement with a function meets the requirements
		//check if user has not entered a valid character placement for the gameboard
		x = charToNum(xChar);
		while (x < 0 || x> 14)
		{
			//messages if user did not enter the proper board placement
			cout << "Please enter a valid starting column position.\n";
			cout << "You are placing the " << names[shipNumber] << ".\n";
			cout << "Its length is " << lengths[shipNumber] << ".\n";
			cout << "Where should the front of the ship be? (A-O): ";
			//prompt user to enter column character again
			cin >> xChar;
			x = charToNum(xChar);
		}
		//clear to make board easier to see
		clear();
		displayPlayerBoard(shipBoard);
		//ask user to enter starting row (1-15)
		cout << "\n\nPlease enter the other starting position. (1-15): ";
		cin >> y;
		//check if the value of y is valid
		while (y < 1 || y > 15)
		{
			//messages if user did not enter the proper board placement
			cout << "Please enter a valid starting row position.\n";
			cout << "You are placing the " << names[shipNumber] << ".\n";
			cout << "Its length is " << lengths[shipNumber] << ".\n";
			cout << "Please enter the other starting position. (1-15): ";
			cin >> y;
		}
		//decrement y by one
		y--;
		int startX = x; //the ship's placement starts at the column specified for the head
		int startY = y; //the ship's placement starts at the row specified for the head
		clear();
		displayPlayerBoard(shipBoard);
		//choose ships direction - 1 for vertical, 2 if the ship is to lie horizontal
		cout << "What direction would you like the ship to face?\n";
		cout << "1. Vertical\n";
		cout << "2. Horizontal\n";
		//user input for ship direction
		cin >> dir;
		//check if user input a valid direction
		while (dir != 1 && dir != 2)
		{
			clear();
			displayPlayerBoard(shipBoard);
			//tell user selected direction was not valid
			cout << "Please enter a valid direction:\n\n";
			cout << "What direction would you like the ship to face?\n";
			cout << "1.) Vertical\n";
			cout << "2.) Horizontal\n";
			cin >> dir;
		}
		//if ship orientation is selected to be horizontal
		if (dir == 2)
		{
			//then the ship shouldn't have vertical orientation - in the original battleship there wasn't placement for diagonals
			dir -= 2;
		}
		//check to see if there is anyoverlap with the ships-function call isShipsOverlap
		isOverlap = isShipsOverlap(shipBoard, startX, startY, dir, lengths[shipNumber]);
		//if there isn't overlap
		if (isOverlap==true)
		{
			//A15 -> //A15, A14,...,A11 -> walk back so that the tail is at the proper spot
			//now that direction == 0
			if (dir == 0)
			{
				//until runs until x is less than the starting position plus the length of the ships
				for (x = startX ; x < startX + lengths[shipNumber]; x++)
				{
					//check if the ship's position needs to be walked back or rotated for the tail
					
					//ship will be positioned with letters marking the ship's orientation
					shipBoard[x][y] = shipLetter[shipNumber];
				}
			}
			//case where ship was selected to have a vertical orientation - ship is aligned across several rows
			else
			{
				//ship will be positioned with letters marking the ship's orientation
				for (y = startY ; y < startY + lengths[shipNumber]; y++)
				{
					shipBoard[x][y] = shipLetter[shipNumber];
				}
			}
			//ships will be placed as long as the user hasn't placed the largest ship yet (ships placed in order from 1 to 5 (0-4 in this case for indices).
			if (shipNumber < 4)
			{
				//increment ship number to next ship to be placed
				shipNumber++;
			}
			else 
			{
				//now the user is done with ship placement
				done = true;
			}
		}
		//if there was overlap
		else 
		{
			//user has not entered valid coordinates for the orientation of the ship based on horizontal/vertical alignment or coordinates, or ship overlap
			cout << "Your ship has not been placed either because your ship's length was outside the board"
				   << " based on placement, overlapped with another ship, or"
				<< " you made an inaccurate assignment of the front of the ship."
				 << "\nPlease enter valid coordinates for the ship...\n";
			pause();
			cin.get();
		}
	}
	return;
}
You don't check if the ship's length will fit on the board (after getting vertical/horizontal placement).

Why is x valid in [0, 14], but y [0,15]? What the relationship between those numbers and MAX_ROWS?
Last edited on
The 150+ line function needs some refactoring.
Like for example, all the user input can easily be moved out into 4 separate functions:
int getColumn();
int getRow();
int getDirection();
getShipParams(int &x, int &y, int &dirn); // just calls the other 3

That would remove a good 50 lines of function bloat.

> isOverlap = isShipsOverlap(shipBoard, startX, startY, dir, lengths[shipNumber]);
Do you also have a function which checks a ship against the board, and not just against other ships?

> walk back so that the tail is at the proper spot
What?
L10: Reading your code in isolation, since you've omitted the first dimension
and you haven't included your declarations, it's not clear how many columns there
are in your game.

L25,27,29: These should be const.

L54,65: Not clear if you're asking for row or column. Your should compute the
upper letter given MAX_COLS.

L59: Don't hard code constants all over your program. You should have a global
constexpr int MAX_COLS {15}; and use that for checking limits.

L74: Not clear you're asking for a row number.

L117-119: Sense of isOverlap is reversed. This is not intuitive.
isOverlap == true should mean there IS overlap.
Last edited on
Here's a cleaned-up version:
1 of 2:
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
#include <string>
#include <iostream>
using namespace std;

constexpr int MAX_ROWS{ 15 };
constexpr int MAX_COLS{ 15 };

//	Globals
//names of ships
const string names[] = { "Aircraft Carrier", "Battleship", "Submarine", "Cruiser", "Destroyer" };
//representation of thoe ships by letter
const char shipLetter[] = { 'A', 'B', 'S', 'C', 'D' };
//ships lengths
const int lengths[] = { 5, 4, 3, 3, 2 };

void placeEnemyShips(char shipBoard[MAX_COLS][MAX_ROWS]);
void displayPlayerBoard(char shipBoard[MAX_COLS][MAX_ROWS]);
void clear();
int charToNum(char);
char numToChar(int);
void pause();

int getColNum(int shipNumber)
{
	char last_col = numToChar(MAX_COLS);
	char xChar;
	int x;

	cout << "Column where the front of the ship should be? (A-" << last_col << "): ";
	cin >> xChar;	
	//check if user has not entered a valid character placement for the gameboard
	x = charToNum(xChar);
	while (x < 0 || x> MAX_COLS - 1)
	{	//messages if user did not enter the proper board placement
		cout << "Please enter a valid starting column position.\n";
		cout << "You are placing the " << names[shipNumber] << ".\n";
		cout << "Its length is " << lengths[shipNumber] << ".\n";
		cout << "Column Where should the front of the ship be? (A-" << last_col << "): ";
		//prompt user to enter column character again
		cin >> xChar;
		x = charToNum(xChar);
	}
	return x;
}

int getRowNum(int shipNumber)
{
	int y;

	cout << "\n\nPlease enter the starting row. (1-" << MAX_ROWS << "): ";
	cin >> y;
	//check if the value of y is valid
	while (y < 1 || y > MAX_ROWS)
	{	//messages if user did not enter the proper board placement
		cout << "Please enter a valid starting row position.\n";
		cout << "You are placing the " << names[shipNumber] << ".\n";
		cout << "Its length is " << lengths[shipNumber] << ".\n";
		cout << "Please enter the starting row (1-" << MAX_ROWS << "): ";
		cin >> y;
	}
	return y;
}

int getDir(char shipBoard[MAX_COLS][MAX_ROWS])
{
	int dir;
	
	//choose ships direction - 1 for vertical, 2 if the ship is to lie horizontal
	cout << "What direction would you like the ship to face?\n";
	cout << "1. Vertical\n";
	cout << "2. Horizontal\n";
	//user input for ship direction
	cin >> dir;
	//check if user input a valid direction
	while (dir != 1 && dir != 2)
	{
		clear();
		displayPlayerBoard(shipBoard);
		//tell user selected direction was not valid
		cout << "Please enter a valid direction:\n\n";
		cout << "What direction would you like the ship to face?\n";
		cout << "1.) Vertical\n";
		cout << "2.) Horizontal\n";
		cin >> dir;
	}
	return dir;
}

//	Return true if row and col refer to a cell on the board
//  Assumes row and col have already been biased to zero.
bool inbounds(int row, int col)
{
	if (row < 0 || row >= MAX_ROWS)
		return false;
	if (col < 0 || col >= MAX_COLS)
		return false;
	return true;
}

//	Return true if cell referred to by x and y is out of bounds.
//	or already contains a ship.  
bool occupied(char shipBoard[MAX_COLS][MAX_ROWS], int x, int y)
{
	if (!inbounds(x, y))
		return true;		//	Treat out of bounds as occupied
	if (isalpha(shipBoard[x][y]))
		return true;		//	Already a character there
	return false;			//	Not occupied
}

//	Return true if any cell of the ship is out of bounds 
//	or already contains a ship.
bool isShipsOverlap(char shipBoard[MAX_COLS][MAX_ROWS], int startX, int startY, int dir, int length)
{		
	if (dir == 1)
	{	//	vertical - Iterate through the rows
		for (int x = startX; x < startX + length; x++)
		{	//	test if occupied or out of bounds
			if (occupied(shipBoard, x, startY))
				return true;	//	Occupied or out of bounds
		}		
	}
	else
	{	// horizontal - Iterate through the columns
		for (int y = startY; y < startY + length; y++)
		{	//	test if occupied or out of bounds	
			if (occupied(shipBoard, startX, y))
				return true;	//	Occupied or out of bounds
		}		
	}
	return false;			// Not occupied
}

void placeOneShip(char shipBoard[MAX_COLS][MAX_ROWS], int startX, int startY, int dir, int length, char letter)
{
	if (dir == 0)
	{	//	Horizontal - Iterate through the columns 
		for (int x = startX; x < startX + length; x++)
		{	//ship will be positioned with letters marking the ship's orientation
			shipBoard[x][startY] = letter;
		}
	}	
	else
	{	//	Vertical - Iterate through the rows
		for (int y = startY; y < startY + length; y++)
		{	//ship will be positioned with letters marking the ship's orientation
			shipBoard[startX][y] = letter;
		}
	}
}

2 of 2:
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
/*******************
Place Player Ships
*******************/
//Places the ships for the user's board - allows the user to confirm before play begins
//
//Preconditions: User has selected to place their ships by hand/choice (through input to console) as opposed
// to allowing the game engine itself to randomly place their ships; ships placement must be valid as well
// 
// Postconditions: Displays the user's board for their ship placement after they have decided to place their ships. 
void placePlayerShips(char shipBoard[MAX_COLS][MAX_ROWS])
{
	//variables for board	
	//x and y coordinates for gameplay
	int x;
	int y;
	//direction for vertical or horizontal placement
	int dir;
	//ship number for the loop
	int shipNumber = 0;
	//check if user wants to enter their ship placement manually or randomly
	int manRand = 0;	
	//booleans to determine if user placement is done and whether or not ships overlap
	bool done = false;
	bool isOverlap = true;

	//ask user if they want to place ships manually or randomized by CPU
	cout << "Would you like to place your ships manually, or at random?\n";
	cout << "1.) Manual\n";
	cout << "2.) Random\n";
	cout << "Enter choice (1/2): ";
	//input for manual implementation - user needs to select choice 1 to place, otherwise, choice 2 will place their ships
	cin >> manRand;
	if (manRand == 2) {
		done = true;
		placeEnemyShips(shipBoard); //function call also used to place user ships
	}
	//while user placement of ships are not done
	while (!done)
	{
		//user defined clear function - helpful with clearing board and displaying new board as ships are set
		clear();
		displayPlayerBoard(shipBoard);
		//tell user what ships they are placing
		cout << "\nYou are placing the " << names[shipNumber] << ".\n";
		cout << "Its length is " << lengths[shipNumber] << ".\n\n";
		//where should the ship be placed by letters
		x = getColNum(shipNumber);
		//clear to make board easier to see
		clear();
		displayPlayerBoard(shipBoard);
		//ask user to enter starting row (1-15)
		y = getRowNum(shipNumber);		
		//decrement y by one
		y--;
		int startX = x; //the ship's placement starts at the column specified for the head
		int startY = y; //the ship's placement starts at the row specified for the head
		clear();
		displayPlayerBoard(shipBoard);
		dir = getDir(shipBoard);
		//if ship orientation is selected to be horizontal
		if (dir == 2)
		{
			//then the ship shouldn't have vertical orientation - in the original battleship there wasn't placement for diagonals
			dir -= 2;
		}
		//check to see if there is anyoverlap with the ships-function call isShipsOverlap
		isOverlap = isShipsOverlap(shipBoard, startX, startY, dir, lengths[shipNumber]);
		//if there isn't overlap
		if (isOverlap == false)
		{	//ships will be placed as long as the user hasn't placed the largest ship yet (ships placed in order from 1 to 5 (0-4 in this case for indices).
			placeOneShip(shipBoard, startX, startY, dir, lengths[shipNumber], shipLetter[shipNumber]);
			if (shipNumber < 4)
			{	//increment ship number to next ship to be placed
				shipNumber++;
			}
			else
			{	//now the user is done with ship placement
				done = true;
			}
		}
		//if there was overlap
		else
		{	//user has not entered valid coordinates for the orientation of the ship based on horizontal/vertical alignment or coordinates, or ship overlap
			cout << "Your ship has not been placed either because your ship's length was outside the board"
				<< " based on placement, overlapped with another ship, or"
				<< " you made an inaccurate assignment of the front of the ship."
				<< "\nPlease enter valid coordinates for the ship...\n";
			pause();
			cin.get();
		}
	}
}
Topic archived. No new replies allowed.