Edited at

【JavaScript】「じゃんけんゲーム」を作ってみた


はじめに

こんにちは。

今回は、HTML、CSS、JavaScriptを使ってオリジナルゲームを作りました。

ゲームの名は「じゃんけんゲーム」です。

私個人の中で、勉強している言語が使えるようになってきたら、確認または一つの到達度のラインとして、じゃんけんゲームを作るようにしています。

ロジックはとても簡単なので、すぐ作れてしまうものなのですが、プログラムをより工夫しようと考えると結構大変でした。

とりあえず自分なりに終止符を打ち、完成させたので、記事にしていこうと思います。

完成したものは次のURLから飛んで、遊んでみてください。

https://michimichix521.github.io/JankenGame/

ファイル等はGitHubにあげていますので、こちらからダウンロードすることもできます。


環境


  • Windows 10 home

  • Google Chrome


じゃんけんゲームの制作

じゃんけんゲームで使う素材については、ぜひこちらを使ってください。

このゲームではimgフォルダを作って、そこに以下の画像ファイルを入れています。

これら素材はPiskelで描いています。


1.構造(HTML)

まずは構造についてです。

このじゃんけんゲームでは、大きく分けると、ディーラーとプレイヤーがいて、その間に、勝敗を表すテキストとリセットボタンがあります。

ソースコードは以下のようになります。


index.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の処理に対して、その都度確認してみてください。


styles.css

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クラスができている形になっています。


main.js

"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のファイルを入れ替えるだけで動くと思いますので、ぜひ試してみてください。


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メソッドで勝敗に関する処理をしているのですが、もっときれいにできると思います。自分的に他のところで力尽きてしまいました。(笑)

興味があれば、これを参考に改良してみてください。

こっちの方がもっときれいにプログラムが書けるよ!とかあれば、ぜひコメントをください!

ここまで読んでいただき、ありがとうございました。