我正在开发纸牌游戏但是我需要有一个功能可以停止程序,直到玩家没有点击他的卡的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
。
答案 0 :(得分:22)
我知道大多数人会争辩说你应该使用事件驱动的方法,但是async/await
功能可以用来轻松实现这样的事情,而无需实现手动状态机。
我已经在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 :(得分:2)
我喜欢Ivan解决方案,因为它看起来不错,并且可以在任何其他需要等待控制的地方轻松重复使用。
但是,我想提供另一种解决方案,因为我觉得这样做的方式要复杂得多。
所以让我们继续这个:
我相信一个简单的解决方案可能是:
完成...
所以这看起来像这样:
//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
}