Saving Game Function

Hello,

I'm trying to implement a save and load game feature for my 2 player 10x10 grid text based Battleships game.

Tried scouring around the internet and haven't found much luck that puts me in the right direction so I thought I'd ask here!

Just after a simple save the game data into a file solution and the ability to load it back up.

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
#include <iostream>
#include <string>

using namespace std;

int main();
void setupGrid(int playerId);
void drawGrid(int playerId);
void doTurn(int playerId);
int countActiveShips(int playerId);

// constants to represent each grid state
const int STATE_COVERED_EMPTY = 0;
const int STATE_COVERED_OCCUPIED = 1;
const int STATE_UNCOVERED_EMPTY = 2;
const int STATE_UNCOVERED_EXPLODED = 3;

// variables to store our game information
string name[2];
int grid[2][100];

int main()
{
	cout << "Welcome to Battleship\n\n";

	// Prompt player 1 for name and store in variable
	cout << "Player 1, what is your name? ";
	cin >> name[0];
	cout << "Thanks, " << name[0] << ".\n\n";

	// Prompt player 2 for name and store in variable
	cout << "Alright player 2, what is your name? ";
	cin >> name[1];
	cout << "And thanks to you too, " << name[1] << ".\n\n";

	cout << name[0] << " it's your turn to place your ships. No peeking " << name[1] << "!\n\n";

	setupGrid(0); // Setup grid for player 1

	cout << "OK " << name[1] << ", now it's your turn. Eyes off, " << name[0] << "!\n\n";

	setupGrid(1); // Setup grid for player 2

	cout << "\n\nAlright, we are ready to start the match!\n";

	// Loop while both players have at least one ship on the field
	int winner, loser;
	while (countActiveShips(0) > 0 && countActiveShips(1) > 0) {
		cout << "\nYou're up, " << name[0] << ".\nHere's what " << name[1] << "'s grid looks like...\n\n";
		doTurn(0);
		if (countActiveShips(1) == 0) {
			// Player 1 has won the game
			loser = 1;
			winner = 0;
			break;
		}

		cout << "\n" << name[1] << ", get in there!\nHere's the damage done to " << name[0] << " so far...\n\n";
		doTurn(1);
		if (countActiveShips(0) == 0) {
			// Player 2 has won the game
			loser = 0;
			winner = 1;
			break;
		}
	}

	// Handle winner
	cout << "\n\nCommander " << name[winner] << ", we have received reports that your arch nemesis " << name[loser] << " was on board that ship.\n";
	cout << "The war is over. We are victorious! Glory to Commander " << name[winner] << "!";
	string endGame;
	cin >> endGame;
    return 0;
}

void setupGrid(int playerId) {

	for (int i = 0; i < 100; i++)
		grid[playerId][i] = 0;

	while (true) {
		// Check if active ships on grid equals 5, if so break out of the loop
		int activeShips = countActiveShips(playerId);
		if (activeShips == 5)
			break;

		cout << "Please enter a location for ship " << activeShips + 1 << "\n";

		int row, col;

		// Start a loop that will continue forever (until we break it)
		while (true) {
			string strcol;

			// Prompt for column number
			cout << "Enter column number (0-9): ";
			cin >> strcol;

			// Attempt conversion to integer
			try {
				col = stoi(strcol); // Convert string strcol to integer col
				if (col >= 0 && col <= 9) // Check if number is valid
					break; // Break out of loop
			}
			catch (const std::exception& e) {
				// This block will be executed if the conversion fails
				// Since the loop will break if conversion was successful
				// we don't need to do anything here
			}

			cout << "Sorry, that number wasn't valid.\n";
		}

		while (true) {
			string strrow;

			// Prompt for row number
			cout << "Enter row number (0-9): ";
			cin >> strrow;

			// Attempt conversion to integer
			try {
				row = stoi(strrow); // Convert string strcol to integer col
				if (row >= 0 && row <= 9) // Check if number is valid
					break; // Break out of loop
			}
			catch (const std::exception& e) {
				// This block will be executed if the conversion fails
				// Since the loop will break if conversion was successful
				// we don't need to do anything here
			}

			cout << "Sorry, that number wasn't valid.\n";
		}

		// Check if player already placed a ship in that location
		if (grid[playerId][col * 10 + row] == 1) {
			cout << "You've already placed a ship there... try again.\n\n";
			continue;
		}

		grid[playerId][col * 10 + row] = 1;
		cout << "Done! Ship " << activeShips + 1 << " has been placed in column " << col << ", row " << row << ".\n\n";
	}
}

