Riot.jsで数字のランダム選択

背景

会社内でコンペみたいな発表会があったのですが、ランダムにチーム番号を選ぶ要件がありました。
フリーソフトを利用していたのですが、広告が頻繁に表示されうざそうだったので作ってみた。

完成品

こんな感じで出来ました。

名称未設定.mov.gif

完成品は下記。
http://game.hasito.com:3001/num.random/

開発

要件

要件としては、ランダムに発表者を選択したい場合に、事前ではなくその場で選択したい。このとき、基本的には同一の番号は選択されないものとする。的な感じ。
また、可能であれば選択したが後半に発表者を回したい要件参加予定の番号だったが参加できなくなったため、選択済みとしたい要件等も網羅できればなお良い。

仕様

下記のような仕様を有する。

番号数の決定

最初に数値を入力可能で、この数値に応じて**連続する数(選択可能な数字)**を生成する。
例えば、100であれば0~99までを生成する。

ランダムな選択

ユーザが任意のタイミングで、クリックなどの動作により生成された選択可能な数字からランダムに1つ選択できる。
また、この時に1度選択されたもの無効化されているものは選択されない。

数字の無効化

ユーザは任意のタイミングで生成されたリストから任意の数字を無効化できる。
無効化された数字はランダムに選択されない。

数字の有効化

ユーザは任意のタイミングで生成されたリストから任意の数字を有効化できる。
有効化された数字はランダムに選択される。

コーディング

今回の肝はjavascriptのオブジェクトの参照関連というイメージでした。
最初にランダムな数字を持った配列と同一のオブジェクトを参照するランダムな配列を生成して、それを介してオブジェクトを変化させる。
主たるコードは下記です。

  
var self     = this;  
self.shuffle = false;  
self.list    = false;  
self.now     = "Click!"  
self.msg     = ""  
// ランダム選択開始関数  
start(e){  
    if(_chk(e.target.value).e){  
        // こちらが正規な順(1,2,3...)で並んでいるオブジェクトリスト  
        self.list    = [...Array(e.target.value*1)].map((v,i)=>{return {idx:i,st:true}});  
        // こちらがランダムな順で並んでいるオブジェクトリスト  
        // ただし、中身は正規な順のリストと同一のオブジェクトを参照  
        self.shuffle = [...Array(e.target.value*1)].map((v,i)=>{return self.list[i];});  
        // sortで戻り値をランダムにさせてshuffleさせる  
        self.shuffle = self.shuffle.sort((a,b)=>{return (Math.random()-0.5)});  
    }  
}  
// 入力値check関数、戻り値は結果とエラー文字列  
var _chk=(v)=>{  
    if(isNaN(v)){return {e:false,msg:"数値を入力してください。"}}  
    var vv = v*1;  
    if(vv<=0){return {e:false,msg:"0より大きな数を入力してください。"}}  
    if(vv>100){return {e:false,msg:"100より小さな数を入力してください。"}}  
    return {e:true,msg:""};  
}  
// 入力チェック関数(入力されるたびに実行)  
chk(e){  
    self.msg = _chk(e.target.value).msg;  
}  
// ランダムな数字の選択関数。  
refresh(){  
    //シャッフルされているので最初から順に取得(st=falseは選択済み)  
    var _now    = self.shuffle.find(v=>{return v.st;});  
    if(_now){  
        _now.st  = false;  
        self.now = _now.idx;  
    }else{  
        self.now = "end"//選択に失敗した場合は終了  
    }  
}  
// 要素の無効化 or 有効化  
rem(e){  
    e.st=!e.st; //無効だったら有効に 有効だったら無効に  
    // 有効無効変えてもシャッフル更新しなければ毎回同じ順なので更新  
    self.shuffle = self.shuffle.sort((a,b)=>{return (Math.random()-0.5)});  
}  

完成コード

  
<test>  
<div class="bk">  
    <div if={!list}>[ランダムな数値選択アプリケーション]<br>下記に数値を入力すると、任意の数の連続する数が生成されランダムに選択する事が可能です。</div>  
    <div if={!list} class="input-bk"><input oninput={chk} onchange={start} type="text"></input></div>  
    <div if={!list}>{msg}</div>  
    <div if={list}  each={v in list} class="item {act:v.st}" onclick={rem.bind(this,v)} class="vv">{v.idx}</div>  
    <div if={list}  class="now" onclick={refresh} class="v">{now}</div>  
</div>  
<style>  
.bk{  
    text-align:center;  
    width:100vw;  
    height:100vh;  
    background-color:#fff;  
    overflow:hidden;  
}  
.input-bk{  
    cursor: pointer;  
    display:block;  
    width:55vw;  
    height:25vw;  
    border-radius:3vw;  
    overflow:hidden;  
    margin:10vh auto;  
}  
input{  
    font-size:25vw;  
    text-align:center;  
    display:block;  
    width:100%;  
    height:100%;  
    margin:0;  
    padding:10px;  
    background-color:#f00;  
    color:#fff;  
    border:none;  
    border-radius:0;  
    outline:none;  
    appearance:none;  
}  
.now{  
    cursor: pointer;  
    line-height:35vw;  
    font-size:15vw;  
    display:block;  
    width:35vw;  
    height:35vw;  
    border-radius:50%;  
    background-color:#00f;  
    color:#fff;  
    margin:10vh auto;  
}  
.item{  
    color:#fff;  
    cursor: pointer;  
    line-height:5vw;  
    font-size:3vw;  
    display:inline-block;  
    width:5vw;  
    height:5vw;  
    border-radius:50%;  
    background-color:#555;  
    margin:0.5vw;  
}  
.act{  
    background-color:#f00;  
}  
</style>  
<script>  
var self     = this;  
self.shuffle = false;  
self.list    = false;  
self.now     = "Click!"  
self.msg     = ""  
start(e){  
    if(_chk(e.target.value).e){  
        self.list    = [...Array(e.target.value*1)].map((v,i)=>{return {idx:i,st:true}});  
        self.shuffle = [...Array(e.target.value*1)].map((v,i)=>{return self.list[i];});  
        self.shuffle = self.shuffle.sort((a,b)=>{return (Math.random()-0.5)});  
    }  
}  
var _chk=(v)=>{  
    if(isNaN(v)){return {e:false,msg:"数値を入力してください。"}}  
    var vv = v*1;  
    if(vv<=0){return {e:false,msg:"0より大きな数を入力してください。"}}  
    if(vv>100){return {e:false,msg:"100より小さな数を入力してください。"}}  
    return {e:true,msg:""};  
}  
chk(e){  
    self.msg = _chk(e.target.value).msg;  
}  
refresh(){  
    var _now    = self.shuffle.find(v=>{return v.st;});  
    if(_now){  
        _now.st  = false;  
        self.now = _now.idx;  
    }else{  
        self.now = "end"  
    }  
}  
rem(e){  
    e.st=!e.st;  
    self.shuffle = self.shuffle.sort((a,b)=>{return (Math.random()-0.5)});  
}  
</script>  
</test>  
  

最後に

もう少しアニメーションとかつけたかった…
今度、時間があればやろうかな…