Don't click here unless you want to be banned.

LSL Wiki : LibraryGoGame

HomePage :: PageIndex :: RecentChanges :: RecentlyCommented :: UserSettings :: You are crawl411.us.archive.org
This is my first (and perhaps only) large scale LSL project. I don't have time to do anything more with it, so I'm going to release it into the wild for others to use, adapt, play with as the like. I only ask that noone sells the original as posted here, as that would be a bit greedy.

So, what is it? A fully functional Go board, which allows two players to play a game of Go and score up at the end. It uses various tricks, such as XYText style textures for the pieces on the board, and a squidgy double-click square selector device thing to keep the number of prims required to a minimum.

There are several components and scripts, so it's all a bit complex to put together, and it's not as well commented as perhaps it should be, but I'll do my best to write some useful instructions.

The board looks very basic. My talents do not lie in making things look pretty. All textures are linked to ones I've created. It would be trivial to put together some nicer textures and change the relevent keys. The code does the basic setting up of size/shape/colour of all parts of the game.

A large number of link messages are used to glue all the various bits and pieces together. I didn't document this as well as I should, and it's all a bit random as things which grow whilst coded tend to be. For my own benefit I eventually put together a little file documenting very briefly each message, so I'll include that here too.

As to the rules of the game of Go, there are plenty of websites out there that can explain it better than I can. However, I will say this, it is an extremely interesting and deep game that you can enjoy quickly, but study for years and not come close to mastering.

First up, GoButtonScript

// GoButtonScript
// Jonathan Shaftoe
// A simple button used on the Go board, can be one of Pass, Done, Info, Resign or Reset
// Drop into a basic prim, call it GoButton, then store in your inventory for later.

key gPlayer; // Current player whose turn it is.
float gSize; // scaling constant.
integer gType; // which type of button we are. 1 - pass, 2 - done, 3 - reset, 4 - resign, 5 - info

set_size(string str) {
    gSize = (float)str;
    llSetScale(<0.2 * gSize, 0.1 * gSize, 0.01 * gSize>);
}

default {
    state_entry() {
        llSetPrimitiveParams([
            PRIM_TYPE, PRIM_TYPE_BOX, PRIM_HOLE_DEFAULT, <0, 1, 0>, 0.0, ZERO_VECTOR, <1, 1, 0>, ZERO_VECTOR,
            PRIM_SIZE, <0.2, 0.1, 0.01>,
            PRIM_TEXTURE, ALL_SIDES, "5748decc-f629-461c-9a36-a35a221fe21f", <1, 1, 0>,  <0, 0, 0>, 0.0,
            PRIM_COLOR, ALL_SIDES, <0.8, 0.6, 0.5>, 1.0
        ]);
        llSitTarget(ZERO_VECTOR, ZERO_ROTATION);
        llSetObjectName("GoButton");
    }

    on_rez(integer start_param) {
        if (start_param > 0) {
            gType = start_param;
            if (gType == 1) {
                llSetTouchText("Pass");
//                llSetText("Pass", <0, 0, 0>, 1.0);
                llSetObjectDesc("Go Game Pass Button.");
                llSetTexture("f8919345-d4ab-613f-c594-5cdafcf6f8a1", 0);
            } else if (gType == 2) {
                llSetTouchText("Done");
//                llSetText("Done", <0, 0, 0>, 1.0);
                llSetObjectDesc("Go Game Done Button.");
                llSetTexture("9db6cc17-c2bc-04e9-bc5e-659e0b20d82f", 0);
            } else if (gType == 3) {
                llSetTouchText("Reset");
//                llSetText("Reset", <0, 0, 0>, 1.0);
                llSetObjectDesc("Go Game Reset Button");
                llSetTexture("619570be-ce9c-11ff-3f8b-9ebab0f3c759", 0);
            } else if (gType == 4) {
                llSetTouchText("Resign");
//                llSetText("Resign", <0, 0, 0>, 1.0);
                llSetObjectDesc("Go Game Resign Button");
                llSetTexture("5ad73a1c-060c-3fba-5662-584ddfcdc115", 0);
            } else if (gType == 5) {
                llSetTouchText("Info");
//                llSetText("Info", <0, 0, 0>, 1.0);
                llSetObjectDesc("Go Game Info Button");
                llSetTexture("d0b9c9de-a289-01df-734a-10a8201841ad", 0);
            }
        }
    }

    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }

    touch_start(integer num) {
        if (gType == 1) {
            if (llDetectedKey(0) == gPlayer) {
                llMessageLinked(LINK_ROOT, 10, "", "");
            }
        } else if (gType == 2) {
            if (llDetectedKey(0) == gPlayer) {
                llMessageLinked(LINK_ROOT, 11, "", "");
            }
        } else if (gType == 3) {
            llMessageLinked(LINK_ROOT, 12, "", llDetectedKey(0));
        } else if (gType == 4) {
            llMessageLinked(LINK_ROOT, 13, "", llDetectedKey(0));
        } else if (gType == 5) {
            llMessageLinked(LINK_ROOT, 14, "", llDetectedKey(0));
        }
    }
    
    link_message(integer sender, integer num, string str, key id) {
        if (num == 1) {
            set_size(str);
        } else if (num == 0) {
            gPlayer = id;
        }
    }
}