void drawGrid(int playerId) {
	// Draw numbers across the top
	cout << "  "; // alignment spacing
	for (int i = 0; i < 10; i++)
		cout << i << " ";

	for (int i = 0; i < 100; i++) {
		// Print a newline and row number for every row
		if (i % 10 == 0)
			cout << "\n" << i / 10 << " ";

		// Print a character depending on value of grid at position i
		switch (grid[playerId][i]) {
			case STATE_COVERED_EMPTY:
			case STATE_COVERED_OCCUPIED:
				cout << " ";
				break;
			case STATE_UNCOVERED_EMPTY:
				cout << "~";
				break;
			case STATE_UNCOVERED_EXPLODED:
				cout << "#";
				break;
		}

		// Space between characters
		cout << " ";
	}

}

void doTurn(int playerId) {
	// If playerId is 0, enemyId is 1, otherwise enemyId is 0
	int enemyId = playerId == 0 ? 1 : 0;

	// Draw opponent's grid
	drawGrid(enemyId);

	cout << "\n\n";

	// Prompt player for their tharget
	int row, col;

	// Start a loop that will continue forever (until we break it)
	while (true) {
		string strcol;

		// Prompt for column number
		cout << "Enter column number (0-9): ";
		cin >> strcol;

		// Attempt conversion to integer
		try {
			col = stoi(strcol); // Convert string strcol to integer col
			if (col >= 0 && col <= 9) // Check if number is valid
				break; // Break out of loop
		}
		catch (const std::exception& e) {
			// This block will be executed if the conversion fails
			// Since the loop will break if conversion was successful
			// we don't need to do anything here
		}

		cout << "Sorry, that number wasn't valid.\n\n";
	}

	while (true) {
		string strrow;

		// Prompt for row number
		cout << "Enter row number (0-9): ";
		cin >> strrow;

		// Attempt conversion to integer
		try {
			row = stoi(strrow); // Convert string strcol to integer col
			if (row >= 0 && row <= 9) // Check if number is valid
				break; // Break out of loop
		}
		catch (const std::exception& e) {
			// This block will be executed if the conversion fails
			// Since the loop will break if conversion was successful
			// we don't need to do anything here
		}

		cout << "Sorry, that number wasn't valid.\n\n";
	}

	cout << "Firing at (" << col << ", " << row << ")!\n";

	// Conditional based on the current state of the target location
	switch (grid[enemyId][col * 10 + row]) {
		case STATE_COVERED_EMPTY:
			cout << "Aww damn, there was nothing there. That's a miss!\n";
			grid[enemyId][col * 10 + row] = STATE_UNCOVERED_EMPTY;
			return;
		case STATE_COVERED_OCCUPIED:
			cout << "Hit! " << name[playerId] << ", you just sunk " << name[enemyId] << "'s battleship!\n";
			grid[enemyId][col * 10 + row] = STATE_UNCOVERED_EXPLODED;
			return;
		case STATE_UNCOVERED_EMPTY:
			cout << "A miss! That position is still empty, whoops!\n";
			return;
		case STATE_UNCOVERED_EXPLODED:
			cout << "The charred hull of the ship you previously bombed is now little more than dust. Good work!\n";
			return;
	}
}

int countActiveShips(int playerId) {
	// Loop over all positions and count active ships
	int ships = 0;
	for (int i = 0; i < 100; i++)
		if (grid[playerId][i] == STATE_COVERED_OCCUPIED)
			ships++;
	return ships;
}


to save to a file you can write all the variables you defined in your main() function to a file, whenever you quit the game.

