アナログ時計

久しぶりの記事投稿になります。

息子の受験も無事・・(ではなく、波乱に富んだものとなりましたが^^; )、一段落がつきましたので、勉強をしつつまたこちらに記録を残していきたいと思います。

〜・〜・〜・〜・〜・

9月の発表では、アナログ時計をつくることにしました。

授業ではデジタル時計をやったので、それを針と目盛りでどのように画像にするのかがポイントです。

メモリを360度に配置するというのは意外に難しくて、canvasという機能を使おうとしたのですが、enchant.jsでそれをやろうとしてもそのままではできないことがわかり、・・というような経緯がありました。その辺りのことも含めて、今回は簡単に紙にメモをしておいて発表しました(前回の発表があらら・・状態だったので)のでそのメモも。

アナログ時計のソースコード

発表のメモ

 

スロット(2)

画面タッチでスロットスタート。
2秒で止まって判定します。

「スロット」の授業をお休みしたので、そのときのプリントを先生がくださいました。・・ら、動画で見た書き方と全然違うじゃないですか(^○^)

こちらは「時計を作る」のときにも使ったsetIntervalを使ってスロットを回転させて、何秒後かに一回だけ実行されるというsetTimeoutの中でsetIntarvalを止めるclearIntervalを使って、3秒後にスロットを止めて当たりかどうかを判定する、というやり方でした。回転中にクリックするとおかしくなるので、回転中はタッチできないようにしました。ハズレのときの音もつけていましたが、繰り返すとどうしてもエラーになってしまうので外しました。

スロット(2)のソースコード

スロット

この時の授業はお休みしたので動画を見ながらなぞって作ったものです。

息子がかわいらしい音をつけるところまで作ったので、そこから少し改良してボタンでスタート・ストップ、揃ったらクリア画面が出て、またボタンを押すと回せるようにしました。ゲームは何度も繰り返して遊びたいのです!これも当たったら終わりでも良かったのですが、また回せるように改良しようとしたら意外に難しくて苦労しました。この「何度も遊べるようにする」ところでいつも苦労しますね。

スロットのソースコード

コメント以外で//消してあるところは、試行錯誤の時にうまくいかなかったものです。消してしまうとまた同じ間違いを書いてしまうので、これは出来なかったよということがわかるように残してあります。すぐ忘れてしまうので・・。こうして作ったものも、どうやったのか1ヶ月もすると忘れてしまっているので、こうして記録しています。

・改良したところ

ボタンを設置

場合分けをするためにcounterを作って、ハズレが出ても当たりが出ても再度スロットを回せるようにしました。当たりが出ると背景が変わり文字がでるので、再度回し始めるときに文字を消して背景を戻すために工夫が必要でした。

0:最初かハズレの判定が出た時(ボタンを押してスロットルをスタートすることができる) 
1:スロットが回っている間(ボタンを押してもスロットはスタートできない)
2:当たりの判定が出たとき(再度ボタンを押すと背景が戻り、スロットが回る)

として動作を制御しました。

一つのボタンでやろうとするとこのように面倒なことになります。

時計を作ってみよう(javaScript)

1.時計の表示

今回の授業は、デジタル時計を作るというものでした。

「一秒ごとに何かが起きるというjavascriptの機能を使う」と先生は説明されました。(子供向けにこのような説明の仕方をされるところが面白いですね)

まず、一定時間ごとに処理をするというsetIntervalを使った関数を作ります。

// 1秒毎に処理をする
setInterval(function(){
    console.log("Hello!!");
},1000);

時間はミリ秒で指定します。ここでは1秒ごとに処理をしたいので、1秒=1000ミリ秒で指定します。

console.logで確かめると、一秒ごとにHello,Hello,と表示されるので、処理されていることが確かめられました。

時計の処理の関数を作って、この中に入れます。(console.logをshowClock();に書き換える)

function showClock(){
    console.log("時計の処理!!")
    // 日付オブジェクト
    var dObj = new Date();
    // 時間を取得する
    var hours = dObj.getHours();//日付から時間をgetする
    console.log(hours);
    var minutes = dObj.getMinutes();//日付から分をgetする
    console.log(minutes);
    var seconds = dObj.getSeconds();//日付から秒をgetする
    console.log(seconds);
}

new Date();で、今現在の日付のデータを取得し、その中に含まれている時間、分、秒を取り出しています。それぞれconsole.logで

10
11
05

のように表示されます。これではわかりにくいので、横並びに表示されるようにします。

    // デジタル時計
    var str = hours + minutes + seconds;//数値として加算されてしまうので間違い
    console.log(str);
    var str = hours + ":" + minutes + ":" + seconds;
    console.log(str);
    var str = hours + "時" + minutes + "分" + seconds + "秒";
    console.log(str);

