じゃんけんをプログラミングしてみた

最近プログラミングした「じゃんけんゲーム」のコードについて、意識した点を解説したいと思います。
目的は
・noteを使ってみたいと思っていた
・プログラミング初学者の方が、少しでも成長するきっかけになれば
という感じです。



何故じゃんけんをプログラミングしたのか

そもそも、プログラミングしようと思ったきっかけはこちらのツイート

コードのレビューをしてもらえるという事で、あきぞーさんの追加課題に参加させて頂きました。
あきぞーさん、その節は大変お世話になりました!
丁寧なレビューありがとうございました。



ルールのおさらい

まずルールをおさらいしておくと・・・
・5人以上が同時にじゃんけんする
・「グー」「チョキ」「パー」以外に「銃」という要素がある
・「銃」を除いたとき、あいこ状態になっていれば「銃」が勝ち
・「銃」を除いたとき、あいこ状態になっていなければ「銃」だけが負け
 (グー・チョキ・銃・銃・銃の場合、グーとチョキが勝ち)
・「銃」を除いたとき、ひとりしか残らなかった場合「銃」の負け
 (グー・銃・銃・銃・銃の場合、グーが勝ち)


ソースコード

そして完成したコードがこちら▼

https://paiza.io/projects/s44X2gcYUZrMYZ9Xi2H7Mg
(paiza.ioってコードを送る事が出来るんですね。すごい!)

// 参加人数
let playerNum = 5;
let players = [];

// 手の種類
var card = {
   GOO : 1,
   CHOKI : 2,
   PAR : 3,
   GUN : 4
};

// プレイヤー[]作成
for(let i = 0; i < playerNum; i++){
   players[i] = null;
}

// 勝利手
let winCards = [];
let drawNum = 0;

// ゲームを開始
do{
   if(drawNum === 0){
       console.log("じゃんけんぽん!");
   }else{
       console.log("あいこ" + drawNum +"回目!");
   }
   initCard(players);
   dispPlayer(players);
   winCards = janken(players);
   drawNum++;
}while (winCards === null);
console.log("勝者は...");
dispHasCardPlayer(players, winCards);

// function() =========================================================================

//1:グー 2:チョキ 3:パー 4:銃
function initCard(players){
   for(let i = 0; i < players.length; i++){
       players[i] = Math.floor( Math.random() * (Object.keys(card).length) + 1);
   }
}

// 勝利cardを返す。あいこはnull
function janken(players){
   // 銃の勝利判定
   if(players.indexOf(card.GUN) != -1){
       // あいこ
       // 銃を排除したプレイヤー[]
       let removedGUNPlayers = players.filter(value => value !== card.GUN);
       // 全員が銃
       if(removedGUNPlayers.length === 0){
           return null;
       }

       // 負け
       // 銃・重複を排除したプレイヤー[]
       let modPlayers = removedGUNPlayers.filter((item, value, self) => self.indexOf(item) === value);
       // 1パターンだけ且つ1人
       if(modPlayers.length === 1 && removedGUNPlayers.length === 1){
           return [card.GOO, card.CHOKI, card.PAR];
       }
       // 2パターン
       if(modPlayers.length === 2){
           return [card.GOO, card.CHOKI, card.PAR];
       }

       // 勝ち
       // 1パターンだけ且つ1人より上
       if(modPlayers.length === 1 && removedGUNPlayers.length > 1){
           return [card.GUN];
       }
       // 3パターン
       if(modPlayers.length === 3){
           return [card.GUN];
       }
   }

   // 通常の判定
   // 勝利手[]またはあいこ[]を返す
   let winCard = searchWinCard(players);
   if(winCard != null){
       return winCard;
   }
   return null;

}

// playersを表示
function dispPlayer(players){
   let str = "";
   for(let i = 0; i < players.length; i++){
       // プレイヤーは1から。cardは配列で取得されindexが0始まりなので-1
       str = str + "【プレイヤー" + (i + 1) + ":" + Object.keys(card)[players[i]-1] + "】";
   }
   console.log(str);
}

// card[]を持つプレイヤー[]だけを返す
function dispHasCardPlayer(players, cards){
   let str = "";
   let i = 0;
   for(let player of players){
       for(let item of cards){
           if(player === item){
               // プレイヤーは1から。cardは配列で取得されindexが0始まりなので-1
               str = str + "【プレイヤー" + (i + 1) + ":" + Object.keys(card)[player-1] + "】";
           }
       }
       i++
   }
   console.log(str);
}

// 通常じゃんけんの勝利手を返す。あいこはnull
function searchWinCard(players){
   let patterns = players.filter((item, value, self) => self.indexOf(item) === value);
   patterns = patterns.filter(value => value !== card.GUN);

   if(patterns.length === 2){
       let winCard = (patterns[0] - patterns[1] + 3 ) % 3;
       if(winCard === 2){
           return [patterns[0]];
       }
       if (winCard === 1) {
           return [patterns[1]];
       }
   }
   return null;
}

