Monday, December 17, 2007

Building A Second Life Checkers Game #4

I was making good progress on the checkers game, but something was nagging at me in the design and I decided to take a step back and see if there might be a better way. In the current design, I've been saving the initial placement of all the pieces (as a list of vectors) on initialization and using those locations when people move or choose the location to move a piece. I had been avoiding calculating the locations programatically for no other reason than it seemed like it might take more thought (ie. lazy). But then there was the nag in my head that said it needed to be investigated, so I took the board apart and made a new one just to test how difficult it would be to programatically calculate the locations on the board.

Here is a screenshot of my mockup environment.

The positions would be numbered 0-63. The starting location would be 0. Touch the green button and the piece would move to the next position.

The first problem is to find the size of the board. Since it is the root object it's pretty easy to find. To note, if it's not the root prim you are screwed and it is very difficult, and I'll talk about that in the next post. The size (PRIM_SIZE) is the actual size of the object in meters. When calculating the location on that prim with local coordinates, the origin is at the center of the prim.

list params = llGetPrimitiveParams([PRIM_SIZE]);
vector sz = llList2Vector(params,0);


Next is to calculate the x and y row and column. This is also very simple given there are 8 rows and 8 columns.

integer px = curPos / 8;
integer py = curPos % 8;


I then calculate the size of each square and save it.

float sqr_x_size = sz.x / 8;
float sqr_y_size = sz.y / 8;


Once you have all that it is fairly straight forward to calculate the location of that square, but look at all the comments and see how many tests I went through to get it right. Tons of experimentation here.


//llOwnerSay(" sqr_x_size="+(string)sqr_x_size+" sqr_y_size="+(string)sqr_y_size);
vector p = < (sqr_x_size * px) - (sz.x/2) + (sqr_x_size/2),
(sqr_y_size * py) - (sz.y/2) + (sqr_y_size/2),
0.051 >;

//vector p = < (sqr_x_size * px) - (sz.x/2),
// (sqr_y_size * py) - (sz.y/2),
// 0.1 >; // the position without centering on the square
//vector p = < sz.x/2, sz.y/2, 0.1 >; // the origin
//vector p = < -sz.x/2, -sz.y/2, 0.1 >; // the negative origin - this didn't work
//vector p = <0,0,0.1>; // the 0,0 location which is the center of the board
//vector p = <(sqr_x_size/2),(sqr_y_size/2),0.1>; // just a futile experiement to offset from the origin some amount
//llOwnerSay(" new pos = "+(string)p); // the debug message seen over and over and over again

Given all that, the actual calculation that I used can be summed up like this.
For x: The location given the x location minus the offset because the origin is the center + the offset of half a square to center on the square.
For y: Same as x
For z: Just a number which I twiddled to get right.

The last step is to set the actual location. This is pretty simple given that you know the link num for the piece you want to move. This number was retrieved on initialization. After that, change curPos and wait for the next touch.

llSetLinkPrimitiveParams(pieceLinkNum,[PRIM_POSITION,p]);
curPos++;


And yes, this was more work, but as I expected it will be a lot more efficient and easier to work with in the long run.

Friday, December 14, 2007

Building a SecondLife checkers game #3

Serious design problems.

My checkers game is now receiving a:
Script run-time error
Stack-Heap Collision

I know what the problems are and I know how to fix it, but it does show how flexible software development needs to be. I knew going in that memory constraints on the scripts were going to be tight, I just didn't figure I would hit them this quick. Here is the latest screen shot.

The good news is that I know what the problem is and I may not need to change the design too much.

The green triangles are not visible during game play except to choose the location where you can move a piece. The mechanic is to touch the piece you want to move, then the green triangles appear in locations that are open, then you click on the triangle. I've got all the movement and placement of the triangles working.

The problem occurred when I created enough extra triangles for the number of moves that might be available. For the pieces I'm saving the original location vectors (three floats) in a list. Including the ones in the center which will become the king pieces. Then removing the kings from the board (you can see the storage box in the back which will be moved under the table at the end. These vectors are big and I was also saving the triangles just for testing and that is where I hit the problem. I can really just keep them off the table at all times and don't need their initial locations. So, even though I hit what seems like a big snag, all is not lost yet... And even if it were, there is always a work around. Always.

Thursday, December 13, 2007

Building A Second Life Checkers Game #2

I'm making progress on the checkers game and thought I would talk about some of the data structures.

The available LSL data structures are quite limited, but enough to make scripts that do anything.

The biggest thing you learn early on is that there are no arrays. At least not like in other languages. What they do have are lists which are basically arrays, but you have to use special functions to manage the arrays.