Next up, GoJoinButtonScript

// GoJoinButtonScript
// Jonathan Shaftoe
// A simple button used for players to join the Go game, and also to start the game.
// Gets disabled and hidden once game is in progress.
// Drop into a simple prim, call it GoJoinButton, then store in your inventory for later.

integer gColour; // what colour are we, 1 - black, 2 - white
float gSize;     // scaling constant
string gPlayAs;  // Play as Black or Play as White
vector gPlayAsCol; // Actual colour, black or white.

set_size() {
    llSetScale(<0.2 * gSize, 0.2 * gSize, 0.1 * gSize>);
}

default {
    state_entry() {
        llSetPrimitiveParams([
            PRIM_TYPE, PRIM_TYPE_SPHERE, PRIM_HOLE_DEFAULT, <0, 0.5, 0>, 0.0, ZERO_VECTOR, <0, 1, 0>,
            PRIM_SIZE, <0.4, 0.4, 0.1>,
            PRIM_TEXTURE, ALL_SIDES, "5748decc-f629-461c-9a36-a35a221fe21f", <1, 1, 0>,  <0, 0, 0>, 0.0,
            PRIM_COLOR, ALL_SIDES, <0.5, 0.5, 0.5>, 1.0
        ]);
        llSetTouchText("Join Game");
        llSetObjectDesc("Go Game Join Button.");
        llSetObjectName("GoJoinButton");
        llSitTarget(ZERO_VECTOR, ZERO_ROTATION);
    }

    on_rez(integer start_param) {
        if (start_param > 0) {
            gColour = start_param;
            if (gColour == 1) { 
                llSetColor(<0, 0, 0>, ALL_SIDES);
                gPlayAs = "Play as Black";
                gPlayAsCol = <0, 0, 0>;
            } else if (gColour == 2) {
                llSetColor(<1, 1, 1>, ALL_SIDES);
                gPlayAs = "Play as White";
                gPlayAsCol = <1, 1, 1>;
            }
        }
    }

    link_message(integer sender, integer num, string str, key id) {
        if (num == 1) {
            gSize = (float)str;
            set_size();
            state enabled;
        }
    }
    
    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }
}

state enabled {
    state_entry() {
        llSetAlpha(1.0, ALL_SIDES);
        llSetText(gPlayAs, gPlayAsCol, 1.0);
        set_size();
    }
     
    touch_start(integer num) {
        llMessageLinked(LINK_ROOT, gColour, "", llDetectedKey(0));
        state disabled;
    }
    
    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }
}

state disabled {
    state_entry() {
        llSetAlpha(0.0, ALL_SIDES);
        llSetScale(<0.01, 0.01, 0.01>);
        llSetText("", <0, 0, 0>, 1.0);
    }
    
    link_message(integer sender, integer num, string str, key id) {
        if (num == 999) {
            state enabled;
        }
    }
    
    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }
}

That's the simple stuff done. Now things get more complicated, you need to create two grids, one of 'tiles' (prim textures to display the pieces on the board), and one of 'sensors' (to detect clicks when specifying where you want to play).

First off sensors, here's the GoSensorScript

// GoSensorScript
// Jonathan Shaftoe
// Script for each individual Go Sensor, used to detect mouse clicks to allow the player to select
// where they want to play. Uses a two click mechanism to reduce prim usage, a grid of these sensors
// moves over the board. For example, using a 19x19 board, 20 sensors in a 4x5 grid are used to allow
// the player to select the square they want to play in. To start with, the 4x5 grid covers the
// whole board, each sensor covering 20 squares. When one is clicked on, the sensors rearrange to
// just be over the 20 squares that were under the sensor clicked, then allowing the player to
// select an individual square. It's hard to explain, but when you see it working it'll be much
// more sense.
// Create a single prim, call it GoSensor, then drop this script in. Then see GoSensorGridScript.

integer gIndex;  // which sensor in the grid am I
float gSize;     // global scaling factor

integer gXSensors; // how many sensors across in grid to cover board
integer gYSensors; // how many sensors up/down in grid to cover board

integer gXSquares; // how many squares on the board across covered by a sensor
integer gYSquares; // how many squares on the board up/down covered by a sensor

integer gGameSize; // size of game board, will be 9, 13 or 19

integer gXPosBase; // x coordinate of sensor in grid
integer gYPosBase; // y coordinate of sensor in grid

