等到点击事件被触发C#

时间:2016-02-19 20:48:54

标签: c#

我正在开发纸牌游戏但是我需要有一个功能可以停止程序,直到玩家没有点击他的卡的PictureBox来丢弃它。 我的游戏算法是:

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玩家),第一个丢弃牌的人是人类玩家,但是第一个丢弃的第二个可能是2个AI玩家,而且玩家必须等到他之前的所有AI玩家丢弃一张牌(在这种情况下,该轮将是2-3-0-1)。

如果人工智能播放器还没有丢弃卡片,如何取消点击事件呢?

更新

我并不总是需要等待那些所有 AI玩家都拿到了一张牌:如果该击球手的获胜者是2号,则该轮将是2-3-0- 1:这意味着玩家必须等待AI玩家2和3抽出,然后玩家必须点击一个PictureBox,然后循环将返回AI玩家,然后AI玩家1被允许丢弃其卡。 / p>

更新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

4 个答案:

答案 0 :(得分:22)

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

我已经在Force loop to wait for an eventA Better Way to Implement a WaitForMouseUp() Function?中发布了类似的方法,所以基本上这与前者相同,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
}
// ...

答案 1 :(得分:2)

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

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

所以让我们继续这个:

  • 在游戏中的某个时刻,你需要玩家选择一张他们不想扔掉它的牌
  • 有一个人类玩家,数组中的数字为
  • 人类玩家并不总是第一个决定扔掉哪张牌。
  • 要决定丢弃哪张卡,您会向播放器显示一个图片框,等待他点击它。

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

  1. 你首先在人类之前移除AI玩家的牌(如果人类首先丢弃,这将无能为力,如果人类是最后一个,所有人工智能将在这里丢弃)
  2. 启用PictureBox并结束功能
  3. 在PictureBox的点击事件中,你移除了用户卡,然后你移除了人类之后的剩余AI玩家的牌(如果人类是第一个,所有AI将在这里移除一张牌,如果人类是最后一个,你什么都不做)
  4. 完成...

    所以这看起来像这样:

    //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 ...随意修改语法问题......

答案 2 :(得分:1)

以下代码演示了一个基于计时器的简单状态机。在这种情况下,机器的状态是当前玩家的转弯状态。这个例子让每个Play通过将状态设置为下一个玩家来决定何时让下一个玩家轮到她。为程序应检查的其他内容添加其他状态。此程序体系结构运行相对平稳,因为程序线程不会在紧密循环中被阻塞。 &#34;更快&#34;每个玩家都可以完成并退出转弯,越好 - 即使玩家在没有做任何事情之前重复10000次而不让下一个玩家玩。

在下面的示例中,Click事件处理程序将机器状态从人类转向AI转向。这有效地暂停了游戏直到人类点击。由于Turn不会在紧密循环中被阻挡,因此您可以使用其他按钮让人类点击,例如&#34; Pass&#34;,&#34;重新开始&#34;和&#34;退出&#34 ;

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

  }
}

答案 3 :(得分:1)

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

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
}