JavaScriptのMath.randomメソッドで何ができそうか考えよう

はじめに

League of Legends(以下LoL)というゲームをプレイしてると一緒にプレイしているフレンドからこんなこと言われました。

フ「チャンピオン(自分が操作するキャラクターの総称)のランダム選択っていつの間にか消えたよな」

昔のピック画面

私「たしかに。でも乱数使えば簡単に作れそうだよね」

フ「じゃあ作って」

私「・・・」

みたいなやり取りがあり、せっかくなのでMath.randomを使って作っちゃえと作りました。

ついでに乱数ってどんな時使うんだと思い

復習がてらに朝礼の発表とさせていただきます。

1. 乱数を使う機会

でgoogle検索した際のAIの回答は

乱数は、プログラム、統計調査、ゲーム、シミュレーションなど、様々な場面で活用されています。乱数を使うことで、より現実的なシミュレーションや、公正な抽選、安全なパスワードなどを実現することができます。

とのことでいろんなことに使えそう。

2. Math.randomとは

Math.random() は 0以上1未満のランダムな小数を返します。

console.log(Math.random()); // 例: 0.234, 0.892 など

3. Math.floorが併用される理由

Math.random() は小数なので、そのままでは整数にできません。

そこで Math.floor() を使って整数にします。

let num = Math.floor(Math.random() * 5); // 0〜4の整数

4. 中間発表順決めツールとデモ

中間発表の際にS先生から頂いたコードを名前だけ変えたものです。

下のボタンをクリックすると、発表順がシャッフルされます。

  1. 徳川 家康
  2. 徳川 秀忠
  3. 徳川 家光
  4. 徳川 家綱
  5. 徳川 綱吉
  6. 徳川 家宣
  7. 徳川 家継
  8. 徳川 吉宗
  9. 徳川 家重
  10. 徳川 家治
  11. 徳川 家斉
  12. 徳川 家慶
  13. 徳川 家定
  14. 徳川 家茂
  15. 徳川 慶喜

このツールのjs解説:


    $(function () {
        $("#btn").click(function () {
            // リストをシャッフル
            // 1を返すとaがbより後に来る
            // -1を返すとaがbより前に来る
            let list = [1, -1];
            $('ol').html(
            $('ol>li').sort(function (a, b) {
                return list[Math.floor(Math.random() * list.length)];
            })
            );
        });
    });


    

5.実際に作ったLoLのツール

