简体   繁体   English

等到点击事件被触发C#

[英]Wait until a click event has been fired C#

I'm developing a card game but I need to have a function that stops the program until the player hasn't clicked in the PictureBox of his card to discard it. 我正在开发一种纸牌游戏,但是我需要有一个功能来停止程序,直到玩家没有点击他的卡的PictureBox来丢弃它。 The algorithm of my game is this: 我的游戏算法是这样的:

int nextDrawer = 0; // the players which will discard a card are determinated in counterclockwise starting from the human player
for (int i = 0; i < players; i++) // untill all the players hasn't drawed a card
{
    if (i == 0) .... // the human player has to click on a picture box to discard a card
    else .... // an AI player will discard a card which is selected randomly from the 3 cards which AI has got in its hand
}

The problem is that when a mance ends, the first who will discard a card could change. 问题是,当一个mance结束时,第一个丢弃卡片的人可能会改变。 If the players are numerated with 0 (human player), 1 (first AI player), 2 (second AI player) and 3 (third AI player), at the first mance the first to discard a card is the human player, but at the second mance the first to discard could be the 2 AI player and the human player has to wait until all the AI players before him discard a card (in this case, the round would be 2-3-0-1). 如果玩家的数字为0(人类玩家),1(第一个AI玩家),2个(第二个AI玩家)和3个(第三个AI玩家),第一个丢弃牌的人是人类玩家,但是第二个放弃的第一个可能是2个AI玩家,而人类玩家必须等到他之前的所有AI玩家丢弃一张牌(在这种情况下,该轮将是2-3-0-1)。

How can I cancel the click event if the AI players hasn't discarded a card yet? 如果AI玩家还没有丢弃卡,我该如何取消点击事件呢?

UPDATE UPDATE

I don't always need to wait that all AI players had drawed a card: if the winner of the mance is the number 2, the round would be 2-3-0-1: that means the player has to wait the AI players 2 and 3 drawed, then the player has to click one PictureBox, and the loop will return back to the AI players and then the AI player 1 is allowed to discard its card. 我并不总是需要等待所有 AI玩家都拿到了一张牌:如果得分是2号,则该轮将是2-3-0-1:这意味着玩家必须等待AI玩家绘制了2和3,然后玩家必须单击一个PictureBox,并且循环将返回到AI玩家,然后AI玩家1被允许丢弃其卡。

UPDATE 2 更新2

I've thought something like that: 我想过这样的事情:

int leader = 0; // who is going to discard first
int nextDiscarder = leader; // next player who's going to discard
for (int i = 0; i < nPlayers; i++) // until all the players hasn't discarded
{
    if (nextDiscarder == 0) // the human has to discard
    {
        enablePictureBoxClickEvent;
        // now before the loop continue the program has to wait the event click on a picture box
    }
    else
    {
        AI[nextDiscarder].discard(); // the ai player will discard
    }
    if (nextDiscarder == players - 1) // if nextDiscarder has reached the end of the table
        nextDiscarder = 0; // return to the begin until all player has discarded a card
    else
        ++nextDiscarder; // continue to discard with the next player
}

and in my event click I'd do something like this: 在我的活动中点击我会做这样的事情:

private myEventClick(object sender, EventArgs e)
{
    .... // do the instructions needed to discard a card
    disableMyEventClick;
    returnToLoop;
}

but the main problem is that I don't know how to write in code my instruction returnToLoop . 但主要问题是我不知道如何在代码中写入returnToLoop指令。

I know most of the people will argue that you should use event-driven approach, but async/await feature can be used for easily implementing things like this w/o the need of implementing manually state machines. 我知道大多数人会争辩说你应该使用事件驱动的方法,但async/await功能可以用来轻松实现这样的事情,而无需实现手动状态机。

I already posted similar approach in Force loop to wait for an event and A Better Way to Implement a WaitForMouseUp() Function? 我已经在Force循环中发布了类似的方法来等待事件更好的方法来实现WaitForMouseUp()函数? , so basically this is the same helper as in the former with Button replaced with Control : ,所以基本上这是与前者相同的助手,用Button替换为Control