integer gXPos;  // x position of sensor in grid 
integer gYPos;  // y position of sensor in grid

integer gZoomPosX; // x coordinate when zoomed in (after first click by player)
integer gZoomPosY; // y coordinate when zoomed in

float gSquareSize; // Size of an individual square on the board.

key gPlayer;  // player whose turn it currently is.

hide() {
    llSetScale(<0.01, 0.01, 0.01>);
    llSetPos(<0, 0, - 0.01>);
}

default {
    state_entry() {
        llSetPrimitiveParams([
            PRIM_TYPE, PRIM_TYPE_BOX, PRIM_HOLE_DEFAULT, <0, 1, 0>, 0.0, ZERO_VECTOR, <1, 1, 0>, ZERO_VECTOR,
            PRIM_SIZE, <0.5, 0.5, 0.5>,
            PRIM_TEXTURE, ALL_SIDES, "5748decc-f629-461c-9a36-a35a221fe21f", <1, 1, 0>,  <0, 0, 0>, 0.0,
            PRIM_COLOR, ALL_SIDES, <0, 0, 1>, 0.25
        ]);
        llSetObjectName("GoSensor");
        llSetTouchText("Play here");
        llSetObjectDesc("Go Game sensor - click once to select region to play in, then again for exact position.");
        llSitTarget(ZERO_VECTOR, ZERO_ROTATION);
    }

    on_rez(integer start_param) {
        gIndex = start_param;
        state ready;
    }

    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }
}

// Do setup, hide sensor and wait for message
state ready {
    state_entry() {
    }
    
    link_message(integer sender, integer num, string str, key id) {
        if (num == 3) {
            hide();
            list params = llParseString2List(str, [","], []);
            gGameSize = llList2Integer(params, 0);
            gXSensors = llList2Integer(params, 1);
            gYSensors = llList2Integer(params, 2);
            gSize = llList2Float(params, 3);
            
            gXSquares = llCeil((float)gGameSize / gXSensors);
            gYSquares = llCeil((float)gGameSize / gYSensors);
            
            gXPosBase = (gIndex % gXSensors);
            gYPosBase = (gIndex / gXSensors);
            
            if ((gXPosBase + gYPosBase) % 2 == 0) { // Alternating colours for checkerboard effect
                llSetColor(<1, 0, 0>, ALL_SIDES);
            } else {
                llSetColor(<0, 1, 0>, ALL_SIDES);
            }

            gXPos = gXPosBase * gXSquares;
            gYPos = gYPosBase * gYSquares;
            
            if (gGameSize - gXPos < gXSquares) {
                gXSquares = gGameSize - gXPos;
            }
            if (gGameSize - gYPos < gYSquares) {
                gYSquares = gGameSize - gYPos;
            }
            gSquareSize = gSize / gGameSize;
        } else if (num == 0) {
            gPlayer = id;
            state zoom1;
        }
    }

    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }
}


// Move to first position, sensor grid covering whole board, wait for player click
state zoom1 {
    state_entry() {
        if (gXPosBase >= gXSensors || gYPosBase >= gYSensors) {
            hide();
        } else {
            float width = gSquareSize * gXSquares;
            float height = gSquareSize * gYSquares;
            llSetScale(<width, height, 0.01 * gSize>);
            llSetPos(<(gXPos + (gXSquares / 2.0)) * gSquareSize - gSize / 2 , (gYPos + (gYSquares / 2.0)) * gSquareSize - gSize / 2, 0.02 * gSize + .02>);
        }
    }
    
    touch_start(integer num) {
        if (llDetectedKey(0) == gPlayer) {
            llMessageLinked(LINK_ALL_CHILDREN, 5, (string)gXPos + "," + (string)gYPos, "");
        } else {
//            llWhisper(0, "It's not your turn, " + llKey2Name(llDetectedKey(0)));
        }
    }
    
    link_message(integer sender, integer num, string str, key id) {
        if (num == 5) {
            list params = llParseString2List(str, [","], []);
            gZoomPosX = llList2Integer(params, 0);
            gZoomPosY = llList2Integer(params, 1);
            state zoom2;
        }
        else if (num == 0) {
            gPlayer = id;
        } else if (num == 105 || num == 999) {
            hide();
            state ready;
        }   
    }

    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }
}