友人にも使ってもらえるように別ページにて公開してるのでこちらでは解説のみを載せます。


        const version = '15.10.1'; //現在のlolのバージョン

        //ロールの振り分けは手打ち
        const roles = {
        top: ['Sett', 'Aatrox', 'Riven', 'Malphite', 'Yorick', 'Fiora', 'Gwen', 'Kayle', 'Renekton', 'Urgot', 'Irelia', 'Warwick', 'Darius', 'Ornn', 'Teemo', 'Mordekaiser', 'Singed', 'Camille', 'Quinn', 'Shen', 'DrMundo', 'Gragas', 'Garen', 'Sion', 'Nasus', 'Olaf', 'Gangplank', "Chogath", 'Volibear', 'Jayce', 'Ambessa', 'Jax', 'Gnar', 'Pantheon', 'Kled', 'Tryndamere', 'Trundle', 'Vladimir', 'Yasuo', 'Kennen', 'Ryze', 'Cassiopeia', 'Vayne', 'Heimerdinger', 'Akali', 'MonkeyKing', 'Poppy', 'Yone', 'Illaoi', 'Sylas', 'Varus', 'Rumble', 'Zac', "KSante", 'TahmKench', 'Galio', 'Viktor', 'Aurora', 'Swain', 'Nidalee', 'Udyr'],
        jungle: ['Naafiri', 'Kayn', 'Nocturne', 'XinZhao', 'Volibear', 'Gwen', 'JarvanIV', 'Warwick', 'Khazix', 'Yorick', 'Nunu', 'Lillia', 'Viego', 'Fiddlesticks', 'Ekko', 'Kindred', 'MasterYi', 'Shaco', 'Vi', 'Hecarim', 'Elise', 'Graves', 'Diana', 'Talon', 'LeeSin', 'Udyr', 'MonkeyKing', 'Ivern', 'Belveth', 'DrMundo', 'Briar', 'Amumu', 'Evelynn', 'Shyvana', 'Zac', 'RekSai', 'Pantheon', 'Darius', 'Rammus', 'Zyra', 'Karthus', 'Poppy', 'Rengar', 'Taliyah', 'Nidalee', 'Sejuani', 'Gragas', 'Jax', 'Brand', 'Qiyana', 'Skarner', 'Maokai', 'DrMundo'],
        mid: ['Naafiri', 'Ahri', 'Anivia', 'Yasuo', 'Sylas', 'Zed', 'Xerath', 'Malzahar', 'Taliyah', 'Zoe', 'Hwei', 'Vex', 'Cassiopeia', 'Diana', 'Galio', 'Viktor', 'Velkoz', 'Quinn', 'Akali', 'Akshan', 'Swain', 'Veigar', 'Katarina', 'TwistedFate', 'Syndra', 'Lux', 'Chogath', 'Orianna', 'Lissandra', 'Talon', 'Kayle', 'Ryze', 'Ekko', 'Qiyana', 'Kassadin', 'AurelionSol', 'Pantheon', 'Neeko', 'Mel', 'Malphite', 'Fizz', 'Irelia', 'Leblanc', 'Yone', 'Garen', 'Gragas', 'Aurora', 'Jayce', 'Kennen', 'Azir', 'Annie', 'Gangplank', 'Brand', 'Ziggs', 'Corki', 'Tristana', 'Smolder'],
        bot: ['Jinx', 'Lucian', 'Jhin', 'Tristana', 'MissFortune', 'Vayne', 'Yasuo', 'Nilah', 'Kaisa', 'Caitlyn', 'KogMaw', 'Varus', 'Swain', 'Xayah', 'Twitch', 'Sivir', 'Ashe', 'Ezreal', 'Smolder', 'Draven', 'Aphelios', 'Samira', 'Zeri', 'Corki', 'Hwei', 'Mel', 'Seraphine', 'Ziggs', 'Kalista','Senna'],
        support: ['Lulu', 'Thresh', 'Nami', 'Braum', 'Fiddlesticks', 'Sona', 'Janna', 'Milio', 'Elise', 'Soraka', 'Velkoz', 'Rell', 'Alistar', 'Poppy', 'Pyke', 'Karma', 'Senna', 'Bard', 'Nautilus', 'Zilean', 'Taric', 'Blitzcrank', 'Maokai', 'Leona', 'Rakan', 'Pantheon', 'Morgana', 'Yuumi', 'Neeko', 'Zyra', 'Lux', 'Seraphine', 'Shaco', 'Xerath', 'Swain', 'Brand', 'TahmKench', 'Leblanc', 'Galio', 'Renata', 'Zoe', 'Hwei', 'Shen', 'Sylas', 'Mel', 'Gragas', 'Nidalee', 'Annie']
        };

        let championData = {};

        // fetch apiでチャンピオン情報を事前に取得する
        fetch(`https://ddragon.leagueoflegends.com/cdn/${version}/data/ja_JP/champion.json`)
        .then(response => response.json())
        .then(data => {
        for (const champKey in data.data) {
        const champ = data.data[champKey];
        championData[champ.id] = champ.name;
        }
        });

        function showRandomChampion(role) {
        let list = [];
        // allを押されたとき
        if (role === 'all') {
        // 全ロールを統合してリストに追加
        for (const champs of Object.values(roles)) {
        list = list.concat(champs);
        }
        } else {
        //押されたロールのリストだけ追加
        list = roles[role];
        }
        //math関数でランダムにチャンピオンを選出
        const randomChampId = list[Math.floor(Math.random() * list.length)];
        const imgUrl = `https://ddragon.leagueoflegends.com/cdn/15.8.1/img/champion/${randomChampId}.png`;
        const displayName = championData[randomChampId] || randomChampId;

        //ランダムに選択したチャンピオンのデータをhtmlに出力
        document.getElementById('championContainer').innerHTML = `
        <h2>${displayName}</h2>
        <img src="${imgUrl}" alt="${displayName}">
        `;
        }

        

6.もっと他にも使えそう

いくつかぱっと思いついた案を並べてみる

ほかにもこんな使い方どう?みたいなのあればください(懇願)

余談(朝礼的には本題)

math.randomメソッドはシード値の設定ができないので再現性のない乱数発生メソッドです。

乱数として正常じゃねと思うだろうし、私もそう思います。ただゲームみたいなものを作りたいと考えた時に、ある程度再現性があったほうが都合が良い時もあります。

例えばDS版ポケモンでは、起動時の本体の時計の時間でシード値が決まり、乱数で出現するポケモンや能力が変わったりしますが、
逆に本体の時計を合わせることでシード値を固定させ同じ乱数を引くことができます。