単に+でつなぐと、数値として扱われるために、加算されて一つの数値となってしまいます。

そこで、間に文字列を挟むことで、

10:11:05

あるいは

10時11分05秒

と表示させることができました。

2.日付の表示

次は日付です。

    // 年月日を取得する
    var year = dObj.getYear() + 1900;
    console.log(year);
    var month = dObj.getMonth() + 1;
    console.log(month);
    var date = dObj.getDate();
    console.log(date);

これもさっきと同じように、dObjから年、月、日のデータをそれぞれ取り出します。

ここで注意が必要なのは、getYearでは1900マイナスの年が取り出されるので(1900年台に始まったコンピュータの世界で、下2桁で処理をしていた為と考えられるそうです)、1900をプラスしたのが現在の年になること(代わりにgetFullYearとすると2018で出すことができるようになっている)、月は1月から配列として扱うのが都合がよいので(月は国によって呼び方が異なるから。日本では1月、2月だが、January、 February・・だったり)、1月は0というデータとして与えられるから1を加えること、日付はそのまま(多分これはどこの国でも1日、2日だから)となることです。

これを横並びで表示させるには、時計のときと同じように

    // デジタル日付
    var str2 = year + "年" + month + "月" + date + "日";
    console.log(str2)

となります。

2018年8月25日

と表示されました。

3.曜日の表示

最後に曜日です。

    // 曜日
    var arr = ["日", "月", "火", "水", "木", "金", "土"];
    var day = dObj.getDay();//日付から曜日をゲットする
    console.log(day);
    console.log(arr[day] + "曜日")

まず、曜日の配列を作ります。次に日付のデータから曜日をゲットします。曜日は、日曜日を0として土曜日まで、0,1,2,3,4,5,6という数字で与えられます。今日は土曜日なので6と出ました。

そこで、曜日の配列から、その数値を添字としたものを取り出せば、

土曜日

と今日の曜日が表示させることができました。

4.【感想】

この日の授業は盛りだくさんの、ぼーっとしている暇のないとても濃い授業でした。どうして年は1900を引かれた値になっているのだろうかとか、コンピュータの起源と歴史にまつわる話が出てきたり、なぜ月のデータは0から始まっているのか、考えてみたら日本では1月2月・・だけど英語圏や他の国では違うよね、日本でも睦月、如月・・と呼んでいたとか、いろいろ興味深い話題もいろいろ出てきました。

それにしても、この日の授業はゲームのように画像が動いたりするのでもなく、ただコンソール画面に表示される数字や文字を見ながら、「おぉー!」とか「わー!」とか盛り上がっているという、なんとまあプログラマーさん(の卵たち)でしょうかと思いました。(知らない人が見たら一体何が面白いの何を喜んでいるのという感じでしょう。)みんな打ち込むのもすごく速くなっているし。

先生方、よくここまで子どもたちを育てられましたね・・と少し感動を覚えた私でした。

2018_8_4_配列と繰り返し処理を使った平均値の計算と複数の配列とランダムを使った自動作文

ソースコードまとめ

Coding泪橋(仮)_No001
平均値を理解する

// 処理1
// 目的:変数と計算を理解する
// 問題:Consoleには何が書き出されるか
var a = 10;
var b = 20;
var c = 30;
var total = a + b + c;
console.log(total);
var avg = total / 3;
console.log(avg);

答え total:60,  avg:20

// 処理2
// 目的:配列と添字を理解する
// Consoleには何が書き出されるか
var arr= [80, 60, 10];
var total = arr[0] + arr[1] + arr[2];
console.log(total);
var avg = total/ 3;
console.log(avg);

答え total:150,  avg:50

// 処理3
// 目的:配列と繰り返し処理を理解する
// 問題:Consoleには何が書き出されるか
var arr = [20, 70, 50];
var total = 0;
for (var i=0; i<arr.length; i++){
    total += arr[i];
}
console.log(total);
var avg = total / arr.length;
console.log(avg);

答え total:140,  avg:46.666666666666664

(繰り返し処理を使って平均値を出す方法。処理2のやり方では数字が多くなると大変だがこれだと簡単)

Coding泪橋(仮)_No002
ランダムを理解する

// 処理1
// 目的:配列を添字を理解する
// 問題:Consoleには何が書き出されるか
var arr = ["プリン", "ヨーグルト", "シュークリーム"];
var str = "おやつに" + arr[0] + "を食べたいぜよ!!";
console.log(str);

答え おやつにプリンを食べたいぜよ!!