// move to zoomed in grid, each sensor covering one square on the board, wait for click.
state zoom2 {
    state_entry() {
        if (gXPosBase >= gXSensors || gYPosBase >= gYSensors || gZoomPosX + gYPosBase >= gGameSize || gZoomPosY + gXPosBase >= gGameSize) {
            hide();
        } else {
            llSetScale(<gSquareSize, gSquareSize, 0.01 * gSize>);
            llSetPos(<(gZoomPosX + gYPosBase + 0.5) * gSquareSize - gSize / 2, (gZoomPosY + gXPosBase + 0.5) * gSquareSize - gSize / 2, 0.02 * gSize + .02>);
        }
    }
    
    touch_start(integer num) {
        if (gZoomPosX + gYPosBase < gGameSize && gZoomPosY + gXPosBase < gGameSize) {
            if (llDetectedKey(0) == gPlayer) {
                llMessageLinked(LINK_ALL_CHILDREN, 20, "", "");
                integer x = gZoomPosX + gYPosBase;
                integer y = gZoomPosY + gXPosBase;
                llMessageLinked(LINK_ROOT, 4, (string)x + "," + (string)y, "");

                state waiting;
            } else {
//                llWhisper(0, "It's not your turn, " + llKey2Name(llDetectedKey(0)));
            }
        }
    }
    
    link_message(integer sender, integer num, string str, key id) {
        if (num == 0) {
            gPlayer = id;
            state zoom1;
        } else if (num == 20) {
            state waiting;
        } else if (num == 105 || num == 999) {
            hide();
            state ready;
        }
    }
    
    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }
}

// Move has been attempted, disable clicks and wait to see what to do next.
state waiting {
    link_message(integer sender, integer num, string str, key id) {
        if (num == 0) {
            gPlayer = id;
            state zoom1;
        } else if (num == 21) {
            state zoom2;
        } else if (num == 105 || num == 999) {
            hide();
            state ready;
        }
    }   

    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    } 
}

And here's the GoSensorGridScript

// GoSensorGridScript
// Jonathan Shaftoe
// Creates the sensor grid by rezzing 19 sensors (to make 20, including the one we're in) and
// giving each one a unique index. Script removes itself once done. Will take a few seconds
// to run, as rezzing has a built in delay. Requires link permission as all the created
// sensors are linked together.
// To use, rez a previously created GoSensor (see GoSensorScript), then put a GoSensor
// in that GoSensor's inventory, then drop this script in. Wait until it has done its stuff,
// then take the resulting GoSensorGrid into your inventory for later.

// If you only wanted to support smaller game boards and use less prims, you could change
// totalSensors below to the relevent number. For a 9x9 board, you would only need 9
// sensors, so make totalSensors 8 - for a 13x13 board, you need 16 sensors, so make
// totalSensors 15.

integer gNumSensors = 0; // how many have we made
integer gCountRez = 0; // how many are still waiting to be linked.

default
{
    state_entry()
    {
        llSetObjectName("GoSensorGrid");
        llRequestPermissions(llGetOwner(), PERMISSION_CHANGE_LINKS);
    }
    
    run_time_permissions(integer perms) {
        state start;
    }
}

state start {
    state_entry() {
        llWhisper(0, "Creating sensors, please wait ...");
        // 19x19 board needs 4x5 sensors, so 19 plus the one we're in to start with..
        integer totalSensors = 19;
        while (gNumSensors < totalSensors) {
            llRezObject("GoSensor", llGetPos(), ZERO_VECTOR, ZERO_ROTATION, gNumSensors + 1);
            gNumSensors++;
            gCountRez++;
        }
    }
    
    object_rez(key id) {
        llCreateLink(id, TRUE);
        gCountRez--;
        if (gCountRez == 0) {
            llRemoveInventory(llGetScriptName());
        } else if (gCountRez % 4 == 0) {
            llWhisper(0, "... " + (string)gCountRez + " more sensors to make ...");
        }
    }

}

Now for the tiles to display textures, first GoTileScript

// GoTileScript
// Jonathan Shaftoe
// A tile prim for displaying textures on the board to show where pieces are. Each tile is a squashed
// flattened cube, to create 3 square faces in a row, like XYText. Each face displays a texture which
// shows 4 game pieces. Using rotation and flipping, just 20 textures are required to display all
// 81 possible states of 4 squares on the board being either empty, black or white.
// The tile prims are made into a tile grid, in the same way as the sensors.
// Create a single prim, drop this script and the GoTileFaceScript in, then take the resulting
// GoTile object into your inventory and see GoTileGridScript

integer gGameSize; // size of game board, 9, 13 or 19
integer gIndex;  // which tile in the grid are we
float gSize;     // scaling factor

integer TILEWIDTH = 6;  // absolute values for making us the right shape
integer TILEHEIGHT = 2;

integer gRotated = 0; // are we one of the rotated tiles used down the edge to conserve prim count

integer gXPosBase;  // Our logical coordinates within the grid
integer gYPosBase;

integer gXPos;  // Our actual physical coordinates
integer gYPos;

float gSquareSize; // Size of an individual square on the board.

integer gFace0Modified = 0; // has one of the three faces been modified and need re-texturing
integer gFace1Modified = 0;
integer gFace2Modified = 0;

