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.
2 Answers 2
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 enum
s 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 inHangData
- The matrix of properties and methods named: "hard/med/easy" combined with every category - goes away.
- The
letters
static property might best be put intoHangData
. 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.
-
\$\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– Denis2016年05月06日 16:46:01 +00:00Commented May 6, 2016 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– Robert Snyder2016年05月06日 16:55:14 +00:00Commented May 6, 2016 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– Denis2016年05月06日 17:05:03 +00:00Commented May 6, 2016 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 theHangEventArgs
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– radarbob2016年05月06日 17:38:16 +00:00Commented May 6, 2016 at 17:38
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.
-
\$\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. MakingWordLettersLabels
static will cause bugs. And they are Controls so i like to have them with capital letters \$\endgroup\$Denis– Denis2016年05月05日 17:51:21 +00:00Commented May 5, 2016 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– Robert Snyder2016年05月05日 19:08:58 +00:00Commented May 5, 2016 at 19:08