// 処理2
// 目的:ランダムを理解し、整数に変換する事ができる
// 問題:Consoleには何が書き出されるか
var rdm = Math.random() * 3;//Math.randam()とは、0以上1未満のランダムな数
console.log(rdm);
var num = Math.floor(rdm);//Math.floorとは小数点以下切り捨て
console.log(num);
var str = "おやつに" + arr[num] + "を食べたいぜよ!!";
console.log(str);

答え rdm:(0以上3未満のランダムな小数),  num:(0,1,2のいずれか), str:おやつに◯◯◯が食べたいぜよ!!(◯◯◯はランダムに変化)

// 処理3
// 目的:複数のランダム値、配列を用意して自動作文を作る
// 問題:Consoleには何が書き出されるか
var arr1 = ["神様", "俺様", "貴様", "何様"];
var num1 = Math.floor(Math.random() * 4);
var arr2 = ["学校", "ファミレス", "火星", "ハワイ"];
var num2 = Math.floor(Math.random() * 4);
var arr3 = ["勉強", "注文", "探検", "旅行"];
var num3 = Math.floor(Math.random() * 4);
var str = arr1[num1] + "が" + arr2[num2] + "で" + arr3[num3] + "した";
console.log(str);

答え (ランダムな作文)

おさるでポイポイ

クリックすると、猿がりんご、バナナ、さくらんぼのどれかをランダムに落とします。

落とした分だけ得点にカウントされますが、地面に落ちた分はマイナスされるので、かごに積んだ分が得点になります。

時間は50秒で、スコアと、りんご、バナナ、さくらんぼの数の分の絵が表示されます。

作り込みの授業のときに困ったところをいくつか先生に助言をいただいたおかげでそのときにすごく進めることができたみたいで喜んでいました。やっぱり一人で作るより助けを借りて作ったほうがいいですね。