言語はJavaScriptです。長ったらしくてすみません。
それでは意識した点を解説していきます。


1.参加人数は変更できるようにする

// 参加人数
let playerNum = 5;

ここを変更すれば人数が変わる・・・はずです。
5人"以上"が条件だったので、人数の変更が出来るイメージでした。



2.通常のじゃんけんをベースとする

通常のじゃんけんをベースに「銃」という新要素を追加するイメージ。なので「銃」要素を簡単に付けたり外したり出来ることを意識しました。
具体的には

// 手の種類
var card = {
   GOO : 1,
   CHOKI : 2,
   PAR : 3
//   GUN : 4
};

という風に、GUNをコメントアウトすればGUN要素を排除できます。
「janken() 関数にがっつり銃の処理書いてんじゃねーか」とは言わないで下さいすみません。もう関数化しなくて良いかなーって思ってしまいました。



3.列挙型を使用する

// 手の種類
var card = {
   GOO : 1,
   CHOKI : 2,
   PAR : 3,
   GUN : 4
};

この部分です。こいつのせいでえらい目に合いました。
端的に言うと

「戻り値として配列にして返そうとしたら文字列になっていた」

です。使わなければ良かったと後悔しました。
ちなみに列挙型(enum)とは定数のリストとなっており、今回でいうと
「cardの種類はこの4種類だけだよ。cardを使うときはこの名前を指定してね!」という感じです。



4.ストーリーのあるコードにする

メイン処理の部分ですが

// ゲームを開始
do{
   if(drawNum === 0){
       console.log("じゃんけんぽん!");
   }else{
       console.log("あいこ" + drawNum +"回目!");
   }
   initCard(players);
   dispPlayer(players);
   winCards = janken(players);
   drawNum++;
}while (winCards === null);
console.log("勝者は...");
dispHasCardPlayer(players, winCards); 

console部分は「これを表示しているのかー」と分かるとして・・・
initCard(players);
 ⇒「プレイヤー達のカード(引数)を初期化しているんだなぁ~」
dispPlayer(players);
 ⇒「プレイヤー達(引数)を表示しているんだなぁ~」
winCards = janken(players);
 ⇒「じゃんけんの結果が勝ったカード(手)なんだなぁ~」
drawNum++;
 ⇒ドローをカウントしているんだなぁ(許して)
}while (winCards === null);
 ⇒「勝ちカードが無ければ繰り返すのかぁ~」
dispHasCardPlayer(players, winCards);
 ⇒「勝ちカードを持ったプレイヤー(引数)を表示しているんだなぁ~」

という感じです。
その為に、きちんと関数名と処理内容を合わせることも意識しました。
じゃんけんの内容(janken())は関数化するか迷いましたが、結局関数化しました。
ちなみに英語は激弱君なので、関数名には定番の単語を組み合わせています。



5.クラスの使用

最初は全く利用する気がなかったのですが、列挙型の関係で
「プレイヤーをクラス化しようかな?」と迷いました。
が、時すでに遅し。変更を加えるには面倒すぎる段階まできていたので諦めました。
ちなみにクラス化するとしたら「card」「name」をクラス変数として持たせる予定でした。
多分こんな感じです

class Player {
    constructor(name, card){
        this.name = name;
        this.card = card;
    }

    getName(){
        return this.name;
    }
    setName(name){
        this.name = name;
    }

    getCard(){
        return this.card;
    }
    setCard(card){
        this.card = card;
    }
}

jsでクラスとか使ったありませんが、こんな感じになると思います。


6.関数や引数の名前について

次の関数ですが、
「勝ったプレイヤーを表示する」為に使用しています。

function dispWinPlayre(players, winCards){
   let str = "";
   let i = 0;
   for(let player of players){
       for(let item of winCards){
           if(player === item){
               // プレイヤーは1から。cardは配列で取得されindexが0始まりなので-1
               str = str + "【プレイヤー" + (i + 1) + ":" + Object.keys(card)[player-1] + "】";
           }
       }
       i++
   }
   console.log(str);
}

どうでしょうか?
これって別に「勝者をconsoleで表示」している訳ではありませんよね?
引数のplayers配列からwinCards配列にマッチするものを表示しているだけなんです。
だけど関数名を「dispWinPlayre」としてしまっている事で

「勝者しか表示できないのかな?」

となってしまいますね。
でも実際には引数のwinCardsに負けたプレイヤーのcardを渡してやれば
「敗者を表示」させる事ができます。
なので関数名と引数は

function dispHasCardPlayer(players, cards){

「cardを持っているplayreを表示する」というようになっています。
この辺りを意識すると「名前詐欺」と呼ばれる関数を作る事は無くなるのでは...と思います。(janken()はそっとしておいて下さい)



以上、最後までお付き合い頂きありがとうございます。

Thank you!!

この記事が気に入ったらサポートをしてみませんか?