4
\$\begingroup\$

today I created a simple hangman game, which has 10 different categories with 10 words each. The words order is being randomized each time you pick a category. You have the chance to pick a wrong letter 8 times, after this it's game over and your progress on the current category is being wiped out. The game also saves all the completed categories in the Settings of the project so you wont loose any progress if you restart the game. There are 3 difficulties - easy, medium, hard. Easy show's you the first and the last letters of the word, medium shows you only the first one and hard shows none. Each one has separate progress completion. This is how the game looks http://prntscr.com/b0hkye you can enter letter by pressing them on your keyboard or by using the alphabet built-in the app. That's pretty much of it now I'm going to quickly go over the code.

The game has a static class which holds some data that will be used over all the forms.

public static class GeneralSettings
{
    public enum Difficulty
    {
        Easy,
        Medium,
        Hard
    }

    public enum Category
    {
        Capitals,
        FoodAndDrinks,
        Animals,
        Plants,
        Objects,
        Movies,
        Cities,
        Furniture,
        Figures,
        Sports
    }

    public static Difficulty GameDifficulty { get; set; }
    public static Category GameCategory { get; set; }
    public static string[] GameWords { get; set; }
}

and I also created another static class which holds all the available words

    public static class Words
{
    public static string[] Capitals =
    {
        "London",
        "Tokyo",
        "Paris",
        "Berlin",
        "Ottawa",
        "Washington",
        "Copenhagen",
        "Kabul",
        "Sofia",
        "Budapest"
    };

    public static string[] FoodAndDrinks =
    {
        "Haggis",
        "Spaghetti",
        "Pizza",
        "Salad",
        "Fanta",
        "Hamburger",
        "Steak",
        "Eggs",
        "Water",
        "Juice"
    };
    //more here
}

Now the game initially start in the MainMenu Form where you select your difficulty

    using static GLS_GuessTheWord.GeneralSettings;
    private void bEasy_Click(object sender, EventArgs e)
    {
        GameDifficulty = Difficulty.Easy;
        ChapterSelection cs = new ChapterSelection();
        cs.ShowDialog();
    }

    private void bMedium_Click(object sender, EventArgs e)
    {
        GameDifficulty = Difficulty.Medium;
        ChapterSelection cs = new ChapterSelection();
        cs.ShowDialog();
    }

    private void bHard_Click(object sender, EventArgs e)
    {
        GameDifficulty = Difficulty.Hard;
        ChapterSelection cs = new ChapterSelection();
        cs.ShowDialog();
    }

    private void bExit_Click(object sender, EventArgs e)
    {
        Application.Exit();
    }