public static class Utils
{
    public static Task WhenClicked(this Control target)
    {
        var tcs = new TaskCompletionSource<object>();
        EventHandler onClick = null;
        onClick = (sender, e) =>
        {
            target.Click -= onClick;
            tcs.TrySetResult(null);
        };
        target.Click += onClick;
        return tcs.Task;
    }
}

Now all you need is to mark your method as async and use await : 现在您只需要将方法标记为async并使用await

// ...
if (nextDiscarder == 0) // the human has to discard
{
    // now before the loop continue the program has to wait the event click on a picture box
    await pictureBox.WhenClicked();
    // you get here after the picture box has been clicked
}
// ...

I like Ivan solution, because it looks good, and is reusable easily anywhere else you need to wait for a control. 我喜欢Ivan解决方案,因为它看起来不错,并且可以在任何其他需要等待控制的地方轻松重复使用。

However, I wanted to provide another solution, because I feel like the way are doing this is far more complicated that it could be. 但是,我想提供另一种解决方案,因为我觉得这样做的方式要复杂得多。

So let's resume this : 所以让我们继续这个:

  • At some point in the game, you need players to select a card they don't want to throw it away 在游戏中的某个时刻,你需要玩家选择一张他们不想丢弃它的牌
  • There is one human player, which is number 0 in your array 有一个人类玩家,阵列中的数字为0
  • The human player is not always the first to decide which card to throw away. 人类玩家并不总是第一个决定丢弃哪张牌的人。
  • To decide which card to throw away, you display a picturebox to the player and you wait for him to click on it. 要决定丢弃哪张卡,您向播放器显示一个图片框,然后等待他点击它。

I believe a simple solution could be : 我相信一个简单的解决方案可能是:

  1. You start by removing the card for the AI players before the human (if human is first to discard, this will do nothing, if human is last, all AI will discard here) 你首先要在人类之前移除AI玩家的牌(如果人类首先丢弃,这将无能为力,如果人类是最后一次,所有人工智能将在此丢弃)
  2. You enable the PictureBox and you end your function 您启用PictureBox并结束您的功能
  3. In the click event of the PictureBox, you remove the user card, then you remove the card for the remaining AI players that are after the human (if human is first, all AI will remove a card here, if human is last, you do nothing) 在PictureBox的点击事件中,你删除了用户卡,然后你移除了人类之后的剩余AI玩家的卡(如果人类是第一个,所有AI将在这里删除一张卡,如果人类是最后一个,你做没有)

Done... 完成...

So this would look like this : 所以这看起来像这样:

//We need an instance variable, to keep track of the first player
int _firstPlayerToDiscard = 0;

private void StartDiscardingProcess(int FirstToDiscard)
{
    _firstPlayerToDiscard = FirstToDiscard;
    if (FirstToDiscard != 0) //If the player is the first, we do nothing
    {
        //We discard for every other AI player after the human player
        for (int i = FirstToDiscard; i < nPlayers; i++)
        {
            AI[i].Discard(); 
        }
    }
    //Now we fill the PictureBox with the cards and we display it to the player
    DiscardPictureBox.Enabled = true;
    //or DiscardPictureBox.Visible = true;
    //and we are done here, we know basically wait for the player to click on the PictureBox.
}

private void pictureBox_click(Object sender, EventArgs e)
{
    //Now we remove the card selected by the player
    // ...
    //And we remove the cards from the other AI players
    //Note that if the player was first to discard, we need to change the instance variable
    if (_firstPlayerToDiscard == 0) { _firstPlayerToDiscard = nbPlayers; }
    for (int i = 1; i < _firstPlayerToDiscard; i++)
    {
        AI[i].Discard();
    }
}

And you're pretty much done... 而且你已经完成了......

NB: Sorry if syntax is bad or unusual, I usually code in VB .Net... Feel free to edit syntax issues... 注意:对不起,如果语法不好或不常见,我通常用VB编写.Net ...随意编辑语法问题...

