Подождите, пока не будет запущено событие click С#

Я разрабатываю карточную игру, но мне нужно иметь функцию, которая останавливает программу, пока игрок не щелкнет в 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).

Как я могу отменить событие click, если игроки AI еще не сбросили карту?

UPDATE

Мне не всегда нужно ждать, чтобы игроки all вытащили карту: если победитель mance - это номер 2, раунд будет 2-3-0-1: это означает, что игроку приходится ждать игроков ИИ 2 и 3, а затем игрок должен щелкнуть один PictureBox, и цикл вернется обратно к игрокам AI, а затем AI-плеер 1 может отбросить свою карту.

ОБНОВЛЕНИЕ 2

Я подумал что-то вроде этого:

int leader = 0; // who is going to discard first
int nextDiscarder = leader; // next player who 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

Я знаю, что большинство людей будут утверждать, что вы должны использовать подход, управляемый событиями, но функция async/await может использоваться для простой реализации таких вещей без необходимости внедрения машин с ручным управлением.

Я уже опубликовал аналогичный подход в Force loop для ожидания события и Лучший способ реализовать 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
}
// ...

Ответ 2

Мне нравится решение Ivan, потому что оно выглядит хорошо, и его можно многократно использовать в любом другом месте, где вам нужно ждать контроля.

Однако я хотел предоставить другое решение, потому что я чувствую, что это делается гораздо сложнее, чем это возможно.

Итак, давайте возобновим это:

  • В какой-то момент игры вам нужны игроки, чтобы выбрать карту, которую они не хотят выбросить.
  • В вашем массиве есть один человек, который является номером 0
  • Человек-игрок не всегда первым выбирает, какую карту выбросить.
  • Чтобы решить, какую карту нужно выбрасывать, вы показываете игровой ящик для изображения, и вы ждете его, чтобы щелкнуть по нему.

Я считаю, что простым решением может быть:

  • Сначала вы удаляете карту для игроков AI перед человеком (если человек сначала отбрасывает, это ничего не сделает, если человек последний, все AI будет отброшен здесь)
  • Вы активируете PictureBox и завершаете свою функцию.
  • В событии щелчка 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();
    }
}

И вы в значительной степени сделали...

NB: Извините, если синтаксис плохой или необычный, я обычно код в VB.Net... Не стесняйтесь редактировать проблемы синтаксиса...

Ответ 3

Следующий код демонстрирует простой конечный автомат на основе таймера. В этом случае состояние машины является текущим поворотом проигрывателя. Этот пример позволяет каждому игроку решить, когда дать игроку следующий поворот, установив состояние на следующего игрока. Добавьте дополнительные состояния для других вещей, которые программа должна проверить. Эта программная архитектура работает относительно гладко, потому что потоки программы не блокируются в узких циклах. "Быстрее" каждый игрок может завершить и выйти из поворота, тем лучше - даже если игрок будет повторять 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 Thread, not the Main Form 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 Thread, not the Main Form 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 Thread, not the Timer Thread

        // Stop the game for a little bit so we can process the Human turn
        machineTimer.Stop();

        // Disable the Click Event, we don't need it until next time
        pictureBox1.Click -= PictureBox1_Click;
        waitForClick = false;

        // To Do:  Human 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 Thread, not the Main Form 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 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 Thread, not the Main Form 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;
    }

  }
}

Ответ 4

Я хотел бы спроектировать процесс по-другому, основываясь на событиях без цикла, но после вашего пути вы должны использовать событие автосекретаря, чтобы уведомить ваш цикл. 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 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
}