2016-02-19 76 views
11

我正在开发一款纸牌游戏,但我需要一个功能来停止该程序,直到玩家没有点击他的卡片的图片框来丢弃它。 我游戏的算法是这样的:等到点击事件被解雇C#

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 
} 

的问题是,当一个曼斯结束,第一谁将会弃一张牌可能会改变。如果玩家以0(人类玩家),1(第一人工智能玩家),2(第二人工智能玩家)和3(第三人工智能玩家)的数值计算,则第一个弃牌的人是玩家,但是第二个是第一个丢弃的可能是2 AI玩家,而人类玩家必须等到所有在他之前丢弃一张牌的AI玩家(在这种情况下,这个回合将是2-3-0-1)。

如果AI玩家还没有丢弃卡,我该如何取消点击事件?

UPDATE

我并不总是需要等待的是所有 AI球员们drawed一张牌:如果曼斯的赢家是数字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

+1

你有没有试过设置'布尔'标志来帮助你?例如:当人类玩过时,将一个标志设置为假,然后只有当另外3个有丢弃的卡时(也许使用计数器)才将其设置为真。然后确保当标志为真时,人只能丢弃他们的卡。我希望这有助于! – andeart

+0

试过吗? https://msdn.microsoft.com/en-us/library/system.componentmodel.canceleventargs.cancel(v=vs.110).aspx – Zuzlx

+0

我认为“returnToLoop”是不需要的,因为事件没有终止循环,所以返回到循环是自动的,因为事件并没有带你离开线程。 – gridtrak

回答

1

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

在下面的例子中,Click事件处理程序将机器状态从人类轮到AI轮到。这有效地暂停了游戏,直到人类点击。由于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; 
    } 

    } 
} 
22

我知道大多数人会说,你应该使用事件驱动方式,但async/await功能可用于轻松实现这样的事情W/O需要手动执行状态机。

我已经张贴类似的方法在Force loop to wait for an eventA Better Way to Implement a WaitForMouseUp() Function?,所以基本上这是相同的助手在前者与ButtonControl取代:

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

+1。有关更多信息,请参阅[提示3:将事件封装在任务返回API中并等待它们]中描述的相同确切想法(https://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03 -TipsForAsyncThreadsAndDatabinding)视频由Lucian Wischik在channel9.msdn中完成。 – YuvShap

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 
} 
+0

问题可能是,如果您正在使用UI线程进入'yourLoop',您将会'WaitOne()',并因此阻塞UI线程,直到用户单击,这将导致死锁...除非WaitOne()不阻止UI线程,但我不知道这是怎么可能的... –

+0

我问这是因为在[文档](https://msdn.microsoft.com/en-us/library/58195swd(v = vs.110).aspx)声明:“阻塞当前线程,直到当前WaitHandle接收到一个信号。”他们从来没有说它是完全异步的。 –

+0

它不是真正的异步,但它感觉相同,waitHandle等待当前线程而不锁定其他像UI线程。 – freakydinde

2

我喜欢伊万的解决方案,因为它看起来不错,可重复使用很容易在其他地方,你需要等待控制。

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

因此,让我们继续这样的:

  • 在游戏中的某些时候,你需要玩家选择一张卡片,他们不想把它扔掉
  • 有一个人的球员,这是数0在你的阵列中
  • 人类玩家并不总是决定丢弃哪张牌的第一人。
  • 要决定将哪张卡扔掉,您需要向玩家展示一个picturebox,然后等待他点击它。

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

  1. 你被人之前取出卡的AI玩家(如果人是第一次放弃,这会做什么,如果人类是最后启动,所有AI将在此丢弃)
  2. 您启用PictureBox并结束您的功能
  3. 在PictureBox的单击事件中,您将删除用户卡,然后删除卡之后剩下的AI玩家人类(如果人类是第一个,所有人工智能将在这里删除一张卡片,如果人类是最后一个,你就是d Ø无)

完成...

所以这应该是这样的:

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

而你几乎完成了...

NB:很抱歉,如果语法是不好或不寻常的,我通常在VB.Net编码...随意编辑语法问题...