JavaScriptで神経衰弱ゲームを作ろう!HTML5&Canvasで簡単に実装
JavaScriptとHTML5 Canvasを使って、ブラウザで遊べる神経衰弱ゲームを作成しました。
マウスやタッチ操作でカードをめくるだけ!
初心者でもすぐに試せる、コピペOKのソースコード付きです。
ぜひあなたのWebサイトや学習教材に活用してみてください。
スマホ対応のポイント:メディアクエリでレイアウト調整
以下のようなCSSを使って、カードの幅をスマホサイズでは広めに設定しています。
<style>
.trumpdiv {
width: 12.5%;
}
@media screen and (max-width: 560px) {
.trumpdiv {
width: 25%;
}
}
</style>
こうすることでPCではカードが8列、スマホではカードが4列に並び操作しやすくなります。
画面の幅に応じて見た目を柔軟に変えるのが「レスポンシブデザイン」の基本です。
たとえば「もっと大きなカードがいい」「縦向きのときは3列にしたい」といった場合も、
この@media
の中身を調整するだけで対応できます。
ぜひ自分なりのレイアウトを試してみてください!
このようなメディアクエリを活用すれば、ひとつのHTML&CSSファイルでPC・スマホどちらにも対応できます。
読みやすさや操作性を保つのは、ゲームでもWebアプリでも大切なポイント。
さらに工夫するなら、縦持ちと横持ちでレイアウトを切り替える方法や、カードサイズを画面幅に合わせて自動でスケーリングする方法などもあります。
初心者の方は、まずこのコードをベースに少しずつ応用していくのがオススメです。
ソースコード
全てのソースコードは以下です。
<div id="memorymain"></div> <style> .trumpdiv{ position:relative; margin:0; padding:2px;box-sizing:border-box; width:12.5%; transform-style: preserve-3d; box-sizing:border-box; } @media screen and (max-width:560px){ .trumpdiv{ width:25%; } } </style> <script> /* this.elm //大外のDIVタグ this.trump[0~15].div //ひっくり返すDIV this.trump[0~15].imgf //表の画像 this.trump[0~15].imgb //裏の画像 this.trump[0~15].kind //0~7までの海苔の種類 this.trump[0~15].status //0:裏 1:表 this.turnnum //ひっくり返した数 0:ひっくり返してない 1:1枚前 2:2つともひっくり返した this.turn[0~1] //ひっくり返した2つの札 this.status //0:何もなし 1:1枚目ひっくり返し中 2:2枚目ひっくり返し中 this.comp //完了時のDIVタグ this.counter //2枚を開けた回数 */ class TMemory{ init(){ this.turn=[-1,-1]; this.turnnum=0; this.status=0; this.counter=0; for(let i=0;i<this.trump.length;i++){ this.trump[i].kind=-1; this.trump[i].status=0;//裏 } for(let i=0;i<imgf.length;i++){ let r=Math.floor(Math.random()*this.trump.length); while(this.trump[r].kind!=-1){ r=Math.floor(Math.random()*this.trump.length); } this.trump[r].kind=i; r=Math.floor(Math.random()*this.trump.length); while(this.trump[r].kind!=-1){ r=Math.floor(Math.random()*this.trump.length); } this.trump[r].kind=i; } for(let i=0;i<this.trump.length;i++){ this.trump[i].imgf.src=imgf[ this.trump[i].kind ].src; } } constructor(elm){ this.elm=elm; this.elm.style.cssText="display:flex;flex-wrap:wrap;width:100%;max-width:1000px;padding:4px;margin:0;box-sizing:border-box;"; this.comp=document.createElement("div"); this.comp.style.cssText="position:fixed;display:none;opacity:0;color:blue;left:5vw;right:5vw;text-align:center;top:calc(50vh - 1em);font-weight:bold;background:#fff;font-size:20px;border:4px solid black;cursor:pointer;"; this.comp.innerHTML=""; this.comp.addEventListener("click",function(){ this.comp.style.display="none"; this.init(); }.bind(this)); document.body.appendChild(this.comp); this.trump=[]; for(let i=0;i<(imgf.length*2);i++){ this.trump[i]=[]; this.trump[i].div=document.createElement("div"); this.trump[i].div.addEventListener("click",this.click.bind(this,i)); this.trump[i].div.style.cssText=""; this.trump[i].div.classList.add("trumpdiv"); this.elm.appendChild(this.trump[i].div); this.trump[i].imgb=document.createElement("img"); this.trump[i].imgb.src=imgb.src; this.trump[i].imgb.style="position:relative;width:100%;height:auto;transform;backface-visibility:hidden;"; this.trump[i].div.appendChild(this.trump[i].imgb); this.trump[i].imgf=document.createElement("img"); this.trump[i].imgf.style="position:absolute;width:calc(100% - 4px);height:auto;left:0;top:0;transform:rotateY(-180deg);backface-visibility: hidden;"; this.trump[i].div.appendChild(this.trump[i].imgf); } this.init(); } click(trumpid){ if(this.turnnum<2 && this.trump[trumpid].status==0){ this.trump[trumpid].status=1; this.turn[this.turnnum]=trumpid; this.turnnum++; this.trump[trumpid].div.animate( [ {transform:"rotateY(0deg)"}, {transform:"rotateY(180deg)"} ], {duration:500, fill:'both',} ); if(this.turnnum==2){//2枚開けたとき this.counter++; if( this.trump[this.turn[0]].kind == this.trump[this.turn[1]].kind ){ //2枚が同じ setTimeout(this.judge.bind(this),500); }else{ //2枚が違う setTimeout(this.judge.bind(this),2000); } } } } judge(){ if( this.trump[this.turn[0]].kind == this.trump[this.turn[1]].kind ){ //2枚が同じ this.turnnum=0; let flag=true; for(let i=0;i<this.trump.length;i++){ if(this.trump[i].status==0){ flag=false; break; } } if(flag){ //全部ひっくり返した //完了文字の表示 this.comp.style.display='block'; this.comp.innerHTML="おめでとう!<br>"+this.counter+"回でクリアー"; this.comp.animate( [ {opacity:0},{opacity:1}], {delay:300,duration:700,fill:'both',} ); } }else{ //2枚が違った this.trump[this.turn[0]].status=0; this.trump[this.turn[1]].status=0; this.trump[this.turn[0]].div.animate( [ {transform:"rotateY(180deg)"}, {transform:"rotateY(0deg)"} ], {duration:500, fill:'both',} ); this.trump[this.turn[1]].div.animate( [ {transform:"rotateY(180deg)"}, {transform:"rotateY(0deg)"} ], {duration:500, fill:'both',} ); this.turnnum=0; } } goto(){ location.href="https://www.shirako-nori.co.jp/"; } } let urls=['./imgs/game_memory/back.png','./imgs/game_memory/0.png','./imgs/game_memory/1.png','./imgs/game_memory/2.png','./imgs/game_memory/3.png','./imgs/game_memory/4.png','./imgs/game_memory/5.png','./imgs/game_memory/6.png','./imgs/game_memory/7.png']; let imgf=[]; let imgb=null; let memory=null; window.addEventListener("load",function(){ loadImages(); }); function loadImages(){ Promise.all( urls.map(function(url){ return new Promise(function(resolve,reject){ let img=new Image(); img.onload=function(){resolve(img);} img.onerror=function(e){reject(e);} img.src=url; }); }) ).then(function(imgs){ imgs.forEach(function(img,i){ if(i==0){ imgb=img; }else{ imgf[i-1]=img; } }); memory=new TMemory(document.querySelector("#memorymain")); }).catch(function(e){ //1つでも画像ファイルがロードできなければエラー console.log('Error'); }); } </script>