As an example, I know my board has 32 spaces so I have a list that contains the piece number at any given space. You first have to allocate space for the 32 spaces.

list board;

InitializeBoard()
{
integer i=0;
for(;i<32;i++)
{
llListInsertList(board, [-1], 0); // -1 means no piece is there
}
}

As I traverse through the linked pieces (see previous entry), I insert them into the board at their location which is defined with a little CSV in their title "r,1,1". The first letter is the color, the second is the piece number and the third is the location which are the same. I may have over designed the piece number and location...

Once you know the piece number you can add it to the data structure (list) holding the locations on the board.

board = llListReplaceList(board,[piece_num],piece_location,piece_location);

One thing to note, ReplaceList creates a whole new list and returns it. You WILL at some point screw this up and not have the board = in there and wonder why your list update didn't occur. It is just part of the learning curve. I'm sure I'll do it again and again and waste more hours. I think the compiler should warn you of this since it seems so necessary and such a common mistake.

When you want to access an element in the list you do so knowing the type that you previously stored in the list. In this case it was an integer and if I want to know what was at location 3, I would do this.
integer pieceAtLocation = llList2Integer(board,3);

For other types stored in lists, there are other functions, llList2String, llList2Vector, llList2Key, etc...

Wednesday, December 12, 2007

Distilling a Second Life harassment incident

When building my checkers game I've had a number of people approach and touch the board and at least two attempts to disrupt and divert my attention. On both I asked them nicely to stop and reported the incident as harassment when they didn't. I sent an IM to both people. The second person (who had built a lot of large cubes over the area I was working and made me move, he also used a lot of expletives) became irate and came back upset when I told him I had reported the harassment. I didn't responded to any of the verbal attacks and I was finally able to distill the incident by saying something like this.

Me: Hey, how about we work together on something together instead being disruptive. I find that life is a lot more fun when we help each other and work together.
Me: I think it would be interesting to work on a project with you. I have a lot of programming experience and help on the scripting side of a build.
Mr. Disruptive: You mean a truce?

(I never knew I was at war)

Me: Yeah, sure.
Mr. Disruptive: Okay, cool.

He had left the area and then asked me for a teleport. He never really talked to me again, but he hung out in the area tweaking a spheroid and soon added me as a friend. So as I've found in life and now in virtual life, bad situations can usually be distilled by an offer to work together even when one person is bent on being disruptive. The charity work I do is the most rewarding thing that I have ever done and I'm always looking for ways to continue those efforts. It seems there is even room for this type of work in the virtual world!

I wrote this up a few days ago and he actually came back around to show me some new emotes he had received from a friend. I think he's just a kid, but its much better than the initial interactions.

Tuesday, December 11, 2007

Building a Second Life Checkers Game

I (SL: Wood Wheels) have been working on a checkers game in Second Life. This is just a learning experience and a chance to really dig into a new language. I've found that the best way to learn a new language is to build something and keep increasing the complexity. I've built a bunch of houses, doors, lamps and a soccer ball, but this is definitely the next step in the evolution of my understanding.
There are a bunch of lessons learned so far and I thought this would be a good opportunity to pass some of those on beyond a couple of updates to the lslwiki and slwiki.

The first lesson was where I started out giving each game piece it's own script and sending messages back to a larger script attached to the board. This was using Link Messages (llMessageLinked). It was good that I figured out early that this was the wrong way after reading the section on Script Effeciency. #1 in efficient design: If you need to have a bunch of buttons, don't put a script in each prim, use llDetectedLink number.

I now have only one script in the board. By default, each linked prim has a link number and you can traverse the list of linked prims using something like this:


integer count = llGetNumberOfPrims();
integer i = 2;
for (;i<=count;++i)
{
string stName = llGetLinkName(i);
list namelist = llCSV2List(stName);
string stType = llLink2String(0);
string stPieceNum = llLink2String(1);
if ((stType=="r") || (stType=="b"))
{
llOwnerSay("have a "+stType+" piece numbered "+(string)stPieceNum);

// I also save the link number, color and piece number
// in a list which I'll show later
}
}

I gave each piece on the board an object name of "r,1" or "r,2" which allows me to identify which piece is touched during the move. I then convert this name to a list (llCSV2List()) to separate out the color and piece number. You might note that i started with 2. The board, which is the root prim (the one selected last when linking), is number 1 and I don't include it in the lookup. I don't have the move mechanic completed, but the early design of it is working and I'm refining the movement code now.

There is probably a lot more to talk about and lists are a big part of the design. I'll try and write more on that tomorrow.