The following script started as an experiment with the llDetectedTouchFace() and llDetectedTouchST() functions, and turned into a two-player Tic-Tac-Toe game using a single prim for both display and user interaction. It uses the two aforenamed functions to determine exactly where on the prim the players have touched, and several of the OpenSim-specific dynamic texture functions to draw the game board and markers.
For best results, drop this script into a cube with the color of face 1 set to white, and every other face set to black, to make it obvious which face is the playing surface (since the dynamic texture is applied to every face on the prim). Ideally, the cube's X and Z dimensions should be identical, and it should be much thinner in the Y dimension (I used a cube scaled to <1.0, 0.05, 1.0>).
There's room for improvement - especially in the CheckForWin() function, which is a horrible kludge - but it works well enough for what it is. I don't recommend trying to play the game while your client is still downloading texture assets from the region, as that seriously hampers the display of the dynamic textures.
{L_CODE}:
// One-Prim Noughts and Crosses - OpenSim LSL script by Warin Cascabel (26 February 2009)
//
// Please note: as this script makes use of OpenSim-specific extensions to the Linden Scripting
// Language, this script will NOT work in Second Life.
// Internal variables.
//
list Spaces = [-1,-1,-1,-1,-1,-1,-1,-1,-1]; // Keeps track of what's in which space of the board. -1=empty, 0=O, 1=X
integer GameOver = 0; // If nonzero, signals which row, column or diagonal has the win (or -1 for a draw)
integer WhoseTurn = 1; // Whose turn is it next? 0=O, 1=X
// DrawBoard() - builds the dynamic texture command based on the contents of the Spaces list and the
// GameOver variable.
//
DrawBoard()
{
string dl; // holds the draw list
integer i; // iterator for our for loop
integer x; // holds the horizontal coordinate of each space
integer y; // holds the vertical coordinate of each space
dl = osSetPenColour("", "white" ); // Set the background to solid white
dl = osMovePen( dl, 0, 0 );
dl = osDrawFilledRectangle( dl, 128, 128 );
dl = osSetPenColour( dl, "black" ); // Draw the grid in black
dl = osSetPenSize( dl, 2 );
dl = osDrawLine( dl, 43, 3, 43, 125 );
dl = osDrawLine( dl, 86, 3, 86, 125 );
dl = osDrawLine( dl, 3, 43, 125, 43 );
dl = osDrawLine( dl, 3, 86, 125, 86 );
dl = osSetPenSize( dl, 6 ); // Increase the pen size for our Xs and Os.
for (i = 0; i < 9; ++i) // For each element in the Spaces list:
{
integer who = llList2Integer( Spaces, i ); // Get its value
if (who > -1) // Do nothing if it's -1 (empty)
{
x = (i % 3) * 43 + 21; // Calculate the center of the square
y = (i / 3) * 43 + 21;
if (who == 0) // If it's an O:
{
dl = osSetPenColour( dl, "lime" ); // Select a bright green pen color
dl = osMovePen( dl, x-17, y-17 ); // Move the pen to the upper left corner of the square
dl = osDrawEllipse( dl, 35, 35 ); // Draw the O
}
else // If it's an X:
{
dl = osSetPenColour( dl, "blue" ); // Select a bright blue pen color
dl = osDrawLine( dl, x-17, y-17, x+17, y+17 ); // Draw the first line of the X
dl = osDrawLine( dl, x-17, y+17, x+17, y-17 ); // Draw the second line of the X
}
}
}
if (GameOver > 0) // if GameOver is 1 or more, we will draw a red line through the winning row, column or diagonal
{
dl = osSetPenColour( dl, "orangered" ); // Select an orange-red color
if (GameOver == 1) dl = osDrawLine( dl, 5, 21, 125, 21 ); // Draw a horizontal, vertical or diagonal line,
else if (GameOver == 2) dl = osDrawLine( dl, 3, 64, 125, 64 ); // based on the value of GameOver. This is
else if (GameOver == 3) dl = osDrawLine( dl, 3, 107, 125, 107 ); // really kind of kludgy, but it gets the job
else if (GameOver == 4) dl = osDrawLine( dl, 21, 3, 21, 125 ); // done.
else if (GameOver == 5) dl = osDrawLine( dl, 64, 3, 64, 125 );
else if (GameOver == 6) dl = osDrawLine( dl, 107, 3, 107, 125 );
else if (GameOver == 7) dl = osDrawLine( dl, 3, 3, 125, 125 );
else if (GameOver == 8) dl = osDrawLine( dl, 125, 3, 3, 125 );
}
osSetDynamicTextureData( "", "vector", dl, "width:128,height:128,alpha:false", 0 ); // Draw the dynamic texture
}
// CheckForWin() - returns an integer value of -1 for a draw, 0 if there are still moves that can be made, or a positive
// integer specifying which row, column or diagonal contains the win. This is REALLY kludgy.
//
integer CheckForWin()
{
integer s1 = llList2Integer( Spaces, 0 ); // Get each space's value into its own integer for use in the tests below,
integer s2 = llList2Integer( Spaces, 1 ); // which calls llList2Integer() 9 times (instead of the 33 times that it
integer s3 = llList2Integer( Spaces, 2 ); // would take to call the function for each space in the tests below).
integer s4 = llList2Integer( Spaces, 3 );
integer s5 = llList2Integer( Spaces, 4 );
integer s6 = llList2Integer( Spaces, 5 );
integer s7 = llList2Integer( Spaces, 6 );
integer s8 = llList2Integer( Spaces, 7 );
integer s9 = llList2Integer( Spaces, 8 );
if ((s1 != -1) && (s1 == s2) && (s1 == s3)) return 1; // Top row
if ((s4 != -1) && (s4 == s5) && (s4 == s6)) return 2; // Middle row
if ((s7 != -1) && (s7 == s8) && (s7 == s9)) return 3; // Bottom row
if ((s1 != -1) && (s1 == s4) && (s1 == s7)) return 4; // Left column
if ((s2 != -1) && (s2 == s5) && (s2 == s8)) return 5; // Middle column
if ((s3 != -1) && (s3 == s6) && (s3 == s9)) return 6; // Right column
if ((s1 != -1) && (s1 == s5) && (s1 == s9)) return 7; // Top left to bottom right
if ((s3 != -1) && (s3 == s5) && (s3 == s7)) return 8; // Top right to bottom left
if ((s1 == -1) || (s2 == -1) || (s3 == -1) || (s4 == -1) || (s5 == -1) ||
(s6 == -1) || (s7 == -1) || (s8 == -1) || (s9 == -1)) return 0; // At least one space is open
return -1; // No more moves can be made, but nobody won.
}
default
{
// Start the game.
//
state_entry()
{
DrawBoard(); // Draw an empty board.
}
// The actual game logic all happens in touch_start(). It retrieves the coordinates of where on the front face the user
// touched, and from that calculates which space was touched. If it's empty, it fills it in with an X or an O, depending
// on whose turn it is, checks to see if there's a winner, and redraws the game board.
//
touch_start( integer numTouchers )
{
if (llDetectedTouchFace( 0 ) != 1) return; // Only accept touches from the "front" face.
if (GameOver) llResetScript(); // If the game's over, start a new one.
vector v = llDetectedTouchST( 0 ); // Get X,Y coords of the touch on the face.
integer index = llFloor( v.x * 3.0 ) + (3 * llFloor( (1-v.y) * 3.0 )); // Turn that into index into the Spaces list.
if (llList2Integer( Spaces, index ) >= 0) return; // If space is already filled, ignore touch.
Spaces = llListReplaceList( Spaces, [ WhoseTurn ], index, index ); // Set the value of the space to X or O.
WhoseTurn = 1 - WhoseTurn; // Swap whose turn it will be next.
GameOver = CheckForWin(); // See if anybody won yet
DrawBoard(); // Draw the new board.
}
}