list gStates = []; // The state of this tile. Each face is a string in the list, each
                   // string is 4 characters of 0 - blank, 1 - black, 2 - white
list gStatesBackup = []; // backup copy of game state used for undoing changes during endgame
integer gGotBackup = 0;  // do we have a backup.
string  TRANSPARENT = "701917a8-d614-471f-13dd-5f4644e36e3c";

add_turn(integer face, integer x, integer y, string colour) {
    string current = llList2String(gStates, face);
//    llWhisper(0, "Got add turn, face: " + (string) face + " coords " + (string)x + ", " + (string)y + " and current state: " + current + ". Index is: " + (string)gIndex);
    string newstr;
    integer pos = (y * 2) + x;
    if (pos == 0) {
        newstr = colour + llGetSubString(current, 1, 3);
    } else if (pos == 3) {
        newstr = llGetSubString(current, 0, 2) + colour;
    } else {
        newstr = llGetSubString(current, 0, pos - 1) + colour + llGetSubString(current, pos + 1, 3);
    }
//    llWhisper(0, "New state is: " + newstr);
    gStates = llListInsertList(llDeleteSubList(gStates, face, face), [newstr], face);
}

show_face(integer face) {
    string statestr = llList2String(gStates, face);
    llMessageLinked(llGetLinkNumber(), 301, statestr, (string)(face + 10 * gRotated));
}

default {
    state_entry() {
        llSetPrimitiveParams([
            PRIM_TYPE, PRIM_TYPE_BOX, PRIM_HOLE_DEFAULT, <0, 1, 0>, 0.0, ZERO_VECTOR, <0.333333, 1, 0>, ZERO_VECTOR,
            PRIM_SIZE, <1.5, 0.5, 0.01>,
            PRIM_TEXTURE, ALL_SIDES, "5748decc-f629-461c-9a36-a35a221fe21f", <1, 1, 0>,  <0, 0, 0>, 0.0,
            PRIM_COLOR, ALL_SIDES, <1, 1, 1>, 1.0
        ]);
        llSetObjectName("GoTile");
        llSetTouchText("");
        llSetObjectDesc("Go Game board tile");
        llSitTarget(ZERO_VECTOR, ZERO_ROTATION);

    }

    on_rez(integer start_param) {
        gIndex = start_param;
//        llSetText((string)gIndex, <0, 0, 0>, 1.0);
        state ready;
    }

    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }
}

state ready {
    link_message(integer sender, integer num, string str, key id) {
        if (num == 3) {
            list params = llParseString2List(str, [","], []);
            gGameSize = llList2Integer(params, 0);
            gSize = llList2Float(params, 3);
            
            integer vertical_strips = 0;
            integer xTiles = gGameSize / TILEWIDTH;
            integer yTiles = gGameSize / TILEHEIGHT;
            if (yTiles * TILEHEIGHT < gGameSize) {
                yTiles++;
            }
            if (xTiles * TILEWIDTH + 2 < gGameSize) {
                xTiles++;
            } else {
                vertical_strips = xTiles + 1;
            }
            integer extra_index = gIndex - (xTiles * yTiles);
            if (extra_index >= vertical_strips) {
                // hide us away, not needed
                llSetScale(<0.01, 0.01, 0.01>);
                llSetPos(<0, 0, -.01>);
                llSetText("", <0,0,0>, 1.0);
                gXPos = -10;
                gYPos = -10;
            } else {
                if (extra_index >= 0 && extra_index < vertical_strips) {
                    gXPosBase = xTiles;
                    gYPosBase = extra_index * (TILEWIDTH / TILEHEIGHT);
                    gRotated = 1;
                } else {
                    gXPosBase = (gIndex % xTiles);
                    gYPosBase = (gIndex / xTiles);
                }

                gXPos = gXPosBase * TILEWIDTH;
                gYPos = gYPosBase * TILEHEIGHT;
            
                gSquareSize = gSize / gGameSize;
                llSetTexture(TRANSPARENT, ALL_SIDES);
                llSetScale(<gSquareSize * TILEWIDTH, gSquareSize * TILEHEIGHT, 0.01>);
                if (gRotated == 1) {
                    llSetPos(<(gXPos + (TILEHEIGHT / 2.0)) * gSquareSize - gSize / 2, (gYPos + (TILEWIDTH /     2.0)) * gSquareSize - gSize / 2, 0.005 * gSize + .005>);
                    llSetLocalRot(llEuler2Rot(<0, 0, PI_BY_TWO>));
                } else {
                    llSetPos(<(gXPos + (TILEWIDTH / 2.0)) * gSquareSize - gSize / 2, (gYPos + (TILEHEIGHT / 2.0)) * gSquareSize - gSize / 2, 0.01 * gSize + .001>);
                }
                gStates = ["0000", "0000", "0000"];
            }
        } else if (num == 201) {
            list params = llParseString2List(str, [","], []);
            integer x;
            integer y;
            string colour;
            x = llList2Integer(params, 0);
            y = llList2Integer(params, 1);
            colour = llList2String(params, 2);
            if (x < gXPos || y < gYPos) {
                return;
            }
            integer face;
            integer facex;
            integer facey;
            if (gRotated == 0) {
                facey = y - gYPos;
                if (facey > 1) {
                    return;
                }
                face = (x - gXPos) / 2;
                facex = (x - gXPos) % 2;
            } else {
                facex = x - gXPos;
                if (facex > 1) {
                    return;
                }
                face = (y - gYPos) / 2;
                facey = (y - gYPos) % 2;
            }
            if (face > 2) {
                return;
            }
            // need to save states in case we have to undo the changes - needed when marking groups for endgame
            if (id == "1" && gGotBackup == 0) {
                gGotBackup = 1;
                gStatesBackup = gStates;
            }
            add_turn(face, facex, facey, colour);
            if (colour != "0") {
                show_face(face);
            } else {
                if (face == 0) {
                   gFace0Modified = 1;
                } else if (face == 1) {
                    gFace1Modified = 1;
                } else if (face == 2) {
                    gFace2Modified = 1;
                }
            }
        } else if (num == 202) {
            if (gFace0Modified == 1) {
                show_face(0);
                gFace0Modified = 0;
            }
            if (gFace1Modified == 1) {
                show_face(1);
                gFace1Modified = 0;
            }
            if (gFace2Modified == 1) {
                show_face(2);
                gFace2Modified = 0;
            }
        } else if (num == 15) {
            if (gGotBackup == 1) {
                gStates = gStatesBackup;
                gStatesBackup = [];
                gGotBackup = 0;
                show_face(0);
                show_face(1);
                show_face(2);
            }
        } else if (num == 203) {
            gGotBackup = 0;
            gStatesBackup = [];
        } else if (num == 999) {
            gGotBackup = 0;
            gStatesBackup = [];
            gStates = ["0000", "0000", "0000"];
            show_face(0);
            show_face(1);
            show_face(2); 
            gFace0Modified = 0;
            gFace1Modified = 0;
            gFace2Modified = 0;
        }
    }

    changed(integer change) {
        if (change & CHANGED_LINK) 
            if (llGetLinkNumber() == 0) 
                llDie(); // so i die        
    }
}

