I'm a newbie in C# and I'm learning it for the past few weeks basically doing tasks and challenges to improve my knowledge. I came across making a "Hangman" console app and I'm close to finishing it. In fact, I can say that I made it but I would like to improve something and learn one new thing.
This is an example of my question if you can't understand it, sorry, English is not my main language.
You know how when you play hangman, once you guess the right letter that letter "shows itself" on its position in a word.
etc. word Head
you guess "e" and it prints _e__
you guess "d" and it prints _e_d
you guess "H" and it prints He_d
you guess "a" and it prints Head
Here is my code :
using System;
namespace Hangman
{
class Program
{
static void Main(string[] args)
{
Random random = new Random();
string word1;
Console.Write("Please enter a random word: ");
word1 = Console.ReadLine();
word1.ToLower();
Console.Clear();
int i = 0;
int j = 0;
do
{
string guess;
Console.Write("Please enter a letter: ");
guess = Console.ReadLine();
if (guess == "" +
"")
{
i += 1;
Console.WriteLine($"Not allowed. You have {word1.Length - i} tries left.\n");
}
else if(word1.Contains(guess) == true)
{
Console.WriteLine("You guessed correctly !\n");
j += 1;
}
else
{
i += 1;
Console.WriteLine($"You guessed wrongly ! You have {word1.Length - i} tries left\n");
}
/* If you guessed all the letters correctly this will print that you won */
if (j == word1.Length)
{
Console.Clear();
Console.WriteLine($"You won ! The guessing word was {word1}.\n");
}
/* If you tried too many times it will end your program and you will lose.
* If you guessed all letters in a word correctly you win and the game is done. */
if(i == word1.Length)
{
Console.Clear();
Console.WriteLine($"You lost ! The guessing word was {word1}.\n");
}
} while (i != word1.Length & j != word1.Length);
}
}
}
There are a few flows with your code, bugs if you will -
If you enter the word head and you keep guessing h you win.
When you enter your guess you can actually enter a string not just a single character.
Also the code does not consider the possibility for multiple character entries. For example if the word is alexleo - and I guess l - it will only give me a single match.
What we have to do is keeping track of the status of each letter in the word - that is whether the letter has been guessed or not.
At each iteration:
The user guesses a letter -
If the letter is contained in the word - we change the status to
guessed.
If the letter has already been guessed we warn the user.
If the letter is not contained in the word -we increase our flag
totalGuessCounts by 1.
We feedback the user and display what has been guessed so far.
The loop completes when we either win or the user has run out of guesses.
Firstly I have defined my letter setting class
public class LetterSetting
{
public char Letter { get; set; }
public bool HasBeenGuessed { get; set; }
public LetterSetting(char letter, bool hasBeenGuessed)
{
this.Letter = letter;
this.HasBeenGuessed = hasBeenGuessed;
}
}
Then I have defined the hangman word class - here is where we keep track of the letter that have been guessed, the logic to see if we have won and the display of what we have so far.
static class HangmanWord
{
static List<LetterSetting> LettersStatus = new List<LetterSetting>();
public static void Initialise(string word)
{
foreach (char letter in word)
{
LettersStatus.Add(new LetterSetting(letter, false));
}
}
public static bool SetGuessStatus(char letter)
{
List<LetterSetting> tempLettersStatus = new List<LetterSetting>();
foreach (LetterSetting item in LettersStatus)
{
if (item.Letter == letter && item.HasBeenGuessed)
{
return false;
}
tempLettersStatus.Add(item.Letter == letter ? new LetterSetting(letter, true) : item);
}
LettersStatus = tempLettersStatus;
return true;
}
public static void Display()
{
StringBuilder hangManWord = new StringBuilder();
foreach (var item in LettersStatus)
{
hangManWord.Append(!item.HasBeenGuessed ? '_' : item.Letter);
}
Console.SetCursorPosition(50, 1);
Console.Write(hangManWord.ToString());
Console.SetCursorPosition(0,1);
}
public static bool HaveWeWon()
{
return !LettersStatus.Any(letter => letter.HasBeenGuessed == false);
}
}
You main class will simply encapsulate the to retrieve the word and the game-loop:
class Program
{
static void Main(string[] args)
{
int totalGuessCounts = 0;
int maxNumberOfGuess = 0;
Console.Write("Please enter a random word: ");
string word = Console.ReadLine()?.ToLower();
while (word != null && word.Contains(" ") || string.IsNullOrWhiteSpace(word))
{
Console.Clear();
Console.WriteLine("Word must not contain spaces or be nothing ");
Console.Write("Please enter a random word: ");
word = Console.ReadLine()?.ToLower();
}
maxNumberOfGuess = word.Length;
HangmanWord.Initialise(word);
Console.Clear();
HangmanWord.Display();
do
{
Console.Write("Please enter a letter: ");
char guess = Console.ReadKey().KeyChar;
Console.WriteLine("\n");
Console.Clear();
if (!char.IsLetterOrDigit(guess))
{
continue;
}
if (word.Contains(guess))
{
Console.WriteLine(HangmanWord.SetGuessStatus(guess)
? "You guessed correctly!"
: "You already guessed that letter.");
}
else
{
totalGuessCounts++;
Console.WriteLine($"You guessed wrongly ! You have {word.Length - totalGuessCounts} tries left\n");
}
if (HangmanWord.HaveWeWon())
{
Console.WriteLine($"You won ! The guessing word was {word}.\n");
break;
}
if (totalGuessCounts == maxNumberOfGuess)
{
Console.WriteLine($"You lost ! The guessing word was {word}.\n");
}
HangmanWord.Display();
} while (totalGuessCounts != word.Length);
}
}
Here are a couple of results:
you can change the feedback layout - to your like - add a dictionary of words so you dont have to enter the word yourself - draw the hangman whilst the user guesses - Ideally you might want to move all the logic within the class HangmanWord - that is for failure , success and full feedback.
I coded something like this. This is my suggestion for solving this problem, but remember that it probably isn't the best. You can try to optimize it.
bool[] guessed = new bool[wordToGuess.Length];
int index = 0;
while ((index = wordToGuess.IndexOf(guess, index, wordToGuess.Length - index)) != -1)
{
guessed[index] = true;
index++;
}
for(int i = 0; i < wordToGuess.Length; i++)
{
if(guessed[i])
Console.Write(wordToGuess[i]);
else
Console.Write('_');
}
I used String.IndexOf(String, Int32, Int32) method for this purpose.
Related
I am making a program to calculate GPA. I have it mostly working, but I can't figure out why it is asking for 2 inputs and why the calculation of GPA is wrong. I also need to make it so that I can input uppercase or lowercase letters. If you can offer a way to rewrite, it would be greatly appreciated.
This is the program:
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
namespace gpa
{
class Program
{
static void Main(string[] args)
{
//create a list for the grades to go into
List<double> GradeList = new List<double>();
Console.Write("\n********************************************\n");
bool LastGrade = false;
while (LastGrade == false)
{
//pass letter as parameter to get the GradePoint
double Grade = Calculations.GetGradePoint();
if (Console.ReadLine() == "")
{
LastGrade = true;
}
else
{
GradeList.Add(Grade);
}
}
//add all the gradepoints and round to 2 decimal places
double TotalGradePoints = Math.Round(GradeList.Sum(), 2);
Console.WriteLine($"your total grade points are {TotalGradePoints}");
Console.WriteLine("How many total credits did you take this semester?");
double TotalCredits = Convert.ToDouble(Console.ReadLine());
Console.WriteLine($"total credits {TotalCredits}");
double EndGPA = Calculations.GPA(TotalGradePoints, TotalCredits);
Console.WriteLine($"Your GPA this semester will be {EndGPA}");
}
}
}
This is the calculations class I added:
using System;
namespace gpa
{
public class Calculations
{
public static double GPA(double points, double credits)
{
double average = points / credits;
return average;
}
public static double GetGradePoint()
{
Console.WriteLine("Enter your letter grade for each class");
double Grade = 0;
string letter = Console.ReadLine();
if (letter == "A")
{
return 4;
}
if (letter == "A-")
{
return 3.7;
}
if (letter == "B+")
{
return 3.3;
}
if (letter == "B")
{
return 3;
}
if (letter == "B-")
{
return 2.7;
}
if (letter == "C+")
{
return 2.3;
}
if (letter == "C")
{
return 2;
}
if (letter == "C-")
{
return 1.7;
}
if (letter == "D+")
{
return 1.3;
}
if (letter == "D")
{
return 1;
}
if (letter == "F")
{
return 0;
}
return Grade;
//do not need to add looping mechanism in this fucntion - loop it in the main function
// Write function that takes a letter grade as input
// and returns the grade-point value of that letter grade
// Replace your switch statement above with a call to this function
}
}
}
This is the output:
enter image description here
Your program logic is flawed. So. You have a loop that keeps looping and invokes a method. In this method GetGradePoint you ask for the Grade input and then read the input using Console.ReadLine().
This is fine. The issue comes after. You wait for an input from the user again and if they input anything other than an empty string, the program will add the first input grade value and then loop back around as LastGrade is still false.
This means that the program starts from the start of the loop which means it will then invoke the GetGradePoint method again which is why it asks for the input again. Also if you then enter an empty string, the grade the user input doesn't get added to the GradeList list.
You need to go through your program as the issue, as I said, is the logic of your program. I suspect the other issue of the GPA being wrong will also correct itself.
Code Tips:
Instead of doing 1,000 if statements, as you're only accepting a single character you can use else if for the rest of the statements. Although this doesn't matter a huge lot as you are returning a value. IE:
if(condition)
{
}
else if(other condition)
{
}
Your while loop accepts a Boolean value as the condition which == provides but you can instead use your LastGrade variable as the condition for the loop IE:
while(!LastPass)
{
}
As for getting input I would rewrite it to something like
//Remove the line in GetGradePoint if you're gonna put it here
Console.WriteLine("Enter your grades");
while(condition)
{
string input = Console.ReadLine();
if(input != "")
{
var grade = GetGradePoint(input);
GradeList.Add(grade);
}
else LastGrade = true;
}
//Rest of code to calculate GPA
This is rough pseudo-c# code.
As for accepting both lower and upper case: You can use .ToUpper() on the input to ensure the input is an uppercase letter even if the user enters a lowercase letter.
I am trying to create a hangman game that picks a random word from a text file and then displays the word as asterisks and has the user guess the letters of the word. If the user guesses correct then the letter gets uncovered and they keep guessing until they uncover the word. It will then display the number of misses and ask if the user wants to try to guess another word.
The problem I am having is when you try to guess the first letter in any word and guess it correctly it still says its incorrect and doesn't uncover the word.The other letters uncover but you then keep guessing forever because the first letter of the word can't be uncovered. I am not sure how to fix this
static void Main(string[] args)
{
char[] guessed = new char[26];
char guess = ' ';
char playAgain= ' ';
int amountMissed = 0, index = 0;
do
{
// initilization of word and testword so that we could generate a testword with the same length as original
char[] word = RandomLine().Trim().ToCharArray();
char[] testword = new string('*', word.Length).ToCharArray();
char[] copy = word;
Console.WriteLine(testword);
Console.WriteLine("I have picked a random word on animals");
Console.WriteLine("Your task is to guess the correct word");
while (!testword.SequenceEqual(word))
{
try
{
Console.Write("Please enter a letter to guess: ");
guess = char.Parse(Console.ReadLine());
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
bool right = false;
for (int j = 0; j < copy.Length; j++)
{
if (copy[j] == guess)
{
Console.WriteLine("Your guess is correct.");
testword[j] = guess;
guessed[index] = guess;
index++;
right = true;
}
}
if (right != true)
{
Console.WriteLine("Your guess is incorrect.");
amountMissed++;
}
else
{
right = false;
}
Console.WriteLine(testword);
}
Console.WriteLine($"The word is {copy}. You missed {amountMissed} times.");
try
{
Console.WriteLine("Do you want to guess another word? Enter y or n: ");
playAgain = char.Parse(Console.ReadLine());
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
} while (playAgain == 'y' || playAgain == 'Y');
Console.WriteLine("Good-Bye and thanks for playing my Hangman game.");
}
public static string RandomLine()
{
// store text file in an array and return a random value
string[] lines = File.ReadAllLines("D:\\CPSC1012 ADV PORTFOLIO\\Advanced Portfolio\\Advanced1.csv");
Random rand = new Random();
return lines[rand.Next(lines.Length)];
}
}
That code works fine for me once I modify RandomLine() to just return zebra (since I don't have access to your words file).
So the first thing I would check is that your animal names are actually all lower case in that file. I note that, if I have it return Zebra instead, it exhibits the behaviour you describe when I try to guess the lower case z.
If that is the case, you just need to make your comparisons case-insensitive, something like modifying RandomLine() to ensure the word is all lower case:
return lines[rand.Next(lines.Length)].ToLower();
and this to ensure your guess is lower case:
guess = char.Parse(Console.ReadLine().ToLower());
I'm trying to create a hangman game in C#, but there are many things I have to be aware of. I need to use StreamReader, I have to keep the user's records in a file (The name of the user, how many times they attempted to win the game, etc.) and I have to make a menu where the user can either choose a new topic of words, or simply reset the current game.
So what I have so far is the "encryption". The letters of the word is written out with dashes, I can ask the user to choose a name, and to choose a topic.
But how am I supposed to let the program know when a user finds a letter?
I want it to be like this:
Word: ----- (apple), and if the user types p, a new line comes up, but this time it displays "-pp--". How do I do that?
The code in it's current state:
static void wordLength()
{
StreamReader sr = new StreamReader(#"C:\Users\hardi\Desktop\Programozás\gyumolcsok.txt", Encoding.GetEncoding("iso-8859-2"));
string szo = Convert.ToString(sr.ReadLine());
{
for (int i = 0; i < szo.Length; i++)
{
Console.Write("_");
}
}
}
static void userName()
{
Console.Clear();
Console.WriteLine("What's your name? ");
string user = Console.ReadLine();
}
static void category()
{
Console.Clear();
Console.WriteLine("Choose a number to specify the topic: ");
Console.WriteLine("1: Fruit \n2: Animals");
int caseSwitch = Convert.ToInt32(Console.ReadLine());
switch (caseSwitch)
{
case 1:
Console.WriteLine("Category: Fruit");
StreamReader sr = new StreamReader(#"C:\Users\hardi\Desktop\Programozás\gyumolcsok.txt", Encoding.GetEncoding("iso-8859-2"));
break;
case 2:
Console.WriteLine("Category: Animals");
StreamReader sr1 = new StreamReader(#"C:\Users\hardi\Desktop\Programozás\allatok.txt", Encoding.GetEncoding("iso-8859-2"));
break;
default:
Console.WriteLine("The given number is not an option.");
return;
}
}
static void Main(string[] args)
{
userName();
category();
wordLength();
Console.ReadKey();
}
}
}
One way you could do this is to treat the word as a char array, and store the guesses that the user has made in another char array. Then, for each character in the word, if it exists in the "guessedLetters" array display it, otherwise display a "-" character.
For example, this method takes a word and a list of characters that the user has guessed, and it displays only the correctly guessed characters:
private static string GetMaskedWord(string word, List<char> lettersGuessed)
{
if (word == null) return word;
if (lettersGuessed == null) return new string('-', word.Length);
return string.Join("", word.Select(c => lettersGuessed.Contains(c) ? c : '-'));
}
Example usage:
private static void Main()
{
string word = "apple";
// This stores the guessed characters, currently only a 'p'
var guessedLetters = new List<char> {'p'};
Console.WriteLine(GetMaskedWord(word, guessedLetters));
GetKeyFromUser("\nDone! Press any key to exit...");
}
Output
Im trying to make a user enter a char and check if they entered that char before. and if that happens ask him to enter another char that he hasn't used before. My knowledge is limited to if statements and loops so I would appreciate it if the solution is something that i can understand.
When i enter any letter e.g E it would throw it into the guessed array, if i enter E again, it throws into the guessed array again instead of asking the user to change the letter.
string check= "";
char wguess ='';
char[] wguess = new char[26];
do
{
check = Console.ReadLine();
if (check!="")
{
wguess = char.ToUpper(Convert.ToChar(check));
for (int i = 0; i <= 25; i++)
{
if (wguess == guessed[i])
{
Console.Write(wguess);
Console.WriteLine(guessed[i]);
Console.WriteLine("Please choose a letter you haven't used yet.");
}
else
{
Console.Write(wguess);
Console.WriteLine(guessed[i]);
temp = wguess;
guessed[i] = wguess;
wguess = ' ';
}
}
}
else
{
Console.WriteLine("Please Enter a letter.");
}
} while (check=="");
Output1 = Guessed Letters: E
Output2 = Guessed Letters: EE
You can use the Contains method, like this:
If(guessed.Contains(wguess)
{
//Whatever happens when the character already has been entered
}
else
{
//Whatever happens when the character has not been guessed
}
Change the code like the following:
do{
check = char.ToUpper(Convert.ToChar(Console.ReadKey()));
if (!guessed.Contains(check))
{
// This is new, not in the guessed
}
else
{
// Already entered
}
} while (check==' ');
I need some help with a C# program that i am creating. So in this scenario i am inputting duplicate values into the program. For Example, a,b,b,c,c.
The exercise is that if there are any duplicated letters inputted (no numbers) i should get an error stating "Duplicate Value. Please Try Again!" and will not accept the duplicate value, and should show the values as a,b,c,d,e.
class Program
{
static void Main(string[] args)
{
char[] arr = new char[5];
//User input
Console.WriteLine("Please Enter 5 Letters only: ");
for (int i = 0; i < arr.Length; i++)
{
arr[i] = Convert.ToChar(Console.ReadLine());
}
//display
for(int i = 0; i<arr.Length; i++)
{
Console.WriteLine("You have entered the following inputs: ");
Console.WriteLine(arrArray[i]);
}
}
}
Choose right data structure at beginning, use HashSet instead of array since the operations are mainly looking up & inserting.
Using a hashtable (Generic Dictionary) is an efficient way to determine if an entered character has already been encountered.
Also, the Char.IsLetter method in the .NET framework is a great way to check for bad data.
static void Main(string[] args) {
Dictionary<char, bool> charsEntered = new Dictionary<char, bool>();
Console.WriteLine("Please enter 5 characters, each on a separate line.");
while (charsEntered.Count() < 5) {
Console.WriteLine("Enter a character:");
char[] resultChars = Console.ReadLine().ToCharArray();
if(resultChars.Length != 1 || !Char.IsLetter(resultChars[0])) {
Console.WriteLine("Bad Entry. Try again.");
} else {
char charEntered = resultChars[0];
if (charsEntered.ContainsKey(charEntered))
Console.WriteLine("Character already encountered. Try again.");
else
charsEntered[charEntered] = true;
}
}
Console.WriteLine("The following inputs were entered:");
Console.WriteLine(String.Join(", ", charsEntered.Keys));
Console.ReadLine();
}
Use Any linq expression to validate duplicates. char.TryParse will validates input and returns true when succeeded.
public static void Main()
{
char[] arr = new char[5];
//User input
Console.WriteLine("Please Enter 5 Letters only: ");
for (int i = 0; i < arr.Length; i++)
{
char input;
if(char.TryParse(Console.ReadLine(), out input) && !arr.Any(c=>c == input))
{
arr[i] = input;
}
else
{
Console.WriteLine( "Error : Either invalid input or a duplicate entry.");
i--;
}
}
Console.WriteLine("You have entered the following inputs: ");
//display
for(int i = 0; i<arr.Length; i++)
{
Console.WriteLine(arr[i]);
}
}
Working Code
Elaborating on Shelvin's answer of using HashSet
HashSet<char> chars = new HashSet<char>();
//User input
Console.WriteLine("Please Enter 5 Letters only: ");
for (int i = 0; i < 5; )
{
char c = Convert.ToChar(Console.ReadLine());
if(!("abcdefghijklmnopqrstuvwxyz".Contains(c.ToString().ToLower())))
{
Console.WriteLine("Please enter an alphabet");
continue;
}
else if (!chars.Contains(c))
{
chars.Add(c);
i++;
}
else
{
Console.WriteLine("Duplicate value please try again");
continue;
}
}
//display
Console.WriteLine("You have entered the following inputs: ");
foreach(char c in chars)
Console.WriteLine(c.ToString());
Console.Read();
Keep it simple, and although a HashSet is nice semantically, it's not needed for 5 elements (it's actually slower than a List in that case). Worse, it requires a parallel structure to track the characters (assuming you care about order).
Clearly none of these considerations matter for such a small example but it's good to learn them up front and don't always jump to big-O notation when actually measured performance and memory consumption should be your guide for most practical applications.
Instead you can simply do:-
List<char> chars = new List<char>(5);
while (chars.Count < 5)
{
char c = Console.ReadKey().KeyChar;
if (!char.IsLetter(c)) continue;
if (chars.Contains(char)) continue;
chars.Add(char);
}
Plus whatever error messages you want to add.