C
C#3mo ago
Karto

Need pointers on code for console app.

I've been using c# inside of game engines for a while now, and I've just written some code for a simple console app for the first time for practice. Code: https://paste.myst.rs/4j1fa1ss I'm unsure about how good/bad this code is, the lack of an 'update loop' found in a typical game engine is throwing me off. My main concern is about the while loops when waiting for the player to make a valid input, is this standard practice? I'm also finding that the execution is a bit hard to follow along my function calls, especially when thrown in with all the while loops. The calls are just nested and following the exection from Start() -> InspectPokemon() -> RequestPokemon() and back out feels wrong/difficult to follow Any advice would be great, thanks.
pastemyst | Pokegam
a powerful website for storing and sharing text and code snippets. completely free and open source.
19 Replies
Pobiega
Pobiega3mo ago
Using loops to "force" a valid choice is quite common, but i would recommend extracting that behaviour to its own method You are doing it a lot and in many places, so it would clean this up a lot. Feel free to ask if you need help with this
Karto
KartoOP2mo ago
Hey sorry for the late response! Could you give an example of how you'd extract that behaviour? Yeah, that was my main concern, it felt like i was always doing it, and that being inside of nested functions made execution a lot harder to follow I rarely use while loops and the few times I do, I've developed this habit of just using while(true) to enter the loop and just break; out when i need to, is this bad practice?
Pobiega
Pobiega2mo ago
It can be, but certainly not always
public static int GetAge()
{
while (true)
{
Console.WriteLine("How old are you?");
var age = Console.ReadLine();
if (int.TryParse(age, out var ageInt))
{
return ageInt;
}

Console.WriteLine("Thats not a number, try again!");
}
}
public static int GetAge()
{
while (true)
{
Console.WriteLine("How old are you?");
var age = Console.ReadLine();
if (int.TryParse(age, out var ageInt))
{
return ageInt;
}

Console.WriteLine("Thats not a number, try again!");
}
}
something like this now, we could parameterize this to make it more useful
int GetNumber(string prompt)
{
while (true)
{
Console.WriteLine(prompt);
if (int.TryParse(Console.ReadLine(), out var number))
{
return number;
}
Console.WriteLine("Thats not a number, try again!");
}
}
int GetNumber(string prompt)
{
while (true)
{
Console.WriteLine(prompt);
if (int.TryParse(Console.ReadLine(), out var number))
{
return number;
}
Console.WriteLine("Thats not a number, try again!");
}
}
ok, a lot better, now we cna pass the question in and the method isnt only for ages in fact, I use a somewhat advanced version of this in almost every program I write
public static T GetFromConsole<T>(string prompt, Func<T, bool>? validator = null,
string errorMessage = "Invalid value, try again.") where T : IParsable<T>
{
while (true)
{
Console.Write(prompt);
var input = Console.ReadLine() ?? throw new EndOfStreamException("EOF");
if (T.TryParse(input, null, out var value))
{
if (validator?.Invoke(value) ?? true)
{
return value;
}

Console.WriteLine(errorMessage);
continue;
}

Console.WriteLine("Invalid format, try again.");
}
}
public static T GetFromConsole<T>(string prompt, Func<T, bool>? validator = null,
string errorMessage = "Invalid value, try again.") where T : IParsable<T>
{
while (true)
{
Console.Write(prompt);
var input = Console.ReadLine() ?? throw new EndOfStreamException("EOF");
if (T.TryParse(input, null, out var value))
{
if (validator?.Invoke(value) ?? true)
{
return value;
}

Console.WriteLine(errorMessage);
continue;
}

Console.WriteLine("Invalid format, try again.");
}
}
this lets you customize what type we want out, what makes a valid value, what error message to print... etc
Karto
KartoOP2mo ago
I see, so we make a generalized function that takes in what to ask the user, handles incorrect entries and returns the valid response for any given situation
Pobiega
Pobiega2mo ago
yeah, exactly
Karto
KartoOP2mo ago
We'd have to pass in a custom validator based on the situation each time we call the function yeah? (In your example)
Pobiega
Pobiega2mo ago
For example, it seems you quite often have the user pick from a list we could write a method that does that generically yep. I can show you an example
var age = Helper.GetFromConsole<int>("Enter your age (18 or above): ", validator: x => x >= 18);
var age = Helper.GetFromConsole<int>("Enter your age (18 or above): ", validator: x => x >= 18);
Karto
KartoOP2mo ago
IParsable<T> is a preexisting interface? Looks like it So i'd have to make my Pokemon class implement IParsable if i want to do something like what you've done with your example?
Pobiega
Pobiega2mo ago
that'd be a bit weird probably easier to write a special method for selecting from lists
Karto
KartoOP2mo ago
Ah right makes sense Thank you very much
Pobiega
Pobiega2mo ago
like
List<Pokemon> pokemons = [
new() { Name = "Pikachu" },
new() { Name = "Charmander" },
new() { Name = "Squirtle" },
new() { Name = "Bulbasaur" },
new() { Name = "Charmander" },
new() { Name = "Squirtle" },
new() { Name = "Bulbasaur" },
];

