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
|
#include <iostream>
#include <windows.h>
#include <vector>
#include <tuple>
#include <algorithm>
#include <string>
#include <sstream>
#include <math.h>
using namespace std;
// Printing functions that I shamelessly stole from myself. The pragma command isn't c++ and is only there so that visual studio collapses the entire section down to one line and makes it look like my code isn't incredibly bloated and inefficient.
#pragma region output
const HANDLE HCONSOLE = GetStdHandle(STD_OUTPUT_HANDLE);
// Uses black magic to print in full color with (optional) automatic line breaks. Requires <windows.h>. Returns nothing and takes up to 4 parameters:
// text (string): The text to be printed. Don't put a newline at the end.
// color (int): The color code of the text. Optional, defaults to white.
// linebreak (bool): Whether to end the line after printing. Optional, defaults to true.
// console (HANDLE) the console the function is printing to, used for changing color. Defaults to HCONSOLE, so as long as you define the console as HCONSOLE somewhere, you can probably ignore it.
void colorPrint(string text, int color = 15, bool linebreak = true, HANDLE console = HCONSOLE)
{
SetConsoleTextAttribute(console, color); // Change the output color to whatever is chosen for the text (defaults to 15, which is white).
cout << text;
SetConsoleTextAttribute(console, 15); // Use moret the color back to white so that we don't start randomly printing other colors.
if (linebreak) // Add a line break to the end of the text unless told not to
{
cout << endl;
}
}
//I made this to avoid having to write a dozen lines of couts and colorPrints just to print multiple colors in a line. It basically just loops through a list of text blocks and colorPrints each of them. Requires <windows.h> and colorPrint. Returns nothing and takes up to 2 parameters:
//text (vector): All the text fragments. Made up of a series of tuples (a different type of list that can contain multiple data types) containing the following elements:
// string: The block of text to be printed. Don't put a newline or endl at the end.
// int: The color of the block of text. 15 is white.
// bool: Whether to add a line break at the end of the block.
//console (HANDLE) the console the function is printing to, used for changing color. Defaults to HCONSOLE, so as long as you define the console as HCONSOLE somewhere, you can probably ignore it.
void multiColorPrint(vector<tuple<string, int, bool> > text, HANDLE console = HCONSOLE)
{
string fragmentText;
int fragmentColor;
bool fragmentBreak;
for (auto i : text) { //Loop through the text data
tie(fragmentText, fragmentColor, fragmentBreak) = i; //"Unpack" the current block tuple and pass its elements to their respective variables.
colorPrint(fragmentText, fragmentColor, fragmentBreak, console); //Print the current block.
}
}
#pragma endregion
// Input functions.
#pragma region input
// It turns out that cin will usually break when you input a string that has whitespace in it, so I wrote this to fix it. Feel free to stick with cin if you're not trying to get input with whitespace, though.
string whitespaceInput()
{
string input;
string afterWhitespace;
cin >> input; // cin stops reading input when it reaches any whitespace, but it doesn't get rid of what's left. Not only does that mean you lose anything the user typed after that whitespace, but it also breaks cin the next time you call it. cin (as far as I can tell) doesn't actually wait for user input, it just waits until there's something in the "input buffer". Since there's still input in the buffer, the next cin you call will see that there's something in the buffer and immediately read it instead of letting the user input something new.
getline(cin, afterWhitespace); // getline gets input from the buffer like cin does, except that it ignores whitespace, and it doesn't wait for something to be in the buffer (if there's nothing there when you call it, it just returns an empty string). I'm using it here as a sort of "cleanup" function to grab anything that's left in the buffer after the cin.
input += afterWhitespace; // Combine the input from the cin with anything that getline picked up.
return input;
}
// Splits a string into segments based on a list of delimiters. Returns a vector containing the segments and takes up to 3 parameters:
// str (string): The string to be split.
// delimiters (vector <char>): A vector containing the characters to split at. Optional, defaults to any whitespace.
// includeSplit (bool): Whether to include the delimiter when splitting. Optional, defaults to false.
vector <string> splitString(string str, vector <char> delimiters = { ' ', '\f', '\n', '\r', '\t', '\v' }, bool includeSplit = false)
{
string temp = ""; // Staging variable for the current segment.
vector <string> segments = {};
for (char s : str)
{
if (find(delimiters.begin(), delimiters.end(), s) == delimiters.end())
{ // If the current character isn't a split character, add it to the end of the temp segment.
temp.push_back(s);
}
else
{ // If it is a split character, push temp into the segment list and reset temp to an empty string.
if (includeSplit) { temp.push_back(s); }
segments.push_back(temp);
temp = "";
}
}
if (temp != "") { segments.push_back(temp); } // If a segment is still staged after the loop ends, add it to the list of segments. Prevents dropping the final segment if the string doesn't end with whitespace.
return segments;
}
// Returns true if the given string is a valid number, and false if it isn't.
bool isNumber(string str) {
istringstream iss(str); // Stringstreams are great
float f;
iss >> noskipws >> f; // Attempts to convert the string to a float. This won't throw an error if it fails, but it will set the stream's failbit to true. noskipws causes a failure if there's any leading whitespace.
return (iss.eof() && !iss.fail()); // If the conversion consumed the entire string (meaning there was no trailing whitespace), and it didn't fail, return true; otherwise return false.
}
// Gets the temperature from the user, and the unit. Returns the temperature value and sets the unit by reference. Sets unit to one of 2 exit codes:
// 0: The user entered the temperature in F
// 1: The user entered the temperature in C
float getTemp(int& unit) {
vector <tuple<string, int, bool> > asktext = {make_tuple("Please enter the current temperature followed by its unit (", 15, 0), make_tuple("Fahrenheit", 11, 0), make_tuple(" or ", 15, 0), make_tuple("Celcius", 11, 0), make_tuple("): ", 15, 0)};
float temp;
while (true)
{
multiColorPrint(asktext);
string input = whitespaceInput(); // Getting input that has whitespace in it is painful, so it's easier to just call getStringInput with an empty string as it's printout.
transform(input.begin(), input.end(), input.begin(), tolower); // Make every character in the string lowercase, because there's no reason for something like this to be case sensitive. I only have a vague idea how transform works, but doing this with a for loop causes some funky errors.
vector <string> iseg = splitString(input);
int size = iseg.size();
if (size < 2) {
colorPrint("Oh no! Something went wrong. Make sure you enter a number and a unit.", 12);
continue;
}
iseg = { iseg[0], iseg[1] }; // Discard anything after the first two segments
if (isNumber(iseg[0]) && (iseg[1] == "f" || iseg[1] == "c" || iseg[1] == "fahrenheit" || iseg[1] == "celcius"))
{
// Representing the unit with a number probably seems weird, but it comes in handy later on.
iseg[1][0] == 'f' ? unit = 0 : unit = 1; // I can skip having to write out conditions for both "f" and "fahrenheit", because either way the first letter is f. Also why did it take me so long to learn about ternary operators? I missed lambdas from python and now I finally have them back.
temp = stof(iseg[0]);
if ((unit == 0 && temp >= -80 && temp <= 121) || (unit == 1 && temp >= -62 && temp <= 49.5))
{
return temp;
}
colorPrint("Oh no! Something went wrong. Make sure you enter a temperature between -80 and 121 Fahrenheit (-62 and 49.5 Celcius).", 12);
} else
{
colorPrint("Oh no! Something went wrong. Make sure you enter a number and a unit.", 12);
}
}
}
// Gets the wind speed from the user, and the unit (yes, I know the assignment only says to use mph, but if you can enter temperature in celcius, you should be able to enter wind speed in kph). Returns the wind speed value and sets the unit by reference. Sets unit to one of 2 exit codes:
// 0: The user entered the wind speed in mph
// 1: The user entered the wind speed in kph
float getWind(int& unit) {
vector <tuple<string, int, bool> > asktext = { make_tuple("Please enter the current wind speed followed by its unit (", 15, 0), make_tuple("miles per hour", 11, 0), make_tuple(" or ", 15, 0), make_tuple("kilometers per hour", 11, 0), make_tuple("): ", 15, 0) };
float wind;
string input;
while (true)
{
multiColorPrint(asktext);
input = whitespaceInput(); // I'm so happy I made this function.
transform(input.begin(), input.end(), input.begin(), tolower); // Make every character in the string lowercase, because there's no reason for something like this to be case sensitive. I only have a vague idea how transform works, but doing this with a for loop causes some funky errors.
vector <string> iseg = splitString(input);
int size = iseg.size();
//The checks for this are a fair bit more complex than in getTemp, since I wanted both "mph" and "miles per hour" to be valid inputs for the unit. I also can't just use a whole bunch of && operators and test everything at once because if some of the conditions aren't true, trying to check some of the other ones will crash the program. My current solution is to use all these ifs to create sort of a "stack" of conditions, where each check only gets made if the previous ones are true.
if (size < 2)
{
colorPrint("Oh no! Something went wrong. Make sure you enter a number and a unit.", 12);
continue;
}
if (!isNumber(iseg[0]))
{
colorPrint("Oh no! Something went wrong. Make sure you enter a number and a unit.", 12);
continue;
}
else
{
wind = stof(iseg[0]);
}
if (size < 4) {
if (iseg[1] == "mph" || iseg[1] == "kph")
{
iseg[1] == "mph" ? unit = 0 : unit = 1;
}
else
{
colorPrint("Oh no! Something went wrong. Make sure you enter a number and a unit.", 12);
continue;
}
}
else
{
string combined = iseg[1] + " " + iseg[2] + " " + iseg[3];
if (combined == "miles per hour" || combined == "kilometers per hour")
{
combined == "miles per hour" ? unit = 0 : unit = 1;
}
else
{
colorPrint("Oh no! Something went wrong. Make sure you enter a number and a unit.", 12);
continue;
}
}
if ((unit == 0 && wind >= 0 && wind <= 231) || (unit == 1 && wind >= 0 && wind <= 370))
{
return wind;
}
else
{
colorPrint("Oh no! Something went wrong. Make sure you enter a wind speed between 0 and 231 miles per hour (0 and 370 kilometers per hour).", 12);
}
}
}
#pragma endregion
// Unit conversion/calculation functions.
#pragma region computation
// Temperature/wind speed unit converters.
float dctodf(float t) {
return t * 9 / 5 + 32;
}
float dftodc(float t) {
return (t - 32) * 5 / 9;
}
float kphtomph(float s) {
return s / 1.609;
}
float mphtokph(float s) {
return s * 1.609;
}
float windChill(float t, float w) {
return 35.74 + 0.6215 * t - 35.75 * pow(w, 0.16) + 0.4275 * t * pow(w, 0.16);
}
#pragma endregion
int main()
{
int tempUnit, windUnit;
float temp, wind, fTemp, cTemp, mphWind, kphWind, fChill, cChill;
// Get temperature and wind speed from the users.
temp = getTemp(tempUnit);
wind = getWind(windUnit);
// Get values for both Fahrenheit and Celcius (and put them in the right places).
if (tempUnit == 0)
{
fTemp = temp;
cTemp = dftodc(temp);
}
else
{
fTemp = dctodf(temp);
cTemp = temp;
}
// Do the same thing for wind speed.
if (windUnit == 0)
{
mphWind = wind;
kphWind = mphtokph(wind);
}
else
{
mphWind = kphtomph(wind);
kphWind = wind;
}
// Calculate the wind chill. The wind chill calculator uses Imperial units, and I just convert it to Celcius.
fChill = windChill(fTemp, mphWind);
cChill = dftodc(fChill);
// Print all the output, using needlessly complicated functions that I'm too stubborn to refine.
vector <tuple <string, int, bool> > tempOut = { make_tuple("The temperature is ", 15, 0), make_tuple(to_string(fTemp) + " degrees Fahrenheit", 11, 0), make_tuple(", or ", 15, 0), make_tuple(to_string(cTemp) + " degrees Celcius", 11, 0), make_tuple(".", 15, 1), };
vector <tuple <string, int, bool> > windOut = { make_tuple("The wind speed is ", 15, 0), make_tuple(to_string(mphWind) + " miles per hour", 11, 0), make_tuple(", or ", 15, 0), make_tuple(to_string(kphWind) + " kilometers per hour", 11, 0), make_tuple(".", 15, 1), };
vector <tuple <string, int, bool> > chillOut = { make_tuple("The wind chill is ", 15, 0), make_tuple(to_string(fChill) + " degrees Fahrenheit", 11, 0), make_tuple(", or ", 15, 0), make_tuple(to_string(cChill) + " degrees Celcius", 11, 0), make_tuple(".", 15, 1), };
multiColorPrint(tempOut);
multiColorPrint(windOut);
multiColorPrint(chillOut);
return 0;
}
|