-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgame.cpp
More file actions
313 lines (276 loc) · 8.43 KB
/
Copy pathgame.cpp
File metadata and controls
313 lines (276 loc) · 8.43 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
/*
Author: Jared Thomas
Date: Thursday, February 2, 2023
This module contains the core game loop
*/
#include <vector>
#include <string>
#include <cmath>
#include <iostream>
#include <cassert>
#include "Tower.h"
#include "TowerDrawer.h"
#include "screen.h"
#include "game.h"
#include "game_parser.h"
#include "parse.h"
#include "help.h"
struct GameState {
GameState(unsigned initialDisks);
~GameState();
std::vector<std::string> tokens;
bool requestQuit;
bool gameOver;
std::vector<Tower> towers;
TowerDrawer towerDrawer;
unsigned numDisks; // Total number of game disks
unsigned moves; // Number of valid moves that the player has made
std::string status; // Message that prints every frame
std::string question;
};
GameState::GameState(unsigned initialDisks):
tokens(),
requestQuit(false),
gameOver(false),
towers(),
towerDrawer(initialDisks + 3),
numDisks(initialDisks),
moves(0),
status(),
question()
{
}
GameState::~GameState()
{
}
static unsigned inputState(GameState& gameState);
static void moveState(GameState& gameState);
const unsigned GOAL_TOWER_VECTOR_INDEX = 2;
/*
Returns the least possible number of moves required to win a game (perfect game).
This is (2^d - 1), where d is the number of disks.
*/
static unsigned leastPossible(unsigned numDisks)
{
return (1U << numDisks) - 1;
}
/*
Calculates and returns the game score as the number of moves the player made
out of the minimum moves required to win
*/
static unsigned getScore(unsigned numDisks, unsigned moves)
{
return (unsigned)(round(100.0 * leastPossible(numDisks) / moves));
}
/*
Prints the player's game statistics after the game has been won.
The statistics are the number of moves made, minimum moves required,
and the final score
*/
static void printResults(unsigned numDisks, unsigned moves)
{
std::cout << "You finished in " << moves << " moves\n";
std::cout << "Best possible is "\
<< leastPossible(numDisks) << " moves\n";
std::cout << "Your score: " << getScore(numDisks, moves) << "%";
}
/*
Clears the screen and draws the towers vector using a tower drawer
*/
static void drawTowers(const std::vector<Tower>& towers, const TowerDrawer& towerDrawer)
{
clearScreen();
towerDrawer.draw(towers);
}
/*
Prints the status message after drawing the towers
*/
static void printStatus(const std::string& statusMessage)
{
std::cout << "\n";
std::cout << statusMessage << "\n";
std::cout << "\n";
}
static void askQuestion(const std::string& question)
{
std::cout << question;
}
/*
Returns true if the winning condition has been reached, returns false
otherwise
*/
static bool checkForGameWon(const Tower& goalTower, unsigned totalDisks)
{
return goalTower.num_disks() == totalDisks;
}
/*
Puts the towers back in their initial state
*/
static void resetTowers(std::vector<Tower>& towers, unsigned totalDisks)
{
towers.clear();
towers.push_back(Tower(totalDisks));
towers.push_back(Tower());
towers.push_back(Tower());
}
/*
Puts the entire game back in its initial state; this includes resetting the
towers and all state variables
*/
static void resetGame(GameState& gameState)
{
resetTowers(gameState.towers, gameState.numDisks);
gameState.moves = 0;
gameState.status = "Type \"help\" at any time for instructions. Good luck!";
gameState.question = "What's your first move? ";
}
/*
Asks the player if they want to play again. Blocks program flow until the
player enters valid input.
Returns true if the player requests to play again, returns false if they
don't.
*/
static bool askPlayAgain()
{
std::string input;
do {
std::cout << "Do you want to play again? (y/n): ";
input = getRawInput();
} while(!(input == "y" || input == "n"));
return input == "y";
}
/*
Asks the player for the number of disks they want to play with.
Blocks program flow until the player enters valid input, where valid input
is a number between 1 and 8 inclusive.
Returns the number of disks the player entered.
*/
unsigned askNumDisks()
{
std::string input;
long numDisks;
do {
input.clear();
numDisks = 0;
do {
std::cout << "How many disks do you want to play with? (1=Easiest, 8=Hardest): ";
input = getRawInput();
} while(parseLong(input.c_str(), &numDisks) != SUCCESS);
} while((numDisks < 1) || (numDisks > 8));
return (unsigned)numDisks;
}
void game(unsigned initialDisks)
{
GameState gameState(initialDisks);
resetTowers(gameState.towers, gameState.numDisks);
std::string rawInput;
gameState.status = "Type \"help\" at any time for instructions. Good luck!";
gameState.question = "What's your first move? ";
while(!(gameState.requestQuit || gameState.gameOver)) {
drawTowers(gameState.towers, gameState.towerDrawer);
printStatus(gameState.status);
askQuestion(gameState.question);
std::string rawInput = getRawInput();
gameState.tokens = tokenize(rawInput);
if(inputState(gameState)) continue;
moveState(gameState);
}
}
/*
Performs input processing and command handling of tokens.
Returns 0 if the input is a tower move and should be passed on to the move
parser.
Returns 1 if the flow should return to the beginning of the game loop.
*/
static unsigned inputState(GameState& gameState)
{
const unsigned NUM_TUTORIAL_DISKS = 3;
const unsigned TUTORIAL_ROD_HEIGHT = NUM_TUTORIAL_DISKS + 2;
std::vector<Tower> tutorialTowers; // Appears on the help text
TowerDrawer tutorialTowerDrawer(TUTORIAL_ROD_HEIGHT);
resetTowers(tutorialTowers, NUM_TUTORIAL_DISKS);
switch(parseInput(gameState.tokens)) {
case MOVE: return 0;
case EMPTY_INPUT: return 1;
case COMMAND:
{
switch(parseCommand(gameState.tokens.front())) {
case REQUEST_QUIT:
{
gameState.requestQuit = true;
return 1;
}
case REQUEST_RESET:
{
gameState.numDisks = askNumDisks();
gameState.towerDrawer.set_pole_height(gameState.numDisks + 3);
resetGame(gameState);
return 1;
}
case REQUEST_HELP:
{
showHelpText(tutorialTowers, tutorialTowerDrawer);
return 1;
}
case INVALID_COMMAND:
{
gameState.status = "No such command...";
return 1;
}
}
break; // This only pacifies the compiler; it cannot really be reached.
}
case INVALID_INPUT:
{
gameState.status = "Huh?";
return 1;
}
}
assert(0); // Impossible to get here
return 0;
}
/*
Takes in tokens from the input stage and attempts to process them as tower
moves.
*/
static void moveState(GameState& gameState)
{
TOWER_MOVE towerMove = parseMove(gameState.tokens, gameState.towers);
switch(towerMove.moveType) {
case VALID_MOVE:
{
doMove(towerMove, gameState.towers);
gameState.moves++;
if(checkForGameWon(gameState.towers.at(GOAL_TOWER_VECTOR_INDEX), gameState.numDisks)) {
drawTowers(gameState.towers, gameState.towerDrawer);
printStatus("You win!");
printResults(gameState.numDisks, gameState.moves);
std::cout << "\n\n";
gameState.gameOver = !askPlayAgain();
if(!gameState.gameOver) {
gameState.numDisks = askNumDisks();
gameState.towerDrawer.set_pole_height(gameState.numDisks + 3);
resetGame(gameState);
}
return;
}
gameState.status = "";
gameState.question = "What's your next move? ";
return;
}
case DISKLESS_TOWER:
{
gameState.status = "Nothing on that tower...";
return;
}
case LARGER_ON_SMALLER:
{
gameState.status = "Can't place a larger disk on a smaller disk...";
return;
}
case INVALID_MOVE_SYNTAX:
{
gameState.status = "Can't do that...";
}
}
}