等待单击事件触发 C#

本文关键字:事件 单击 等待 | 更新日期: 2023-09-27 18:35:22

我正在开发一个纸牌游戏,但我需要一个函数来停止程序,直到玩家没有点击他的卡的图片框来丢弃它。我的游戏算法是这样的:

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
}

问题是,当一个 mance 结束时,第一个丢弃一张牌的人可能会改变。如果玩家被编号为 0(人类玩家)、1(第一个 AI 玩家)、2(第二个 AI 玩家)和 3(第三个 AI 玩家),则在第一次 mance 时,第一个丢弃一张牌的是人类玩家,但在第二次 mance 时,第一个丢弃的可能是 2 个 AI 玩家,人类玩家必须等到他之前的所有 AI 玩家丢弃一张牌(在这种情况下, 回合将是2-3-0-1)。

如果AI玩家还没有丢弃一张牌,如何取消点击事件?

更新

我并不总是需要等待所有AI玩家抽出一张牌:如果mance的获胜者是数字2,则回合将是2-3-0-1:这意味着玩家必须等待AI玩家2和3抽出,然后玩家必须单击一个PictureBox,循环将返回AI玩家,然后AI玩家1被允许丢弃其牌。

更新 2

我曾这样想过:

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
}

在我的事件点击中,我会做这样的事情:

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

但主要问题是我不知道如何在代码中编写我的指令returnToLoop.

等待单击事件触发 C#

我知道大多数人会争辩说你应该使用事件驱动的方法,但async/await功能可用于轻松实现这样的事情,而无需实现手动状态机。

我已经在 Force 循环中发布了类似的方法来等待事件和实现 WaitForMouseUp() 函数的更好方法?,所以基本上这与前者的帮助程序相同,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;
    }
}

现在您所需要的只是将您的方法标记为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
}
// ...
我喜欢

Ivan 解决方案,因为它看起来不错,并且可以在您需要等待控件的其他任何地方轻松重用。

但是,我想提供另一种解决方案,因为我觉得这样做的方式比它可能要复杂得多。

所以让我们恢复这个:

  • 在游戏的某个时刻,你需要玩家选择一张他们不想扔掉的牌。
  • 有一个人类玩家,在你的数组中排名第 0 位
  • 人类玩家并不总是第一个决定扔掉哪张牌的人。
  • 要决定扔掉哪张牌,您需要向玩家显示一个图片框,然后等待他单击它。

我相信一个简单的解决方案可能是:

  1. 你首先在人类之前为AI玩家移除卡片(如果人类是第一个丢弃的,这将什么也做不了,如果人类是最后一个,所有AI都会在这里丢弃)
  2. 启用图片框并结束函数
  3. 在 PictureBox 的点击事件中,你删除用户卡,然后你删除了人类之后的剩余 AI 玩家的卡(如果人类是第一个,所有 AI 都会在这里删除一张卡,如果人类是最后一个,你什么都不做)

做。。。

所以这看起来像这样:

//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();
    }
}

你差不多完成了...

注意:对不起,如果语法不好或不寻常,我通常用VB .Net编码...随意编辑语法问题...

以下代码演示了一个简单的基于计时器的状态机。 在这种情况下,机器的状态是当前玩家回合。 此示例允许每个 Play 通过将状态设置为下一个玩家来决定何时让下一个玩家轮到她。 为程序应检查的其他内容添加其他状态。 此程序体系结构运行相对平稳,因为程序线程不会在紧密循环中阻塞。 每个玩家完成和退出回合的"速度"越好 - 即使玩家的回合重复 10000 次而不做任何事情,然后让下一个玩家玩。

在下面的示例中,Click 事件处理程序将机器状态从轮到人类前进到 AI 轮到。 这有效地暂停了游戏,直到人类点击。 由于转弯不会在紧密循环中被阻塞,因此您可以单击其他按钮供人类单击,例如"通过"、"重新开始"和"退出"。

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;
    }
  }
}

我会根据没有循环的事件以不同的方式设计该过程,但按照您的方式,您应该使用自动重置事件来通知循环 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

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
}