var selectedPokemon = Helper.Pick("Select a pokemon: ", pokemons, x => x.Name);


public class Pokemon
{
public string Name { get; set; }
}
List<Pokemon> pokemons = [
new() { Name = "Pikachu" },
new() { Name = "Charmander" },
new() { Name = "Squirtle" },
new() { Name = "Bulbasaur" },
new() { Name = "Charmander" },
new() { Name = "Squirtle" },
new() { Name = "Bulbasaur" },
];

var selectedPokemon = Helper.Pick("Select a pokemon: ", pokemons, x => x.Name);


public class Pokemon
{
public string Name { get; set; }
}
Karto
KartoOP2mo ago
@Pobiega
public void Start()
{
Console.WriteLine("Welcome to PokeGaam!");
Console.WriteLine("");

while(true)
{
Console.WriteLine("Choose your pokemon!");

Pokemon inspectedPokemon = Input.GetChoiceFromArray("Enter a number to inspect the pokemon", starterPokemonArray);

Console.WriteLine("");
string inspectMessage = inspectedPokemon.GetDetails() + "\n" + "Press 'Y' to select this starter pokemon or 'N' go back.";
ConsoleKeyInfo info = Input.GetKey(inspectMessage, (info) => info.Key == ConsoleKey.Y || info.Key == ConsoleKey.N);

if(info.Key == ConsoleKey.Y)
{
Console.Clear();
Console.WriteLine($"You have selected {inspectedPokemon.Name} as your starter pokemon!");
break;
}
else
{
Console.Clear();
}
}
}
public void Start()
{
Console.WriteLine("Welcome to PokeGaam!");
Console.WriteLine("");

while(true)
{
Console.WriteLine("Choose your pokemon!");

Pokemon inspectedPokemon = Input.GetChoiceFromArray("Enter a number to inspect the pokemon", starterPokemonArray);

Console.WriteLine("");
string inspectMessage = inspectedPokemon.GetDetails() + "\n" + "Press 'Y' to select this starter pokemon or 'N' go back.";
ConsoleKeyInfo info = Input.GetKey(inspectMessage, (info) => info.Key == ConsoleKey.Y || info.Key == ConsoleKey.N);

if(info.Key == ConsoleKey.Y)
{
Console.Clear();
Console.WriteLine($"You have selected {inspectedPokemon.Name} as your starter pokemon!");
break;
}
else
{
Console.Clear();
}
}
}
Input.cs
public static ConsoleKeyInfo GetKey(string prompt, Func<ConsoleKeyInfo, bool>? validator = null, bool clearOnInvalidResponse = true)
{
while(true)
{
Console.WriteLine(prompt);
var key = Console.ReadKey();

if(validator?.Invoke(key) ?? true)
{
return key;
}
else
{
ShowErrorMessage("Invalid input. Please try again.", clearOnInvalidResponse);
}
}
}
public static ConsoleKeyInfo GetKey(string prompt, Func<ConsoleKeyInfo, bool>? validator = null, bool clearOnInvalidResponse = true)
{
while(true)
{
Console.WriteLine(prompt);
var key = Console.ReadKey();

if(validator?.Invoke(key) ?? true)
{
return key;
}
else
{
ShowErrorMessage("Invalid input. Please try again.", clearOnInvalidResponse);
}
}
}
I've refactored my code to be a lot neater thanks to your help. Both the GetChoiceFromArray() and GetKey() methods have a while loop in them, so I feel the urge to get rid of the while loop in Start() but I also have the feeling that it's fine to leave it in. Does the refactored code make sense?
Pobiega
Pobiega2mo ago
Seems fine. You could clean up Start a bit by using return inspectedPokemon or similar once the user has confirmed their selection, you probably want your program to "remember" what pokemon was selected
Karto
KartoOP2mo ago
I also wanted to ask about where to handle the recieved input. We have the validator to mask out completely irrelevant responses, I was thinking of ways to pass in what should happen when each particular choice is picked similarly, but then figured it might be easier to just get back a valid response and run conditional logic on the recieved response. What do you think?
Pobiega
Pobiega2mo ago
yeah, handle that afterwards
Karto
KartoOP2mo ago
Could you elaborate? Do you mean extracting out that logic to it's own function? Or returning inspectedPokemon from start?
Pobiega
Pobiega2mo ago
returning it from start you dont currently do anything with the selected pokemon at all so once start ends, you no longer know what pokemon was selected
Karto
KartoOP2mo ago
Oh yeah I haven't gotten to that yet The Start function is just my non static main method
public class Program
{
static void Main(string[] args)
{
new PokeGam().Start();

Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
public class Program
{
static void Main(string[] args)
{
new PokeGam().Start();

Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
So ideally I should put all the content currently in Start() into a some new method like ChoosePokemon() and have that return the chosen pokemon yeah?
Pobiega
Pobiega2mo ago
maybe PickStarterPokemon()
Want results from more Discord servers?
Add your server