Riot.jsで神経衰弱

# 背景
CSSを色々と触っていてそれっぽいトランプ形状にできそうだったので、神経衰弱ゲームを作ってみました。

# 結論
こんな感じで完成しました
http://game.hasito.com:3001/

猿の絵柄にしたんだけどなんか…ムカつく

![名称未設定.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/89227/097b018d-4adf-7f12-6320-29146377508f.gif)


# 実装について
## 処理系

主たるところは下記

``` javascript
        open(i,m){
// 時間がfalseの場合は 神経衰弱ゲーム開始！ 今の時間を取得
            if(!self.usr.tm){
                self.usr.tm=moment();
            }
            // --選択しているcardが無い場合--
            if(self.usr.select===false){
                self.usr.select=[i];// 選択したカードを追加
                self.closes.forEach(v=>{// close要望がある場合はclose状態（0）に更新
                    self.cards[v].st=0;
                })
                self.closes=[];
            // --選択しているcardがある場合--
            }else{
                self.usr.select.push(i);// 選択カードに追加
                //**もし、選択したカード2枚が同じ数の場合は取得処理を開始**
                if(self.cards[i].num == self.cards[self.usr.select[0]].num){
                    self.usr.log.push({tm:moment(),get:self.usr.select})// ログに追加
                    Array.prototype.push.apply(self.usr.cards,self.usr.select);//取得カードに追加
                    // 取得されたカードを手持ちカード置き場に移動
                    // ※CSSでアニメーション設定しているため移動がアニメーションになります
                    self.usr.select.forEach((v,i)=>{
                        self.refs[`card${v}`].style.zIndex  = self.usr.cards.length*10;
                        self.refs[`card${v}`].style.top      = `${10+Math.random()*5-2.5}vh`;
                        self.refs[`card${v}`].style.left     = `${70+i*3+Math.random()*5-2.5}vw`;
                        self.refs[`card${v}`].style.transform= `rotate(${220*Math.random()+500}deg)`;
                    });
                    // もし、全カード取得している場合は終了
                    if(self.usr.cards.length>=self.cards.length){
                        end();
                    }
                    // 選択カードをクリア
                    self.usr.select = false;
                //**選択したカードの番号が異なる場合**
                }else{
                    self.closes = self.usr.select;// クローズ対象に追加
                    self.usr.select = false;// 選択カードをクリア
                }
            }
            self.cards[i].st=4;//選択されたカードをオープン状態（4）へ
        }
```
## コード全体

``` html:index.html
<html>
  <head>
    <title>神経衰弱</title>
    <meta charset="UTF-8"/>
  </head>
  <style>
    body{
      margin: 0%;
    }
  </style>
  <body>
    <index></index>
    <script type="riot/tag" src="index.tag"></script>
    <script src="https://cdn.jsdelivr.net/npm/riot@3.9/riot+compiler.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
    <script>riot.mount('index')</script>
  </body>
</html>
```

