さんもくならべ

 

このブログはセカンドライフ 技術系 Advent Calendar 2013向けに書いてみました。

 

普段ゲーム類を作っているので、せっかくなのでゲーム類をと思い、簡単そうな「三目並べ(まるばつ)」を作って見る事にします。

全部をがっつり説明すると長くなってしまうので、ざっくり説明ですがゲームを作って見たいと言う方はご参考までにどうぞ。

 

※簡易式に作ったので、製品化と考えるといろんな処理が抜けていますがロジックの説明がメインなのでご了承ください。

※僕はすべて独学でスクリプトを覚えたので、スマートなやり方では無く、もっと簡単に実装できる方法もあると思います。

 何か指摘等あれば、コメントかIMでお知らせいただけると嬉しいですw

 

完成系のフルパーミッションの物を本店に置いておきますので、それを見ながら記事を読むと分かりやすいかもしれません。

 

まずは用意したテクスチャがこちら

黒板のテクスチャ
黒板のテクスチャ
まるばつテクスチャ(黒背景)
まるばつテクスチャ(黒背景)
まるばつテクスチャ(アルファ)
まるばつテクスチャ(アルファ)

※まるばつテクスチャは背景透明で作りましたが、ブログではわかりやすいように黒背景にしてあります。

 一番右に見にくいですが透明背景のUPしたテクスチャもあります。

 

そして、UPして組み立てていきます。


まるばつの記入部分は1プリムで3面表示できる形にして作りました。

BOXプリムのテーパーを0.67にして Z軸を0にすればこんな感じになります。

 

それを組み合わせて上記の様な感じで作ります。

スクリプト

先に全部のスクリプトを載せちゃいます。

コメントアウト多めで書きましたので、説明はスクリプト内でー

////////////////////////////////////////////////////////
//
//   3目並べ
//
//   Ver.1.001
//   Created by TOMOZOO Lomu on 2013/12/16
//   Copyright (c) 2013 LIZAIL. All right reserved.
////////////////////////////////////////////////////////
 
//まるばつテクスチャ
string tex = "32052757-8ebb-8133-365e-4e910825ae1f";
list player_list;
string next_player;
integer game_flag = FALSE;
integer game_count;
 
/////////////////////////
//フローティングテキストセット
text_set()
{
    //未エントリー時用に名前リストの後ろに "------"を二つ足しておく
    list temp_list = player_list + ["------","------"];
    //テキストの色 待機時は白
    vector text_color = <1,1,1>;
    //テキストのメッセージ
    string text_msg = "Player1 = "+llList2String(temp_list,0)+"\n"+
                      "Player2 = "+llList2String(temp_list,1);
    if(game_flag == TRUE)
    {//ゲーム中の場合は 誰の番か表示を付けたす
        text_msg += "\n**\nNext :: "+next_player;
        //ゲーム中はテキストの色を赤にする
        text_color = <1,0,0>;    
    } 
    llSetText(text_msg,text_color,1.0);
}
 
/////////////////////////
//ボードのまるばつセット
 
//ボードのまるばつ情報のリスト 空白は ○は"0" ×は"1" 空は"-"が入る
list now_data_list = ["-","-","-","-","-","-","-","-","-"];
//○ × 空 の順番のテクスチャ座標のリスト
list tex_pos_list = [
    "0",<-0.25,0.25,0>,
    "1",<0.25,0.25,0>,
    "-",<-0.25,-0.25,0>];
 
disp_set()
{
    integer i;
    for(i=0; i<3; i++)
    {
        list v_pos_list;//1プリム分のテクスチャの座標を入れておく箱
        integer int;
        for(int=0; int<3; int++)
        {
            //面の○か×か空かという情報取得
            string taget = llList2String(now_data_list,i*3+int);
            //取り出した情報の座標を取得
            v_pos_list += llList2Vector(tex_pos_list,
                llListFindList(tex_pos_list,[taget])+1);     
        }
        llSetLinkPrimitiveParamsFast(i+2,[
            PRIM_TEXTURE,4,tex,<0.5,0.5,0>,llList2Vector(v_pos_list,0),0,
            PRIM_TEXTURE,0,tex,<0.5,0.5,0>,llList2Vector(v_pos_list,1),PI_BY_TWO,
            PRIM_TEXTURE,2,tex,<0.5,0.5,0>,llList2Vector(v_pos_list,2),0]);
    }
}
 