Once you've picked your difficulty you are taken to the ChapterSelection Form which has different Checkboxs foreach difficulty as mentioned earlier.

    private void UpdateEasyCompletedCategories()
    {
        cbCapitals.Checked = Properties.Settings.Default.EasyCapitalsCompleted;
        cbFoodAndDrinks.Checked = Properties.Settings.Default.EasyFoodAndDrinksCompleted;
        cbAnimals.Checked = Properties.Settings.Default.EasyAnimalsCompleted;
        cbPlants.Checked = Properties.Settings.Default.EasyPlantsCompleted;
        cbObjects.Checked = Properties.Settings.Default.EasyObjectsCompleted;
        cbMovies.Checked = Properties.Settings.Default.EasyMoviesCompleted;
        cbFurniture.Checked = Properties.Settings.Default.EasyFurnitureCompleted;
        cbCities.Checked = Properties.Settings.Default.EasyCitiesCompleted;
        cbFigures.Checked = Properties.Settings.Default.EasyFiguresCompleted;
        cbSports.Checked = Properties.Settings.Default.EasySportsCompleted;
    }

    private void UpdateMediumCompletedCategories()
    {
        cbCapitals.Checked = Properties.Settings.Default.MediumCapitalsCompleted;
        cbFoodAndDrinks.Checked = Properties.Settings.Default.MediumFoodAndDrinksCompleted;
        cbAnimals.Checked = Properties.Settings.Default.MediumAnimalsCompleted;
        cbPlants.Checked = Properties.Settings.Default.MediumPlantsCompleted;
        cbObjects.Checked = Properties.Settings.Default.MediumObjectsCompleted;
        cbMovies.Checked = Properties.Settings.Default.MediumMoviesCompleted;
        cbFurniture.Checked = Properties.Settings.Default.MediumFurnitureCompleted;
        cbCities.Checked = Properties.Settings.Default.MediumCitiesCompleted;
        cbFigures.Checked = Properties.Settings.Default.MediumFiguresCompleted;
        cbSports.Checked = Properties.Settings.Default.MediumSportsCompleted;
    }

    private void UpdateHardCompletedCategories()
    {
        cbCapitals.Checked = Properties.Settings.Default.HardCapitalsCompleted;
        cbFoodAndDrinks.Checked = Properties.Settings.Default.HardFoodAndDrinksCompleted;
        cbAnimals.Checked = Properties.Settings.Default.HardAnimalsCompleted;
        cbPlants.Checked = Properties.Settings.Default.HardPlantsCompleted;
        cbObjects.Checked = Properties.Settings.Default.HardObjectsCompleted;
        cbMovies.Checked = Properties.Settings.Default.HardMoviesCompleted;
        cbFurniture.Checked = Properties.Settings.Default.HardFurnitureCompleted;
        cbCities.Checked = Properties.Settings.Default.HardCitiesCompleted;
        cbFigures.Checked = Properties.Settings.Default.HardFiguresCompleted;
        cbSports.Checked = Properties.Settings.Default.HardSportsCompleted;
    }

    private void UpdateCompletedCategories()
    {
        switch (GameDifficulty)
        {
            case Difficulty.Easy:
                UpdateEasyCompletedCategories();
                break;
            case Difficulty.Medium:
                UpdateMediumCompletedCategories();
                break;
            case Difficulty.Hard:
                UpdateHardCompletedCategories();
                break;
        }
    }

    private void bCapitals_Click(object sender, EventArgs e)
    {
        GameWords = Words.Capitals;
        GameCategory = Category.Capitals;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

    private void bFandD_Click(object sender, EventArgs e)
    {
        GameWords = Words.FoodAndDrinks;
        GameCategory = Category.FoodAndDrinks;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

    private void bAnimals_Click(object sender, EventArgs e)
    {
        GameWords = Words.Animals;
        GameCategory = Category.Animals;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

    private void bPlants_Click(object sender, EventArgs e)
    {
        GameWords = Words.Plants;
        GameCategory = Category.Plants;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

    private void bObjects_Click(object sender, EventArgs e)
    {
        GameWords = Words.Objects;
        GameCategory = Category.Objects;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

    private void bMovies_Click(object sender, EventArgs e)
    {
        GameWords = Words.Movies;
        GameCategory = Category.Movies;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

    private void bCities_Click(object sender, EventArgs e)
    {
        GameWords = Words.Cities;
        GameCategory = Category.Cities;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

    private void bHouse_Click(object sender, EventArgs e)
    {
        GameWords = Words.Furniture;
        GameCategory = Category.Furniture;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

    private void bFigures_Click(object sender, EventArgs e)
    {
        GameWords = Words.Figures;
        GameCategory = Category.Figures;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

    private void bSports_Click(object sender, EventArgs e)
    {
        GameWords = Words.Sports;
        GameCategory = Category.Sports;
        MainGame mg = new MainGame();
        mg.ShowDialog();
        UpdateEasyCompletedCategories();
    }

And after this you can start playing the game in your desired category, which happens in the MainGame Form :

public partial class MainGame : Form
{
    private static readonly char[] letters =
    {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
        'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
    };

    private static readonly Button[] LetterButtons = new Button[letters.Length];
    private readonly List<Label> WordLettersLabels = new List<Label>();
    private readonly string[] categoryWords = GameWords;
    private static int hangManState = 0;
    private string currentWord;

    private static readonly Image[] HangManStateImages =
    {
        Image.FromFile(@"Assets\States\State1.png"),
        Image.FromFile(@"Assets\States\State2.png"),
        Image.FromFile(@"Assets\States\State3.png"),
        Image.FromFile(@"Assets\States\State4.png"),
        Image.FromFile(@"Assets\States\State5.png"),
        Image.FromFile(@"Assets\States\State6.png"),
        Image.FromFile(@"Assets\States\State7.png"),
        Image.FromFile(@"Assets\States\State8.png"),
    };

    public MainGame()
    {
        InitializeComponent();
        CreateLetterButtons();
        ShuffleWords();
        currentWord = categoryWords[0];
        CreateLetterLabels(currentWord.Length);
        DisplayStartingLetters();
        lbStage.Text = @"1 " + @"/ " + categoryWords.Length;
    }

    private void CreateLetterButtons()
    {
        const int startingHorizontal = 25;
        int horizontal = startingHorizontal;
        int vertical = 375;
        for (int i = 0; i < LetterButtons.Length; i++)
        {
            LetterButtons[i] = new Button
            {
                Size = new Size(35, 35),
                Location = new Point(horizontal, vertical),
                Text = letters[i].ToString(),
                Font = new Font("Microsoft Sans Serif", 14),
            };
            LetterButtons[i].Click += LetterClick;
            Controls.Add(LetterButtons[i]);
            horizontal += LetterButtons[i].Width + 2;
            if (i == 12) //break row
            {
                vertical += LetterButtons[i].Height + 5;
                horizontal = startingHorizontal;
            }
        }
    }

    private void CreateLetterLabels(int wordLength)
    {
        int horizontal = 20;
        int vertical = 310;
        for (int i = 0; i < wordLength; i++)
        {
            Label newLetterLabel = new Label
            {
                Font = new Font("Microsoft Sans Serif", 30),
                AutoSize = true,
                Text = @"_",
                Location = new Point(horizontal, vertical)
            };
            horizontal += newLetterLabel.Width/2;
            Controls.Add(newLetterLabel);
            WordLettersLabels.Add(newLetterLabel);
        }
    }

    private void LetterClick(object sender, EventArgs e)
    {
        Button pressedButton = (Button) sender;
        char currentLetter = pressedButton.Text.ToLower()[0];
        CheckLetter(currentLetter, pressedButton);
    }

    private void CheckLetter(char currentLetter, Button pressedButton)
    {
        if (LetterIsContained(currentLetter))
        {
            IEnumerable<int> letterPositions = GetLetterPositions(currentLetter);
            foreach (int letterPosition in letterPositions)
            {
                WordLettersLabels[letterPosition].Text = currentLetter.ToString();
            }
            pressedButton.Dispose();
        }
        else
        {
            pbHangman.BackgroundImage = HangManStateImages[hangManState];
            hangManState++;
            pressedButton.Dispose();
        }
        if (IsLost)
        {
            Lost();
            return;
        }
        if (IsFinishedCategory)
        {
            FinishedCategory();
            UpdateCompletedCategories();
            Close();
        }
        if (IsCorrectWord)
        {
            CorrectWord();
        }
    }

    private bool IsFinishedCategory => IsCorrect() && Array.IndexOf(categoryWords, currentWord) + 1 == categoryWords.Length;

    private bool IsCorrectWord => IsCorrect() && Array.IndexOf(categoryWords, currentWord) + 1 < categoryWords.Length;

    private static bool IsLost => hangManState == HangManStateImages.Length;

    private void CompletedTheGame()
    {
        if (GameDifficulty == Difficulty.Easy && Properties.Settings.Default.EasyAnimalsCompleted &&
            Properties.Settings.Default.EasyCapitalsCompleted && Properties.Settings.Default.EasyCitiesCompleted &&
            Properties.Settings.Default.EasyFiguresCompleted &&
            Properties.Settings.Default.EasyFoodAndDrinksCompleted &&
            Properties.Settings.Default.EasyFurnitureCompleted && Properties.Settings.Default.EasyMoviesCompleted &&
            Properties.Settings.Default.EasyObjectsCompleted && Properties.Settings.Default.EasyPlantsCompleted &&
            Properties.Settings.Default.EasySportsCompleted)
        {
            DialogResult dialogResult =
                MessageBox.Show(
                    @"You've finished all the available categories on Easy difficulty!" + Environment.NewLine +
                    @"Go back to the main menu and try different difficulty.", @"Congratulations");
        }

        else if (GameDifficulty == Difficulty.Medium && Properties.Settings.Default.MediumAnimalsCompleted &&
                 Properties.Settings.Default.MediumCapitalsCompleted &&
                 Properties.Settings.Default.MediumCitiesCompleted &&
                 Properties.Settings.Default.MediumFiguresCompleted &&
                 Properties.Settings.Default.MediumFoodAndDrinksCompleted &&
                 Properties.Settings.Default.MediumFurnitureCompleted &&
                 Properties.Settings.Default.MediumMoviesCompleted &&
                 Properties.Settings.Default.MediumObjectsCompleted &&
                 Properties.Settings.Default.MediumPlantsCompleted &&
                 Properties.Settings.Default.MediumSportsCompleted)
        {
            DialogResult dialogResult =
                MessageBox.Show(
                    @"You've finished all the available categories on Medium difficulty!" + Environment.NewLine +
                    @"Go back to the main menu and try different difficulty.", @"Congratulations");
        }

        else if (GameDifficulty == Difficulty.Hard && Properties.Settings.Default.HardAnimalsCompleted &&
                 Properties.Settings.Default.HardCapitalsCompleted &&
                 Properties.Settings.Default.HardCitiesCompleted &&
                 Properties.Settings.Default.HardFiguresCompleted &&
                 Properties.Settings.Default.HardFoodAndDrinksCompleted &&
                 Properties.Settings.Default.HardFurnitureCompleted &&
                 Properties.Settings.Default.HardMoviesCompleted &&
                 Properties.Settings.Default.HardObjectsCompleted && Properties.Settings.Default.HardPlantsCompleted &&
                 Properties.Settings.Default.HardSportsCompleted)
        {
            DialogResult dialogResult =
                MessageBox.Show(
                    @"You've finished all the available categories on Hard difficulty!" + Environment.NewLine +
                    @"Go back to the main menu and try different difficulty.", @"Congratulations");
        }
        Close();
    }

    private void FinishedCategory()
    {
        CompletedTheGame();
        DialogResult dialogResult =
            MessageBox.Show(
                @"You've finished all the available word for this category !" + Environment.NewLine +
                @"Go back to the main menu and pick another category.", @"Congratulations");
        Close();
    }

    private void CorrectWord()
    {
        DialogResult dialogResult =
            MessageBox.Show(
                @"Good job you got the word correctly !" + Environment.NewLine +
                @"Would you like to move to the next word ?", @"Congratulations", MessageBoxButtons.YesNo);
        if (dialogResult == DialogResult.Yes)
        {
            currentWord = categoryWords[Array.IndexOf(categoryWords, currentWord) + 1];
            lbStage.Text = Array.IndexOf(categoryWords, currentWord) + 1 + @" / " + categoryWords.Length;
            Reset();
        }
    }

    private void Lost()
    {
        DialogResult dialogResult =
            MessageBox.Show(
                @"You've lost your progress on the current category !" + Environment.NewLine +
                @"Would you like to try again ?", @"You've lost", MessageBoxButtons.YesNo);
        if (dialogResult == DialogResult.Yes)
        {
            ShuffleWords();
            currentWord = categoryWords[0];
            lbStage.Text = Array.IndexOf(categoryWords, currentWord) + 1 + @" / " + categoryWords.Length;
            Reset();
        }
    }

    private bool IsCorrect()
    {
        string formedWord = WordLettersLabels.Aggregate(string.Empty, (current, wordLettersLabel) => current + wordLettersLabel.Text);
        return formedWord == currentWord.ToLower();
    }

    private void Reset()
    {
        for (int i = 0; i < LetterButtons.Length; i++)
        {
            LetterButtons[i].Dispose();
            LetterButtons[i] = null;
        }
        foreach (var wordLettersLabel in WordLettersLabels)
        {
            wordLettersLabel.Dispose();
        }
        WordLettersLabels.Clear();
        CreateLetterButtons();
        CreateLetterLabels(currentWord.Length);
        DisplayStartingLetters();
        pbHangman.BackgroundImage = null;
        hangManState = 0;
    }

    private IEnumerable<int> GetLetterPositions(char inputLetter)
    {
        List<int> letterPositions = new List<int>();
        for (int i = 0; i < currentWord.Length; i++)
        {
            if (currentWord.ToLower()[i] == inputLetter)
            {
                letterPositions.Add(i);
            }
        }
        return letterPositions.ToArray();
    }

    private bool LetterIsContained(char inputLetter) => currentWord.ToLower().Contains(inputLetter);

    private void DisplayStartingLetters()
    {
        switch (GameDifficulty)
        {
            case Difficulty.Easy:
                WordLettersLabels[0].Text = currentWord[0].ToString().ToLower();
                WordLettersLabels[WordLettersLabels.Count - 1].Text = currentWord[currentWord.Length - 1].ToString().ToLower();
                break;
            case Difficulty.Medium:
                WordLettersLabels[0].Text = currentWord[0].ToString().ToLower();
                break;
        }
    }

    private void ShuffleWords()
    {
        Random random = new Random();
        for (int i = categoryWords.Length; i > 0; i--)
        {
            int j = random.Next(i);
            string k = categoryWords[j];
            categoryWords[j] = categoryWords[i - 1];
            categoryWords[i - 1] = k;
        }
    }

    private static void UpdateCompletedCategories()
    {
        switch (GameDifficulty)
        {
            case Difficulty.Easy:
                switch (GameCategory)
                {
                    case Category.Capitals:
                        Properties.Settings.Default.EasyCapitalsCompleted = true;
                        break;
                    case Category.FoodAndDrinks:
                        Properties.Settings.Default.EasyFoodAndDrinksCompleted = true;
                        break;
                    case Category.Animals:
                        Properties.Settings.Default.EasyAnimalsCompleted = true;
                        break;
                    case Category.Plants:
                        Properties.Settings.Default.EasyPlantsCompleted = true;
                        break;
                    case Category.Objects:
                        Properties.Settings.Default.EasyObjectsCompleted = true;
                        break;
                    case Category.Movies:
                        Properties.Settings.Default.EasyMoviesCompleted = true;
                        break;
                    case Category.Furniture:
                        Properties.Settings.Default.EasyFurnitureCompleted = true;
                        break;
                    case Category.Cities:
                        Properties.Settings.Default.EasyCitiesCompleted = true;
                        break;
                    case Category.Figures:
                        Properties.Settings.Default.EasyFiguresCompleted = true;
                        break;
                    case Category.Sports:
                        Properties.Settings.Default.EasySportsCompleted = true;
                        break;
                }
                break;
            case Difficulty.Medium:
                switch (GameCategory)
                {
                    case Category.Capitals:
                        Properties.Settings.Default.MediumCapitalsCompleted = true;
                        break;
                    case Category.FoodAndDrinks:
                        Properties.Settings.Default.MediumFoodAndDrinksCompleted = true;
                        break;
                    case Category.Animals:
                        Properties.Settings.Default.MediumAnimalsCompleted = true;
                        break;
                    case Category.Plants:
                        Properties.Settings.Default.MediumPlantsCompleted = true;
                        break;
                    case Category.Objects:
                        Properties.Settings.Default.MediumObjectsCompleted = true;
                        break;
                    case Category.Movies:
                        Properties.Settings.Default.MediumMoviesCompleted = true;
                        break;
                    case Category.Furniture:
                        Properties.Settings.Default.MediumFurnitureCompleted = true;
                        break;
                    case Category.Cities:
                        Properties.Settings.Default.MediumCitiesCompleted = true;
                        break;
                    case Category.Figures:
                        Properties.Settings.Default.MediumFiguresCompleted = true;
                        break;
                    case Category.Sports:
                        Properties.Settings.Default.MediumSportsCompleted = true;
                        break;
                }
                break;
            case Difficulty.Hard:
                switch (GameCategory)
                {
                    case Category.Capitals:
                        Properties.Settings.Default.HardCapitalsCompleted = true;
                        break;
                    case Category.FoodAndDrinks:
                        Properties.Settings.Default.HardFoodAndDrinksCompleted = true;
                        break;
                    case Category.Animals:
                        Properties.Settings.Default.HardAnimalsCompleted = true;
                        break;
                    case Category.Plants:
                        Properties.Settings.Default.HardPlantsCompleted = true;
                        break;
                    case Category.Objects:
                        Properties.Settings.Default.HardObjectsCompleted = true;
                        break;
                    case Category.Movies:
                        Properties.Settings.Default.HardMoviesCompleted = true;
                        break;
                    case Category.Furniture:
                        Properties.Settings.Default.HardFurnitureCompleted = true;
                        break;
                    case Category.Cities:
                        Properties.Settings.Default.HardCitiesCompleted = true;
                        break;
                    case Category.Figures:
                        Properties.Settings.Default.HardFiguresCompleted = true;
                        break;
                    case Category.Sports:
                        Properties.Settings.Default.HardSportsCompleted = true;
                        break;
                }
                break;
        }
        Properties.Settings.Default.Save();
    } // this needs to be shorten

    private void MainGame_KeyPress(object sender, KeyPressEventArgs e)
    {
        foreach (var letterButton in LetterButtons)
        {
            if (e.KeyChar == letterButton.Text.ToLower()[0])
            {
                CheckLetter(e.KeyChar, letterButton);
            }
        }
    }
}

Here I have a few ugly methods which are performing operations with the Properties.Settings.Default because I cant seem to find a way to pack them in some collection. If something is unclear I will happily answer in the comments. I'm looking for answer's concerning the code style and ideas on how to shorten the long methods or overall how to shorten the code.

\$\endgroup\$
2
\$\begingroup\$

A Single "Difficulty" Event Handler

Extend EventArgs so you can pass Difficulty.

public class HangEventArgs : EventArgs {
   public Difficulty challenge {get; set;}
}

// one handler to rule them all
private void Difficulty_Click(object sender, HangEventArgs e)
{
    GameDifficulty = e.challenge;
    ChapterSelection cs = new ChapterSelection();
    cs.ShowDialog();
}

More HangEventArgs Goodness

Same idea as above. BONUS: new categories use this handler too.

public class HangEventArgs : EventArgs {
   public Difficulty Challenge {get; set;}
   public Category Jeopardy {get; set;}
}

private void Category_Click(object sender, HangEventArgs e)
{
    GameWords = Words.Capitals;      // this will be dealt with below
    GameCategory = e.Jeopardy;
    MainGame mg = new MainGame();
    mg.ShowDialog();
    UpdateEasyCompletedCategories();  // this will be dealt with below
}

Data Structure Simplifies Everything

By data structure I mean a class design making the hangman data accessible. You almost got this with the GeneralSettings class. But we need instances.

The big insight to all of this is that there are not "easy difficulty", "medium difficulty", etc. There is just a "difficulty". Likewise with categories. And the enums say as much.

public class HangData {
    // moved the enums out
    // enums do not need to be inside of a class. They can be
    //    defined in the namespace.

    public Difficulty GameDifficulty { get; protected set; }
    public Category   GameCategory   { get; protected set; }
    public string[]   GameWords      { get; protected set; }

    public HangData (Difficulty howHard, Category askWhat) {
         GameDifficulty  = howHard;
         GameCategory    = askWhat;
         GameWords = GetGameWords(); 
    }        

    protected string[] GetGameWords() { // do it } 
}

Meanwhile, back at the Category_Click Handler

 private void Category_Click(object sender, HangEventArgs e)
{
    MainGame mg = new MainGame( new HangData(e.Challenge, e.Jeopardy));
    mg.ShowDialog();
    UpdateEasyCompletedCategories();  // this will be dealt with below
}

Back to HangData class

Put the "completed" properties in here too.

public class HangData {
    public bool CapitalsCompleted {get; set;}
    // et cetera

    public HangData (Difficulty howHard, Category askWhat) {
        // all the earlier stuff, then
        SetCategoryCompleteFlags();
    }

    protected void SetCategoryCompleteFlags() { // whatever it takes }
}

At this point I'm not seeing a use for a collection of these flags.


One UpdateCompletedCategories() method

private void UpdateCompletedCategories() {
    cbCapitals.Checked = myHangData.CapitalsCompleted;
    // et cetera
}

You're going to use this in the category click event handler (not shown).


MainGame

I'm not going into the nit-picking of code changes, but note:

  • Don't need properties like categoryWords. It's all neatly encapsulated in HangData
  • The matrix of properties and methods named: "hard/med/easy" combined with every category - goes away.
  • The letters static property might best be put into HangData. Try it out and see how it feels.

The Big Picture

We made a data structure for the more abstract ideas of "difficulty" and "category" (vice "easy capitals" for example). This very dramatically reduced code volume and simplified client code.

\$\endgroup\$
  • \$\begingroup\$ I have the beginner's tag so i have a few questions. One handler to rule them all i understand what the idea is but how does it get triggered, do i still need all the rest event handlers? I would like to see you going in a little bit more detail. I upvoted your answer since it does bring some good ideas to the table but not explained enough for a beginner to understand. Thank you. \$\endgroup\$ – Denis May 6 '16 at 16:46
  • \$\begingroup\$ @denis you'll want to look up how to make events. There are lots of good examples. To trigger an event is identical to calling a method, with the exception of in order for that "method" to not be null you have to have someone listening to the event. \$\endgroup\$ – Robert Snyder May 6 '16 at 16:55
  • \$\begingroup\$ Yes i know how events are triggered my question was rather where is it handled ? Pressing all 10 categories will trigger just 1 event which is implemented in just 1 place ? Right now i have 10 event handlers for each click on each button will i have just 1 if i use your technique \$\endgroup\$ – Denis May 6 '16 at 17:05
  • \$\begingroup\$ To get the "one handler" literally then it has to evaluate the sender to figure out which button and go from there; and then the HangEventArgs suddenly seems unnecessary. But the motivating goal here is DRYing up the redundant code and so just go with how @RobertSnyder shows how to do it. \$\endgroup\$ – radarbob May 6 '16 at 17:38
1
\$\begingroup\$

First off there is a minor bug. In the constructor of MainGame you call InitializeComponent() twice.

Inconsistant Naming

There are a few instances of names where they don't match either capitalization or naming. For instance in MainGame: LetterButtons and WordLettersLabels. One is static the other is not but the naming convention says they are the same. Similar thoughts for categoryWords, hangManState, currentWord, and HangManStateImages. Also some of the names of the buttons and check boxes in ChapterSelection are not consistant with their name. cbFoodAndDrinks vs bFandD

Long Methods

You know that long methods are hard to read and understand. You said in your code and you said in your post. This is probably one of the hardest things to do in programming. Although the concept is easy to help in the area it is also the area that requires a fair amount of work and understanding. The trick to it is learning how and when to use design patterns. I would recommend taking a quick peek at DoFactory's list of design patterns. It isn't exhaustive but it has enough to keep you busy for a long time. Some of the more simple ones though are pulling out methods that are similar if not duplicate. For example in ChapterSelection you this button click event:

private void bCapitals_Click(object sender, EventArgs e)
{
    GameWords = Words.Capitals;
    GameCategory = Category.Capitals;
    MainGame mg = new MainGame();
    mg.ShowDialog();
    UpdateEasyCompletedCategories();
}

and if you look at the rest of them they are all basically the same. You set the Words, and the Category and show the MainGameDialog. (side note I think you have a small bug in each of your click methods. I think you meant to use UpdateCompletedCategories and not UpdateEasyCompletedCategories) This could easily be extracted into a method and called by each button click. This would clear up atleast 40 lines of code. something like this is what i'm thinking.

private void bCapitals_Click(object sender, EventArgs e)
{
    ShowGame(Words.Capitals, Category.Capitals);
}

private void ShowGame(string[] words, Category category)
{
    GameWords = words;
    GameCategory = category;
    using (var mg = new MainGame())
        mg.ShowDialog();

    UpdateCompletedCategories();
}

This has 2 advantages. 1 is that it is easier to understand. 2nd is that if I want to change the behavior of what happens when I am going to show the game I can do it in 1 place instead of 10 (or more if you add more Categories)

Dynamic Code

This is probably where you will struggle the most at. Is making code that is dynamic. Think about this scenario. You want to add more Categories to choose from? How hard is that going to be for you? I can think of it being 3 new Properties for easy, medium, and hard. 1 new button and check box plus wiring it up. 1 new check for IsCompleted..and the list goes on and on. Kind of a major pain. This is going to require a design pattern of some type. In the end it would hopefully be as simple as making a new class that extends/implements something. Then your UI would update and pickup the changes, and life would be good. That is going to be difficult though.

\$\endgroup\$
  • \$\begingroup\$ Thank you for the noted bugs they really passed without my notice. Yes I meant to use UpdateCompletedCategories but since I did some name refactoring i guess it didn't rename them properly. I would like to see some of that dynamic code implementation. I came to look for exactly such tips, since i cant seem to deal with resources in a neat way. Thank you. Making WordLettersLabels static will cause bugs. And they are Controls so i like to have them with capital letters \$\endgroup\$ – Denis May 5 '16 at 17:51
  • \$\begingroup\$ @denis if I get a moment later tonight I'll see if I can come up with something somewhat simple. It can be tricky though. It would really help to have the logic and stuff out of your form classes and into a separate class. \$\endgroup\$ – Robert Snyder May 5 '16 at 19:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.