二重のfor文を使ってビンゴカードを作る

今回の授業は、for文を使って、ビンゴカードを自動作成しようというものでした。

二重のfor文

ポイントは、二重のfor文です。「5回の繰り返し」を5回繰り返すことで、5×5の25個の数字を並べます。

// 二重のfor文(c0,c1,c2,c3,c4を5回繰り返す)
for(var r=0; r<5; r++){
    console.log("r" + r);//r0~r4
    for(var c=0; c<5; c++){
        console.log(" c" + c);
        // 座標に応用
        var x = 32 * r;// 授業とはrとcが逆(列と行を逆にしたいから)
        var y = 32 * c;
        console.log(x + ", " + y);
        // ラベル
        var label = new Label();
        label.x = x;
        label.y = y;
        label.text = "[" + r +","+ c +"]";// こちらも逆にする(これで[列、行]の並びになる)
        scene.addChild(label);
         label.text = getRandom(1, 75);
    }
}

c0からc4をr0からr4まで5回繰り返すということです。(授業でやったものとはrとcを逆にしました。列でまとめたかったからです)

c0~c4が5列に並ぶという形になります。

ラベルのところでテキストを「“[” + r +”,”+ c +”]”」とすることで、

[00][10][20][30][40]

[01][11][21][31][41]

[02][12][22][32][42]

[03][13][23][33][43]

[04][14][24][34][44]

となりました。(rが列、cが行となっています)

あとは label.text = getRandom(1, 75); で、それぞれの表示を1〜75の数字をランダムに出るようにすれば、ビンゴカードの出来上がりです。

・・しかし、これだとそれぞれの数のランダムなので、数が重複する場合があります。(それはそれで二つ空けられるラッキーナンバーということにすればいいのかもしれませんが、一般的なビンゴゲームにも一応ルールがあって、その中には数は重複しないというのがあります)

数が重複しないようにシャッフルを使う

そこで、数が重複しないように作るにはどうしたらいいかということで、その方法も教えてくださいました。

ここでダステンフェルドのシャッフルを使います。

// 配列を使ってダブらないビンゴカードにする
// 配列を作る
var nums = [];
for (var i=0; i<25; i++){
    nums.push(i);//numsの中にiがプッシュされていく。今iは0~24の25個の数字
}
console.log(nums);
// ダステンフェルドの手法でシャッフルする
for(var i=nums.length-1; 0<i; i--){// iが24から1まで24回繰り返す
    var rdm = getRandom(0, i);// rdm:自分の数以下からランダムに選んだ数字一つ
    var tmp = nums[i];// 新しくtmpを用意してnum[i]を入れておく
    nums[i] = nums[rdm];// nums[i]に選ばれたランダムな数を入れる
    nums[rdm] = tmp;// nums[rdm]にtmp(muns[i])を入れる。これで交換完了
}
console.log("nums", nums);

0から24までの数をシャッフルしています。

どうなっているのかわかりにくいのでそれぞれconsole.logで見てみました。

// ダステンフェルドの手法でシャッフルする
for(var i=nums.length-1; 0<i; i--){// iが24から1まで24回繰り返す
    console.log("i:", i, "," , "muns[i]:", nums[i]);
    var rdm = getRandom(0, i);// rdm:自分の数以下からランダムに選んだ数字一つ
    console.log("rdm:" , rdm , "," , "nums[rdm]:" , nums[rdm]);
    var tmp = nums[i];// 新しくtmpを用意してnum[i]を入れておく
    console.log("tmp(=nums[i]):" , tmp);
    nums[i] = nums[rdm];// nums[i]に選ばれたランダムな数を入れる
    console.log("muns[i](=nums[rdm]):" , nums[i]);
    nums[rdm] = tmp;// nums[rdm]にtmp(muns[i])を入れる。これで交換完了
    console.log("nums[rdm](=tmp):" , nums[rdm]);
    console.log(nums);
    //全部consoleで見てみる
}
console.log("nums", nums);

すると、このような動きになっていました。

for文の繰り返しの中でその都度配列の順番が変わって上書きされるので、最初はnums[22]は22だったのが、入れ替わったことで途中でnums[22]は24に変わっています。

一度交換の相手になったランダムで選ばれた「数字」は、後ろから順番に入っていって固定され(iは減少していくから)二度と交換されることはありません。そうでないと精度的に正しいシャッフルとなりません。

ラベルで表示する

あとは、ランダムに並べ替えられた数字にindexを用意して、num[0]~num[24]までがラベルのテキストに入るように書き換えて、ランダムに並んだ数字を表示できました。

// インデックス
var index = 0;
//label.text = "[" + r +","+ c +"]";// こちらも逆にする(これで[列、行]の並びになる)
label.text = nums[index];// 初期値は0
index++;// ラベルのテキストがnums[0]からnums[24]になる

一般的なルールのビンゴカードを作る

つぎに、これを応用して、日本でのビンゴの一般的なルールにしたがったものを作ってみたいと思ってやってみました。

wikipediaのビンゴより

ルールには無数にバリエーションがあるが、ここでは日本で一般的に行われている代表的なものを示す。一般的にビンゴカードはタテ・ヨコ5マスずつ、計25個のマス目が書かれている。その内、中央を除く24マスには1から75までの番号のうち24個の番号が書かれており、中央はフリースポットとして最初から有効な番号として扱われる。1枚のカードの中で同じ番号が重複することはない。

カードに書かれる番号は、通常はまったくのランダムで配置されているわけではなく、一番左の列は1-15の中から5個選ばれている。同様に、左から2列目は16-30、中央列は31-45、右から2列目は46-60、一番右の列は61-75から5個ずつ(中央列のみフリースポットがあるので4個)選ばれている

全くのランダムではなく、15づつの数の固まりから5個ずつ選ぶようになっているようですね。

ちょっとややこしそうですが、for文をたくさん使ってやってみました。

1〜15まで、16から30まで、31から45まで、46から60まで、61から75までの数からそれぞれ5つずつランダムに選ぶ必要があるので、それぞれシャッフルした15個の中から5つを選んでそれを左の列から並べるというやり方で作りました。

まず、nを始めの数とする変数として15個の数のシャッフルを作る関数 Nums15(n) を作ります。ここでやっていることは、要素15個の配列をシャッフルして、その最初から5つの目までの数を取り出してnumsselという配列に入れていくという処理です。既にシャッフルされた数なので、そこから5つの数を取り出せば、ランダムで重複しない数となります。

    // 目的の配列を用意
    var numssel = [];

    //nをはじめの数とする15個の数字のシャッフルを作る関数
    function Nums15(n){
        var nums15 = [];// 15個の配列の入れ物を用意
        for (var i=n; i<n+15; i++){
            nums15.push(i);// n~n+15までの数を[]に入れる
        }
        // シャッフル
        for (var i=nums15.length-1; 0<i; i--){
            var rdm = getRandom(0, i);
            var tmp = nums15[i];
            nums15[i] = nums15[rdm];
            nums15[rdm] = tmp;
        }
        // 出来たランダムな配列nums15から5つの数字を取り出してnumsselという配列に入れていく。
        for(var i=0; i<5; i++){
            numssel.push(nums15[i]);// 最初から5つをnumsselに入れていく
        }
    }

あとは、そのかたまりの始めの数は1,16,31,46,61なので、その関数をつぎのようなfor文で実行します。

// 実行する。ここでnumsselが最終的に5*5で25個となる
for(var i=1; i<76; i+=15){ 
    Nums15(i);
}

すると、ランダムに選ばれた5つの数を5回入れていくので、numsselには、1~15から5つ、16~30から5つ、31~45から5つ、46~60から5つ、61~75から5つ、合計25個の数が選ばれて入ります。

ただし、真ん中はFreeにしたいので、真ん中はmussel[12]なので

numssel[12] = "free";

とします。

あとは、授業でやったときとおなじように、この配列musselにindexを使ってラベルに対応させて表示させれば、左の列からルールのようにならんだビンゴカードができました。

forをたくさん使ったので、すごくforの練習になりました。functionも使ったので、途中、頭がこんがらがって死にそうになりました。関数は作ってもその実行は別の場所でやる感じなので、コードでは後ろに書いてあっても処理としては戻るような感じになって、あっちこっちで訳がわからなくなりそうでしたが、これも慣れなのでしょうか・・頭の中でforループの処理を同時にシミュレーションできないと書けないと思うので、これがサラッとできちゃうプログラマさんてすごいね。でもfunctionmとforを使うと本当に短く書くことが出来て、達成感がありました。あと今見て思ったんですが「配列の配列」ってあるんかな、当然あるでしょうね。(私は汚いコードでも動けばいいという^^;だめよねそれじゃ)

文字の基準が中心で揃っていなっていないのが気になります・・そのうちこれを使って、何かゲームが作れたら作ってみたいと思います・・(授業で話しが出た独りビンゴなんかいいと思うけどなー)

完成したもの

グループの中の最上位置の判定とスクロール

下書きしたまま放置の記事が結構ありますが、あんまりきちんとと堅苦しくなると続けられなくなるので、思いつくまま軽い感じでいきたいと思います。

今日の授業は最近の中でも面白く感じたので、記録として書いておきます。

今チャレンジしているゲームですが、実は息子は、今回のテーマの初回の授業を受けた後から、偶然にも、先生がモデルにされたサリータワーと全く同じようなものを作りたいと作り始めていました。左右に移動するもの(授業ではヘリ)から物(授業ではブロック)を落として、積み上げるというものです。

前回の授業では、ブロックが積み上げられたら、ヘリにぶつかってしまうので、そうならないようにヘリを上昇させるにはどうしたらいいでしょうね、というところで終わりました。

息子は続きを作るべく、そこに挑んでみましたが、これは難しいのです。なぜなら、「ブロックが上のほうまで積まれてきたら」というのが問題なのですが、これを判定しようにも、ブロックは一番上の位置から落としているのです。落とす時点で既に一番上にあるのですから、「ブロックの位置から・・」とやろうとすると、落とす時点でもう積んでいる。だから「積まれたブロックの一番上のものから・・」とやろうとするなら、いつ判定するのか、どうやって判定するのかということになります。この時点で、私はもう面倒くさそうと思ってしまい、授業では擬似的な方法で簡単にそれっぽく見せる方法を採られるのではないかと思っていました。

そこで、今日の授業です。授業で示された方法は、「ブロックを落としてから2秒後(連続して落としていても最後に落としたものから2秒後)に、落としたブロックをすべて調べて一番上にあるものの高さを取り、それとヘリの高さを比較して一定以上近ければ離す」というものでした。

先生は「如何に誤魔化すか」と言われましたが、私は結構な正攻法じゃないかと思ってさすが先生と思いました。

    // タッチするとブロックが落ちる(と同時にreadyAlt()が発動)
    scene.on(Event.TOUCH_START, function(e){
        dropBlock();
        // (1) タッチしたタイミングで処理を開始しよう
        readyAlt();    
    });

    function dropBlock(){
        var block1 = new PhyBoxSprite(31, 31, enchant.box2d.DYNAMIC_SPRITE);
        block1.image = core.assets["images/do_daruma.png"];
        block1.x = heli.centerX;
        block1.y = heli.centerY;
        group.addChild(block1);
    }

    // (2) 処理を遅れさせよう(readyAlt()の中身)
    function readyAlt(){
       scene.tl.clear();// これを入れることで、落とす度にリセットされ、最後に落としたものからのカウントになる
       scene.tl.delay(32);// 32フレーム経ってからceckAlt();が発動
       scene.tl.then(function(){
          checkAlt(); 
       });
    }

    // (3) ブロックを調べる処理(checkAlt()の中身)
    function checkAlt(){
        console.log("高さを調べる");
        // (4-1) グループで最も高い位置にあるブロックを調べる
        //for(初期化; 条件; 後処理;){処理}
        var borderY = 480;// 画面の一番下。これよりブロックは下へ行くことがないので、これをブロックの最上位の位置の初期値とする。
(これとブロックの位置と比較して、それより上の位置なら最上位として更新するという仕組み)
        for(var i=0; i<group.childNodes.length; i++){ // ブロックの数だけ繰り返す(ブロック全部調べる)
            console.log(i);
            var y = group.childNodes[i].y;// ブロックのi番目のy座標が取得される。
            console.log(y);
            //高い位置にあれば更新
            if(y < borderY){
                borderY = y;// 繰り返すことで最終的には一番高いyがborderYとなる。
            }
        }
        console.log("最も高い位置にあるのは:" + borderY);
        // (4-2) 高い位置から一定の距離を保つ
        var padding = 130;// ヘリと一番上のブロックの距離
        if(borderY < heli.y + padding){
              heli.y = borderY - padding;
        }
    }
    
    // (5) スクロールさせる
    scene.setScrollRange(heli,80,0);// ヘリが上に上がっていくと見えなくなるのでスクロールさせる

どうやって一番上のブロックのy座標を取り出すのかというと、まずブロックはy座標480より下に行くことはないので、borderYの初期値を480としておきます。そして、1個めのブロックのy座標と比較して、これより上ならそのy座標の値をborderYに書き換え、さらに2個めのブロックも同様・・とブロックの個数分つまり全部で繰り返せば、最終的にborderYが一番高いブロックのy座標の値に書き換えられているという訳です。プログラミングではこうやってやるんだーと感心、納得。

こうして順序立てて教えていただくと、想像したより難しくなかったですし、考え方ややり方はほかにも色々応用できそうです。