The following code demonstrates a simple timer based state machine. 以下代码演示了一个基于计时器的简单状态机。 In this case, the machine's state is the current Player's Turn. 在这种情况下,机器的状态是当前玩家的转弯。 This example lets each Play decide when to let the next player have her turn by setting the state to the next player. 这个例子让每个Play通过将状态设置为下一个玩家来决定何时让下一个玩家轮到她。 Add additional states for other things the program should check for. 为程序应检查的其他内容添加其他状态。 This program architecture runs relatively smoothly because the program threads are not blocked in tight loops. 此程序体系结构运行相对平稳,因为程序线程不会在紧密循环中被阻塞。 The "faster" each player can complete and exit the turn, the better - even if the player's turn repeats 10000 times without doing anything before letting the next player play. 每个玩家可以完成并且退出转弯的“更快”越好 - 即使玩家在让下一个玩家玩之前没有做任何事情重复10000次。

In the example below, the Click event handler advances the machine state from the Human's turn to the AI's turn. 在下面的示例中,Click事件处理程序将机器状态从人的转弯推进到AI的转弯。 This effectively pauses the game until the Human Clicks. 这有效地暂停了游戏直到人类点击。 Since the Turn is not blocked in a tight loop, you can have other buttons for the Human to click on like "Pass", "Start Over", and "Quit". 由于Turn不会在紧密循环中被阻挡,因此您可以使用其他按钮让人类点击“Pass”,“Start Over”和“Quit”。

using System;
using System.Windows.Forms;
using System.Timers;

namespace WindowsFormsApplication1
{
  public partial class Form1 : Form
  {
    private System.Timers.Timer machineTimer = new System.Timers.Timer();

    // These are our Machine States
    private const int BEGIN_PLAY = 0;
    private const int HUMAN_PLAYER_TURN = 1;
    private const int AI_PLAYER_TURN = 2;

    // This is the Current Machine State
    private int currentPlayer = BEGIN_PLAY;

    // Flag that lets us know that the Click Event Handler is Enabled
    private bool waitForClick = false;

    // The AI members, for example 100 of them
    private const int AIcount = 100;
    private object[] AIplayer = new object[AIcount];
    private int AIcurrentIndex = 0;    // values will be 0 to 99


    public Form1()
    {
        InitializeComponent();
        this.Show();

        // The Timer Interval sets the pace of the state machine. 
        // For example if you have a lot of AIs, then make it shorter
        //   100 milliseconds * 100 AIs will take a minimum of 10 seconds of stepping time to process the AIs
        machineTimer.Interval = 100;  
        machineTimer.Elapsed += MachineTimer_Elapsed;

        MessageBox.Show("Start the Game!");
        machineTimer.Start();
    }


