Tic Tac Toe/Game Logic Two

From Second Life Wiki
Jump to: navigation, search

Now with our uploader in place, we can add another message to the TouchDisplay script to color them red when we detect three in a row:

// 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);
    }
}

We then go into the Controller script and add the logic to detect the game end:

// 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;
 
// Helper function to linearize our 3x3 array
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)
    {
        // The magic number "2" is needed because the smallest child prim index is 2
        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)
    {
        // The magic number "2" is needed because the smallest child prim index is 2
        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)
    {
        // The magic number "2" is needed because the smallest child prim index is 2
        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)
    {
        // Arithmetic has been applied, reconstruction left as an exercise to the reader
        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; // magic "2" = smallest link prim index
                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; // magic "2" = smallest link prim index
                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; // magic "2" = smallest link prim index
                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; // magic "2" = smallest link prim index
                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;
    }
}

Note that we don't have to check all possibilities, but only those which could be made possible by the last move.

We also needed to reorder our handling of incoming touch events slightly to ensure that the event timer is off when we process moves.

Then we type \1 update into the chat and play a couple of games to test.

Done.

Have fun!