Tic Tac Toe/Summary

From Second Life Wiki
Jump to: navigation, search

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>