Skip to content

Commit 3e3a51b

Browse files
committed
My attempt at writing the MathGame
1 parent f17bd55 commit 3e3a51b

12 files changed

Lines changed: 690 additions & 0 deletions

File tree

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
using System.Globalization;
2+
using mathgame.GameModes;
3+
using mathgame.Model;
4+
5+
namespace mathgame.GameManager;
6+
7+
/// <summary>
8+
/// Manages the main game flow, including menus, gameplay, and highscore persistence.
9+
/// </summary>
10+
public class GameManager
11+
{
12+
/// <summary>
13+
/// List of all players who have played the game.
14+
/// </summary>
15+
private static List<Player> players = new List<Player>();
16+
17+
/// <summary>
18+
/// Indicates whether the game is currently running.
19+
/// </summary>
20+
private static bool GameIsRunning = true;
21+
22+
/// <summary>
23+
/// Initializes a new instance of the GameManager class.
24+
/// Loads existing highscores, displays the main menu, and saves highscores on exit.
25+
/// </summary>
26+
public GameManager()
27+
{
28+
LoadGame();
29+
MainMenu();
30+
SaveHighscore(players);
31+
}
32+
33+
/// <summary>
34+
/// Displays the main menu and handles user navigation between game options.
35+
/// Allows the user to play the game, view highscores, or quit.
36+
/// </summary>
37+
private static void MainMenu()
38+
{
39+
while (true)
40+
{
41+
Console.Clear();
42+
43+
Console.WriteLine("Welcome to the math game");
44+
Console.WriteLine("Choose one of the options by numbers shown:");
45+
Console.WriteLine("1. Play the game");
46+
Console.WriteLine("2. Highscore");
47+
Console.WriteLine("3. Quit the game");
48+
//intercepts the entry of the key so it doesn't show on the screen
49+
ConsoleKeyInfo keyInfo = Console.ReadKey(intercept: true);
50+
char input = keyInfo.KeyChar;
51+
switch (input)
52+
{
53+
case '1':
54+
PlayGame();
55+
break;
56+
case '2':
57+
ShowHighscore(players);
58+
break;
59+
case '3':
60+
SaveHighscore(players);
61+
ExitGame(GameIsRunning);
62+
break;
63+
}
64+
Console.ReadKey();
65+
}
66+
}
67+
68+
/// <summary>
69+
/// Prompts the user to select a math operation and starts the corresponding game mode.
70+
/// Creates a new Player instance, executes the selected game, and adds the result to the players list.
71+
/// </summary>
72+
private static void PlayGame()
73+
{
74+
Console.Clear();
75+
Console.WriteLine("Choose an operation (press a number key):");
76+
Console.WriteLine("1. Multiplication");
77+
Console.WriteLine("2. Division");
78+
Console.WriteLine("3. Addition");
79+
Console.WriteLine("4. Subtraction");
80+
ConsoleKeyInfo keyInfo = Console.ReadKey(intercept: true);
81+
char input = keyInfo.KeyChar;
82+
83+
Console.Clear();
84+
switch (input)
85+
{
86+
case '1':
87+
players.Add(new MultiplicationGame().Play(new Player()));
88+
GameEnd(players.Last());
89+
break;
90+
case '2':
91+
players.Add(new DivisionGame().Play(new Player()));
92+
GameEnd(players.Last());
93+
break;
94+
case '3':
95+
players.Add(new AdditionGame().Play(new Player()));
96+
GameEnd(players.Last());
97+
break;
98+
case '4':
99+
players.Add(new SubtractionGame().Play(new Player()));
100+
GameEnd(players.Last());
101+
break;
102+
default:
103+
break;
104+
}
105+
}
106+
/// <summary>
107+
/// Saves current highscore from the last loaded highscore text file with the new players
108+
/// Overwrites the previous content with this one as a csv
109+
/// </summary>
110+
private static void SaveHighscore(List<Player> players)
111+
{
112+
using StreamWriter writer = new StreamWriter("Highscore.txt", false); // false = overwrite
113+
foreach (var player in players)
114+
{
115+
// save them as name, points, date
116+
writer.WriteLine($"{player.GetName()}, {player.GetPoints()}, {player.GetDate()}, {player.GetMinutesTaken()}:{(player.GetSecondsTaken() > 0 ? player.GetSecondsTaken().ToString("D2") : "00")}");
117+
}
118+
119+
}
120+
/// <summary>
121+
/// Loads existing highscore data from a text file.
122+
/// Creates the highscore file if it doesn't exist.
123+
/// </summary>
124+
private static void LoadGame()
125+
{
126+
string currentDir = Directory.GetCurrentDirectory();
127+
Console.WriteLine(currentDir);
128+
129+
bool exists = File.Exists("Highscore.txt");
130+
if (exists == false)
131+
{
132+
// could have used File.WriteAllText, but that's not clear cut as file.Create
133+
using(File.Create("Highscore.txt")){};
134+
}
135+
else
136+
{
137+
List<string> highscore = File.ReadAllLines("Highscore.txt").ToList();
138+
139+
// the second line is the content of the table, separated by comma
140+
foreach (var line in highscore)
141+
{
142+
if (line.Contains(','))
143+
{
144+
string[] playerstats = line.Split(',');
145+
if (
146+
!DateTime.TryParseExact(
147+
playerstats[2].Trim(),
148+
"yyyy-MM-ddTHH:mm:ss",
149+
CultureInfo.InvariantCulture,
150+
DateTimeStyles.None,
151+
out DateTime parsedDate
152+
)
153+
)
154+
{
155+
throw new ArgumentException(
156+
"Invalid date format for Player in Method Loadgame"
157+
);
158+
}
159+
players.Add(new Player(playerstats[0], playerstats[1], parsedDate));
160+
}
161+
}
162+
Console.WriteLine($"The file was succesfully loaded: {players.Count} entries.");
163+
}
164+
165+
}
166+
167+
/// <summary>
168+
/// When game ends, asks the user to enter name. Is anonymous by default
169+
/// Inserts name, points, date and time it took to finish the game mode
170+
/// </summary>
171+
private static void GameEnd(Player player)
172+
{
173+
Console.WriteLine("What's your name? (Press Enter to stay anonymous)");
174+
string name = Console.ReadLine() ?? "Anonymous";
175+
player.SetName(String.IsNullOrWhiteSpace(name) ? "Anonymous" : name);
176+
177+
while (true)
178+
{
179+
Console.WriteLine($"Is \"{player.GetName()}\" correct?");
180+
Console.WriteLine("[Y]es [N]o");
181+
ConsoleKeyInfo key = Console.ReadKey();
182+
if (key.KeyChar == 'Y' || key.KeyChar == 'y')
183+
break;
184+
else if (key.KeyChar == 'N' || key.KeyChar == 'n')
185+
{
186+
Console.WriteLine(
187+
"\nPlease enter your name (or press Enter to stay anonymous):"
188+
);
189+
name = Console.ReadLine() ?? "Anonymous";
190+
player.SetName(String.IsNullOrWhiteSpace(name) ? "Anonymous" : name);
191+
}
192+
}
193+
Console.WriteLine("Game over! Thank you for playing.");
194+
// Optionally: Show highscore or update leaderboard here
195+
}
196+
197+
/// <summary>
198+
/// shows highscore by using the currently loaded players List
199+
/// </summary>
200+
private static void ShowHighscore(List<Player> players)
201+
{
202+
Console.Clear();
203+
Console.WriteLine("\t\t---HighScore---");
204+
Console.WriteLine("Name:\tPoints:\tDate:\t\t\tTime Taken:");
205+
Console.WriteLine("-----------------------------------------------------");
206+
207+
if (players.Count == 0)
208+
{
209+
Console.WriteLine("\tNo entries in the table");
210+
}
211+
else
212+
{
213+
foreach (var player in players)
214+
{
215+
Console.WriteLine($"{player.GetName()}\t{player.GetPoints()}\t{player.GetDate()}\t{player.GetMinutesTaken()}:{player.GetSecondsTaken()}");
216+
}
217+
}
218+
Console.WriteLine("Press any key to return...");
219+
}
220+
///<summary>
221+
/// Game stops, everyone goes home.
222+
/// </summary>
223+
private static bool ExitGame(bool GameRunning)
224+
{
225+
Console.Clear();
226+
Console.WriteLine("Thanks for playing!");
227+
Thread.Sleep(500);
228+
Environment.Exit(exitCode: 0);
229+
return !GameRunning;
230+
}
231+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using mathgame.Model;
2+
3+
namespace mathgame.GameModes;
4+
5+
/// <summary>
6+
/// Represents an addition game mode where players solve addition problems.
7+
/// </summary>
8+
public class AdditionGame : GameMode
9+
{
10+
/// <summary>
11+
/// Generates a random addition question with two operands and the correct answer.
12+
/// </summary>
13+
/// <returns>A tuple containing the left operand, right operand, and the sum as the answer.</returns>
14+
protected override (int left, int right, int answer) GenerateQuestion()
15+
{
16+
int leftSide = _random.Next(1, 100);
17+
int rightSide = _random.Next(1, 100);
18+
return (leftSide, rightSide, leftSide + rightSide);
19+
}
20+
21+
/// <summary>
22+
/// Gets the name of the game mode.
23+
/// </summary>
24+
/// <returns>The string "Addition".</returns>
25+
protected override string GetModeName() => "Addition";
26+
27+
/// <summary>
28+
/// Gets the operator symbol used in this game mode.
29+
/// </summary>
30+
/// <returns>The addition operator symbol "+".</returns>
31+
protected override string GetOperatorSymbol() => "+";
32+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using mathgame.Model;
2+
3+
namespace mathgame.GameModes;
4+
5+
/// <summary>
6+
/// Represents a division game mode where players solve division problems.
7+
/// </summary>
8+
public class DivisionGame : GameMode
9+
{
10+
/// <summary>
11+
/// Generates a division question with two random numbers where the division results in a whole number.
12+
/// </summary>
13+
/// <returns>A tuple containing the dividend, divisor, and quotient.</returns>
14+
protected override (int left, int right, int answer) GenerateQuestion()
15+
{
16+
17+
int leftSide, rightSide;
18+
do
19+
{
20+
leftSide = _random.Next(1, 100);
21+
rightSide = _random.Next(1, 100);
22+
23+
} while (leftSide % rightSide != 0); // ensure divisibility
24+
return (leftSide, rightSide, leftSide / rightSide);
25+
}
26+
27+
/// <summary>
28+
/// Gets the name of this game mode.
29+
/// </summary>
30+
/// <returns>The string "Division".</returns>
31+
protected override string GetModeName()=> "Division";
32+
33+
/// <summary>
34+
/// Gets the operator symbol for division.
35+
/// </summary>
36+
/// <returns>The division operator "/".</returns>
37+
protected override string GetOperatorSymbol() => "/";
38+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
using System.Diagnostics;
3+
using mathgame.Model;
4+
5+
namespace mathgame.GameModes;
6+
7+
/// <summary>
8+
/// Base class for all game modes in the math game.
9+
/// </summary>
10+
public abstract class GameMode
11+
{
12+
/// <summary>
13+
/// Random number generator used for creating math questions.
14+
/// </summary>
15+
protected readonly Random _random = new();
16+
17+
/// <summary>
18+
/// Runs the game mode for the specified player.
19+
/// </summary>
20+
/// <param name="player">The player participating in the game.</param>
21+
/// <returns>The player with updated points and time taken.</returns>
22+
public Player Play(Player player)
23+
{
24+
Stopwatch stopwatch = Stopwatch.StartNew();
25+
int maxRound = 5, round = 0;
26+
while (round < maxRound)
27+
{
28+
Console.WriteLine($"Testing stopwatch {stopwatch.Elapsed} ");
29+
var (left, right, answer) = GenerateQuestion();
30+
bool awaitingAnswer = true;
31+
while (awaitingAnswer)
32+
{
33+
Console.WriteLine($"{GetModeName()} mode:");
34+
Console.WriteLine($"Solve this: {left} {GetOperatorSymbol()} {right}");
35+
Console.WriteLine($"You have currently: {player.GetPoints()}");
36+
Console.WriteLine($"Round {round + 1} of {maxRound}");
37+
Console.WriteLine("Enter q or quit to quit the game");
38+
39+
string input = Console.ReadLine() ?? "";
40+
if (int.TryParse(input, out int result))
41+
{
42+
if (result == answer) { Console.WriteLine("Correct! You gain a point"); player.GainPoint(); round++; awaitingAnswer = false; }
43+
else { Console.WriteLine("Wrong! You lose a point"); player.LosePoint(); }
44+
}
45+
else if (input.Equals("q", StringComparison.OrdinalIgnoreCase) || input.Equals("quit", StringComparison.OrdinalIgnoreCase))
46+
{ Console.WriteLine("Exiting the game mode"); Console.WriteLine($"Your Score is {player.GetPoints()}"); Console.WriteLine("Press any key."); return player; }
47+
else { Console.WriteLine("Please enter a valid command"); }
48+
49+
Console.Write("Enter a key to continue:"); Console.ReadKey(); Console.Clear();
50+
}
51+
Thread.Sleep(1500); Console.Clear();
52+
}
53+
stopwatch.Stop();
54+
player.SetTimeTaken(stopwatch.Elapsed.Minutes, stopwatch.Elapsed.Seconds);
55+
return player;
56+
}
57+
protected abstract (int left, int right, int answer) GenerateQuestion();
58+
protected abstract string GetModeName();
59+
protected abstract string GetOperatorSymbol();
60+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using mathgame.Model;
2+
3+
namespace mathgame.GameModes;
4+
5+
/// <summary>
6+
/// Represents a multiplication game mode where players solve multiplication problems.
7+
/// </summary>
8+
public class MultiplicationGame : GameMode
9+
{
10+
/// <summary>
11+
/// Generates a multiplication question with two random operands between 1 and 12.
12+
/// </summary>
13+
/// <returns>A tuple containing the left operand, right operand, and the correct answer (product).</returns>
14+
protected override (int left, int right, int answer) GenerateQuestion()
15+
{
16+
17+
int leftSide = _random.Next(1, 13);
18+
int rightSide = _random.Next(1, 13);
19+
return (leftSide, rightSide, leftSide * rightSide);
20+
}
21+
22+
/// <summary>
23+
/// Gets the name of this game mode.
24+
/// </summary>
25+
/// <returns>The string "Multiplication".</returns>
26+
protected override string GetModeName() => "Multiplication";
27+
28+
/// <summary>
29+
/// Gets the operator symbol used in this game mode.
30+
/// </summary>
31+
/// <returns>The multiplication operator symbol "*".</returns>
32+
protected override string GetOperatorSymbol() => "*";
33+
}

0 commit comments

Comments
 (0)