Due to script length restrictions, it wouldn't all fit in one, so there's another script required here, GoTileFaceScript

// GoTileFaceScript
// Jonathan Shaftoe
// Texture-handling side of functionality required by GoTiles. Wouldn't all fit in one
// script due to script size limits. Also has problems with runtime size, hence the way
// the lists are joined together in bits. Contains a mapping from all of the possible
// game states of a 2x2 grid of squares, each of which being 0 - empty 1 - black or
// 2 - white, to the texture to be used to display it, and with what rotation/flipping.
// Also, each of the three faces on a tile has a different rotation to apply to make
// things all be the right way around.
// Some heavy borrowing from XYText in here - thank you!
// For usage, see GoTileScript


// List of 20 textures used to display board pieces.
list gTextureIndex = ["701917a8-d614-471f-13dd-5f4644e36e3c",
                        "b51f6506-2156-a8ea-15f5-172d80add211",
                        "d48ab9c0-c49d-e009-a893-84bd7326de84",
                        "3823e129-e09f-b0fb-8717-90bc88e8ef46",
                        "0f3113bb-39c2-e0b9-475e-00e59afd85b0", 
                        "bc806a54-3b0e-8f96-c105-d9da31160be5",
                        "58b6e53a-8b13-726f-24e7-ec94798bc9c4",
                        "760aa9bf-c25f-6880-2292-0c3c56421985",
                        "633464b2-2d1b-b050-2af9-f6dd2427465f",
                        "9f71ccc5-63e1-f5f0-6407-43577019f408",
                        "ad62bcb5-a607-21da-66eb-15548aad6b0a",
                        "d56bad91-3d05-e90e-080f-31224b353c42",
                        "00e8a3c3-996e-ed81-5e37-a86b099a9be7",
                        "5b206598-0c5d-352f-14e2-d18b520919a6",
                        "cf839003-eaf4-4af3-abe9-5d8bf3f28f0a",
                        "ab6e5d0b-16b9-3aee-b898-8c532c5e691c",
                        "e05ef3e7-f2a3-4b8c-f4ea-86d8b00a1c20",
                        "b5014c6e-d0b6-c6c7-6c25-d532fe08476f",
                        "15c19f1d-ee3f-22ba-541c-c44c62ea75c7",
                        "3f563f39-1c4d-297a-3956-cc174a9209e6",
                        "6879b0ca-a295-dd20-1e08-cc60da377a1a"];
                        
