OpenLightGroup Blog

rss

Blogs from OpenLightGroup.net


ooNaLife – Game of Life Implementation

See the live example here:

http://www.adefwebserver.com/Richard/ooNaLifeSite/

NOTE: I just noticed the column of radio buttons that starts with ‘Single Cell’ doesn’t indicate what it’s for.  It determines what will appear on the simulation when you click the left mouse button. ‘Single Cell’ will toggle a single cell on or off. All the others will turn on the cells to ‘draw’ the selected pattern to the right of the mouse click, or to the left if you’re in mirror mode.

After Seeker 2, I wanted to add a control panel, but I wasn’t clear how to tack one on. So far I’ve just slapped everything up onto the main UI, LayoutRoot, so I decided to write a new app and divide everything up from the start. I wanted to stick with the virtual world concept, so I went with James Conway’s ‘Game of Life’.  You probably know the rules of the game, but if you don’t, there are zillions of descriptions out there http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life for instance, so I’m not going to try and re-word one here.

image image

I actually ended up getting more out of writing it than I expected as I experimented with different ways of counting each cell’s neighbors. Something about Blend, especially in combination with Visual Studio, gives you a heightened sense of how things are fitting together, giving you more insight on how to improve and extend them.

The point was to add a control panel, so let me get that out of the way. You may want to skip this part if you’re already familiar with Canvas and StackPanel controls. If not, then it’s an easy introduction into how to divide up a page.

The default UI in MainPage and that wraps around everything else is a Grid named LayoutRoot; I changed it to a Canvas, but it doesn’t really matter because it’s just a holder for the UserControl I wrote to encapsulate both the simulation and the control panel. It just seems cleaner to have the entire UI wrapped inside its own control.

Now I have to digress so you’ll understand the names I chose. I want my virtual world to have an identity, so I’m calling it ‘ooNa’. Nothing to do with OOP, I just like the name. It’s not necessarily a planet, it’s a plane of existence in the ooNaverse, created and completely under the control of the omnipotent and unseen ooBer.

Accordingly, I named my new UserControl ooNaverseControl. In ooNaverseControl I use a StackPanel I named ooNaverse to place a Canvas named ooNaLifeCanvas on top of a StackPanel named ooBerControlPanel.

The ooBerControlPanel StackPanel is oriented Horizontally and further divided into a series of columns by more StackPanels oriented vertically. Within those StackPanels are the individual controls. When those individual controls need to appear side-by-side, still another level of StackPanel is used to arrange them. Just look at ooNaverseControl.xaml and you’ll see it’s pretty easy.

A couple of things to take note of if you don’t already know. MainPage, which you get by default, inherits from UserControl, which is the same class you inherit from when you create a UserControl. So by default every UserControl, including MainPage will have LayoutRoot as the outermost UI.  Any controls you place in LayoutRoot or nest within other controls in LayoutRoot can be addressed by name from the UserControl code-behind without being qualified. But if you want to access them in the control collections where the references are stored, then you use the Children collection and qualify it, such as:

var lifeList = from child in this.ooNaLifeCanvas.Children
                               where child is ooNaLifeform
                               select child as ooNaLifeform;

If I had placed the ooNaLifeforms directly on LayoutRoot, then I would have addressed them in this.LayoutRoot.Children. That’s one reason I only like to use LayoutRoot as an outer wrapper within which I divide up the page. I don’t want to change the name, but I want to address meaningful names from the code-behind. Addressing LayoutRoot is like being on the holo-deck of the Enterprise with no simulation running.

ooNaLifeform

I wanted to stick with the idea of controls that act based on their own internal logic but still subject to intervention by a higher power. In this case, we’re dealing with the change of entire generations, which seems kind of an ooBer thing. What happens to each ooNaLifeform in the next generation depends on his neighbors, so it makes sense for each one to keep track of who his neighbors are.

When the time comes to decide who will live or who will die… that’s obviously an ooBer decision, and then the individual ooNaLifeforms will change their states on command from ooBer and the new generation will come to pass.

