じゃんけんをプログラミングしてみた
最近プログラミングした「じゃんけんゲーム」のコードについて、意識した点を解説したいと思います。
目的は
・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!!
この記事が気に入ったらサポートをしてみませんか?