WedX - журнал о программировании и компьютерных науках

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

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

Проблема в том, что когда манс заканчивается, может измениться тот, кто сбросит карту первым. Если игроки пронумерованы 0 (игрок-человек), 1 (первый ИИ-игрок), 2 (второй ИИ-игрок) и 3 (третий ИИ-игрок), при первом шаге первым сбрасывает карту игрок-человек, но при вторым человеком, который первым сбрасывается, может быть 2 ИИ-игрока, а игрок-человек должен ждать, пока все ИИ-игроки перед ним сбросят карту (в этом случае раунд будет 2-3-0-1).

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

ОБНОВЛЕНИЕ

Мне не всегда нужно ждать, пока все ИИ-игроки вытянут карты: если победителем манса является номер 2, раунд будет 2-3-0-1: это означает, что Игрок должен дождаться, когда ИИ-игроки 2 и 3 вытянут карты, затем игрок должен щелкнуть один PictureBox, и цикл вернется обратно к ИИ-игрокам, а затем ИИ-игроку 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.

c#
19.02.2016

  • Вы пытались установить bool флаги, чтобы помочь вам? Пример: Установите флаг в false, когда человек сыграл, а затем установите его обратно в true, только если еще 3 сбросили карты (возможно, с помощью счетчика). Затем убедитесь, что человек может сбросить свою карту только тогда, когда флаг установлен. Надеюсь, это поможет! 19.02.2016
  • Пробовали это? msdn.microsoft.com /en-us/библиотека/ 20.02.2016
  • Я думаю, что returnToLoop не нужен, потому что событие не завершило цикл, поэтому возврат в цикл происходит автоматически, так как событие не вывело вас из потока. 20.02.2016
  • Я думаю, что вы можете быть недовольны, когда вы блокируете поток цикла в // теперь, прежде чем цикл продолжится, программа должна ждать точку в вашем коде. Ваш графический интерфейс зависнет! Я думаю, вам будет удобнее использовать конечный автомат вместо одного многопоточного цикла. 20.02.2016
  • Использование подхода, управляемого событиями, вместо цикла может быть более элегантным. 26.10.2016
  • Вы знаете, это, вероятно, не слишком чисто, но: действительно легко сделать, это иметь while(true) {//checks for flag change and breaks loop} Это, конечно, нарушит поток, так что все остальное в этом потоке также будет приостановлено. Другой вариант — рекурсия checkFlag(flag) { if(flag==false) checkFlag(); else //something} 29.10.2016

Ответы:


1

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

Я уже публиковал аналогичный подход в Force loop для ожидания события и лучший способ реализации 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
}
// ...
22.10.2016
  • +1. Для получения дополнительной информации та же самая идея описана в Совет 3. : Оборачивайте события в API-интерфейсы, возвращающие задачи, и ожидайте их, видео Люциана Вишика в channel9.msdn. 22.10.2016

  • 2

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

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

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

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

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

    1. Вы начинаете с удаления карты для игроков ИИ перед человеком (если человек сбрасывает первым, это ничего не даст, если человек последним, все ИИ сбросятся здесь)
    2. Вы включаете PictureBox и завершаете свою функцию
    3. В событии щелчка PictureBox вы удаляете карту пользователя, затем вы удаляете карту для оставшихся игроков ИИ, которые идут после человека (если человек на первом месте, все ИИ удалят карту здесь, если человек на последнем месте, вы делаете ничего такого)

    Сделанный...

    Так это будет выглядеть так:

    //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... Не стесняйтесь редактировать проблемы с синтаксисом...

    27.10.2016

    3

    Следующий код демонстрирует простой конечный автомат на основе таймера. В этом случае состоянием машины является текущий ход игрока. Этот пример позволяет каждому игроку Play решить, когда позволить следующему игроку сделать свой ход, установив состояние для следующего игрока. Добавьте дополнительные состояния для других вещей, которые программа должна проверять. Эта программная архитектура работает относительно гладко, потому что потоки программы не зациклены. Чем «быстрее» каждый игрок сможет завершить ход и выйти из него, тем лучше — даже если ход игрока повторяется 10000 раз без каких-либо действий, прежде чем позволить следующему игроку сыграть.

    В приведенном ниже примере обработчик события Click переводит состояние машины с хода человека на ход ИИ. Это эффективно приостанавливает игру до тех пор, пока Человек не щелкнет. Поскольку поворот не заблокирован в узком цикле, у вас могут быть другие кнопки, на которые человек может нажимать, например «Пропустить», «Начать заново» и «Выйти».

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

    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'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
    }
    
    27.10.2016
  • Проблема может заключаться в том, что если вы входите в yourLoop с потоком пользовательского интерфейса, вы будете WaitOne() и, следовательно, заблокируете поток пользовательского интерфейса до тех пор, пока пользователь не щелкнет, что вызовет взаимоблокировку... Если только WaitOne() не блокирует поток пользовательского интерфейса, но я не знаю, как это возможно... 27.10.2016
  • Я спрашиваю об этом, потому что в документах указано: блокирует текущий поток до тех пор, пока текущий WaitHandle не получит сигнал. и они никогда не говорят о полной асинхронности. 27.10.2016
  • на самом деле это не асинхронно, но похоже, что waitHandle ждет текущего потока, не блокируя другой, например поток пользовательского интерфейса. 27.10.2016
  • Вот что я сказал, если вы вводите метод yourLoop() с потоком пользовательского интерфейса, вы создаете тупик... 27.10.2016
  • чтобы запустить метод yourLoop с потоком пользовательского интерфейса, вам придется использовать диспетчер пользовательского интерфейса, зачем вам использовать его для чего-либо, кроме обновления пользовательского интерфейса? 27.10.2016
  • Новые материалы

    Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что это выглядит сложно…
    Просто начните и учитесь самостоятельно Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что он кажется мне сложным, и я бросил его. Это в основном инструмент..

    Лицензии с открытым исходным кодом: руководство для разработчиков и создателей
    В динамичном мире разработки программного обеспечения открытый исходный код стал мощной парадигмой, способствующей сотрудничеству, инновациям и прогрессу, движимому сообществом. В основе..

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..


    Для любых предложений по сайту: [email protected]