// Will contain complete list, put together from parts below. Striped list of 4 items,
// first is game state represented as a string, second is index into texture list for
// which texture to use, third is mirroring/flipping required of texture, fourth is
// rotation.                      
list gStateList = [];

// Index of prim faces for each of the three faces we're using to display textures
list gFaceIndex = [4, 0, 2];
// hack for apparent bug, can't just negate PI_BY_TWO.
float MINUS_PI_BY_TWO = -1.5707963267948966192313216916398;
// rotation needed for each of the three faces.
list gFaceRotations = [PI_BY_TWO, 0, MINUS_PI_BY_TWO];

// parts used to create gStateList, see boave.
list part1 =    ["0000", 0, <1, 1, 0>, 0.0,
                "1000", 1, <1, -1, 0>, 0.0,
                "0100", 1, <-1, -1, 0>, 0.0,
                "0010", 1, <1, 1, 0>, 0.0,
                "0001", 1, <-1, 1, 0>, 0.0,
                "2000", 2, <1, -1, 0>, 0.0,
                "0200", 2, <-1, -1, 0>, 0.0,
                "0020", 2, <1, 1, 0>, 0.0,
                "0002", 2, <-1, 1, 0>, 0.0,
                "1200", 3, <1, -1, 0>, 0.0,
                "2100", 3, <-1, -1, 0>, 0.0,
                "0012", 3, <1, 1, 0>, 0.0,
                "0021", 3, <-1, 1, 0>, 0.0,
                "1020", 3, <1, 1, 0>, PI_BY_TWO,
                "2010", 3, <-1, 1, 0>, PI_BY_TWO];
list part2 = [
                "0102", 3, <1, -1, 0>, PI_BY_TWO,
                "0201", 3, <-1, -1, 0>, PI_BY_TWO,
                "1002", 4, <1, -1, 0>, 0.0,
                "0120", 4, <-1, -1, 0>, 0.0,
                "2001", 4, <-1, 1, 0>, 0.0,
                "0210", 4, <1, 1, 0>, 0.0,
                "1100", 5, <1, -1, 0>, 0.0,
                "0011", 5, <1, 1, 0>, 0.0,
                "1010", 5, <1, 1, 0>, PI_BY_TWO,
                "0101", 5, <1, -1, 0>, PI_BY_TWO,
                "1001", 6, <-1, 1, 0>, 0.0,
                "0110", 6, <1, 1, 0>, 0.0,
                "2200", 7, <1, -1, 0>, 0.0,
                "0022", 7, <1, 1, 0>, 0.0,
                "2020", 7, <1, 1, 0>, PI_BY_TWO];
list part3 = [
                "0202", 7, <1, -1, 0>, PI_BY_TWO,
                "2002", 8, <-1, 1, 0>, 0.0,
                "0220", 8, <1, 1, 0>, 0.0,
                "1110", 9, <-1, -1, 0>, 0.0,
                "1101", 9, <1, -1, 0>, 0.0,
                "1011", 9, <-1, 1, 0>, 0.0,
                "0111", 9, <1, 1, 0>, 0.0,
                "0211", 10, <1, 1, 0>, 0.0,
                "2011", 10, <-1, 1, 0>, 0.0,
                "1102", 10, <1, -1, 0>, 0.0,
                "1120", 10, <-1, -1, 0>, 0.0,
                "1012", 10, <1, 1, 0>, PI_BY_TWO,
                "1210", 10, <-1, 1, 0>, PI_BY_TWO,
                "0121", 10, <1, -1, 0>, PI_BY_TWO,
                "2101", 10, <-1, -1, 0>, PI_BY_TWO];
list part4 = [
                "0112", 11, <1, 1, 0>, 0.0,
                "1201", 11, <1, -1, 0>, 0.0,
                "1021", 11, <-1, 1, 0>, 0.0,
                "2110", 11, <-1, -1, 0>, 0.0,
                "0122", 12, <1, 1, 0>, 0.0,
                "2201", 12, <1, -1, 0>, 0.0,
                "1022", 12, <-1, 1, 0>, 0.0,
                "2210", 12, <-1, -1, 0>, 0.0,
                "2021", 12, <1, 1, 0>, PI_BY_TWO,
                "2120", 12, <-1, 1, 0>, PI_BY_TWO,
                "0212", 12, <1, -1, 0>, PI_BY_TWO,
                "1202", 12, <-1, -1, 0>, PI_BY_TWO,
                "0221", 13, <1, 1, 0>, 0.0,
                "2102", 13, <1, -1, 0>, 0.0,
                "2012", 13, <-1, 1, 0>, 0.0];
