C# - Hangman with specified requirements - c#

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

Related

How to display or hide letters in a string - Hangman game

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.

displaying and uncovering letters using asterisks

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());

Comparing userinput to a list of string from a text file

I have a text file that a list of these words on it
Laptop
Laser
Macho
Sam
Samantha
Mulder
Microphone
Aardvark
And what I want to do is have user input type in a word and the console will basically respond with we have your word or we do not have your word. This is my code so far:
TextReader file = new StreamReader("Files/Exercise_Files/Words.txt");
Console.WriteLine("Welcome to FindWord");
string wordInput;
Console.WriteLine("Type in a word so we can try and find it: ");
wordInput = Console.ReadLine();
string sLine = file.ReadToEnd();
if (String.Compare(wordInput, sLine, true) == 0)
{
Console.WriteLine("We have found your word!");
}
else
{
Console.WriteLine("We have not found your word");
}
file.Dispose();
I have tried doing a couple of versions trying to solve this and one included adding a for-each loop but that confused me quite a bit. I'm not 100% sure when to use a for-each loop. I also want to have string comparison be case insensitive but the code I have now will always say word not found no matter what I input.
You can use the File.ReadAllLines() method to read the file lines into an array, and then the Linq extension method Contains to determine if a word exists in that list (and you can do a case-insensitive search):
static void Main(string[] args)
{
var filePath = #"c:\temp\temp.txt";
var words = File.ReadAllLines(filePath);
Console.Write("Enter a search term: ");
var searchTerm = Console.ReadLine();
if (words.Contains(searchTerm, StringComparer.OrdinalIgnoreCase))
{
Console.WriteLine("We have your word!");
}
else
{
Console.WriteLine("We do not have your word");
}
Console.ReadKey();
}
Load file into array
string[] words = File.ReadAllLines(path);
Then use LINQ to find
if (words.Any(w => string.Equals(w, input, StringComparison.InvariantCultureIgnoreCase)))
// do your thing here
Performance Contains vs Any
Contains is quicker but in this case difference will not be visible since searched collection is small.
public class Program
{
public static HashSet<string> _hs = new HashSet<String>(Enumerable.Range(1,1000).Select(x=> "Item " + x));
public static string[] _arr = Enumerable.Range(1,1000).Select(x=> "Item " + x).ToArray();
public static void Main()
{
var sw = new Stopwatch();
sw.Start();
bool f;
for (int i = 1; i < 1001; i++)
{
//f = _hs.Contains("Item " + i, StringComparer.OrdinalIgnoreCase);
f = _arr.Contains("Item " + i, StringComparer.OrdinalIgnoreCase);
}
Console.WriteLine(sw.Elapsed);
sw.Restart();
for (int i = 1; i < 1001; i++)
{
f = _hs.Any(w => string.Equals(w, "Item " + i, StringComparison.InvariantCultureIgnoreCase));
}
Console.WriteLine(sw.Elapsed);
}
}
String.Compare , compares two strings. I think, you may want to use Contains method. Please see below-
if (sLine.Contains(wordInput))
{
Console.WriteLine("We have found your word!");
}
else
{
Console.WriteLine("We have not found your word");
}

Why are the contents of my array being printed twice in C#?

I do not understand why this code prints the contents of the array twice.
static void Main(string[] args)
{
Int64 userlength;
Int64 userlengthcounter;
String unencrypted;
char current;
start:
Console.WriteLine("Please enter how many characters the string you want encrypyted to be:");
userlength = Convert.ToInt64(Console.ReadLine());
Console.WriteLine("Please enter the string you want to be encrypted:");
unencrypted = Console.ReadLine();
int[] first = new int[userlength];
int[] second = new int[userlength];
if (userlength != unencrypted.Length)
{
Console.WriteLine("The string you entered was not the same length as the number of characters you specified");
goto start;
}
for (int i = 0; i < userlength; i++)
{
Console.WriteLine(unencrypted[i]);
current = unencrypted[i];
first[i] = current;
}
foreach (char item in first)
{
Console.WriteLine(item.ToString());
}
Console.ReadLine();
}
For example entering abcd would return abcdabcd and i don't understand why. Any help would be appreciated thanks.
It's because you have two loops, first you print each character in unencrypted in the for loop and store the chars in first array.
Then you loop over the array and print the chars again with foreach.
Additional Note: Using goto is almost always a bad idea because it makes your code hard to follow and unreadable. Because you have to manually track where the code jumps.
You can do the same thing with a do-while loop instead.
do {
Console.WriteLine("Please enter how many characters the string you want encrypyted to be:");
userlength = Convert.ToInt64(Console.ReadLine());
Console.WriteLine("Please enter the string you want to be encrypted:");
unencrypted = Console.ReadLine();
int[] first = new int[userlength];
int[] second = new int[userlength];
if (userlength != unencrypted.Length)
{
Console.WriteLine("The string you entered was not the same length as the number of characters you specified");
}
} while(userlength != unencrypted.Length);
you specifically build "first" then you print it in foreach, basically displaying the same content twice:
for (int i = 0; i < userlength; i++)
{
Console.WriteLine(unencrypted[i]);
current = unencrypted[i];
first[i] = current;
}
foreach (char item in first)
{
Console.WriteLine(item.ToString());
}

Need to remove duplicate values from C# program

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.

Categories

Resources