はじめに
こんにちは。
今回は、HTML、CSS、JavaScriptを使ってオリジナルゲームを作りました。
ゲームの名は「じゃんけんゲーム」です。
私個人の中で、勉強している言語が使えるようになってきたら、確認または一つの到達度のラインとして、じゃんけんゲームを作るようにしています。
ロジックはとても簡単なので、すぐ作れてしまうものなのですが、プログラムをより工夫しようと考えると結構大変でした。
とりあえず自分なりに終止符を打ち、完成させたので、記事にしていこうと思います。
完成したものは次のURLからプレイできます!
ファイル等はGitHubにあげていますので、こちらからダウンロードすることもできます。
環境
- Windows 10 home
- Google Chrome
じゃんけんゲームの制作
じゃんけんゲームで使う素材については、ぜひこちらを使ってください。
このゲームではimgフォルダを作って、そこに以下の画像ファイルを入れています。
これら素材はPiskelで描いています。
1.構造(HTML)
まずは構造についてです。
このじゃんけんゲームでは、大きく分けると、ディーラーとプレイヤーがいて、その間に、勝敗を表すテキストとリセットボタンがあります。
ソースコードは以下のようになります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>じゃんけんゲーム</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<div class="dealer"></div>
<div class="container">
<div class="result"></div>
<div id="resetButton" class="hidden">もう一度!</div>
</div>
<div class="player"></div>
</main>
<script src="main.js"></script>
</body>
</html>
2.見た目(CSS)
CSSのソースコードは以下のようになります。
あまり解説はしませんが、HTMLやJavaScriptの処理に対して、その都度確認してみてください。
body{
background-color:#088A4B;
}
main{
width:500px;
margin:0 auto;
text-align:center;
font-weight:bold;
}
.dealer{
height:160px;
}
.dealer img{
width:150px;
height:160px;
}
.player,.container{
display:flex;
}
.player{
justify-content:space-between;
}
.player img{
width:100px;
height:110px;
cursor:pointer;
user-select:none;
}
.container{
margin:70px 0;
height:100px;
line-height:100px;
justify-content:space-around;
}
.result{
font-size:48px;
font-family:Arial, sans-serif;
}
#resetButton{
width:100px;
font-size:18px;
border-radius:50%;
background-color:pink;
border:3px solid #fff;
box-sizing: border-box;
}
#resetButton:hover{
opacity:0.8;
cursor:pointer;
user-select:none;
}
.hidden{
display:none;
}
3.振る舞い(JavaScript)
動作としては、ディーラーのじゃんけんのアイコンは、プレイヤーにあるグー・チョキ・パーのアイコンを1つクリックするまで、ランダムに入れ替わるようになっています。
クリックされたタイミングで、ディーラーのアイコンの入れ替わりが止まり、プレーヤーがクリックしたアイコンと、ディーラーの止まったアイコンで勝敗を決めるといったものになります。
ソースコードは以下の通りです。
プログラムの構造では、親クラスとして、Userクラスがあり、それを継承し、子クラスとして、DealerクラスとPlayerクラスができている形になっています。
"use strict";
{
class User{ //親クラス
constructor(tag,type){
this.tag=tag;
this.type=type;
this._img=document.createElement("img");
this._img.addEventListener("click",()=>{
this.click();
});
}
addImage(){ //画像ソースの追加
this._img.src="img/"+this.type+".png";
}
addHTML(){ //HTMLに生成したタグの追加
this.tag.appendChild(this._img);
}
click(){ //クリックされた時の処理
if(!RESET_BUTTON.classList.contains("hidden")){ //何度もクリックできないようにする
return;
}
clearTimeout(dealer._timeoutId);
RESET_BUTTON.classList.remove("hidden");
this.result();
}
result(){ //じゃんけんの結果判定
let _dealerType=dealer.type[dealer._rdNum];
switch(this.type){
case "rock": //グーをクリックした場合
if(_dealerType === "rock"){
RESULT.textContent="引き分け";
}else if(_dealerType === "scissors"){
RESULT.textContent="勝ち!";
}else{
RESULT.textContent="負け";
}
break;
case "scissors": //チョキをクリックした場合
if(_dealerType === "rock"){
RESULT.textContent="負け";
}else if(_dealerType === "scissors"){
RESULT.textContent="引き分け";
}else{
RESULT.textContent="勝ち!";
}
break;
case "paper": //パーをクリックした場合
if(_dealerType === "rock"){
RESULT.textContent="勝ち!";
}else if(_dealerType === "scissors"){
RESULT.textContent="負け";
}else{
RESULT.textContent="引き分け";
}
break;
default:
console.log("タイプなし");
break;
}
}
}
class Dealer extends User{ //子クラス:ディーラー
constructor(tag,type){
super(tag,type);
this._rdNum=Math.floor(Math.random()*type.length);
this._timeoutId;
this.addImage();
this.addHTML();
}
//オーバーライド
addImage(){ //画像ソースの追加
this._img.src="img/"+this.type[this._rdNum]+".png";
}
shuffleImage(){ //ランダムにじゃんけん画像ソースの入れかえ
this._rdNum=Math.floor(Math.random()*this.type.length);
this.addImage();
this._timeoutId = setTimeout(()=>{
this.shuffleImage();
},20); //50fps
}
//オーバーライド
click(){return;} //クリックしても何も起こらない
}
class Player extends User{ //子クラス:プレイヤー
constructor(tag,type){
super(tag,type);
this.addImage();
this.addHTML();
}
}
const DEALER_TAG=document.querySelector(".dealer"); //タグの取得
const PLAYER_TAG=document.querySelector(".player"); //タグの取得
const RESET_BUTTON=document.getElementById("resetButton"); //タグの取得
const RESULT=document.querySelector(".result"); //タグの取得
const JANKEN_TYPE=["rock","scissors","paper"]; //じゃんけんタイプの配列
init();
const dealer=new Dealer(DEALER_TAG,JANKEN_TYPE) //ディーラーのインスタンスの生成
let player=new Array(3);
for(let i=0;i<player.length;i++){
player[i]=(new Player(PLAYER_TAG,JANKEN_TYPE[i])); //プレイヤーのインスタンスの生成
}
dealer.shuffleImage();
RESET_BUTTON.addEventListener("click",()=>{ //リセットボタンが押された時の処理
dealer.shuffleImage();
init();
});
function init(){ //初期化
RESULT.textContent="";
RESET_BUTTON.classList.add("hidden");
}
}
じゃんけんゲームは以上で終了になります。
2019/09/12 修正
コメントでクラスの責務に関して指摘を受けたので、以下が修正したソースコードになります。
上記のmain.jsのファイルを入れ替えるだけで動くと思いますので、ぜひ試してみてください。
"use strict";
{
class GameMaster{ //ゲームの進行用のクラス
constructor(){
this.init(); //初期化
this._opponent=new Opponent(DEALER_TAG,JANKEN_TYPE) //相手
//プレイヤー側のじゃんけんのアイコンの表示
this._player=new Array(3);
for(let i=0;i<this._player.length;i++){
this._player[i]=document.createElement("img");
this._player[i].src="img/"+JANKEN_TYPE[i]+".png";
PLAYER_TAG.appendChild(this._player[i]);
this._player[i].addEventListener("click",()=>{
this.click(JANKEN_TYPE[i]); //プレイヤー側のアイコンがクリックされた時に動作するメソッド
});
}
this._opponent.shuffleImage(); //相手のアイコンをシャッフルする
RESET_BUTTON.addEventListener("click",()=>{ //リセットボタンが押された時の処理
this._opponent.shuffleImage();
this.init();
});
}
init(){ //初期化
RESULT.textContent="";
RESET_BUTTON.classList.add("hidden");
}
click(clickType){ //クリックされた時の処理
if(!RESET_BUTTON.classList.contains("hidden")){ //何度もクリックできないようにする
return;
}
clearTimeout(this._opponent._timeoutId);
RESET_BUTTON.classList.remove("hidden");
this._opponent.result(clickType);
}
}
class Opponent{ //相手のクラス
constructor(){
this._img=document.createElement("img");
this._rdNum=Math.floor(Math.random()*JANKEN_TYPE.length);
this._img.src="img/"+JANKEN_TYPE[this._rdNum]+".png";
DEALER_TAG.appendChild(this._img);
this._timeoutId;
}
shuffleImage(){ //ランダムにじゃんけん画像ソースの入れかえ
this._rdNum=Math.floor(Math.random()*JANKEN_TYPE.length);
this._img.src="img/"+JANKEN_TYPE[this._rdNum]+".png";
this._timeoutId=setTimeout(()=>{
this.shuffleImage();
},20); //50fps
}
result(clickType){ //じゃんけんの結果判定
let _dealerType=JANKEN_TYPE[this._rdNum];
switch(clickType){
case "rock": //グーをクリックした場合
if(_dealerType === "rock"){
RESULT.textContent="引き分け";
}else if(_dealerType === "scissors"){
RESULT.textContent="勝ち!";
}else{
RESULT.textContent="負け";
}
break;
case "scissors": //チョキをクリックした場合
if(_dealerType === "rock"){
RESULT.textContent="負け";
}else if(_dealerType === "scissors"){
RESULT.textContent="引き分け";
}else{
RESULT.textContent="勝ち!";
}
break;
case "paper": //パーをクリックした場合
if(_dealerType === "rock"){
RESULT.textContent="勝ち!";
}else if(_dealerType === "scissors"){
RESULT.textContent="負け";
}else{
RESULT.textContent="引き分け";
}
break;
default:
console.log("タイプなし");
break;
}
}
}
const DEALER_TAG=document.querySelector(".dealer"); //タグの取得
const PLAYER_TAG=document.querySelector(".player"); //タグの取得
const RESET_BUTTON=document.getElementById("resetButton"); //タグの取得
const RESULT=document.querySelector(".result"); //タグの取得
const JANKEN_TYPE=["rock","scissors","paper"]; //じゃんけんタイプの配列
const gameMaster=new GameMaster(); //ゲームの進行用のクラスのインスタンスの生成
}
おわりに
resultメソッドで勝敗に関する処理をしているのですが、もっときれいにできると思います。自分的に他のところで力尽きてしまいました。(笑)
興味があれば、これを参考に改良してみてください。
こっちの方がもっときれいにプログラムが書けるよ!とかあれば、ぜひコメントをください!
ここまで読んでいただき、ありがとうございました。