``` html:index.tag

<index>
    <div class="bk">
        <div each={v,i in cards} class="card" ref={"card"+i} style="transform:rotate({v.r}deg);top:{Math.floor(i/10)*12+5}vh;left:{(i%10)*4.9+5}vw;">
            <div onclick={open.bind(this,i)} if={v.st==0} class="cardbk">
                🐒
            </div>
            <span if={v.st==4}  style="color:{v.color};">
                <div class="num1">{v.num}</div>
                <div class="type">{v.t}</div>
                <div class="num2">{v.num}</div>
            </span>
        </div>
        <div class="board">
            <div class="name_text">
                <label>お名前</label>
                <input type="text" oninput={chg_name} ref="name"></input>
            </div>
            <div onclick={restart} class="btn">restart</div>
            <div if={usr.tm} class="watch">{ms2time(moment()-usr.tm)}</div>
            <div if={usr.lap} class="watch" style="color:red">{usr.lap}</div>
            <div if={usr.tm||usr.lap} class="log">
                <label>LOG</label>
                <div each={v in usr.log}>
                    {ms2time(v.tm-usr.tm)} >> {("00"+cards[v.get[0]].num).slice(-2)} GET!
                </div>
            </div>
            <div if={rank} class="log">
                <label>ランキング</label>
                <div each={v,i in rank}>
                    {i+1}位 {v.name} >> {v.record}sec
                </div>
            </div>

        </div>
    </div>
    <style>
    .name_text{
        margin:10px;
    }
    .log{
        height:20vh;
        overflow: scroll;
    }
    .watch{
        font-size:4vw;
    }
    .btn{
        height:5vh;
        width:5vw;
        background-color:#222; 
        line-height:5vh;
        margin:10px;
        cursor:pointer;
    }
    .board{
        position:fixed;
        top:30vh;
        left:60vw;
        height:70vh;
        width:40vw;
        background-color:#111; 
    }
    .bk{
        width:100vw;
        height:100vh; 
        background-color:#000; 
        text-align:center;
        color:#fff;
        padding:30px;
    }
    .card{
        overflow: hidden;
        cursor:pointer;
        position:fixed;
        width:4.9vw;
        height:12vh;   
        margin:0.1vw;
        background-color:#eee; 
        border-radius: 15%;
        border: double 1px #000;
        transition: all 1000ms 0s ease;
    }
    .num1{
        text-align:right;
        margin:5px;
    }
    .num2{
        text-align:left;
        margin:5px;
        position: absolute;
	    bottom: 0;
    }
    .type{
        text-align:center;
        font-size:30px;     
    }
    .cardbk{
        width:4.9vw;
        height:12vh;  
        line-height:12vh;
        text-align:center;
        font-size:4vw;
    }


    </style>
    <script>
        var self = this;
        self.cnst_nums=[...Array(13)].map((v,i)=>{return i+1;})
        self.cnst_types={'♥':'red','♦':'red','♣':'black','♠':'black'}
        self.cards =[];
        self.rank  =[];
        self.name  ="";
        // 名前変更イベント
        chg_name(){
            self.name=self.refs.name.value;
        }
        // 秒数を00:00:00 という形式の文字列に変更する関数
        ms2time(ms){
            var h = Math.floor(ms/(3600000))
            var m = Math.floor((ms-h*3600000)/60000)
            var s = Math.floor((ms-h*3600000-m*60000)/1000)
            var _ms = (ms-h*3600000-m*60000-s*1000)
            return `${('0'+h).slice(-2)}:${('0'+m).slice(-2)}:${('0'+s).slice(-2)}:${('00'+_ms).slice(-3)}`;
        }

        // カード初期化関数（カード生成）
        // ※「st」は0:close 4:open
        var card_init=()=>{
            var _random=()=>{return Math.random()*20-10}// 回転角のランダム
            self.cards=[];
            self.cards.push({t:'jk',num:0,color:'black',st:0,r:_random()});
            self.cards.push({t:'jk',num:0,color:'black',st:0,r:_random()});
            Object.keys(self.cnst_types).forEach((t)=>{
                self.cnst_nums.forEach((n)=>{
                    self.cards.push({t:t,num:n,color:self.cnst_types[t],st:0,r:_random()});
                })
            });
        };
        // カード初期化関数（カードシャッフル）
        var card_shuffle=()=>{
            self.cards = self.cards.sort((a,b)=>{return (Math.random()-0.5)});
        };
        // タイマーストップ関数
        var timer_stop=()=>{
            if(self.usr && (self.usr.timer!==false)){
                clearInterval(self.usr.timer);
                self.usr.timer=false;
            }
        }
        var cnt=0;
        // User情報初期化関数
        var usr_init=()=>{
            timer_stop();
            self.usr={cards:[],select:false,tm:false,log:[],timer:false,lap:false,rhis:[]};
            setInterval(()=>{
                self.update();
            },89);
            self.closes=[];
        }
        // 全体初期化
        var init=()=>{
            card_init();
            card_shuffle();
            usr_init();
        }
        // 終了関数
        var end=()=>{
            timer_stop();
            self.usr.lap=self.ms2time(moment()-self.usr.tm);
            self.usr.tm =false;
        }
        // マウント時に初期化
        self.on("mount",_=>init());
        // リセットボタンでリセット
        restart(){init();}

        open(i,m){
// 時間がfalseの場合は 神経衰弱ゲーム開始！ 今の時間を取得
            if(!self.usr.tm){
                self.usr.tm=moment();
            }
            // --選択しているcardが無い場合--
            if(self.usr.select===false){
                self.usr.select=[i];// 選択したカードを追加
                self.closes.forEach(v=>{// close要望がある場合はclose状態（0）に更新
                    self.cards[v].st=0;
                })
                self.closes=[];
            // --選択しているcardがある場合--
            }else{
                self.usr.select.push(i);// 選択カードに追加
                //**もし、選択したカード2枚が同じ数の場合は取得処理を開始**
                if(self.cards[i].num == self.cards[self.usr.select[0]].num){
                    self.usr.log.push({tm:moment(),get:self.usr.select})// ログに追加
                    Array.prototype.push.apply(self.usr.cards,self.usr.select);//取得カードに追加
                    // 取得されたカードを手持ちカード置き場に移動
                    // ※CSSでアニメーション設定しているため移動がアニメーションになります
                    self.usr.select.forEach((v,i)=>{
                        self.refs[`card${v}`].style.zIndex  = self.usr.cards.length*10;
                        self.refs[`card${v}`].style.top      = `${10+Math.random()*5-2.5}vh`;
                        self.refs[`card${v}`].style.left     = `${70+i*3+Math.random()*5-2.5}vw`;
                        self.refs[`card${v}`].style.transform= `rotate(${220*Math.random()+500}deg)`;
                    });
                    // もし、全カード取得している場合は終了
                    if(self.usr.cards.length>=self.cards.length){
                        end();
                    }
                    // 選択カードをクリア
                    self.usr.select = false;
                //**選択したカードの番号が異なる場合**
                }else{
                    self.closes = self.usr.select;// クローズ対象に追加
                    self.usr.select = false;// 選択カードをクリア
                }
            }
            self.cards[i].st=4;//選択されたカードをオープン状態（4）へ
        }

    </script>
</index>
```


## 残課題
 * 🐒がムカつく
 * 縦横比がパソコンでしか確認していないためiPhoneなどで見ると悲惨  
<img width="203" alt="スクリーンショット 2018-06-11 3.17.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/89227/0ac265a4-a871-fbd7-35bd-df176aa537d4.png">
 * 開くときのアニメーションはない
 * リンク先にランキング機能を実装したがココらへんのサバクラ通信に関する学習が不足している
 * 全て画面の大きさに依存するためリサイズ時に重たく感じる