/////////////////////////
//メインコードスタート
default
{
    state_entry()
    {
        text_set();//フローティングテキストセット
        disp_set();//ボードをすべて空にセット
        llSetLinkColor(LINK_SET,<1,1,1>,ALL_SIDES); //ボードを全て白に戻す。
    }
 
    touch_start(integer total_number)
    {
        //プレイヤー追加 UUIDを名前に変換して名前リストに突っ込む
        player_list += llKey2Name(llDetectedKey(0));
        //エントリーしてどっか行くのを防ぐため2分でリセットかける
        llSetTimerEvent(120.0); 
        text_set();//フローティングテキストセット
        if(llGetListLength(player_list) == 2)
        {//名前リストが二人になったらゲームステートへ以降
            state game;
        }
    }
 
    timer() 
    {//タイムアウトに付きリセット 
        llSay(0,"time out.");
        llResetScript(); 
    }
}
 
//ゲーム中ステート
state game
{
    state_entry()
    {
        //先行決め 名前リストをランダムで入れ替え
        player_list = llListRandomize(player_list, 1);
        //次のプレイヤーの名前を取得
        next_player = llList2String(player_list,game_count%2);
        llSay(0,next_player+"さん先行でゲームスタートです。");
        game_flag = TRUE;  //ゲームフラグをONに
        text_set();//フローティングテキストセット
        llSetTimerEvent(120.0);//放置対策に2分でリセット 
    }
 
    touch_start(integer total_number) 
    {
        integer touchd_link_num = llDetectedLinkNumber(0);
        if(llKey2Name(llDetectedKey(0)) == next_player && touchd_link_num != LINK_ROOT)
        {//タッチした人が順番の人だった時 && タッチされたプリムがまるばつ部分だった時
            //タッチされた面のリスト上での番号を取得
            //タッチされたプリムのリンクナンバーの順番×3+面の番号
            //右上だと[0*3+0=0] 左下だと[2*3+2=11] と言う感じ
            integer touchd_link_face_num = 
                llListFindList([4,0,2],[llDetectedTouchFace(0)]);
            touchd_link_num = (touchd_link_num-2)*3;
            integer touchd_num = touchd_link_num+touchd_link_face_num;
            //タッチされた面のデータを取得して空のマスか調べる
            if(llList2String(now_data_list,touchd_num) == "-")
            {//タッチされた面が空だった時
                //リスト内のデーター入れ替え
                //該当場所の"-"を削除
                now_data_list = llDeleteSubList(now_data_list,touchd_num,touchd_num);
                //該当場所に"0"か"1"を突っ込む
                now_data_list = llListInsertList
                    (now_data_list,[(string)(game_count%2)],touchd_num);
                disp_set();//ボードセット
                //3目揃ってないか判定
                //3目並べにて3目揃う可能性のある盤面リスト
                list cleared_list =[
                    "0,1,2","3,4,5","6,7,8",//横揃い
                    "0,3,6","1,4,7","2,5,8",//縦揃い
                    "0,4,8","2,4,6"];//斜め揃い
                integer cleared_flag = FALSE; //揃っているかどうかのフラグを[揃ってない]にする
                integer i;
                for(i=0; i<8; i++)
                {
                    //3目揃う可能性リストからstring形式で抜き出してCSV形式でリスト化
                    list temp_list = llCSV2List(llList2String(cleared_list,i));
                    //それぞれのリスト上でのデータを抜き出して3個並べる
                    // ○,×,空の場合は "10-"と返ってくる
                    string data = 
                        llList2String(now_data_list,llList2Integer(temp_list,0))+
                        llList2String(now_data_list,llList2Integer(temp_list,1))+
                        llList2String(now_data_list,llList2Integer(temp_list,2));
                    // ○○○ or ××× になっていないか判定
                    if(data == "000" || data == "111")
                    {//3目揃っている時
                        //揃ったラインに色付け
                        vector color = <1,0,0>;
                        integer i;
                        for(i=0; i<3; i++)
                        {
                            integer num = llList2Integer(temp_list,i);
                            llSetLinkPrimitiveParamsFast(num/3+2,[
                            PRIM_COLOR,llList2Integer([4,0,2],num%3),color, 1.0]);
                        }
                        llSay(0,next_player+"さんの勝利です。");
                        llSleep(3.0); 
                        llResetScript();//3秒待機してからリセット
                    }
                }
                //すべてのマスが埋まっていたら引き分けで終了。 
                game_count++;
                if(game_count == 9)
                {//9ラウンドで埋まるのでラウンド数が9になっていたら引き分け
                    llSay(0,"引き分けです。");
                    llSleep(3.0); 
                    llResetScript(); //3秒待機してからリセット
                }
                //ゲームが続く
                //次のプレイヤーの名前を取得
                next_player = llList2String(player_list,game_count%2);
                text_set();//フローティングテキストセット
                llSetTimerEvent(120.0);//放置対策に2分でリセット
            }
        }   
    } 
 
    timer() 
    {//タイムアウトに付きリセット
        llSay(0,"時間切れに付きリセットします。"); 
        llResetScript(); 
    }
}
 

スクリプト説明

ゲームの当たり判定の処理のあたりだけ説明しておきます。

 

まず上の図のようにマスに番号を付けます。

タッチされた面を番号に変換するには

 

タッチされたリンクナンバーの上から数えた順番(0番スタート)×3

 +

タッチされた面の左から数えた順番(0番スタート)

 =

上の画像での番号となります。 

 

例えば左下だと 2×3+0=6 って感じですね。

 

次に3目並べのルールは 縦、横、斜めのいずれかで3マス揃えば勝ち というゲームです。

なのでまず 縦、横、斜めのリストを作りました。

 

list cleared_list =[

  "0,1,2","3,4,5","6,7,8",//横揃い

    "0,3,6","1,4,7","2,5,8",//縦揃い

    "0,4,8","2,4,6"];//斜め揃い

 

タッチされるたびにこの8個のリストと盤のデータリストを合わせて当たっていないか判定しています。

 

ちなみにこれは8通りしか当たりが無かった為こうしましたが、オセロや5目並べなど、

当たりのマスの種類が多いゲームでは次の様な方法をとる方がいい時もあります。

list now_data_list = [
    "*","*","*","*","*",
    "*","-","-","-","*",
    "*","-","-","-","*",
    "*","-","-","-","*",
    "*","*","*","*","*"];
list a = [1,-1,5,-5,-4,-6,4,6];//右,左,下,上,右上,左上,左下,右下

3×3の盤面に壁となる"*"を上下左右に足したリストです。

そのリストを for文でlist aに書かれた 右や左が同じかどうか判定して行く方法です。

壁を足したのは 例えば左下のマスの左を取得しようとした時3×3のリストのままでは中段の右を取得してしまうためです。

オセロなどにもこの方法を使われてる方は多いのではないでしょうか?

ちなみに僕のビンゴは100人規模のイベントでも耐えれるように負荷を減らそうとちょと違う方法を使っていますが、それはまた機会がありましたら書いていこうと思います。

まとめ

ある程度スクリプトに慣れている方でしたら、このコードを見て特に難しい処理をしているわけではない事が分かってもらえると思います。

ゲーム作りは難しそうだと敬遠されがちですが、意外と普段使ってる関数だけで特別な事をしないで作れるものも多いですので、作って行きましょう!!

 

最後に宣伝w

本店でスクリプトもフルパーミッションのスクリプトアイテムを30個ほど並べてありますので、コピペなり改造なりして使ってやってくださいませ。

※かなり昔に書いた物をそのまま並べていますので無駄な処理が多いとかスパゲッティーになっているとか、あんまり気にしないでねw