Tic Tac Toe/Leaf Objects
LSL Portal | Functions | Events | Types | Operators | Constants | Flow Control | Script Library | Categorized Library | Tutorials |
It all begins with a cube...
I'm basically a bottom up guy, so I tend to start with the user interface elements first. Of course you need a general idea of what your project is gonna look like when it's done, but it is not worthwhile to spend too much time beyond the general idea. The logic of the project will guide you along, and investing too much time on the grand plan will make you reluctant to change your mind if the evolution of the project seems to indicate a problem.
So, for tic tac toe, it seems like a 3x3 array of cubes mounted on a backboard is a good general idea, so let's start with the cube.
Obviously, we want to be able to display three states: empty, X and O. We could use three textures, but that would be quite wasteful - besides costing L$30, they would all rez separately and make the game slow at the beginning until all the textures are cached.
A much better technique is to use a single texture, displaying only a small part of it at a time using llOffsetTexture. Not only do we save L$20, but the whole texture rezzes once and we only have to go through the fuzzies once.
This is an approximate look of the texture I used:
+--------------------+ | @@ @@ @@@@@ | | @@ @@ @@ @@ | | @@@ @@ @@ | | @@ @@ @@ @@ | | @@ @@ @@@@@ | | | | | | | | | | | +--------------------+
In this example, I chose to use states to represent the three possible - well - states of the cube. The question on when to use states and when to represent state in data can be difficult, but I found the following guidelines to work pretty well:
- Use states when the event-processing in each state differs significantly and there is not much data to be shared among the states.
- Use data to store the state when event processing remains essentially the same or you need to share data between states anyway.
The reason why event processing matters is because every state gets to have its own event handlers. This is both good and bad. It means you can efficiently ignore events irrelevant to the state by simply not providing a handler, but it also means that if you do provide a handler, you need to do all the setup work for it - hence the recommendations above.
In practice, the use cases for states are mostly two:
- "leaf" objects with no dependents, so no worries about other objects having to know about your state
- "boot" sequences where you expect to end up in a final, semi-permanent single state.
In our case here, we fall squarely into the first, and since we are definitively a leaf object and we do wish to modify our event processing behaviour depending on the state, states are the way to go.
And here's the code going into the cube:
// Cached texture positions
float e_pos_s = 0.75;
float e_pos_t = 0.75;
float x_pos_s = 0.75;
float x_pos_t = 0.25;
float o_pos_s = 0.25;
float o_pos_t = 0.25;
integer display_face = 4;
default
{
state_entry()
{
llOffsetTexture(e_pos_s, e_pos_t, display_face);
}
touch_start(integer total_number)
{
state x;
}
}
state x
{
state_entry()
{
llOffsetTexture(x_pos_s, x_pos_t, display_face);
}
touch_start(integer total_number)
{
state o;
}
}
state o
{
state_entry()
{
llOffsetTexture(o_pos_s, o_pos_t, display_face);
}
touch_start(integer total_number)
{
state default;
}
}
At this point we're just playing around, and the code doesn't do anything immediately useful, it just cycles through the states on touch.