アニメや演出はさておき、クジの部分だけを考えますが。
1~ 100 の範囲で乱数を作った時、乱数の出方に偏りがないことを前提とすると、ある特定の1つの数字(仮に1とします)が出る確率は、1 ÷ 100 x 100 で1%です。
1か2のどちらかでいいとなれば、2 ÷ 100 x 100 で2%にアップします。
つまり、例えば 10 %の確率とは、1~ 100 の間で乱数を作り、この数が1~ 10 の間であれば何かをする、という方法でも表現できます。
例えばパチスロで、大当たりの確率が 10 %であれば
(↓各行頭に全角のスペースが入っています。コピーする際は、全て半角のスペースかタブに置き換えてください)
//1~100の間で乱数を作る
rnd = Math.floor( Math.random() * ( 100 - 1 + 1 ) ) + 1;
//10%の確率で大当たり、それ以外はハズレ
if( rnd >= 1 && rnd <= 10 )
{
/*ここに、大当たりの処理を書く*/
}
else
{
/*ここに、ハズレの処理を書く*/
}
こんな感じになります。
福引のように、当たる確率が1等 10 %・2等 30 %・3等 60 %という、全部合わせて 100 %になり、必ずいずれかの等級に決まるような場合でも、考え方は同じです。
範囲で確率を決める場合、大切なのは出た乱数が含まれうる範囲の広さだけであって、どの範囲に属するかはどうでもいいことです。
上記の例では1~ 10 までの乱数が出れば大当たりとしましたが、その代わりに 91 ~ 100 の数字であれば大当たりとしても、10 %の確率であることに変わりはないからです。
福引ですと、スクリプトは次のようになります。
//1~100の間で乱数を作る
rnd = Math.floor( Math.random() * ( 100 - 1 + 1 ) ) + 1;
//10%の確率で1等
if( rnd >= 1 && rnd <= 10 )
{
/*ここに、1等当せんの処理を書く*/
}
//30%の確率で2等
else if( rnd >= 11 && rnd <= 41 )
{
/*ここに、2等当せんの処理を書く*/
}
//それ以外は3等
else
{
/*ここに、3等当せんの処理を書く*/
}
------------------------------------------------------------
ところで、福引の確率ですが。
福引のポスターに
1等 × 10 本
2等 × 30 本
3等 × 60 本
とあったら、1等の確率は 10 ÷ 100 × 100 = 10 で、10 %だと思いますよね。
しかし、ガラガラ式(^^;)の福引で途中で玉を追加しないものとすると、これは、まだ誰も引いていない、福引が始まった直後の状態での確率にすぎません。
既に1等が 10 本出た後だったら、自分が引く頃には1等は絶対に出ない、つまり1等の確率は0%ということになります。
逆に、2等と3等が全て出た後でガラガラの中に1等の玉しか残っていないようなら、1等の確率は 100 %です。
ご提示のサンプルでも、当せん本数に限りがあり、既に指定の本数だけ出てしまった等は絶対に出ないようになっています。
この確率の作り方を考えてみましょう。
いろいろな考え方があります。
簡単なのは、クジを引く度にガラガラの中の玉を減らしていく要領で、配列に情報を用意し、抽せんの度に出た要素を削除していく方法です。
配列変数とは、同じ名前で管理番号(インデックスといいます)だけが違う複数の変数の集まりです。
インデックスは他の変数などを使っても指定できます。
配列変数の各要素の中に等級を記述しておき、乱数を作って配列変数内の要素を1つ、ランダムに選択します。
要素がいくつあるかは、Array.length というプロパティを見ると分かります。
例えば length が5の時、配列変数の要素は5つありますが、インデックスは0から始まることに注意しましょう。配列変数の名前が lot なら、lot[ 0 ] ~ lot[ 4 ] という名前の変数があります。
従って、0~ length - 1 までの範囲で乱数を作ることで、配列内の要素をランダムに1つ取り出せるようになります。
なお、指定の範囲内で乱数を作る方法についてはこちらをご参考になさってください。
・Math.random() でランダムな整数を取得する方法
http://www.macromedia.com/jp/support/flash/ts/documents/fl0173.html
取り出す番号を作成できたら、そのインデックスの配列変数に格納された値を見ます。
このあたりは、#1の方と同じ発想です。
1度引いたクジは無効になります。同じクジを重複して引かないように、配列変数から要素を削除してしまいましょう。
配列を扱う Array クラス( Flash MX 以前はオブジェクト)には、指定のインデックスを持つ要素を削除する、splice という機能(メソッドといいます)があります。このメソッドで、1度出た要素は削除していきます。
削除するインデックスは、乱数で出てきた数値を指定します。
要素を削除すると、length の値も変化します。乱数を作る時には、length を参照して作るスクリプトにするといいでしょう。
length が0になっていれば、クジは1本も残っていないことになります。福引終了のアニメなどに利用してください。
この方法は考え方もシンプルで分かりやすいのですが、1つ欠点があります。
クジの総数が 100 本くらいならまだしも、空クジが大量にあるなどで総数が 1000 ・ 2000 本と多い場合、length が 1000 や 2000 になる巨大な配列変数が必要になります。
配列変数はメモリを占有しますので、要素数が多いほどメモリを圧迫してしまいます。
今時のパソコンであればあまりケチくさく考えなくても大丈夫ですが、できれば効率よくデータを管理したいものです。
そこで、冒頭でご紹介しました、ランダムに選んだ数字がどの範囲に属するかで決める方法を思い出していただきたいのですが。
これと配列変数を組み合わせた方法をご紹介します。
Flash の配列変数には、仕切りを設定して複数の情報を管理できる機能があります。
例えば
lot[ 0 ] = { prize : 1 , num : 10 };
とすると、lot[ 0 ] には prize と num という仕切りがあり、同じ lot[ 0 ]という名前の変数で2つの情報を保持できます。
参照する時は
lot[ 0 ].prize
このように書きます。
この機能を利用して、何等の情報であるかの情報と、等ごとの残り本数を1つの要素で管理します。
としますと、配列変数は等の数だけ用意しておけば用が足ります。
これとは別に、クジの総数を変数で管理します。仮に、この変数を lot_max とします。
クジを引くごとに lot_max を減算していけば、クジの残り総数が分かります。
更に、当せんした等級の本数も減らしていきます。
乱数は1から lot_max までの範囲で作り、できた数がどの範囲に属するかで、当せんした等級を決めます。
例えば、1等 x 10 ・2等 x 30 ・3等 x 60 の福引では、
1 ~ 10 :1等
11 ~ 40 :2等
41 ~100 :3等
と判断するものと考えます。
クジを引く度に本数が減っていきますので、この範囲は変動します。
そこで、配列変数に保存されている各等ごとの残り本数をもとに、随時範囲を決め直して判定することにします。
スクリプトにしてみますと、大体、次のようになります。
まずはクジのデータとクジの総本数を管理する変数を作ります。
このスクリプトはフレームに設定してください。
ここではクジのデータを予めスクリプトで作っていますが、ご質問文にあるサンプルのように、ユーザーから入力してもらった数値から作ることもできます。
//クジのデータを作成
//要素 prize:等級 num:本数
lot = new Array();
lot[ 0 ] = { prize : 1 , num : 10 };
lot[ 1 ] = { prize : 2 , num : 30 };
lot[ 2 ] = { prize : 3 , num : 60 };
//クジの総数を算出
lot_max = 0;
for( i = 0 ; i < lot.length ; i++ )
{
lot_max += lot[ i ].num;
}
ステージに、クジを引くボタンインスタンス lot_btn があるとします。
このボタンには、実際にクジの処理をするスクリプトを書きます。
on(release)
{
//クジが全てなくなったら終了
if( lot_max <= 0 )
{
//ボタンを押せなくする
lot_btn._alpha = 30;
lot_btn.enabled = false;
trace( "終了しました" );
}
else
{
//1~クジの総数の範囲で乱数を作る
rnd = Math.floor( Math.random() * ( lot_max - 1 + 1 ) ) + 1;
//クジの等級を調べる
//配列の要素を全てチェックするか、等級が確定したらチェック終了
lot_result = -1;
range_begin = 1;
for( i = 0 ; ( i < lot.length ) && ( lot_result == -1 ) ; i++ )
{
//各等の残り本数から、範囲の終点を決める
range_end = range_begin + lot[ i ].num;
//引いた乱数がこの範囲にあれば、等級を確定
if( rnd >= range_begin && rnd < range_end )
{
//等級と配列の番号を記録
lot_result = lot[ i ].prize;
array_index = i;
}
//次の範囲の始点を変更
range_begin = range_end;
}
//本数を更新
lot_max--;
lot[ array_index ].num--;
//空になった等級は配列から削除
if( lot[ array_index ].num <= 0 )
{
lot.splice( array_index , 1 );
}
}
}
クジのデータは lot という配列変数の中に作ります。
確定した等級は、lot_result という変数に入っています。この値を元に、賞品配送の手続きや画面演出等を行うことになるかと思います。
最初は福引が始まったばかりで、クジが 100 本まるまる残っている状態です。
1~ 100 の範囲で乱数を作り、仮に、5が出たとしましょう。
1~ 10 までが1等ですから、いきなり1等当せんですね ^^;
クジの総数を管理する lot_max と、1等の情報を管理している lot[ 0 ] の num から、それぞれ本数を1ずつ減らします。
次の乱数の範囲は1~ 99 、1等の範囲は1~9までとなります。
他の等級も同様に、当せんする度に本数を減らしていきます。
もし1等が 10 本全て出た、つまり lot[ 0 ].num が0になった場合は、lot[ 0 ] を配列変数から削除します。
すると、lot の中には2等と3等の情報だけが残ります。
例えば、2等が 10 本、3等が 20 本残っているとしましょう。
引き続き1~クジの残り( lot_max = 30 )の範囲で乱数を作り、残った等の本数から改めて範囲を設定して等級を判断します。
範囲は
1 ~ 10 :2等
11 ~ 30 :3等
となり、1本も残っていない1等は出なくなります。
1等の情報を削除すると、この時に配列変数 lot の要素数( length )は2つに減り、欠番になったインデックスには後ろの要素が順次前に詰められます。
最初は lot[ 0 ] = 1等・lot[ 1 ] = 2等・lot[ 2 ] = 3等となっていたのですが、1等が尽きた後は lot[ 0 ] = 2等・lot[ 1 ] = 3等に変わります。
つまり、配列のインデックスと等級は一致しないため、インデックスをもとに等級を決めたり、等級からインデックスを指定するといったことはできません。
上記のスクリプトでは、インデックスが変わっても等級を取得できるようにとの意味で、配列変数の中に prize という仕切りを作って等級の情報を保存しています。
等級の判断と範囲の決め方が、少々分かりにくいかと思います。
できた乱数がどの範囲に含まれるかで等級を決めるわけですが、ループを使って配列変数にある要素の数だけ判定します。
もし、ある等が全て出ていれば配列からは削除されているはずなので、本数が尽きた等級と判断されることはありません。
仮に、1等x5本、2等x 10 本、3等x 20 本、残っているとします。
この状態では、範囲は
1 ~ 5 :1等
6 ~ 15 :2等
16 ~ 35 :3等
となります。
範囲は常に1から始まりますから、ループに入る前に範囲の始点を1に設定しておきます。上記のスクリプトでは、range_begin という変数に入れています。
範囲の終点である range_end は、始点+各等の残り本数で求めます。上記の例ですと、1等なら1+5=6になるので、判定する時は range_end 未満(終点は含まない)です。
出た乱数が最初の範囲になければ、次の等級のデータを見て、次の等に当せんしているかどうかを調べます。
次の等級の範囲の始点は、前の range_end がそのまま使えます。次のループに入る前に range_start に代入しておくことで、次の範囲を指定できます。
これを繰り返して、等級を確定します。
ループ内で等級が確定しても、本数を減らしたり配列変数から削除するといった後処理は、クジの結果を判定するループ内では行わないようにしましょう。
ある等級の本数が尽きて配列変数から削除した場合、lot.length の値が変化してループが正常に継続しなくなることがあります。
後始末のためには、どの要素を操作するのかを控えておく必要があります。
実はループ後のループカウンタ i を利用することもできますが、分かりにくいので、等級が確定した時に array_index に i の値を保存して、ループの外でも操作する要素を参照しやすくしています。
・・・文字だけの説明ですと、ちょっと分かりにくいかもしれません。
データを少なくして計算だけで何とかする方法が難しいならば、まずは簡単な、配列に全クジのデータを作って少しずつ切り落としていく方法で作ってみるといいと思いますよ。
長くなってすみませんでした。
お礼
ありがとうございます。 仕組みがわかりました。 動きはともかく、設定した確率であたりが出るようになりました。 ありがとうございます。 またよろしくお願いします。