2

I am trying to design a Blackjack game where, at some point, I need to make a choice on which action to take:Hit,Stand,Split or Double-Down. I have a button for each of those actions and I have added all of their click event handlers to the same method in the form designer, like so (I don't really know the exact way of describing it, but I'm refering to the link between the event handler and the btnRound_Click method)

this.btnSplit.Click += new System.EventHandler(this.btnRound_Click);
this.btnDoubled.Click += new System.EventHandler(this.btnRound_Click);
this.btnHit.Click += new System.EventHandler(this.btnRound_Click);
this.btnStand.Click += new System.EventHandler(this.btnRound_Click);
//Inside the Form1 class

enum RoundPlay
        {
            hit,
            stand,
            doubled,
            split
        }
        RoundPlay playchoice = RoundPlay.none;

private void btnRound_Click(object sender, EventArgs e)
        {
            switch (((Button)sender).Text)
            {                
                case "Hit":
                    playchoice = RoundPlay.hit;
                    break;
                case "Stand":
                    playchoice = RoundPlay.stand;
                    break;
                case "Split":
                    playchoice = RoundPlay.split;
                    break;
                case "Double Down":
                    playchoice = RoundPlay.doubled;
                    break;
            }            
        }

Inside the main method I would have:

//enabling the buttons so that I can click on one of them

//place to wait for the user to click on one of the buttons
//this is where I tried using await operator 

switch (playchoice)
                {
                    case RoundPlay.hit:
                        //one action
                        break;
                    case RoundPlay.stand:
                        //other action
                        break;
                    case RoundPlay.doubled:
                        //other action
                        break;
                    case RoundPlay.split:
                        //other action
                        break;
                }

Now then, what is the best way to "wait" for user input? I've searched quite a bit but all I could find were solutions to problems with only one button or situations where it didn't matter if the main method changed (creating a method for each eventhandler). In this case, I want to stay in the same method, so that I can repeat rounds, meaning, I want to have this inside a Do...While() loop.

I've tried using ManualResetEvent and async/await, but I haven't got much far with any of those, so if anyone has got any recomendations, I would appreciate it! :)

On another less important matter, I just noticed I'm writting all game progress related code inside the Form1_Load method, how incorrect is this? Is there a better place to write this inside? Sorry if anything was unclear, this is my second post here Thanks

  • `I want to stay in the same method, so that I can repeat rounds, meaning, I want to have this inside a Do...While() loop`...you're conceptualising it wrong. Have you been writing console applications up till now, by any chance? A button click should trigger a user selection. Once that's compete, you can start a new round - with a call to a method which moves things along until it gets to a state where user input would be needed again, and then completes. Then the code does nothing until the user presses another button, to trigger the next step. – ADyson Jan 07 '21 at 22:50
  • `I just noticed I'm writting all game progress related code inside the Form1_Load method, how incorrect is this`...it's ok for a quick and dirty / beginner solution, but it's not good in the long term, or for larger applications. Most appplications would typically have classes logically arranged to handle different parts of the logic / represent different entities within the program etc. You need to learn more about object-oriented programming, it's a big topic. But stick to working out how to handle the UI for now. – ADyson Jan 07 '21 at 22:51
  • @ADyson Yes I have...I'm trying to "replicate" the Console.ReadLine() method basically. So I should make methods for each state of the game, and separate them into those that run without input in them and the ones that are solely for input? Thanks! – Nuno Catalão Jan 07 '21 at 23:08
  • @ADyson so for now it is not that horrible to stuff the game related code inside Form1_Load()? I was used to console, where I would put everything inside Main(), but here there are several spots to write code in, and I happened to notice that putting things in Form1_Load does the job – Nuno Catalão Jan 07 '21 at 23:11

2 Answers2

1

Winforms is a framework not a library. Frameworks usually have Inversion of Control (IoC) where they call your code instead of you controlling them.

Easiest change is to add your while loop body into a new method like

private void NextAction() 
{
  switch(playchoice) 
  {
    ...
  }
  playchoice = RoundPlay.none;
}

and then call this method at the end of the button even handlers

private void btnRound_Click(...) 
{
  switch (((Button)sender).Text)
  {
    ...
  }
  NextAction(); 
}
funnymay
  • 341
  • 1
  • 6
0

The method below creates a Task<Control> that completes when any of the supplied Controls is clicked:

public static Task<Control> OnClickAnyAsync(params Control[] controls)
{
    var tcs = new TaskCompletionSource<Control>();
    foreach (var control in controls) control.Click += OnClick;
    return tcs.Task;

    void OnClick(object sender, EventArgs e)
    {
        foreach (var control in controls) control.Click -= OnClick;
        tcs.SetResult((Control)sender);
    }
}

You could use this method to await for a click from any of the four buttons, and return a Task<RoundPlay> with the appropriate value depending on which button was clicked:

private async Task<RoundPlay> GetPlayChoiceAsync()
{
    var clickedButton = await OnClickAnyAsync(btnHit, btnStand, btnDoubled, btnSplit);
    if (clickedButton == btnHit) return RoundPlay.hit;
    if (clickedButton == btnStand) return RoundPlay.stand;
    if (clickedButton == btnDoubled) return RoundPlay.doubled;
    if (clickedButton == btnSplit) return RoundPlay.split;
    throw new NotImplementedException();
}

Finally you could use the GetPlayChoiceAsync method like this:

// Enabling the buttons so that I can click on one of them
// Wait the player to make a choice, without blocking the UI
RoundPlay playchoice = await GetPlayChoiceAsync();
// Do something with the playchoice
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • That is more along the lines of what I was trying to do, even though I don't really understand tasks that much yet I'll have to look into it, mainly the TaskCompletionSource class and its Task property Thanks! – Nuno Catalão Jan 07 '21 at 23:30
  • @NunoCatalão the [`TaskCompletionSource`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) is a simple mechanism that allows to control a `Task` programmatically, by calling one of the `SetResult`/`SetException`/`SetCanceled` methods at the right moment. The `Task` property is what you give to the consumers to observe. – Theodor Zoulias Jan 07 '21 at 23:36