#include #include #include #include using namespace std; // This assignment is worth 15 points // +3 for proper grid display and setup // +3 for proper random ship placement // +3 for proper turn looping, game operation // +3 for proper input checks // +3 for high score file read/write void printGrid(int rows, int cols); bool checkInput(int x, int y); int translateCoords(int x, int y); void checkStatus(); int row_count = 6, col_count = 6; int gridSize = row_count*col_count; int orien_avail = 4; // Possible orientations /* Orientation Index Table * 0 = ship points East * 1 = ship points South * 2 = ship points West * 3 = ship points North */ int sel_x, sel_y; string gridStatus = "", statusHidden; bool allShipsSunk = false; int turnCount = 0; // Design note: the grid's coordinates are flattened into a 1D array (string) // Multiply the row index by the column size and add the column index to translate // the coordinates into a single value to access the correct gridStatus index const int CRUISER_SIZE = 2, DESTROYER_SIZE = 3, CARRIER_SIZE = 4; int location_Cruiser[CRUISER_SIZE]; // Holds the location coordiantes per ship int location_Destroyer[DESTROYER_SIZE]; int location_Carrier[CARRIER_SIZE]; void placeAllShips(); void placeCruiser(); // Use RNG to place the ships, also checks that the location void placeDestroyer(); // used per ship is valid by checking against the other ships void placeCarrier(); bool orientationCheck(int frontIndex, int shipSize, int orientation); void fillLocation(int * arr, int frontIndex, int shipSize, int orientation); bool overlapCheck(int frontIndex, int orientation, int shipSize1, int * location, int shipSize2); int main() { int seed = time(NULL); srand(seed); for(int i = 0; i < gridSize; i++) // Initialize the game grid gridStatus += ' '; placeAllShips(); while(!allShipsSunk && turnCount < gridSize) { cout << "Current Battle Status:" << endl; // DEBUG ONLY: CONFIRM SHIP POSITIONS /* for(int i = 0; i < CARRIER_SIZE; i++) gridStatus[location_Carrier[i]] = 'C'; for(int i = 0; i < DESTROYER_SIZE; i++) gridStatus[location_Destroyer[i]] = 'D'; for(int i = 0; i < CRUISER_SIZE; i++) gridStatus[location_Cruiser[i]] = 'I'; */ printGrid(row_count, col_count); do // Get input { cout << "Fire at: "; cin >> sel_x >> sel_y; cin.ignore(); } while(!checkInput(sel_x, sel_y)); if(statusHidden[translateCoords(sel_x, sel_y)] != ' ') { cout << "A hit! "; statusHidden[translateCoords(sel_x, sel_y)] = 'X'; gridStatus[translateCoords(sel_x, sel_y)] = 'X'; } else { cout << "A miss! "; statusHidden[translateCoords(sel_x, sel_y)] = 'O'; gridStatus[translateCoords(sel_x, sel_y)] = 'O'; } checkStatus(); cout << endl; turnCount++; } cout << "Final Battle Status:" << endl; printGrid(row_count, col_count); cout << "All enemy ships sunk in " << turnCount << " turns" << endl; ifstream highScoreFile; string fileName = "battleship_highscore.txt"; highScoreFile.open(fileName); if(highScoreFile.fail()) { ofstream newHighScore; newHighScore.open(fileName); cout << "New high score!" << endl; newHighScore << turnCount; newHighScore.close(); highScoreFile.close(); } else { int highScore; highScoreFile >> highScore; if(turnCount < highScore) { cout << "New high score!" << endl; highScoreFile.close(); ofstream newHighScore; newHighScore.open(fileName); newHighScore << turnCount; newHighScore.close(); } } return 0; } int translateCoords(int x, int y) // Get an index from the coordinates { return (x-1)*col_count+(y-1); } void printGrid(int rows, int cols) { cout << '+'; for(int i = 0; i < cols; i++) cout << "---+"; cout << endl; for(int i = 0; i < rows; i++) { cout << '|'; for(int j = 0; j < cols; j++) { cout << ' ' << gridStatus[i*cols+j] << " |"; } cout << endl << '+'; for(int j = 0; j < cols; j++) { cout << "---+"; } cout << endl; } } bool checkInput(int x, int y) { int index = translateCoords(x, y); if(x > row_count || y > col_count || x <= 0 || y <= 0) { cout << "Error: Invalid index!" << endl; return false; } else if(index >= gridSize || index < 0) { cout << "Error: Out of bounds!" << endl; return false; } else if(gridStatus[index] != ' ') { cout << "Error: Coordinate already used!" << endl; return false; } else return true; } void placeAllShips() { placeCarrier(); placeDestroyer(); placeCruiser(); statusHidden = gridStatus; // A second string holds the location of the ships for convenience, but hides it from view for(int i = 0; i < CARRIER_SIZE; i++) statusHidden[location_Carrier[i]] = 'C'; for(int i = 0; i < DESTROYER_SIZE; i++) statusHidden[location_Destroyer[i]] = 'D'; for(int i = 0; i < CRUISER_SIZE; i++) statusHidden[location_Cruiser[i]] = 'I'; } void placeCruiser() { // Place the Cruiser last. Just like the destroyer, any valid position of the ship's bow // will have at least one valid orientation even with the other two ships already present. int headpoint = rand() % gridSize; int orien = rand() % orien_avail, attempts = 0; bool orien_valid = false, headpoint_valid = false; while(!headpoint_valid) // Check that the RNG selected position isn't already occupied { int i = 0, j = 0; while(headpoint != location_Carrier[i] && i < CARRIER_SIZE) i++; while(headpoint != location_Destroyer[j] && j < DESTROYER_SIZE) j++; if(i == CARRIER_SIZE && j == DESTROYER_SIZE) headpoint_valid = true; else headpoint = rand() % gridSize; } while(!orien_valid && attempts < orien_avail) { // Start with a random orientation, then go through them if necessary orien_valid = orientationCheck(headpoint, CARRIER_SIZE, orien); // You also need to check whether if the ships will overlap orien_valid &= overlapCheck(headpoint, orien, DESTROYER_SIZE, location_Carrier, CARRIER_SIZE); orien_valid &= overlapCheck(headpoint, orien, CRUISER_SIZE, location_Destroyer, DESTROYER_SIZE); if(!orien_valid) { attempts++; orien = (orien + 1) % orien_avail; } } fillLocation(location_Cruiser, headpoint, CRUISER_SIZE, orien); } void placeDestroyer() { // Place the Destroyer second. It faces orientation and placement restrictions so // you'll have to check it against location_Carrier[] in a loop int headpoint = rand() % gridSize; int orien = rand() % orien_avail, attempts = 0; bool orien_valid = false, headpoint_valid = false; while(!headpoint_valid) // Check that the RNG selected position isn't already occupied { int i = 0; while(headpoint != location_Carrier[i] && i < CARRIER_SIZE) i++; if(i == CARRIER_SIZE) headpoint_valid = true; else headpoint = rand() % gridSize; } // No matter how the Carrier was placed, once you have a valid start point for the head // of the Destroyer, there's always at least one valid orientation for it to use. while(!orien_valid && attempts < orien_avail) { // Start with a random orientation, then go through them if necessary orien_valid = orientationCheck(headpoint, CARRIER_SIZE, orien); // You also need to check whether if the ships will overlap orien_valid &= overlapCheck(headpoint, orien, DESTROYER_SIZE, location_Carrier, CARRIER_SIZE); if(!orien_valid) { attempts++; orien = (orien + 1) % orien_avail; } } fillLocation(location_Destroyer, headpoint, DESTROYER_SIZE, orien); } void placeCarrier() { // When you place the Carrier first, it occupies 4 units, so you can place the // head point anywhere on the grid. Only the orientation will be restricted. // However, there are always two guaranteed orientations available for every // possible start position. int headpoint = rand() % gridSize; int orien = rand() % orien_avail, attempts = 0; bool orien_valid = false; // Max number of attempts will never be reached, but it's a good guard against infinite loops while(!orien_valid && attempts < orien_avail) { // Start with a random orientation, then go through them if necessary orien_valid = orientationCheck(headpoint, CARRIER_SIZE, orien); if(!orien_valid) { attempts++; orien = (orien + 1) % orien_avail; } } fillLocation(location_Carrier, headpoint, CARRIER_SIZE, orien); } bool orientationCheck(int frontIndex, int shipSize, int orientation) { switch(orientation) { case 0: if(frontIndex % col_count - (shipSize-1) >= 0) return true; break; case 1: if(frontIndex - col_count*(shipSize-1) >= 0) return true; break; case 2: if(frontIndex % col_count + (shipSize-1) < col_count) return true; break; case 3: if(frontIndex + col_count*(shipSize-1) < gridSize) return true; break; } return false; } void fillLocation(int * arr, int frontIndex, int shipSize, int orientation) { switch(orientation) { case 0: for(int i = 0; i < shipSize; i++) arr[i] = frontIndex - i; break; case 1: for(int i = 0; i < shipSize; i++) arr[i] = frontIndex - i*col_count; break; case 2: for(int i = 0; i < shipSize; i++) arr[i] = frontIndex + i; break; case 3: for(int i = 0; i < shipSize; i++) arr[i] = frontIndex + i*col_count; break; } } bool overlapCheck(int frontIndex, int orientation, int shipSize1, int * location, int shipSize2) { int tentativePos[shipSize1]; fillLocation(tentativePos, frontIndex, shipSize1, orientation); for(int i = 0; i < shipSize2; i++) { for(int j = 0; j < shipSize1; j++) if(location[i] == tentativePos[j]) return false; } return true; } void checkStatus() // Checks which ships have been sunk { int cruise = 0, destroy = 0, carry = 0; // HP meter per ship static bool sunk_cruiser = false, sunk_destroyer = false, sunk_carrier = false; for(int i = 0; i < statusHidden.length(); i++) { if(statusHidden[i] == 'C') carry++; else if(statusHidden[i] == 'D') destroy++; else if(statusHidden[i] == 'I') cruise++; } if(cruise == 0 && !sunk_cruiser) { cout << "Enemy cruiser sunk!"; sunk_cruiser = true; } else if(destroy == 0 && !sunk_destroyer) { cout << "Enemy destroyer sunk!"; sunk_destroyer = true; } else if(carry == 0 && !sunk_carrier) { cout << "Enemy carrier sunk!"; sunk_carrier = true; } allShipsSunk = sunk_cruiser && sunk_destroyer && sunk_carrier; }