patterncsharpMinor
Memory test in windows forms
Viewed 0 times
windowsformstestmemory
Problem
I've made a memory test game in windows forms, where you need to find and match 2 cards with the same images.
It has 3 difficulties :
The game starts in the main menu where you can select the difficulty or the play-mode (only single player is available at the moment)
The form is implemented as follows :
```
public partial class MainMenu : Form
{
public MainMenu()
{
InitializeComponent();
}
private void bSinglePlayer_Click(object sender, EventArgs e)
{
flpSingleDifficulties.Visible = true;
flpCoopDifficulties.Visible = false;
}
private void bCooperative_Click(object sender, EventArgs e)
{
flpCoopDifficulties.Visible = true;
flpSingleDifficulties.Visible = false;
}
private void bExit_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void OnGameClosing(object sender, FormClosingEventArgs e)
{
if (InvokeRequired)
{
this.Invoke(new MethodInvoker(Show));
}
}
private void bEasySingle_Click(object sender, EventArgs e)
{
SinglePlayerEasy singleEasyForm = new SinglePlayerEasy();
Task.Run(() => singleEasyForm.ShowDialog());
singleEasyForm.FormClosing += OnGameClosing;
Hide();
}
private void bMediumSingle_Click(object sender, EventArgs e)
{
SinglePlayerMedium singleMediumForm = new SinglePlayerMedium();
Task.Run(() => singleMediumForm.ShowDialog());
singleMediumForm.FormClosing += OnGameClosing;
Hide();
}
private void bHardSingle_Click(object sender, EventArgs e)
{
SinglePlayerHard singleHardForm = new SinglePlayerHard();
Task.Run(() => singleHardForm.ShowDialog());
sing
It has 3 difficulties :
- Easy - 4 different pairs of cards, no time limit, no mistakes limit.
- Medium - 8 different pairs of cards, no time limit, 8 mistakes limit.
- Hard - 16 different pairs of cards, 30 seconds time limit, 16 mistakes limit.
The game starts in the main menu where you can select the difficulty or the play-mode (only single player is available at the moment)
The form is implemented as follows :
```
public partial class MainMenu : Form
{
public MainMenu()
{
InitializeComponent();
}
private void bSinglePlayer_Click(object sender, EventArgs e)
{
flpSingleDifficulties.Visible = true;
flpCoopDifficulties.Visible = false;
}
private void bCooperative_Click(object sender, EventArgs e)
{
flpCoopDifficulties.Visible = true;
flpSingleDifficulties.Visible = false;
}
private void bExit_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void OnGameClosing(object sender, FormClosingEventArgs e)
{
if (InvokeRequired)
{
this.Invoke(new MethodInvoker(Show));
}
}
private void bEasySingle_Click(object sender, EventArgs e)
{
SinglePlayerEasy singleEasyForm = new SinglePlayerEasy();
Task.Run(() => singleEasyForm.ShowDialog());
singleEasyForm.FormClosing += OnGameClosing;
Hide();
}
private void bMediumSingle_Click(object sender, EventArgs e)
{
SinglePlayerMedium singleMediumForm = new SinglePlayerMedium();
Task.Run(() => singleMediumForm.ShowDialog());
singleMediumForm.FormClosing += OnGameClosing;
Hide();
}
private void bHardSingle_Click(object sender, EventArgs e)
{
SinglePlayerHard singleHardForm = new SinglePlayerHard();
Task.Run(() => singleHardForm.ShowDialog());
sing
Solution
I guess you already know that having three almost identical forms isn't very good. In case there is a bug somewhere, you need to fix it three times.
Let me try to show you how you could implement it with only one form. Keep in mind that I couldn't actually test it and I wrote it in notepad. It might have some bugs or might not (or rather certainly isn't) be complete or even compile but it should give you a rough idea how to start.
The
I name it
I picked an abstract class because it provides the
The derived classes implement the logic for each level.
In the easy one there is probably nothing to do...
The medium level adds some new stuff like new controls and card-click logic. Use the
Do the same for the hard-level. Add timers, labels, buttons, events, etc.
The last part to change is the
In this new form the
It is passed a new type of
This is used with the new
```
public partial class SinglePlayerForm : Form
{
private readonly SinglePlayerLevel _level;
private List cards;
private readonly CardImage[] cardsImages;
private Card previousClickedCard;
private bool isShowingCards = false;
public SinglePlayerForm()
{
InitializeComponent();
cardsImages = Common.Instance.GetImages(Settings.CardCountByDifficulty.Easy);
Common.Instance.Shuffle(cardsImages);
cards = Common.Instance.CreateCards(flpCardHolder, cardsImages, 2, 4, OnCardClick);
}
public SinglePlayerForm(SinglePlayerLevel level) : this()
{
_level = level;
_level.Initialize(this);
}
public event EventHandler CardClick { get; set; }
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
private async void OnCardClick(Card sender)
{
if (sender.IsFlipped || isShowingCards)
{
return;
}
sender.Flip();
if (previousClickedCard == null)
{
previousClickedCard = sender;
}
else
{
if (previousClickedCard.ImageIndex != sender.ImageIndex)
{
var e = new CardClickEventArgs(sender);
CardClick(this, e);
if (e.Handled) { return; }
isShowingCards = true;
await Task.Delay(250);
Let me try to show you how you could implement it with only one form. Keep in mind that I couldn't actually test it and I wrote it in notepad. It might have some bugs or might not (or rather certainly isn't) be complete or even compile but it should give you a rough idea how to start.
The
SinglePlayerEasy form seems to have everything you need to play the game. The other forms just extend it. I suggest renaming it to SinglePlayerForm and move the logic of other levels to separate classes. I name it
SinglePlayerLevel and I start with two methods: one that extends the form and an event handler for the card-click. By the way, your CardClick delegate does not follow the event handler convention although it tries to be one.I picked an abstract class because it provides the
GameOver event. You may need to handle it in the hard-level where the timer runs out.abstract class SinglePlayerLevel
{
public event EventHandler GameOver { get; set; }
public abstract void Initialize(SinglePlayerForm form);
protected abstract void CardClick(object sender, CardClickEventArgs e);
}The derived classes implement the logic for each level.
In the easy one there is probably nothing to do...
class SinglePlayerEasy : SinglePlayerLevel
{
public override void Initialize(SinglePlayerForm form) {..}
protected override void CardClick(object sender, CardClickEventArgs e) {..}
}The medium level adds some new stuff like new controls and card-click logic. Use the
Initialize method to extend the form and the event handler to implement the custom card-click handling.class SinglePlayerMedium : SinglePlayerLevel
{
public SinglePlayerMedium()
{
}
private int _mistakesCount;
private int mistakesCount
{
get { return _mistakesCount; }
set
{
_mistakesCount = value;
lbMistakes.Text = $@"Mistakes {mistakesCount} / {maxMistakes}";
}
}
private const int maxMistakes = 8;
public override void Initialize(SinglePlayerForm form)
{
// add mistakesLabel
// add event handlers
form.CardClick += CardClick;
}
protected override void CardClick(object sender, CardClickEventArgs e)
{
mistakesCount++;
if (mistakesCount >= maxMistakes)
{
Common.Instance.Loose(Restart);
e.Handled = true;
}
}
}Do the same for the hard-level. Add timers, labels, buttons, events, etc.
class SinglePlayerHard : SinglePlayerLevel
{
// implement it for the hard-logic and extensions
}The last part to change is the
SinglePlayerForm. It needs two constructors. The default one that the designer needs and another one that requries a level and that you can use at runtime to create the form for the chosen level.In this new form the
OnCardClick (this needs to be fixed) method raises the CardClick event so that the SinglePlayerLevel implementation can handle it. It is passed a new type of
EventArgs:class CardClickEventArgs : EventArgs
{
public CardClickEventArgs() {..}
public Card Card { get; } // set in the constructor
public bool Handled { get; set; }
}This is used with the new
CardClick event that I've added to this form.```
public partial class SinglePlayerForm : Form
{
private readonly SinglePlayerLevel _level;
private List cards;
private readonly CardImage[] cardsImages;
private Card previousClickedCard;
private bool isShowingCards = false;
public SinglePlayerForm()
{
InitializeComponent();
cardsImages = Common.Instance.GetImages(Settings.CardCountByDifficulty.Easy);
Common.Instance.Shuffle(cardsImages);
cards = Common.Instance.CreateCards(flpCardHolder, cardsImages, 2, 4, OnCardClick);
}
public SinglePlayerForm(SinglePlayerLevel level) : this()
{
_level = level;
_level.Initialize(this);
}
public event EventHandler CardClick { get; set; }
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
private async void OnCardClick(Card sender)
{
if (sender.IsFlipped || isShowingCards)
{
return;
}
sender.Flip();
if (previousClickedCard == null)
{
previousClickedCard = sender;
}
else
{
if (previousClickedCard.ImageIndex != sender.ImageIndex)
{
var e = new CardClickEventArgs(sender);
CardClick(this, e);
if (e.Handled) { return; }
isShowingCards = true;
await Task.Delay(250);
Code Snippets
abstract class SinglePlayerLevel
{
public event EventHandler GameOver { get; set; }
public abstract void Initialize(SinglePlayerForm form);
protected abstract void CardClick(object sender, CardClickEventArgs e);
}class SinglePlayerEasy : SinglePlayerLevel
{
public override void Initialize(SinglePlayerForm form) {..}
protected override void CardClick(object sender, CardClickEventArgs e) {..}
}class SinglePlayerMedium : SinglePlayerLevel
{
public SinglePlayerMedium()
{
}
private int _mistakesCount;
private int mistakesCount
{
get { return _mistakesCount; }
set
{
_mistakesCount = value;
lbMistakes.Text = $@"Mistakes {mistakesCount} / {maxMistakes}";
}
}
private const int maxMistakes = 8;
public override void Initialize(SinglePlayerForm form)
{
// add mistakesLabel
// add event handlers
form.CardClick += CardClick;
}
protected override void CardClick(object sender, CardClickEventArgs e)
{
mistakesCount++;
if (mistakesCount >= maxMistakes)
{
Common.Instance.Loose(Restart);
e.Handled = true;
}
}
}class SinglePlayerHard : SinglePlayerLevel
{
// implement it for the hard-logic and extensions
}class CardClickEventArgs : EventArgs
{
public CardClickEventArgs() {..}
public Card Card { get; } // set in the constructor
public bool Handled { get; set; }
}Context
StackExchange Code Review Q#150703, answer score: 4
Revisions (0)
No revisions yet.