    private void MachineTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        // Stop the Timer
        machineTimer.Stop();
        try
        {
            // Execute the State Machine
            State_Machine();

            // If no problems, then Restart the Timer
            machineTimer.Start();
        }
        catch (Exception stateMachineException)
        {
            // There was an Error in the State Machine, display the message
            // The Timer is Stopped, so the game will not continue
            if (currentPlayer == HUMAN_PLAYER_TURN)
            {
                MessageBox.Show("Player Error: " + stateMachineException.Message, "HUMAN ERROR!",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else if (currentPlayer == AI_PLAYER_TURN)
            {
                MessageBox.Show("Player Error: " + stateMachineException.Message, "AI ERROR!",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else
            {
                MessageBox.Show("Machine Error: " + stateMachineException.Message, "Machine ERROR!",
                                MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }



    private void State_Machine()
    {
        // This routine is executing in the Timer.Elapsed Event's Thread, not the Main Form's Thread
        switch (currentPlayer)
        {
            case HUMAN_PLAYER_TURN:
                Play_Human();
                break;

            case AI_PLAYER_TURN:
                Play_AI();
                break;

            default:
                Play_Begin();
                break;
        }
    }


    private void Play_Human()
    {
        // This routine is executing in the Timer.Elapsed Event's Thread, not the Main Form's Thread
        // My Turn!
        if (!waitForClick)
        {
            // Please Wait until I take a card...
            // I am using this.Invoke here because I am not in the same thread as the main form GUI
            // If we do not wrap the code that accesses the GUI, we may get threading errors.
            this.Invoke((MethodInvoker)delegate
            {
                pictureBox1.Click += PictureBox1_Click;
            });

            // set this flag so we do not re-enable the click event until we are ready next time
            waitForClick = true;
        }
    }


    private void PictureBox1_Click(object sender, EventArgs e)
    {
        // This routine is executing in the Main Form's Thread, not the Timer's Thread

        // Stop the game for a little bit so we can process the Human's turn
        machineTimer.Stop();

        // Disable the Click Event, we don't need it until next time
        pictureBox1.Click -= PictureBox1_Click;
        waitForClick = false;

        // To Do:  Human's Turn code...

        // Let the AI Play now
        currentPlayer = AI_PLAYER_TURN;
        machineTimer.Start();
    }


    private void Play_AI()
    {
        // This routine is executing in the Timer.Elapsed Event's Thread, not the Main Form's Thread
        if (AIcurrentIndex < AIcount)
        {
            // If we do not wrap the code that accesses the GUI, we may get threading errors.
            this.Invoke((MethodInvoker)delegate
            {
                // To Do:  AI Player's Turn code...
            });

            // Advance to the next AI
            AIcurrentIndex++;
        }
        else
        {
            // Reset to the beginning
            AIcurrentIndex = 0;
            currentPlayer = BEGIN_PLAY;
        }
    }


    private void Play_Begin()
    {
        // This routine is executing in the Timer.Elapsed Event's Thread, not the Main Form's Thread
        // If we do not wrap the code that accesses the GUI, we may get threading errors.
        this.Invoke((MethodInvoker)delegate
        {
            // ... do stuff to setup the game ...
        });

        // Now let the Human Play on the next Timer.Elapsed event
        currentPlayer = HUMAN_PLAYER_TURN;

        // After the Human is done, start with the first AI index
        AIcurrentIndex = 0;
    }

  }
}

i would have design the process in a different way based on events without loop, but following your way you should use an autoreset event to notify your loop myEvent have been fired. 我会根据没有循环的事件以不同的方式设计过程,但按照你的方式你应该使用autoreset事件来通知你的循环myEvent已被触发。

AutoResetEvent clickEventFired = new AutoResetEvent(false); // instanciate event with nonsignaled state
AutoResetEvent clickEventFired = new AutoResetEvent(true); // instanciate event with signaled state

clickEventFired.Reset(); // set state to nonsignaled
clickEventFired.Set();  // set state to signaled
clickEventFirect.WaitOne(); // wait state to be signaled

https://msdn.microsoft.com/en-us/library/system.threading.autoresetevent(v=vs.110).aspx https://msdn.microsoft.com/en-us/library/system.threading.autoresetevent(v=vs.110).aspx

public static void yourLoop()
{
    int leader = 0; // who is going to discard first
    int nextDiscarder = leader; // next player who's going to discard

    // instanciate auto reset event with signaled state
    AutoResetEvent clickEventFired = new AutoResetEvent(true);

    for (int i = 0; i < nPlayers; i++) // until all the players hasn't discarded
    {
        if (nextDiscarder == 0) // the human has to discard
        {
            enablePictureBoxClickEvent;
            clickEventFired.WaitOne(); // wait for event to be signaled
        }
        else
        {
            AI[nextDiscarder].discard(); // the ai player will discard
            clickEventFired.Reset(); // set event state to unsignaled
        }

        if (nextDiscarder == players - 1) // if nextDiscarder has reached the end of the table
            nextDiscarder = 0; // return to the begin until all player has discarded a card
        else
            ++nextDiscarder; // continue to discard with the next player
    }
}

private myEventClick(object sender, EventArgs e)
{
    .... // do the instructions needed to discard a card
    disableMyEventClick;
    clickEventFired.Set(); // signal event
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM