views:

445

answers:

4

Hi, I'm trying to create a very simple version of the game Simon with the WiiMote, using WPF. What I'm stuck on is how to make it turned-based, where the program blocks until the GUI is done displaying a sequence.

Here's the code I have so far (mostly based on an answer here: http://stackoverflow.com/questions/1511870/wpf-sequential-animation-simple-example):

public partial class Window1 : Window
{

    public enum SimonSquare { BLUE = 1, GREEN = 3, RED = 5, YELLOW = 7 };

    List<int> _correctSequence;
    int _currentLevel = 1;
    Random random = new Random();
    Wiimote _wiiMote;
    List<int> _squaresEntered;
    private IEnumerator<Action> _actions;
    Rectangle blueRect;
    Rectangle redRect;
    Rectangle greenRect;
    Rectangle yellowRect;

    AutoResetEvent autoEvent;

    public Window1()
    { 
        InitializeComponent(); 
        blueRect = new Rectangle() { Fill = 
            System.Windows.Media.Brushes.Blue, Name = "Blue"};
        redRect = new Rectangle() { Fill = 
            System.Windows.Media.Brushes.Red, Name = "Red" }; 
        greenRect = new Rectangle() { Fill = 
            System.Windows.Media.Brushes.Green, Name = "Green" };
        yellowRect = new Rectangle() { Fill = 
            System.Windows.Media.Brushes.Yellow, Name = "Yellow" };

        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        UniformGrid1.Children.Add(blueRect);
        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        UniformGrid1.Children.Add(redRect);
        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        UniformGrid1.Children.Add(greenRect);
        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        UniformGrid1.Children.Add(yellowRect);
        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        //connectWiiRemote();

    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        _actions = AnimationSequence().GetEnumerator();
        autoEvent = new AutoResetEvent(false);
        Thread thread = new Thread(RunNextAction);
        thread.Start();
        autoEvent.WaitOne(); // need to block here somehow!
        int x = 5;
    }   

    IEnumerable<Action> AnimationSequence() 
    {
        getSequence();
        foreach(int square in _correctSequence)
        {
            if(square == (int) SimonSquare.BLUE)
                yield return () => animateCell(blueRect, Colors.Blue); 
            else if(square == (int) SimonSquare.RED)
                yield return () => animateCell(redRect, Colors.Red);
            else if (square == (int)SimonSquare.GREEN)
                yield return () => animateCell(greenRect, Colors.Green);
            else if (square == (int)SimonSquare.YELLOW)
                yield return () => animateCell(yellowRect, Colors.Yellow);
        }
    }

    private void animateCell(Rectangle rectangle, Color fromColor)
    {
        this.Dispatcher.BeginInvoke(new Action(delegate
        {
            Color toColor = Colors.White;
            ColorAnimation ani = new ColorAnimation(toColor, 
                new Duration(TimeSpan.FromMilliseconds(300)));
            ani.AutoReverse = true;
            SolidColorBrush newBrush = new SolidColorBrush(fromColor);
            ani.BeginTime = TimeSpan.FromSeconds(2);
            rectangle.Fill = newBrush;
            ani.Completed += (s, e) => RunNextAction();
            newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);

        }));
    }

    private void RunNextAction()
    {
        if (_actions.MoveNext())
            _actions.Current();
        else
        {
            autoEvent.Set();
            _currentLevel++;
        }
    }

    private void getSequence()
    {
        _correctSequence = new List<int>();
        int[] values = 
            Enum.GetValues(typeof(SimonSquare)).Cast<int>().ToArray();
        for (int i = 0; i < _currentLevel + 2; i++)
        {
            _correctSequence.Add(values[random.Next(values.Length)]);
        }

    }
}

However, autoSet's waitOne/set aren't working correctly. It currently calls RunNextAction once, but then blocks on waitOne indefinitely. What am I doing wrong?

EDIT: Let me try to rephrase the question. If I take out the Threading and AutoResetEvent, in Window_Loaded I have:

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        _actions = AnimationSequence().GetEnumerator();
        RunNextAction(); // shows all of the flashing squares
        // need to wait here until the flashing squares are all shown
        // process player's events etc.
    }

When I run the above code, it'll call RunNextAction once, which will keep calling itself until all of the squares are shown (it seems like on its own thread), BUT the WindowLoaded method keeps going. After I call RunNextAction(), I need Window_Loaded to block until RunNextAction is completely done.

A: 

I may be misunderstanding your question, because this seems very simple. In your Window_Loaded event, delete everything after thread.Start();. Add a class-level variable named _ImDoneSimonizing, and initialize it to false. For any methods that receive user input, wrap this around the code:

if (_ImDoneSimonizing)
{
    // do whatever
}

When the animation is done, set _ImDoneSimonizing to true.

Two other points:

  1. Very cool that you're bringing back Simon. Best game since Twister.
  2. You can create games for the Wii with WPF? You have rocked my world, sir.
MusiGenesis
It's not for the Wii per se, it's actually a student project. A WiiMote can talk with a Bluetooth dongle, and there's a pretty good C# library for processing WiiMote events here: http://blogs.msdn.com/coding4fun/archive/2007/03/14/1879033.aspx. A researcher at Carnegie Mellon, Johnny Lee, has come up with some pretty ingenious ways of extending the uses of the WiiMote, and has some pretty neat videos at: http://johnnylee.net/projects/wii.
David Hodgson
@David: I lied anyway - Simon sucked. :)
MusiGenesis
@David: I've seen some of Johnny Lee's projects for the WiiMote on youtube and they look really cool. Good Luck!
Lucas McCoy
A: 

The AutoResetEvent is doing what it was designed to do, block thread execution until Set() is called. This probably just means you aren't calling Set(). Maybe your iterator isn't working the way you expect (although it looks ok without testing).

Are you calling Window_Loaded somewhere else?

scottm
No, Window_Loaded should only be called once. Maybe the problem is with using Dispatcher to call RunNextAction?
David Hodgson
+1  A: 

You should not call WaitOne on Dispatcher Thread !!

You are calling WaitOne on the Dispatcher Thread itself, the dispatcher thread is the main thread of WPF APP, if you block it, any calls to Dispatcher.BeginInvoke or Invoke will never get called and it will wait indefinately.

Instead the better way to do would be, Move your animation to another window called "AnimationDialog" and load it as Modal Dialog.

private void window_loaded(object sender, EventArgs e){

    AnimationDialog dialog = new AnimationDialog();
    dialog.Owner = this;
    dialog.ShowDialog(); // this will wait here 

}

In AnimationDialog Window...

private void Window_Loaded(object sender, EventArgs e){
    StartAnimationThread();
    // dont do any wait or block here at all...
}

// in your end of animation call "EndAnimation()"

private void EndAnimation(){
    Dispatcher.BeginInvoke()((Action)delegate(){
        this.DialogResult = true; 
        // this will stop this dialog and
        // and execute the parent window's
        // code where showdialog was called... 
    }
    )
}
Akash Kava
A: 

You might be able to solve this by pumping the windows message queue. Basically, if you post a callback to the message queue, then the calling thread will block until rendering is complete. Here's an article explaining how to do it: http://graemehill.ca/wpf-rendering-thread-synchronization

Graeme Hill