to load you simply do the contrary, you read from the file and define your variables from there.

Additionally you can have a third function that deletes all contend of the save file if you end a game and have no reason to save at all.

Hope this helps.

Regards,

Hugo.
so, what do you need to save... ?

- the board. It could be saved more efficiently but given the small size of the data, just dump the whole thing. this would be ship locations and shot or not and by who?
- the player names
- whose turn it is
-- what else??

the most simple form I can think of looks like
ofstream ofs;
ofs.open(savefile, flags);
ofs << name1 <<endl;
ofs << name2 << endl;
ofs << turnindication<< endl; //probably simply 1 or 2
for(..)
for(..)
ofs<< grid[x][y]<< endl;

ofs.close();

and the inverse, using an ifstream, to read it.
you could do it a bit more efficiently using binary block writes, but this will make a human readable file and you can debug it easier.

I didn't even try compiling this so you may need to fix a syntax error or two.
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
void save(int playerID) {
    ofstream fout("savefile"); // or have them enter a file name
    fout << playerID << '\n';  // save whose turn it is
    for (int player = 0; player < 2; player++) {
        fout << name[player] << '\n';
        for (int row = 0; row < 10; row++) {
            for (int row = 0; row < 10; row++)
                fout << grid[player][row * 10 + col] << ' ';
            fout << '\n';
        }
    }
    // file is automatically closed when fout goes out of scope
}

int load() {
    ifstream fin("savefile"); // or have them enter a file name
    int playerID;
    fin >> playerID;
    fin.ignore(999, '\n');  // eat the newline after the playerID
    for (int player = 0; player < 2; player++) {
        getline(fin, name[player]); // getline allows spaces in name
        for (int row = 0; row < 10; row++)
            for (int row = 0; row < 10; row++)
                fin >> grid[player][row * 10 + col];
    }
    return playerID; // return whose turn it is
    // file is automatically closed when fin goes out of scope
}

Last edited on
Thanks for the replies.

Getting the following syntax error for the ofstream/ifstream commands.

1
2
3
4
5
6
7
bs1.cpp: In function 'void save()':
bs1.cpp:267:15: error: variable 'std::ofstream fout' has initializer but incomplete type
  ofstream fout("savefile"); // or have them enter a file name
               ^
bs1.cpp: In function 'void load()':
bs1.cpp:280:14: error: variable 'std::ifstream fin' has initializer but incomplete type
  ifstream fin("savefile"); // or have them enter a file name 
You need to include <fstream> for those.
Awesome that compiled!

How does the save function actually get triggered. Is it once "savefile" is entered that is meant to start the save process?
How does the save function actually get triggered

That's up to you. It's only going to be "triggered" if you call it somewhere. "savefile" is just an arbitrary file name that I put there. You can call the file whatever you want, or ask the user to enter a file name.

To call the function, you need to give the user the ability to say "save" instead of entering a column number. You also need to give them an option to "quit" (with an option to save). And you need to give them the ability to "load" a game instead of starting a new game when the program starts.

doTurn should perhaps return a bool that tells main whether the game should continue or not. So in main you would call it like:
1
2
    if (!doTurn(0))  // if doTurn returns false, quit
        break;  // or maybe return 0; 

Same with the doTurn(1) call.

Then doTurn would be something like this
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
bool doTurn(int playerID) {

    ...

    while (true) {
        string input; // a more general name than "strcol"

        while (true) {

            cout << "Enter column number (0-9) or "save" or "exit"\n";
            cin >> input;

            if (input == "save") {
                save(playerID);
                continue; // back to reading the column number
            }
            else if (input == "quit") {
                cout << "Do you want to save your game? (y/n)\n";
                cin >> input;
                if (input == 'y' || input == 'Y')
                    save(playerID);
                return false;  // tell main to quit
            }
            break;
        }

        // Attempt conversion to integer
        ...

    }

    ...

    return true;
}


This is just some code I whipped up in a couple of minutes, so you may need to fix some stuff. It's just the idea.
Last edited on
Topic archived. No new replies allowed.