等到点击事件被解雇C#
我正在开发一款纸牌游戏,但我需要一个功能来停止该程序,直到玩家没有点击他的卡片的图片框来丢弃它。 我游戏的算法是这样的:等到点击事件被解雇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
。
以下代码演示了一个简单的基于计时器的状态机。在这种情况下,机器的状态是当前玩家的转向。这个例子让每个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;
}
}
}
我知道大多数人会说,你应该使用事件驱动方式,但async/await
功能可用于轻松实现这样的事情W/O需要手动执行状态机。
我已经张贴类似的方法在Force loop to wait for an event和A 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。有关更多信息,请参阅[提示3:将事件封装在任务返回API中并等待它们]中描述的相同确切想法(https://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03 -TipsForAsyncThreadsAndDatabinding)视频由Lucian Wischik在channel9.msdn中完成。 – YuvShap
我将不得不设计过程中基于无环路的事件以不同的方式,而是按照你娃你应该使用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
}
问题可能是,如果您正在使用UI线程进入'yourLoop',您将会'WaitOne()',并因此阻塞UI线程,直到用户单击,这将导致死锁...除非WaitOne()不阻止UI线程,但我不知道这是怎么可能的... –
我问这是因为在[文档](https://msdn.microsoft.com/en-us/library/58195swd(v = vs.110).aspx)声明:“阻塞当前线程,直到当前WaitHandle接收到一个信号。”他们从来没有说它是完全异步的。 –
它不是真正的异步,但它感觉相同,waitHandle等待当前线程而不锁定其他像UI线程。 – freakydinde
我喜欢伊万的解决方案,因为它看起来不错,可重复使用很容易在其他地方,你需要等待控制。
但是,我想提供另一种解决方案,因为我觉得这样做的方式要复杂得多。
因此,让我们继续这样的:
- 在游戏中的某些时候,你需要玩家选择一张卡片,他们不想把它扔掉
- 有一个人的球员,这是数0在你的阵列中
- 人类玩家并不总是决定丢弃哪张牌的第一人。
- 要决定将哪张卡扔掉,您需要向玩家展示一个picturebox,然后等待他点击它。
我相信一个简单的解决方案可能是:
- 你被人之前取出卡的AI玩家(如果人是第一次放弃,这会做什么,如果人类是最后启动,所有AI将在此丢弃)
- 您启用PictureBox并结束您的功能
- 在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编码...随意编辑语法问题...
你有没有试过设置'布尔'标志来帮助你?例如:当人类玩过时,将一个标志设置为假,然后只有当另外3个有丢弃的卡时(也许使用计数器)才将其设置为真。然后确保当标志为真时,人只能丢弃他们的卡。我希望这有助于! – andeart
试过吗? https://msdn.microsoft.com/en-us/library/system.componentmodel.canceleventargs.cancel(v=vs.110).aspx – Zuzlx
我认为“returnToLoop”是不需要的,因为事件没有终止循环,所以返回到循环是自动的,因为事件并没有带你离开线程。 – gridtrak