Tic Tac Toe/Summary

The object we are scripting is composed of 9 cubes with an X/O texture pre-set and a back pane prim as the root prim. The link order of the child prims is assumed to be in any sequential reading order (left to right, top to bottom or similar).
== Root Prim ==
The root prim is populated with the scripts below:
=== RootNanny (processes upload requests onto the child prims and manages object name) ===
<lsl>
// Message constants
integer MSG_RESET = 0;
integer MSG_LINK_QUERY = 10;
integer MSG_LINK_REPLY = 11;
// Globals to propagate
string version = "1.5";
integer pin = 321;
// State
integer current_link_nr;
// Stuff to send out
list remote_scripts = [ "TouchDisplay", "LinkNanny" ];
request_next_object_id(integer link_nr)
{
 // Send message, transmitting pin, hoping to get back objectid
 llMessageLinked(link_nr, MSG_LINK_QUERY, (string)pin, NULL_KEY);
 
 // Set timer in case link isn't replying
 llSetTimerEvent(2); // 2 secs seems generous
}
default
{
 state_entry()
 {
 string name = llGetObjectName();
 integer space = llSubStringIndex(name, " v");
 if (space < 0) llSetObjectName(name + " v" + version);
 else llSetObjectName(llDeleteSubString(name, space+2, -1) + version);
 llListen(1, "", llGetOwner(), "update");
 }
 listen(integer channel, string name, key id, string message)
 {
 state uploading;
 }
}
state uploading
{
 state_entry()
 {
 current_link_nr = llGetNumberOfPrims();
 // Check if it's more than one
 if (1 < current_link_nr)
 {
 // avatars sitting on us get added at the end, so subtract...
 while (llGetAgentSize(llGetLinkKey(current_link_nr)))
 --current_link_nr;
 request_next_object_id(current_link_nr);
 }
 }
 
 link_message(integer from, integer msg_id, string str, key id)
 {
 if (from == current_link_nr && msg_id == MSG_LINK_REPLY)
 {
 llSetTimerEvent(0); // Cancel timeout
 integer i;
 for (i = 0; i < llGetListLength(remote_scripts); ++i)
 {
 string script = llList2String(remote_scripts, i);
 llSay(0, "Uploading '"+script+"' to link nr "+(string)current_link_nr);
 llRemoteLoadScriptPin(id, script, pin, TRUE, 0);
 }
 current_link_nr--;
 if (1 < current_link_nr)
 {
 request_next_object_id(current_link_nr);
 }
 else
 {
 llSay(0, "Done uploading remote scripts.");
 state default;
 }
 }
 }
 
 timer()
 {
 llSay(0, "Failed to receive reply from link nr "+(string)current_link_nr);
 current_link_nr--;
 if (1 < current_link_nr)
 {
 request_next_object_id(current_link_nr);
 }
 else
 {
 llSay(0, "Done uploading remote scripts.");
 state default;
 }
 }
}
</lsl>
=== Controller (game logic) ===
<lsl>
// Message constants
integer MSG_RESET = 0;
integer MSG_TOUCH = 1;
integer MSG_SET_X = 2;
integer MSG_SET_O = 3;
integer MSG_IDLE = 4;
integer MSG_IDLE_TOUCH = 5;
integer MSG_WIN = 6;
// Game timeout
integer GAME_TIMEOUT = 20;
// Game state
key player_x;
key player_o;
string turn;
string game;
string at(integer u, integer v)
{
 integer p = u*3+v;
 return llGetSubString(game, p, p);
}
// return TRUE if game ends
integer game_ends(string player, integer move)
{
 game = llInsertString(llDeleteSubString(game, move, move), move, player);
 llSay(0, "game = "+game);
 // Check if this made three in a row
 integer u = move / 3;
 integer v = move % 3;
 integer i;
 integer c;
 
 // check horizontal
 c = 0;
 for (i = 0; i < 3; i++) if (at(i, v) == player) c++;
 if (c == 3)
 {
 for (i = 0; i < 3; i++) llMessageLinked(2 + 3*i + v, MSG_WIN, "", NULL_KEY);
 return TRUE;
 }
 
 // check vertical
 c = 0;
 for (i = 0; i < 3; i++) if (at(u, i) == player) c++;
 if (c == 3)
 {
 for (i = 0; i < 3; i++) llMessageLinked(2 + 3*u + i, MSG_WIN, "", NULL_KEY);
 return TRUE;
 }
 
 // check if we are on one diagonal
 c = 0;
 if (u == v) for (i = 0; i < 3; i++) if (at(i, i) == player) c++;
 if (c == 3)
 {
 for (i = 0; i < 3; i++) llMessageLinked(2 + 4*i, MSG_WIN, "", NULL_KEY);
 return TRUE;
 }
 
 // check if we are on the other diagonal
 c = 0;
 if (u + v == 2) for (i = 0; i < 3; i++) if (at(i, 2-i) == player) c++;
 if (c == 3)
 {
 for (i = 0; i < 3; i++) llMessageLinked(4 + 2*i, MSG_WIN, "", NULL_KEY);
 return TRUE;
 }
 
 // Check if there are any spaces left
 if (llSubStringIndex(game, "-") < 0) return TRUE;
 // Game goes on
 return FALSE;
}
default
{
 state_entry()
 {
 player_x = NULL_KEY;
 player_o = NULL_KEY;
 game = "---------";
 turn = "x";
 llMessageLinked(LINK_ALL_CHILDREN, MSG_RESET, "", NULL_KEY);
 state playing;
 }
}
state playing
{
 link_message(integer from, integer msg_id, string str, key id)
 {
 if (msg_id == MSG_TOUCH)
 {
 llSay(0, "touch from "+(string)from);
 if (turn == "x" && NULL_KEY == player_x)
 {
 llSetTimerEvent(0);
 llMessageLinked(from, MSG_SET_X, "", NULL_KEY);
 player_x = id;
 if (game_ends(turn, from-2)) state idle;
 turn = "o";
 llSetTimerEvent(GAME_TIMEOUT);
 }
 else if (turn == "o" && NULL_KEY == player_o)
 {
 llSetTimerEvent(0);
 llMessageLinked(from, MSG_SET_O, "", NULL_KEY);
 player_o = id;
 if (game_ends(turn, from-2)) state idle;
 turn = "x";
 llSetTimerEvent(GAME_TIMEOUT);
 }
 else if (turn == "x" && id == player_x)
 {
 llSetTimerEvent(0);
 llMessageLinked(from, MSG_SET_X, "", NULL_KEY);
 if (game_ends(turn, from-2)) state idle;
 turn = "o";
 llSetTimerEvent(GAME_TIMEOUT);
 }
 else if (turn == "o" && id == player_o)
 {
 llSetTimerEvent(0);
 llMessageLinked(from, MSG_SET_O, "", NULL_KEY);
 if (game_ends(turn, from-2)) state idle;
 turn = "x";
 llSetTimerEvent(GAME_TIMEOUT);
 }
 }
 }
 timer()
 {
 llSetTimerEvent(0);
 state idle;
 }
}
state idle
{
 state_entry()
 {
 llSay(0, "state idle");
 llMessageLinked(LINK_ALL_CHILDREN, MSG_IDLE, "", NULL_KEY);
 }
 
 link_message(integer from, integer msg_id, string str, key id)
 {
 if (msg_id == MSG_IDLE_TOUCH) state default;
 }
}
</lsl>
== Child Prims ==
The child prims all have the same set of scripts:
=== LinkNanny (answers queries from the RootNanny and manages the upload pin) ===
<lsl>
// Message constants
integer MSG_RESET = 0;
integer MSG_LINK_QUERY = 10;
integer MSG_LINK_REPLY = 11;
default
{
 state_entry()
 {
 // If we are in the the root prim...
 if (llGetLinkNumber() < 2)
 {
 // ... disable ourselves
 llSetScriptState(llGetScriptName(), FALSE);
 llSleep(2);
 }
 }
 
 link_message(integer from, integer msg_id, string str, key id)
 {
 if (msg_id == MSG_LINK_QUERY)
 {
 // Set pin given
 llSetRemoteScriptAccessPin((integer)str);
 // Tell caller who we are
 llMessageLinked(from, MSG_LINK_REPLY, "", llGetKey());
 }
 }
}
</lsl>
=== TouchDisplay (handles clicks, displays game elements) ===
<lsl>
// Locations of various bits on the texture
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;
// Face on wjich the texture lives
integer DISPLAY_FACE = 4;
vector RED = <1,0,0>;
vector WHITE = <1,1,1>;
// Message constants
integer MSG_RESET = 0;
integer MSG_TOUCH = 1;
integer MSG_SET_X = 2;
integer MSG_SET_O = 3;
integer MSG_IDLE = 4;
integer MSG_IDLE_TOUCH = 5;
integer MSG_WIN = 6;
default
{
 state_entry()
 {
 // If we are in the the root prim...
 if (llGetLinkNumber() < 2)
 {
 // ... disable ourselves
 llSetScriptState(llGetScriptName(), FALSE);
 llSleep(2);
 }
 
 // Reset color and texture
 llSetColor(WHITE, DISPLAY_FACE);
 llOffsetTexture(e_pos_s, e_pos_t, DISPLAY_FACE);
 }
 touch_start(integer touching_agents)
 {
 while (touching_agents--)
 {
 llMessageLinked(LINK_ROOT, MSG_TOUCH, "", llDetectedKey(touching_agents));
 } 
 }
 
 link_message(integer from, integer msg_id, string str, key id)
 {
 if (msg_id == MSG_SET_X) state x;
 if (msg_id == MSG_SET_O) state o;
 if (msg_id == MSG_IDLE) state idle;
 }
}
state x
{
 state_entry()
 {
 llOffsetTexture(x_pos_s, x_pos_t, DISPLAY_FACE);
 }
 link_message(integer from, integer msg_id, string str, key id)
 {
 if (msg_id == MSG_RESET) state default;
 if (msg_id == MSG_IDLE) state idle;
 if (msg_id == MSG_WIN) llSetColor(RED, DISPLAY_FACE);
 }
}
state o
{
 state_entry()
 {
 llOffsetTexture(o_pos_s, o_pos_t, DISPLAY_FACE);
 }
 link_message(integer from, integer msg_id, string str, key id)
 {
 if (msg_id == MSG_RESET) state default;
 if (msg_id == MSG_IDLE) state idle;
 if (msg_id == MSG_WIN) llSetColor(RED, DISPLAY_FACE);
 }
}
state idle
{
 link_message(integer from, integer msg_id, string str, key id)
 {
 if (msg_id == MSG_RESET) state default;
 }
 touch_start(integer touching_agents)
 {
 llMessageLinked(LINK_ROOT, MSG_IDLE_TOUCH, "", NULL_KEY);
 }
} 
</lsl>