The first thing that needs to happen is for each ooNaLifeForm to know who his neighbors are. Therefore, each ooNaLifeform has a Neighbors property, which is a List of references to the 8 neighboring ooNaLifeforms. The following code is from inside a loop that steps through all the ooNaLifeforms setting the Neighbors property of each one.

List neighbors = new List();

(neighbors List is populated)

ooNaLifeform currentLife = lifeList[(int)(nMidRowIndex + nCol)];
currentLife.Neighbors = neighbors;

Now when ooBer decides it’s time, each ooNaLifeform will be ready to be evaluated. When the time does come, ooBer will call the PrepareForNextGeneration method on each ooNaLifeForm. Probably an event is more logical, but I’m going to follow with another event, and I don’t want to worry about the first completing before the second starts.

In order that each ooNaLifeform be able to respond to this call, ooBer has provided each a way to offer themselves up for judgment and determination whether they will live in the next generation. Since I like for each ooNaLifeform to be as self-contained as possible, I give each one a NextGenerationFate property, which is a reference to an IooNaFate.myFate() method. Note that’s a reference to an interface, meaning it’s backed up by an object that implements IooNaFate. I can come up with alternate rule sets and swap them out. As a matter of fact, ooNaLife supports the traditional Conway version of life and another version called ‘HighLife’ by creating objects of two different classes that implement the IooNaFate interface. Giving each ooNaLifeForm its own reference to IooNaFate opens up the possibility of giving different fates (rule sets) to individual ooNaLifeForm based on the quadrant of the matrix they were on, for instance.

I’m excited about applying the combination of a list of ‘neighbors’ combined with a flexible way of attaching rule sets to The Seeker. Neighbors can be extended to mean anything you want, it could be ooNaThings, for instance, from which all things ooNa would inherit. Then it would be up to whatever rule set was plugged in, designed and assigned by ooBer of course, to evaluate everything and decide the consequences.

Here’s how easy it is to use interfaces

Define what the class must support to implement the interface. In this case, it’s one method

public interface IooNaFate
   {
       ooNaLifeform.LifeState myFate(ooNaLifeform currentIncarnation);
   }

Define one or more classes that inherit from and implement the interface, such as (these are just the declarations):

public sealed class ooNaFateConwayLife : IooNaFate

public sealed class ooNaFateB36S23 : IooNaFate

Provide a property to be used as a reference to an implementation of the interface:

private IooNaFate _nextGenerationFate;
public IooNaFate NextGenerationFate
{
    get { return _nextGenerationFate; }
    set { _nextGenerationFate = value; }
}

Set the property:

private void ReloadFates()
{
    IooNaFate iFate = null;
    if (radConwayLife.IsChecked.Value)
        iFate = _ooNaLifeFate;
    else
        iFate = _ooNaB36S23Fate;

    var lifeList = from child in this.ooNaLifeCanvas.Children
                   where child is ooNaLifeform
                   select child as ooNaLifeform;

    foreach (ooNaLifeform life in lifeList)
        life.NextGenerationFate = iFate;

}


Make the call through the interface reference

public void PrepareForNextGeneration()
{
    if (_nextGenerationFate != null)
        NextLifeState = _nextGenerationFate.myFate(this);
}

One last note about interfaces. You can inherit from as many interfaces as you want. So let’s say you are MainForm, and you don’t want to implement a class to support IooNaFate. You could just inherit directly from IooNaFate:

public partial class MainPage : UserControl, IooNaFate

implement the myFate() method, then in ReloadFates you could say

life.NextGenerationFate = this;

Model-View-ViewModel

I like my idea of having each object be as self-sufficient as possible and in large part just wander around reacting with other objects. And now I have a pretty solid model of how to easily program their behavior. But this is about Silverlight and it’s time to get back to more Silverlight oriented themes. So after I post the next version of The Seeker, I’m going to start looking at the Model_View-ViewModel pattern and other things Silverlight such as behaviors, dependency properties, and triggers and figuring out how to apply them to ooNa.

Download the code here:

http://www.adefwebserver.com/Richard/ooNaverse.zip





Comments are closed.