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());
Related
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.
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.
Hej, does anyone know how i can make my method for guessing letters remember
the guesses before? Currently it turns all the "right" guesses into the last right letter guessed :(
public void myGuess(char letter)
{
string wordToGuess = label4.Text;
string wordToShow = label5.Text;
for (int i = 0; i < wordToGuess.Length; i++)
{
if (wordToGuess[i] == letter || wordToGuess[i] == wordToShow[i])
wordToShow = wordToShow.Remove(i, 1).Insert(i, Char.ToString(letter));
}
label5.Text = wordToShow;
if (wordToGuess == wordToShow)
this.Close();
Form Win = new Win();
Win.Show();
}
Have a global list of char and add to it after every guess
List<char> guesses = new List<char>();
Then in your method just add to it
guesses.Add(letter);
and then you can check using this
if (guesses.Contains(letter))
{
//DoSomething
}
The problem is with your loop.
Your test checks if the character equals either the guessed letter or the letter at the same position in the already guessed words.
If the test succeeds you then effectively replace the value with the guessed letter.
You need to either remove the second part of the test:
if (wordToGuess[i] == letter)
wordToShow = wordToShow.Remove(i, 1).Insert(i, Char.ToString(letter));
or change the replacement
if (wordToGuess[i] == letter || wordToGuess[i] == wordToShow[i])
wordToShow = wordToShow.Remove(i, 1).Insert(i, Char.ToString(wordToShow[i]));
Also instead of removing/inserting if you change the wordToShow to a char array first you can then just directly change it's value and convert back to a string when finished, this makes the code a little easier to read. It may also be better performance than all the insert/removes.
var newWord = wordToShow.ToCharArray();
for (var i = 0; i<wordToGuess.Length; i++) {
if (wordToGuess[i] == letter) {
newWord[i] = letter;
}
}
wordToGuess = new string(newWord);