最後に、結果画面のところに、「積んだ果物をそれぞれの個数分で画像で並べて出したい」と言い出して、(ノ∀`)アチャーまた難しそうなことを(ヤメテー)・・と思いましたが、偶然にも私の助言のおかげで(エッヘン)実現することができました。

その結果画面について、難しかったので覚えで書いておきます。息子に説明してもらいました。

何が難しいかというと、

①りんご、バナナ、さくらんぼの数はそれぞれその都度異なる。

②それぞれその都度異なる数のものを、等間隔に並べて表示する。(x座標の問題)

③端まできたら、次の行に移して並べていく。(x,y座標の問題)

どういうアルゴリズムでやればいいのでしょうか?

①りんご、バナナ、さくらんぼの数を配列にしてセーブします

var howmanyF = [0,0,0];//フルーツの数を入れておく配列をつくる(howmanyF[0]がりんごの数、
howmanyF[1]がバナナの数、howmany[2]がさくらんぼの数)

function dropBlock(){
    var fruits1 = new PhyBoxSprite(32,32, enchant.box2d.DYNAMIC_SPRITE,1,0.5,0);
    var rdm = getRandom(0,2);
    //ifと同じだが2つ以上だとこっちが楽
    switch(rdm){
        case 0: fruits1.image= core.assets["images/cf307/monkeyapple.png"];
            howmanyF[0] +=1;//りんごを落としたらりんごの数が+1される
            break;
        case 1: fruits1.image= core.assets["images/cf307/banana.png"];
            howmanyF[1] +=1;//バナナを落としたらバナナの数が+1される
            break;
        case 2: fruits1.image = core.assets["images/cf307/sakuranbo.png"];
            howmanyF[2] +=1;//さくらんぼを落としたらさくらんぼの数が+1される
            break;
    }

(略)

// (2)当たり判定(ground x group)//フルーツが地面に当たったら
ground.addCollision(group);
ground.on(Event.COLLISION, function(e){
    //console.log(e);
    var target = e.collision.target;
    target.remove();
    switch(target.image){
        case core.assets["images/cf307/monkeyapple.png"]:
            console.log("aplle");
            howmanyF[0] -=1;//りんごだったらりんごの数を-1する
            break;
        case core.assets["images/cf307/banana.png"]:
            console.log("banana");
            howmanyF[1] -=1;//(以下同様)
            break;
        case core.assets["images/cf307/sakuranbo.png"]:
            console.log("さくらんぼ");
            howmanyF[2] -=1;//(以下同様)
            break;
    }
    if(score != 0){
        score--;
    }
});

rope.tl.delay(16);
rope.tl.then(function(){
    timer--;
    console.log("あと"+ timer + "秒");
    timelabel.text = "TIME: " + timer;
    if(timer <= 10){
        timelabel.color = "red";
    }
    if(timer == 0){//0秒になったら
        BGM.stop();//音を止める
        console.log("おしまい");
        console.log(howmanyF);
        localStorage.setItem("howmanyF", howmanyF);//howmanyFをセーブする
        localStorage.setItem("monkey_score", score);
        endStart();
    }
});
rope.tl.loop();

②りんご、バナナ、さくらんぼをセーブした個数分画像で並べる

例えば、りんごを15個並べたいとき、10個目までは32ピクセルずつずらしてならべ、11個目からはまたx座標は0に戻さないといけないのです。

x座標で言うと、0のときは0、1のときは32×1、2のときは32×2、・・・9のときは32×9、そして10のときはまた0に戻る、これはどう書けばいいのか・・

息子は悩みはじめました。こんな数学の問題は学校ではでてきません。どんな数式でしょうか。

要は0〜9まではそれぞれ対応して0〜9で、10〜19までまた戻って0〜9、20から29までまた戻って0〜9(この繰り返し)になればいいのです。だから、「10で割ってその余りの数の・・」というところまで息子は考えたのですがわかりませんでした。

そこで私が思い出したのは、ブロック崩しもどきのゲームを作ったときに、先生のサンプルから真似して見よう見まねで書いたものの中に、ブロックをきっちりすきまなく順番に並べて置いていくというものを作ったことをうっすら思い出しました。意味もわからず見よう見まねで書いたものがあったよね、それがもしかしたら参考になるのでは?と息子に言って見せると

block.x = 0 + ((i % 40) * 8);//(iを40で割った数の余り)×8
block.y = 0 + (Math.floor(i / 40) * 8);//(iを40で割った商の小数点を切り捨てた数)×8

↑これだ!これがやりたかったの!これのことだったんだ!と感激していました。

これって・・言われればそのとおりだけど、思いついた人はすごいと思う。

%というのは、「割った余りの数値」です。Math.floorとは「小数点を切り捨て」です。

上の例だと、8ピクセルのブロックを40個ずつ並べたいので、iを40で割った余りの8倍をx座標にすれば40までは i (×8)ずつ増えて40で0に戻り、iを40で割った商の小数点を切り捨てた数の8倍をy座標にすれば、iが40増えるごとに 1(×8)ずつ増えるので40個ずつで行が下に行くことになります。

ということで、母のかすかな記憶のおかげで偶然にも解決できました。見よう見まねで作ってみたものがこんなところで役に立つとは。おかげで私も、初めてこれの意味を理解することができました。

さらに、ブロックは一種類でしたが今回はりんご、バナナ、さくらんぼを三種類を画像を変えて並べないといけないので、iの数を前の果物の数だけ加えてずらすという工夫が必要でした。

以下コメント部分はゆうじの解説です。

function endStart(){// 結果画面
    scene = new Scene();
    core.replaceScene(scene); core.resume();

    //==========
    // ここから
    //==========

    var sound = core.assets["sounds/cf307/trumpet1.mp3"].clone();
    sound.play();

    Fruits = Class.create(Sprite, { // フルーツというクラスを、Spriteを継承させてつくる
        initialize: function(image){ // クラスのものをつくるときに行う imageが引数
            Sprite.call(this,32,32); // 継承したものを呼び出す(これ、32,32)
            this.image = core.assets[image]; //そのクラスのプロパティを設定
        }
    });

    var howmany = localStorage.getItem("howmanyF"); // howmanyFで名付けた配列を取り出して
                                                     howmanyと名付ける
    var howmanyA = howmany.split(","); // howmanyを区切って取りしたものをhowmanyAとする
    var howmanyN =[];// 空っぽの配列を作る
    for (var n in howmanyA) { // howmanyAの中身を順番に数字にする(長さ分だけ繰り返す例えば、
                               2,3,5とかなら3)
    var N = Number(howmanyA[n]); // howmanyAのn番目を文字列から数値にする(文字列として保存
                                 されているから)
    howmanyN.push(N); // 数値にしたものを作っておいた空っぽの配列howmanyNに入れる 
    console.log(howmanyN); // howmanyNというのは(りんごの数、バナナの数、さくらんぼの数)
                            という配列なので、howmanyN[0]はりんごの数、
                            howmany[1]はバナナの数、howmany[2]はさくらんぼの数になる
    }

   //林檎をさっき皿の上にのったかずだけ出す
    for(var Fx = 0; Fx < howmanyN[0]; Fx++){ // りんごの数分だけ繰り返す(howmanyN[0]という
                           のが今りんごの個数になっているのでその分繰り返す)
        var apple = new Fruits("images/cf307/monkeyapple.png");
        apple.x = 0 + ((Fx % 10) * 32); // 10個まで32ずつずらして並べ、かつ10個ずつで折り返し
                        たいので、「りんごの個数を10で割った余り×32」をx座標と
                        する
        console.log(apple.x);
        apple.y = (Math.floor(Fx / 10) * 32); // 10個ならべたら次の行にするには「りんごの数を
                           10で割った数の小数点を切り捨てた数×32」をりんご
                           のy座標とする
        scene.addChild(apple);
    }

  //バナナをその続きからだす
    for(var Fx2 = 0; Fx2 < howmanyN[1]; Fx2++){
        var banana = new Fruits("images/cf307/banana.png");
        banana.x = 0 + (((Fx2 + Fx) % 10) * 32);// バナナもりんごと同様だが、りんごの後に並べるので
                             りんごの分をずらさないといけないので個数はりんごの
                             分も加えた(Fx2 + Fx)とする
        console.log(Fx);
        banana.y = (Math.floor((Fx2 + Fx) / 10) * 32);
        scene.addChild(banana);
    }

    //さくらんぼをバナナの続きからだす
    for(var Fx3 = 0; Fx3 < howmanyN[2]; Fx3++){
        var cherry = new Fruits("images/cf307/sakuranbo.png");
        cherry.x = 0 + (((Fx3 + Fx2 + Fx) % 10) * 32);// りんごとバナナの分ずらさないといけないの
                                でその分の個数を加えた(Fx3 + Fx2 + Fx)と
                                する
        console.log(Fx3);
        cherry.y = (Math.floor((Fx3 + Fx2 + Fx) / 10) * 32);
        scene.addChild(cherry);
    }

    scene.backgroundColor = "green";
    var score = localStorage.getItem("monkey_score");

    var scorelabel = new Label("結果: " + score);
    scorelabel.x = 320 / 2 -100;
    scorelabel.y = 340;
    scorelabel.color = 'yellow';
    scorelabel.font = "48px 'PixelMplus10'";
    scene.addChild(scorelabel);
    //console.log(scorelabel);

//==========
// ここまで
//==========

}

【感想】

今回の発表は、発表用の原稿の紙がなかったこともあって、アドリブの効かない息子は、デモプレイを一回やったっきり、だんまりで間が持たず、(デモプレイを繰り返ししていればよかったのにそれもなぜかせず ^♢^; )、苦労したんだからそのあたりもうちょっと説明したらいいのになと可笑しかったのですが、それも一つの個性なので、まあいいかと思います。(みんな違ってみんないい)

奇をてらわずシンプルで、息子の真面目さが出たようなゲームだと思いました。ゲームとして突き抜けたようなアイデアはないけれど、こういうふうに動かしたい、こういうものを出したいという構想を思い描いたとおりに実現させました(プログラミングの醍醐味だね)。そういうところを見ると、もしかすると息子はゲームよりも、実用的なアプリとかシステムをつくるプログラミングのほうが向いているかも知れないなと思いました。

ゲーム投稿サイト9leapに投稿してみました。こちらは得点の登録ができるプラグインがあるので、得点ランキングがでるところがいいです!

http://9leap.net/games/5633

シャッフルを紙とペンでやってみる

前回の、「フィッシャ–イェーツのシャッフルを視覚化してみる」の続きです。

フィッシャー-イェーツのシャッフル

紙とペンで、実際やってみるとよくわかります。一番簡単な、3つの数字、123の並びをシャッフルしてみます。このやり方に従ってすべての場合を出したときに、6通りが均等になっていれば、確率的に正しいことがわかります。

簡単なのですぐできます。

ダステンフェルトの手法(正しいやり方)

これをプログラミング的に書いたダステンフェルトのやり方もやってみましょう。

            (↑この部分、3つとも j=2 ではなくて、j=1 の間違いです。書き間違えてる!!)

ダステンフェルトの手法(間違ったやり方)

前の記事フィッシャ–イェーツのシャッフルを視覚化してみるの中の、赤いやり方の処理と青いやり方の処理をごちゃまぜにした間違ったやり方です。当初、ウィキペディアの説明が間違っているのではないかと思ってやってみたのですが、それは私の勘違いで、そちらの説明は合っていました。前の記事も訂正しておきました。でもこれはこれで間違いの例なので載せておきます。数字でやるなら数字で、順番でやるなら順番で、統一しないといけないということです。

結果、4通りとなってしまいました。

これは、1順目で、2以外の数字が二番目にくる結果が1通りあるので、それに対して2順目で2通りあり、合計1×2通りが狂うからです。

ということで、やはり間違いであることがわかりました。

ところで、このダステンフェルトの手法

要素数が n の配列 a をシャッフルする(添字は0からn-1):
  in - 1 から 1 まで減少させながら、以下を実行する
       j に 0 以上 i 以下のランダムな整数を代入する
       a[j] と a[i]を交換する

は、フィッシャー−イェーツをコンピュータ向けに改良したものとあって、内容がプログラミングの書き方(forループ)そのものです。

だからもし、プログラミングを習っていなかったら、これが何を言おうとしているのか、多分わからなかったと思います。そんなところからも、またすごく面白みを感じたのでした。

【追記】

あれから大学時代の古い記憶がかすかに蘇ってきて、何の科目か何の勉強かも思い出せないのですが、レポートをやるために本を調べていたときに、iとjの出てくる、こんなような説明の意味がさっぱりわからなくて半日以上考えたり調べたりしたけれどわからなくて諦めたことがあって(当時はまだパソコンもなかったし)、もしかするとこれだったのかな・・とふと思いました。上の「ダステンフェルドの手法」の文には、日本語的に致命的な欠陥があると思います。(笑

〜減少させながら、以下を「繰り返し」実行する

と書くのが正確なのではなかろうか٩(๑`^´๑)۶。繰り返し処理というのは普通の数学教育では一般的ではないので、プログラミングをやっている人にはパッと見ですぐわかることでも、やったことのない人には「減少させながら以下を実行する」とだけ書かれても意味不明だと思います。

フィッシャ–イェーツのシャッフルを視覚化してみる

・swap視覚化

・80本の棒のシャッフル

1.フィッシャ–イェーツのシャッフルとは

先日の授業では、シャッフルを教えてもらいました。

その中で、「フィッシャ–イェーツのシャッフル」を簡単にしたものを扱いました。そこで、フィッシャ–イェーツのシャッフルとは実際どういう動きでシャッフルしているのか知りたくて、視覚化した物を作ってみようと思って、その為に調べてみました。

ウィキペディアのフィッシャ–イェーツのシャッフルによると、

  1. 1 から N までの数字を書く。
  2. まだ消されてない数字の数を数え、1 からその数以下までのランダムな数字 k を選ぶ。
  3. 残っている数字から k 番目の数字を消し、別の場所にその数字を書き出す。
  4. すべての数字が消されるまで手順 2, 3 を繰り返す。
  5. 手順3で書かれた数列が元の数値からのランダム順列となるとなる

というものです。

何かを訳したものだと思いますが「消されていない」「消されるまで」となっていてわかりにくいですが、これは「選ばれていない」「選ばれる」と読み換えるとわかりやすいと思います。

例えば1〜5までの数字、12345をシャッフルする場合、

⑴ 1から5までの数字を書く→12345

⑵ まだ選択されていない数字の数を数え→ここではまだ一つも選択されていないので5つ

 1からその数以下までのランダムな数字kを選ぶ→1から5までのランダムな数字例えば3を選んだとする

⑶ 残っている数字(ここでは12345)から3番目の数字を選び(ここでは3)、それを結果として書き出す(シャッフルの結果の5桁の最初の数字が3となる)残った数は1245となる

⑷ ⑵⑶を繰り返す。次は二巡目なので、まだ選択されていないのは4つ、1から4までのランダムな数、また3を選んだとする

残っている数(1245)の3番目の数字(4)を選び、結果に加える。(先の3と合わせて、34となる。残った数は125となる)

これをなくなるまで繰り返す。(3巡目)3個数字が残っているので、1〜3までのランダムな数字、1が選ばれたとして125の1番目の数字、1を結果として加えて、341となり、残ったのは25。

(4巡目)2個数字が残っているので、1〜2までのランダムな数字、2が選ばれたとして、25の2番目の数字、5が選択されて、結果として加えられて3415、残った数字は2の一つ。

(5巡目)最後2という数字がが1個残っていて、ランダムな数字といっても1しかないので、1番目の数2が選択されて、結果に加えられて、最終結果は34152となる、

これがこのシャッフルの仕方です。

2.ダステンフェルドの手法

さらに、現代ではこれをコンピュータでの使用を想定して改良した、ダステンフェルドの手法が採用されているとのことです。これは

要素数が n の配列 a をシャッフルする(添字は0からn-1):
  in - 1 から 1 まで減少させながら、以下を実行する
       j に 0 以上 i 以下のランダムな整数を代入する
       a[j] と a[i]を交換する

というもので、先のフィッシャ–イェーツとの違いは・・これもnを5としてやってみたらわかるでしょう。

a[0]~a[4]までの5つの配列がある。a[0]a[1]a[2]a[3]a[4]

iを4から1まで減少させながら(つまりは4回。5回ではない)、jに0以上i以下(こちらは一巡目は0から5までの5つの数字となる)のランダムな数字を入れて、a[ j ]とa[ i ]を交換する。

・・先のフィッシャ–イェーツはランダムに選んでそれを結果として最後に加えていく方法でしたが、交換という方法を使うことによって後ろから付け加えていくという形で無駄を省いてやっているようなイメージですね。

ダステンフェルドの手法で同じように12345でやってみると・・

(1巡目)i=4、jを0から4までのランダムな数字、例えば3とすれば、a[3]とa[4]を交換して、(4と5)

12354。

(2巡目)i=3、jは0から3までのランダムな数字、例えば1とすれば、a[1]とa[3]を交換して、(2と4)

14352。

(3巡目)i=2、jは0から2までのランダムな数字、例えば0とすれば、a[0]とa[2]を交換して、(1と3)

34152。

(4巡目)i=1、jは0から1までのランダムな数字、例えば1とすれば、a[1]とa[1]を交換して、(2と2)

34152 

となります。

なぜ5つの数字に対して5巡ではなくて4巡なのかというと、仮に5巡目をやったとしても、a[0]とa[0]の交換になると決まっているので、やる意味がないからです。

これを別の言い方で説明すると、

1巡目・・5つの全部の数字のうちどれかをランダムに選んで5と交換する。
(「5」と「1~5のどれか」を交換する)

2巡目・・1巡目で交換した5以外の4つの数字からランダムに一つ選んでそれと4を交換する。
(「4」と「1~4のどれか」を交換する)

3巡目・・1、2巡目で交換した5、4以外の3つ数字からランダムに一つ選んでそれと3を交換する。
(「3」と「1~3のどれか」を交換する)

4巡目・・1、2、3巡目で交換した5、4、3以外の数字2つのうちランダムに一つ選んでそれと2を交換する。(「2」と「1~2のどれか」を交換する)

つまりもっと簡単に書くと、

(1)「5」と「1~5のどれか」を交換する

(2)「4」と「1~4のどれか」を交換する

(3)「3」と「1~3のどれか」を交換する

(4)「2」と「1~2のどれか」を交換する

あるいは、以下の処理でも同じです。

(1)「5番目」と「1~5番目」のどれかを交換する

(2)「4番目」と「1~4番目」のどれかを交換する

(3)「3番目」と「1~3番目」のどれかを交換する

(4)「2番目」と「1~2番目」のどれかを交換する              

やっていることはこれだけです。

青い方のやり方と赤い方のやりかたの違いがわかるでしょうか?例えば青い方の4は、数字の4のことですが(配列のどこにあっても)、赤い方は「そのとき4番目にある数字」であって、数字の4とは限りません。青い方なら青い方でやって、赤い方のやり方と混ぜてはいけません。(ごっちゃにした間違いの例

ダステンフェルドの手法のプログラミングでは、赤い方の処理をやっています。どうしてかというと、forループの繰り返し処理の中でその都度配列の並びが上書きされるからです。紙とペンでやってすべての場合がでるかどうか確かめてみました(メモ)が面白いことにどちらもすべての場合が出ます。ダステン先生はすごいですね。

これをコードで書くと授業でやったものを使って、

//シャッフル
function shuffle() {
    // カードグループの一番最後の添え字
    var last = cardGroup.childNodes.length - 1;

    // 繰り返し処理
    // for(初期化; 繰り返し条件; 後処理){処理}
    for(var i=last; i>0; i--){
    // for(var i=0; i<=last; i++){ 授業ではこのように書きました
        console.log(i);

        // 添え字を使ってカードを呼び出す
        var card1 = cardGroup.childNodes[i];

        // ランダムで添え字を作る
        var r = getRandom(0, i); // ここをlastではなく、iにするのが精度的には正しいそうです
        //var r = getRandom(0, last); 授業ではこのように書きました

        // 添え字を使ってカードを呼び出す
        var card2 = cardGroup.childNodes[r];

        // 交換処理
        swap(card1, card2);
    }
}

となります。

授業で書いたものは、簡単にしたものということで、正確なものとは異なっているようです。

ランダムで添字を作るの部分は、配列の全部、(上のコードの場合(0、last))から取り出すと、厳密にいうとシャッフルの精度が下がる(確率が均等でなくなる)のだそうです。ランダムの範囲は、ずっと配列のどこでも好きなところからとるのではなく、まだ取り変えていない範囲(0、i)に減らしていくというのが、正しいやり方だそうです。うーん難しい。サイトにも間違えて書かれてあるところが多いそうです。

こちらのサイトが参考になりました。

これはこれで、一つの間違いがわかりました。

 

これとはまた別の疑問がでてきてしまいました。上のソースコードの書き方はWikipediaの説明部分の

改良されたアルゴリズム(ダステンフェルドの手法)

要素数が n の配列 a をシャッフルする(添字は0からn-1):
  in - 1 から 1 まで減少させながら、以下を実行する
       j に 0 以上 i 以下のランダムな整数を代入する
       a[j] と a[i]を交換する

のやり方に従ったものです。

でも、Wikipediaには続きがあって、「紙とペンを使用した場合」として実際にやってみる説明が書かれているのですが、これとは違う(間違ってる?)ということに気づいてしまいました。Wikipediaですから、間違いがあるのはあることなのかも知れませんね、どちらが正しいのだろう・・多分、下の「紙とペンを使用した場合」の説明のほうが間違っていると私は思います。どこが違うかというと、下の説明の部分で、赤字にした部分です。これが、下のやり方では8番目の数字、7番目の数字、6番目の数字・・2番目の数字となっているのですが、先の説明のやり方に従うなら、「8番目の数字」ではなくて、「数字の8」(どこの順番にあろうが)、「7番目の数字」ではなくて、「数字の7」と交換しなければならないはずです。

たぶん、そのほうが正しいです。(わかる方いたら教えていただきたいです)正しくないです、下のWikipediaの説明で合ってました。(後述)

間違っている気がするWikipediaの説明部分↓

紙とペンを使用した場合

ここでは同じ例をダステンフェルドの手法で行う。上記の例では数字の消去と書き出しを行ったが、今回は選ばれていない数字の中で最後の数字との入れ替えを行う。前と同様に1から8までの数字をメモしておく。

範囲 乱数 メモ 結果
    1 2 3 4 5 6 7 8  

1から8までの数字の中でランダムな数字を取得する。今回は6だったとする。この場合、6番目と8番目の数字を交換する。

範囲 乱数 メモ 結果
1–8 6 1 2 3 4 5 8 7 6

次は1から7までの数字の中でランダムな数字を取得する。2だった場合、2番目と7番目の数字を交換する。

範囲 乱数 メモ 結果
1–7 2 7 3 4 5 8 2 6

続けて1から6までの数字の中でランダムな数字を取得する。ここで取得した数字が6だった場合、6番目の数字をそのまま結果とする。今回の例で言えば8を結果とする。結果の数列が完成するまで、ここまでの処理を繰り返す。

範囲 乱数 メモ 結果
1–6 6 1 7 3 4 5 8 2 6
1–5 1 5 7 3 4 1 8 2 6
1–4 3 5 7 4 3 1 8 2 6
1–3 3 5 7 4 3 1 8 2 6
1–2 1 7 5 4 3 1 8 2 6

この時点で処理が終了し、「7 5 4 3 1 8 2 6」という順列が得られる。

また調べてみてわかったら書きます。👩🏻‍💻 (→紙とペンでやってみて確かめてみました。やはり間違ってました)やっぱり合ってました^^; すみません〜💦この前の授業(9/8)の後、プログラミングでは上記の赤い方の処理でやっているのだということに気づきました。この説明を、私は青い方のやり方が書いてあると思って間違いと思ってしまったのですが、赤い方のやり方で書いてあったということがわかりました。すごい勉強になりました。

結果、ダステンフェルドの手法を視覚化してつくって以下のようにつくってみました。

3.完成作品

・12345のシャッフル

もっとたくさんで見てみたくて、80本のバーでも作ってみました。

上の段も下の段も同じシャッフルをしています。上は色が混ざっていくことでシャッフルされている様子がわかります。下の段は、移動済みのものが青から赤に色が変わっていきます。

・80本の棒のシャッフル

AIマンドラゴラ

AIに挑戦せよという授業の中で作ったものです。

前の、45.ブッタ様大噴火47.おならして帰る を応用してシューティングゲームにして、ダメージによって敵の画像・攻撃・動きが変わるようにしました。

マンドラゴラのつもりで描いた敵が、にんじんに見えてしまうのは、頭の葉っぱが短すぎたからでしょう。

(授業での発表のとき、デモ用に当たり判定を外してゲームオーバーにならないようにしておきました。発表が終わったので元に戻したら、先生が「ああ、これマンドラゴラか〜」と言われて再度プレイされて、一瞬でやられてゲームオーバーになってしまい可笑しかったです)

工夫やこだわり

・ボスのビームは関数 function bossbeam(w, h, b)を作って、変化させた

・クリアのときの大爆発

ソースコード