I'm a beginner at C#, and this is a console application that I wrote as I'm attempting to recreate simple games such as Hangman, TicTacToe, etc. I'm wondering if there's any logic in my code that's inefficient. Also, there's probably way too much nested statements around the part where the program checks the validity of the input, so is there any way to rearrange that so it's easier to read?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
namespace HangmanGame
{
internal class Program
{
static void Main(string[] args)
{
bool isPlaying = true;
bool needSetup = false;
Console.Title = "Hangman Game";
Console.WriteLine("Welcome to Hangman!");
HangmanGame hangmanGame = new HangmanGame();
do
{
if (!needSetup)
{
needSetup = true;
hangmanGame.GameSetup();
hangmanGame.Play();
}
Console.Write("Do you want to continue playing Hangman (Y/N)? ");
switch (Console.ReadLine()?.ToUpper())
{
case "Y":
isPlaying = true;
needSetup = false;
Console.Clear();
Console.WriteLine("Welcome to Hangman!");
break;
case "N":
isPlaying = false;
break;
default:
Console.WriteLine("Please enter an acceptable answer.");
break;
}
} while (isPlaying);
}
}
class HangmanGame
{
Random random = new Random();
List<string> wordList = new List<string>()
{
"scythe", "apple", "osmosis", "laughing", "extend", "brown", "expert", "tired", "humidity", "backpack", "wares", "soup",
"mount", "crust", "dent", "market", "knock", "smite", "windy", "coin", "throw", "silence", "bluff", "downfall", "mass",
"game", "stocking", "folly", "action",
};
char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
string[] hangmanStages =
{
// final state: head, torso, both arms, and both legs
@"
--------
| |
| O
| \|/
| |
| / \
-",
// head, torso, both arms, and one leg
@"
--------
| |
| O
| \|/
| |
| /
-",
// head, torso, and both arms
@"
--------
| |
| O
| \|/
| |
|
-",
// head, torso, and one arm
@"
--------
| |
| O
| \|
| |
|
-",
// head and torso
@"
--------
| |
| O
| |
| |
|
-",
// head
@"
--------
| |
| O
|
|
|
-",
// initial empty state
@"
--------
| |
|
|
|
|
-",
};
private List<char> correctGuesses = new List<char>();
private List<char> incorrectGuesses = new List<char>();
private List<char> availableGuesses = new List<char>();
private string? correctWord;
private char[]? currentGuess;
private string? input;
private char inputChar;
private const int MAXIMUM_LIVES = 6;
private int numOfLives;
public void GameSetup()
{
correctGuesses = new List<char>();
incorrectGuesses = new List<char>();
availableGuesses = alphabet.ToList();
correctWord = wordList[random.Next(0, wordList.Count)].ToUpper();
currentGuess = new char[correctWord.Length];
wordList.Remove(correctWord);
numOfLives = MAXIMUM_LIVES;
for (int i = 0; i < correctWord.Length; i++)
{
if (correctWord[i] == ' ')
{
currentGuess[i] = ' ';
}
}
}
public void Play()
{
DrawDisplay();
// Checks to end game if the current guess is correct or if all lives are lost
while (correctWord != (new string(currentGuess)).ToUpper() && numOfLives > 0)
{
Console.Write("Please enter your guess: ");
input = Console.ReadLine()?.ToUpper();
// Allows the player to type the entire word, and if correct ends the game
if (String.Equals(input?.ToUpper(), correctWord))
{
break;
}
// If the player types nothing or guess more than one letter, replaces the guess with an incorrect character
if (input?.Length != 1)
{
input = "$";
}
inputChar = Convert.ToChar(input);
/*
* Checks to see if the input is in the array of available guesses
* Matches against correct word if it does
* If the guess is correct, replace letters in current guess with the input
* If the guess is incorrect, deduct lives
*/
if (availableGuesses.Contains(inputChar))
{
if (correctWord.Contains(inputChar))
{
for (int i = 0; i < correctWord.Length; i++)
{
if (correctWord[i] == inputChar)
{
currentGuess[i] = inputChar;
availableGuesses.Remove(inputChar);
correctGuesses.Add(inputChar);
Console.WriteLine("Added Char: " + inputChar);
}
else if (correctWord[i] == ' ')
{
currentGuess[i] = ' ';
}
else if (!(correctGuesses.Contains(correctWord[i])))
{
currentGuess[i] = '\0';
}
}
}
else
{
availableGuesses.Remove(inputChar);
incorrectGuesses.Add(inputChar);
numOfLives--;
}
DrawDisplay(true);
}
else if (correctGuesses.Contains(inputChar) || incorrectGuesses.Contains(inputChar))
{
Console.WriteLine("You already entered that!");
}
else
{
Console.WriteLine("Please enter an acceptable input.");
}
}
Console.Clear();
if (numOfLives > 0)
Console.WriteLine($"Congratulations! You won with {numOfLives} {(numOfLives == 1 ? "life" : "lives")} left! "
+ "The correct word was "
+ CultureInfo.CurrentCulture.TextInfo.ToTitleCase(correctWord.ToLower()));
else
Console.WriteLine("Sorry! You lost! The correct word was " +
CultureInfo.CurrentCulture.TextInfo.ToTitleCase(correctWord.ToLower()));
currentGuess = correctWord.ToCharArray();
DrawDisplay();
}
private void DisplayCharArray(char[] chars)
{
for (int i = (int) (8 - Math.Round((decimal) (chars.Length / 2), MidpointRounding.AwayFromZero)); i >= 0; i--)
{
Console.Write(" ");
}
// Converts a char array into hangman form with underscores or the guessed letters
foreach (char c in chars)
{
if (c == '\0')
{
Console.Write("_ ");
}
else
{
Console.Write(c + " ");
}
}
Console.WriteLine("\n");
}
private void DrawDisplay(bool isGuessing = false)
{
// Only writes when a guess has already occurred, so on the first guess it doesn't write it
if (isGuessing)
{
Console.Clear();
if (correctWord.Contains(inputChar))
Console.WriteLine("That guess was correct!");
else
Console.WriteLine("That guess was incorrect!");
Console.Write("Incorrect Guesses: ");
for (int i = 0; i < incorrectGuesses.Count; i++)
{
Console.Write(incorrectGuesses[i] + " ");
}
Console.WriteLine($"\nYou have {numOfLives} {(numOfLives == 1 ? "life" : "lives")} left!");
}
Console.WriteLine(hangmanStages[numOfLives]);
DisplayCharArray(currentGuess);
}
}
}