再現性が無いということで検証などは難しく、検証が必要な物を実装したいときには不向きかもです。

(シード値を固定させることができる乱数もjsでやろうと思えばやれるのですが、その辺は興味があれば調べてみてください...)

(5/22追記)

ページ制作でシード値があったほうが良い実装例として、1つ案を考えてみました。

余談のデモを用意できました(?)

できました。3ページ作りました。

cssの装飾は勿論無い、簡素なものになりますが、カンプ代わりに作ったメモ書きです

条件
1.20枚の写真をこちらで用意する
2.ページにはh1で「今日の写真」
 その下に写真を一枚表示
 その他操作できるものはない
3.写真の名前は1.jpg~20.JPGとする

①math.random()を使って、20枚の写真をランダムに1枚表示
 →再読み込みすれば別の写真が表示されるはず

②jsで現在の年月日の午前0時のタイムスタンプを取得。その数値をシード値として、再現性のある乱数を生成。その数値に応じて画像を1枚表示
 →日付を跨がないと別の写真にならないはず

③h1の下に日付入力欄を表示。入力された日の午前0時のタイムスタンプを取得。その数値をシード値として、再現性のある乱数を生成。その数値に応じて画像を一枚表示
 →②のデモとして用意。入力された日付が変わるごとに写真が変わるはず(もちろんたまたま同じ写真が連続して出る可能性もあるはず。例えば2025/05/07では1.jpgが表示。この後5/8では2.jpgが表示。また5/7を入力しても1.jpgが表示されるはず)
        

まず①から。Math.random()を使ってランダムに表示させています。

"use strict";

//0~19までの整数をランダム生成したのちに、画像ファイル名が1~20なので最後1足す
const randomIndex = Math.floor(Math.random() * 20) + 1;

//htmlのphoto要素のsrcを更新(上で出た数字の画像ファイルを読み込む)
document.getElementById('photo').src = `images/${randomIndex}.JPG`;
        
このscriptを使ったページ

想定通り再読み込みで別の写真が表示されました。

では②。今日の日付を取得してその日の午前0時0分のタイムスタンプを取得。それをシード値として乱数を発生させています。

参考サイト:線形合同法についての英語wiki
"use strict";

// シード値から乱数を生成する関数(線形合同法)
// ぶっちゃけm,a,cには何入れてもいいけど、下の数値が安定するらしい
// ソース(https://en.wikipedia.org/wiki/Linear_congruential_generator)
function seededRandom(seed) {

    //m,a,cは定数
    const m = 2 ** 31;
    const a = 1103515245;
    const c = 12345;
    seed = (a * seed + c) % m;

    //できたシード値をmで割って1以下の小数点にしています。
    return seed / m;
}

//現在の日時を取得
const now = new Date();
now.setHours(0, 0, 0, 0); // 午前0時に固定

//その日のミリ秒のタイムスタンプを取得してseedとして利用
const seed = now.getTime();
const rand = seededRandom(seed);

//seed値を1以下にした理由がここで、Math.floor()で0~19に数字を作る必要があった
const photoIndex = Math.floor(rand * 20) + 1;
document.getElementById('photo').src = `images/${photoIndex}.JPG`;

        
このscriptを使ったページ

ページを更新しても画像は変わりません。あとは翌日画像が変わっていれば成功でしょう

③です。これは朝礼用に日付を操作できるものになってます。

"use strict";

//これは②と一緒
function seededRandom(seed) {
    const m = 2 ** 31;
    const a = 1103515245;
    const c = 12345;
    seed = (a * seed + c) % m;
    return seed / m;
}

//デモ用で日付を入力欄を取得しています。
const input = document.getElementById('dateInput');

//ユーザーが日付を変更したとき中の関数を実行します。
input.addEventListener('change', () => {
    const selectedDate = new Date(input.value);
    selectedDate.setHours(0, 0, 0, 0);
    const seed = selectedDate.getTime();
    const rand = seededRandom(seed);
    const photoIndex = Math.floor(rand * 20) + 1;
    document.getElementById('photo').src = `images/${photoIndex}.JPG`;
});
        
このscriptを使ったページ

ページを更新すれば日付情報が消えるので画像も表示されませんが、同じ日付を選んでも同じ画像が表示されているので成功でしょう

以上を朝礼『技術シェア』の再現性のある乱数の生成についての発表とさせてください。させていただきます。

その他制作物に戻る