list part5 = [
                "1220", 13, <-1, -1, 0>, 0.0,
                "2220", 14, <-1, -1, 0>, 0.0,
                "2202", 14, <1, -1, 0>, 0.0,
                "2022", 14, <-1, 1, 0>, 0.0,
                "0222", 14, <1, 1, 0>, 0.0,
                "1111", 15, <1, 1, 0>, 0.0,
                "1121", 16, <1, 1, 0>, 0.0,
                "2111", 16, <1, -1, 0>, 0.0,
                "1112", 16, <-1, 1, 0>, 0.0,
                "1211", 16, <-1, -1, 0>, 0.0,
                "1122", 17, <1, 1, 0>, 0.0,
                "2211", 17, <1, -1, 0>, 0.0,
                "2121", 17, <1, 1, 0>, PI_BY_TWO,
                "1212", 17, <1, -1, 0>, PI_BY_TWO];
list part6 = [
                "1221", 18, <1, 1, 0>, 0.0,
                "2112", 18, <-1, 1, 0>, 0.0,
                "1222", 19, <1, 1, 0>, 0.0,
                "2212", 19, <1, -1, 0>, 0.0,
                "2122", 19, <-1, 1, 0>, 0.0,
                "2221", 19, <-1, -1, 0>, 0.0,
                "2222", 20, <1, 1, 0>, 0.0];
                // 2111
                
default {
    state_entry() {
        gStateList = part1 + part2 + part3 + part4 + part5 + part6;
    }
    
    link_message(integer sender, integer num, string str, key id) {
        if (num == 301) {
            integer face = (integer)((string)id) % 10;
            integer rotated = (integer)((string)id) / 10;
            integer realface = llList2Integer(gFaceIndex, face);
            string mystate;
            if (rotated == 1) {
                // need to horizontally flip the state we're looking for if we're part of a rotated tile.
                mystate = llGetSubString(str, 1, 1) + llGetSubString(str, 3, 3) + llGetSubString(str, 0, 0) + llGetSubString(str, 2, 2);
            } else {
                mystate = str;
            }
            integer pos = llListFindList(gStateList, [mystate]);
            if (pos == -1) {
                llWhisper(0, "Failed to find state for: " + mystate);
                return;
            }
            integer texture = llList2Integer(gStateList, pos + 1);
            key realtexture = llList2Key(gTextureIndex, texture);
            llSetPrimitiveParams([PRIM_TEXTURE, realface, realtexture, llList2Vector(gStateList, pos + 2), <0, 0, 0>, llList2Float(gStateList, pos + 3) + llList2Float(gFaceRotations, face)]);
        }
    }
}

As with the sensors, we need a utility script to create the grid, GoTileGridScript.

// GoTileGridScript
// Jonathan Shaftoe
// Utility script to create a linked grid of GoTile prims, each with a unique index.
// To use, rez a previously created GoTile from your inventory (see GoTileScript), then
// put a GoTile into the inventory of the one you rezzed, then put this script into it.
// It will spend a while rezing and linking all the GoTiles required, then once finished
// the script will delete itself from the object, now called GoTileGrid, which you take
// into your inventory and save for later.

// By default, this creates 33 sensors to make a total of 34, needed to cover a 19x19 board.
// If you only want to support smaller board sizes, you can reduce the totalTiles variable
// below to reduce the prim usage. For example, a 9x9 board just uses 10 tiles, so you can
// set totalTiles to 9.


integer gNumTiles = 0; // how many have we created
integer gCountRez = 0; // how many are waiting to be rezzed and linked.

default
{
    state_entry()
    {
        llSetObjectName("GoTileGrid");
        llRequestPermissions(llGetOwner(), PERMISSION_CHANGE_LINKS);
    }
    
    run_time_permissions(integer perms) {
        state start;
    }
}

state start {
    state_entry() {
        llWhisper(0, "Creating tiles, please wait ...");
        // 19x19 board needs 34, 6x2 sensors, minus the one we're in to start with
        integer totalTiles = 33;
//        integer totalTiles = 9; // just 9x9 for testing
        while (gNumTiles < totalTiles) {
            llRezObject("GoTile", llGetPos(), ZERO_VECTOR, ZERO_ROTATION, gNumTiles + 1);
            gNumTiles++;
            gCountRez++;
        }
    }
    
    object_rez(key id) {
        llCreateLink(id, TRUE);
        gCountRez--;
        if (gCountRez == 0) {
            llRemoveInventory(llGetScriptName());
        } else if (gCountRez % 4 == 0) {
            llWhisper(0, "... " + (string)gCountRez + " more tiles to make ...");
        }
    }

}

At this point, I seem to have hit the limit of wiki page size, so please carry on to Second page
Comments [Hide comments/form]
if you want to reduce the memory requirements of GoTileFaceScript put the value of gStateList in the state_entry directly and not have the "part*" globals.
-- BlindWanderer (2005-12-28 18:08:10)
Attach a comment to this page: