2016-02-19 16 views
11

Zajmuję się tworzeniem gier karcianych, ale muszę mieć funkcję, która zatrzymuje program, dopóki gracz nie kliknie w PictureBox swojej karty, aby ją odrzucić. Algorytm mojej grze jest taka:Poczekaj, aż zostanie uruchomione zdarzenie kliknięcia 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 
} 

Problem polega na tym, że gdy kończy Mance, pierwszy, który będzie odrzucić karty może się zmienić. Jeśli gracze są liczeni z 0 (gracz ludzki), 1 (pierwszy gracz AI), 2 (drugi gracz AI) i 3 (trzeci gracz AI), przy pierwszej mance pierwszym, który odrzucił kartę, jest gracz ludzki, ale przy Druga mania, którą jako pierwszy odrzuci, może być graczem 2 AI, a gracz ludzki musi poczekać, aż wszyscy gracze AI przed nim odłożą kartę (w tym przypadku runda wynosiłaby 2-3-0-1).

Jak mogę anulować zdarzenie click, jeśli gracze AI nie wyrzucili jeszcze karty?

UPDATE

Nie zawsze trzeba czekać tak wszystkie gracze AI miał drawed kartę: jeśli zwycięzca Mance jest numerem 2, runda byłaby 2-3-0 -1: oznacza to, że gracz musi czekać na losowanie 2 i 3 graczy AI, a następnie gracz musi kliknąć jeden PictureBox, a pętla wróci do graczy AI, a następnie gracz AI 1 może odrzucić swoją kartę.

UPDATE 2

Myślałem coś takiego:

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 
} 

iw moim przypadku kliknij zrobiłbym coś takiego:

private myEventClick(object sender, EventArgs e) 
{ 
    .... // do the instructions needed to discard a card 
    disableMyEventClick; 
    returnToLoop; 
} 

ale głównym Problem polega na tym, że nie wiem jak napisać w kodzie moją instrukcję returnToLoop.

+1

Czy próbowałeś ustawić flagę 'bool', by ci pomóc? Przykład: Ustaw flagę jako fałszywą, gdy człowiek gra, a następnie ustaw ją ponownie na true, tylko jeśli 3 kolejne odrzuciły karty (prawdopodobnie za pomocą licznika). Następnie upewnij się, że człowiek może odrzucić swoją kartę tylko wtedy, gdy flaga jest prawdziwa.Mam nadzieję że to pomogło! – andeart

+0

Próbowałem tego? https://msdn.microsoft.com/en-us/library/system.componentmodel.canceleventargs.cancel(v=vs.110).aspx – Zuzlx

+0

Myślę, że "returnToLoop" nie jest potrzebny, ponieważ wydarzenie nie zakończyło pętli, więc powrót do pętli jest automatyczny, ponieważ zdarzenie nie usunęło Cię z wątku. – gridtrak

Odpowiedz

1

Poniższy kod przedstawia prostą maszynę stanów z timerem. W tym przypadku stan maszyny to aktualna tura gracza. Ten przykład pozwala każdemu graczowi zdecydować, kiedy pozwolić kolejnemu graczowi na turę, ustawiając stan dla następnego gracza. Dodaj dodatkowe stany dla innych rzeczy, które program powinien sprawdzić. Ta architektura programu działa stosunkowo płynnie, ponieważ wątki programu nie są blokowane w ciasnych pętlach. "Szybszy" każdy gracz może ukończyć i zejść z zakrętu, tym lepiej - nawet jeśli tura gracza powtarza się 10000 razy, nie robiąc nic, zanim pozwoliłby następnemu graczowi na grę.

W poniższym przykładzie program obsługi zdarzeń "Postęp" przesuwa stan maszyny z toru Człowieka na ruch AI. To skutecznie zatrzymuje grę aż do ludzkich kliknięć. Ponieważ Zakręt nie jest zablokowany w ciasnej pętli, możesz mieć inne guziki, na które człowiek może kliknąć "Przełęcz", "Rozpocznij za daleko" i "Wyjdź".

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

Wiem, że większość ludzi będzie twierdzić, że trzeba zastosować podejście zdarzeniami, ale async/await funkcja może być łatwo wykorzystane do realizacji takich rzeczy w/o konieczności wykonania ręcznie maszyny państwowe.

już pisał podobnego podejścia w Force loop to wait for an event i A Better Way to Implement a WaitForMouseUp() Function?, więc w zasadzie jest to ten sam pomocnik jak w pierwszym z Button zastąpione 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; 
    } 
} 

Teraz wszystko, co potrzebne jest, aby zaznaczyć swoją metodę jako async i używać 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. Aby uzyskać więcej informacji, ten sam dokładny pomysł został opisany w [Tip 3: Wrap events up w API zwracających zadania i oczekuj na nie] (https://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03 -TipsForAsyncThreadsAndDatabinding) wideo Luciana Wischika w channel9.msdn. – YuvShap

1

bym zaprojektować proces w inny sposób, w oparciu o zdarzenia, bez pętli, ale po swoje wa y powinieneś użyć zdarzenia autoreset, aby powiadomić twoją pętlę 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

Problem może polegać na tym, że jeśli wchodzisz w 'yourLoop' z wątkiem UI, będziesz' WaitOne() 'i tym samym zablokował wątek UI, aż użytkownik kliknie, co spowoduje zakleszczenie ... O ile WaitOne() nie będzie zablokować wątek UI, ale nie wiem, jak to jest możliwe ... –

+0

Pytam o to, ponieważ w [docs] (https://msdn.microsoft.com/en-us/library/58195swd (v = vs.110) .aspx) jest określony: "Blokuje bieżący wątek, aż bieżący komunikat WaitHandle otrzyma sygnał." i nigdy nie mówią o tym, że jest w pełni asynchroniczne. –

+0

to nie jest tak naprawdę asynchroniczne, ale czuje to samo, waitHandle czekać na bieżący wątek bez blokowania innych jak wątek UI. – freakydinde

2

Lubię rozwiązanie Ivan, bo to wygląda dobrze, i jest wielokrotnego użytku łatwo nigdzie indziej trzeba czekać na kontrolę.

Jednak chciałem zapewnić inne rozwiązanie, ponieważ czuję, że sposób, w jaki to robię, jest o wiele bardziej skomplikowany niż mógłby być.

Warto więc wznowić to:

  • W pewnym momencie w grze, trzeba graczy, aby wybrać kartę oni nie chcą go wyrzucić
  • Jest jeden ludzki gracz, który jest numerem 0 w twojej tablicy
  • Gracz ludzki nie zawsze pierwszy decyduje, którą kartę wyrzucić.
  • Aby zdecydować, którą kartę wyrzucić, wyświetlasz pole graficzne graczowi i czekasz, aż go kliknie.

wierzę prostym rozwiązaniem mogłoby być:

  1. zacząć od wyjmowania karty dla graczy AI przed ludzką (jeśli człowiek jest najpierw odrzucić, to nic nie robić, jeśli człowiek jest ostatni , wszystkie AI odrzuci tutaj)
  2. Włączasz PictureBox i kończysz swoją funkcję
  3. W zdarzeniu kliknięcia PictureBox, usuwasz kartę użytkownika, następnie usuwasz kartę dla pozostałych graczy AI, którzy są po człowiek (jeśli człowiek jest pierwszy, cała sztuczna inteligencja usunie tu kartę, jeśli człowiek jest ostatni, ty O nic)

Sporządzono ...

Więc będzie to wyglądać tak:

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

I jesteś dość dużo zrobić ...

NB: Przepraszam, jeśli składnia jest zły lub nietypowy, zazwyczaj koduję w VB .Net ... Możesz edytować problemy z składnią ...

Powiązane problemy