Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

プログラマ勉強会 第2回

Last updated at Posted at 2024-07-02

条件分岐 if文

 ゲームを作る際、ある条件下のみでこんな動作を実装したい!といった場面が多く表れます。
 例えば、横スクロールアクションゲーム(マリオのようなゲーム)を作る場合、以下のような例が挙げられます。

  • プレイヤーが敵とぶつかったら、ダメージを与えたい(残機を減らしたい)
  • プレイヤーの残機が0になったら、ゲームオーバーにしたい
  • 残り時間が0になったら、ゲームオーバーにしたい

このような処理は、「if文」を用いることで簡単に実装できます!
試しに以下のコードを書いてみましょう。

《コード1》

#include <iostream>

int main() {
    int playerLife = 0;// プレイヤーの残機数
	
    if(playerLife == 0){
        std::cout << "ゲームオーバー!" << std::endl;
    }
    
    return 0;
}

まず簡単に説明すると、ifの隣の()内には条件を、{}内には条件を満たした際に実行する内容を書きます。
上の例で言うと、条件は playerLife == 0 です。
== は「右辺の値と左辺の値が一致しているかどうか」を判定する役割を担っており、例ではplayerLifeの値が0の場合に「ゲームオーバー!」と出力してくれます。

比較演算子

上の == のように、右辺と左辺の値を比較してくれるものを比較演算子と呼びます。

演算子 trueになる条件(if文内が実行される条件)
< 左辺が右辺より小さい場合
<= 左辺が右辺以下である場合
> 左辺が右辺より大きい場合
>= 左辺が右辺以上である場合
== 左辺と右辺が一致する場合
!= 左辺と右辺が異なる場合

bool型

 プログラマ勉強会第1回にて、bool型は真理値(trueかfalse)を扱う型名と軽く説明しました。if文の()内はbool型である必要があります。(trueかfalseで表せるもの、といったイメージ)
 if文が条件を満たすことを「真になる」だったり「trueになる」と言ったり、満たさない場合は「偽になる」や「falseになる」と言ったりします。以後はこのように説明させていただきます。

else if, else

 playerLifeが0の時に○○、1の時に○○、maxの時に○○...といった具合に、3つ以上に分岐させたい場合もありますよね。
 ライフが減るにつれて、注意を促すように赤めのエフェクトが表示されるようになる、といった演出を見たことはありませんか?次はこれをイメージしてみましょう。

《コード2》

#include <iostream>
#include <string>

int main(){
	int playerLife;//ライフの数
	std::string playerLifeColor;//ライフの色名
	
	std::cin >> playerLife;

	if(playerLife >= 3){
		playerLifeColor = "Blue";
	}else if(playerLife == 2){
		playerLifeColor = "Yellow";
	}else{
		playerLifeColor = "Red";
	}
	
	std::cout << playerLifeColor << std::endl;
	
	return 0;
}

 新たにelse if()が出てきました。これは、一つ上のif文がfalseだった場合に実行してくれるイメージです。
《コード2》の場合だと、「playerLifeが3以上ではない」とき、「playerLifeが2かどうか」を判定してくれるわけです。
最後に else です。この後ろには条件を書く必要がありません。elseに続く{}内のコードは、
elseより前の一連のif()及びif else()がすべてfalseだった場合」に実行してくれる感覚で大丈夫です。
《コード2》では「playerLifeが3以上でも2でもない場合」にelse内を実行してくれます。

論理演算子

 しかし、ライフが3つは少ないですよね。快適なゲームを求めるのなら10個ぐらいあってもよいかもしれません。次は、
・ライフが6以上のとき、青色。
・ライフが3~5のとき、黄色。
・ライフが1~2のとき、赤色。
となるようにしてみましょう。

↑のようにライフの色を黄色にする場合、「ライフが3以上である」「ライフが5以下である」の二つの条件が必要そうです…
こんなときは&&を使って以下のように書けます!

《コード3》

#include <iostream>
#include <string>

int main(){
	int playerLife;//ライフの数
	std::string playerLifeColor;//ライフの色名
	
	std::cin >> playerLife;
	
	if(6 <= playerLife){
		playerLifeColor = "Blue";
	}else if(3 <= playerLife && playerLife <= 5){
		playerLifeColor = "Yellow";
	}else{
		playerLifeColor = "Red";
	}
	
	std::cout << playerLifeColor << std::endl;
	
	return 0;
}

else if(3 <= playerLife && playerLife <= 5)の部分は、「playerLifeが3以上かつplayerLifeが5以下の場合」と言い換えることができます。この「かつ」こそが&&の役割です。

3 <= playerLife <= 5 のように、複数の条件を一つにまとめることはできないので注意!

論理演算子一覧

論理演算子 意味 基本構造 真になる条件
&& and if(「条件1」&&「条件2」) 「条件1」がtrueかつ「条件2」がtrue
|| or if(「条件1」||「条件2」) 「条件1」がtrueまたは「条件2」がtrue
! not if(!「条件」) 「条件」がfalse

:

しかし、《コード3》は一か所修正できる点があります。
「ライフが3~5のとき、黄色」という条件をそのまま表したものがelse if(3 <= playerLife && playerLife <= 5)ですが、else if文は前のif文がfalseだったことを踏まえて書くことが多いです。(今回の場合、「ライフが6以上」という条件がfalseである場合)
 よって、playerLife <= 5は省くことができます。

修正後のコード↓↓

#include <iostream>
#include <string>

int main(){
  int playerLife;//ライフの数
  std::string playerLifeColor;//ライフの色名
  
  std::cin >> playerLife;
  
  if(6 <= playerLife){
  	playerLifeColor = "Blue";
-	}else if(3 <= playerLife && playerLife <= 5){
+	}else if(3 <= playerLife){
  	playerLifeColor = "Yellow";
  }else{
  	playerLifeColor = "Red";
  }
  
  std::cout << playerLifeColor << std::endl;
  
  return 0;
}

もし、ライフが青色である条件を変えたくなった場合、修正前のコードだと、
if(6 <= playerLife){}else if(3 <= playerLife && playerLife <= 5){ の2か所の数値を変える必要がありますが、修正後のコードだと、if(6 <= playerLife){ の修正だけで済みます。ちょっとしたテクニックとして覚えておきましょう!

switch-case文

 次はライフが10まである中、1~10それぞれで色を変えてみましょう。ここで、《コード2》のようにelse if(playerLife == 1)を10個繰り返すのはやや大変ですし、コードも長く、見づらくなってしまいます。ここでswitch-case文の出番です!

《コード4》

#include <iostream>
#include <string>

int main(){
   int playerLife;//ライフの数
   std::string playerLifeColor;//ライフの色名
   
   std::cin >> playerLife;

   switch(playerLife){
   	case 1: playerLifeColor = "Red"; break;
   	case 2: playerLifeColor = "Orange"; break;
   	case 3: playerLifeColor = "Magenta"; break;
   	case 4: playerLifeColor = "Pink"; break;
   	case 5: playerLifeColor = "Yellow"; break;
   	case 6: playerLifeColor = "Blue"; break;
   	case 7: playerLifeColor = "SkyBlue"; break;
   	case 8: playerLifeColor = "LightBlue"; break;
   	case 9: playerLifeColor = "Green"; break;
   	case 10: playerLifeColor = "YellowGreen"; break;
   	default: playerLifeColor = "Black"; break;
   }
   
   std::cout << playerLifeColor << std::endl;
   
   return 0;
}

 switchの隣の()内には変数や条件を書きます。今回はplayerLifeを入れてますね。そして次の行からはcaseがずらっと並んでいます。仕組みをざっくり言うと、()内の値とcaseの横の値が一致するとき、そのcaseの部分からbreakまでのコードを実行します。上の例で言えば、playerLifeが3の時は、case 3:break;の間のplayerLifeColor = "Magenta";を実行してくれます。

breakの役割

 breakには、実行時switch文から抜け出してくれるという役割があります。各caseには基本breakが必要になります。
 上の例で説明すると、もしもcase 1:と同行のbreakが無かったら、playerLifeColor = "Red";だけでなく、playerLifeColor = "Orange";も実行されてしまいます。これはbreakが無いためにswitch文が終わらず、次の行のcase 2:も実行されてしまうためです。break忘れには注意しましょう。

defaultの役割

 defaultはif文で言うelseのようなもので、()内の値がどのcaseの値とも一致しないときに実行される箇所です。上の例で言えば、playerLifeが0や11、100のときにplayerLifeColor = "Black";が実行されるのです。

練習問題

以下のテンプレが与えられる。
ダメージ量(0以上の整数)が入力されるので、初期のライフ数からダメージ分を引き、残りライフ数によって以下のように出力が変わるようにしよう。

#include <iostream>
#include <string>

int main(){

    int playerLife = 3;//ライフの数
    int damage;//ダメージ量
    std::string status;//ステータス

    std::cout << "HP 3" << std::endl;
    std::cout << "damage: ";
    std::cin >> damage;

    ----your code----

    -----------------

    std::cout << status << std::endl;
	
	return 0;
}

《出力例》

HP 3
damage: 3
gameover
--------
HP 3
damage: 2 //1のときも同じ出力となるようにする。
damaged
--------
HP 3
damage: 0
no damage
--------
HP 3
damage: 72
gameover
--------

if文を用いる場合とswitch文を用いる場合の2パターンを考えてみよう。

解答(if)
#include <iostream>
#include <string>

int main(){

    int playerLife = 3;//ライフの数
    int damage;//ダメージ量
    std::string status;//ステータス

    std::cout << "HP 3" << std::endl;
    std::cout << "damage: ";
    std::cin >> damage;

    if(playerLife - damage == 3){
        status = "no damage";
    }else if(playerLife - damage == 2 || playerLife - damage == 1){//論理演算子を使おう
        status = "damaged";
    }else{
        status = "gameover";
    }

    std::cout << status << std::endl;
	
	return 0;
}
解答(switch)

switch文の()には、式を入れることもできる。

#include <iostream>
#include <string>

int main(){

    int playerLife = 3;//ライフの数
    int damage;//ダメージ量
    std::string status;//ステータス

    std::cout << "HP 3" << std::endl;
    std::cout << "damage: ";
    std::cin >> damage;

    switch(playerLife - damage){
        case 3:status = "no damage";break;
        case 2:status = "damaged";break;
        case 1:status = "damaged";break;
        default:status = "gameover";break;
    }

    std::cout << status << std::endl;
	
	return 0;
}

ループ文

 ゲームを作っていると、「100個分の床ブロックを生成したい」だとか、「40個分の弾幕を生成したい」ということがあります。生成するためのコードを100個分書くなんてことは非常に面倒なので、ここでループ文を身につけましょう!
 今まで数値や文字を出力してきましたが、今回は_を床ブロック1つ分と見なして、ステージを作るような感じでやっていきましょう。イメージは↓のような感じです。

_______________________________

while文

まず以下のコードを書いてみましょう。

《コード5》

#include <iostream>

int main(){
	int blockNum = 0;//ブロックの数

	while(blockNum < 30){
		std::cout << "_";
		blockNum++;//インクリメント
	}
	
	return 0;
}

 whileの横の()には、ifと同じように「条件」を書きます。ifと違うのは、()内がtrueである限り、{}内を実行し続けるという点です。
1行づつ見ていきましょう。また、「プログラムは原則main関数内を上から1行ずつ実行する」ということも念頭に置いておいてください。

 まず最初にwhile(blockNum < 30)に到達時、blockNumは0でblockNum < 30を満たすため{}内を実行し、_を1つ出力してblockNumの値に1を足します。(1ループ目)
 次にwhile(blockNum < 30)に到達時、blockNumは1でblockNum < 30を満たすため{}内を実行し、_を1つ出力してblockNumの値に1を足します。(2ループ目) これを繰り返します。
ざっと飛ばして30ループ目、blockNumは29でblockNum < 30を満たすため、_を1つ出力してblockNumの値に1を足します。
 そして31ループ目、blockNumは30であるため、blockNum < 30を満たしません。よって、{}内を実行せずにループが終了します。

for文

ループ文にはもう一つ、for文というものがあります。↑と同じ出力結果を
こちらのループ文でも書いてみましょう。

《コード6》

#include <iostream>

int main(){

	for(int i = 0;i < 30;i++){
		std::cout << "_";
	}
	
	return 0;
}

 for文の()には、for(「初期化式」;「条件式」;「更新式」)というように記述します。 「条件式」がtrueである限り、{}内を実行し続けるというのはwhile文と同じです。「初期化式」と「更新式」は↓で説明します。

 また1行ずつ見ていきましょう。
 まず最初に「初期化式」により、i = 0と初期化します。1ループ目のみに実行される箇所だと思ってくれればよいです。この時点でiは0でi < 30を満たすため{}内を実行し、_を1つ出力します。その後に更新式の内容を反映させます。例の場合、iに1を加えています。
 2ループ目に入った時点で、iは1でi < 30を満たすため{}内を実行し、_を1つ出力します。その後更新式により、iに1を加えます。
 ざっと飛ばして30ループ目、iは29でi < 30を満たすため{}内を実行し、_を1つ出力します。その後更新式により、iに1を加えます。
 最後に31ループ目、iは30でi < 30を満たしません。よって{}内を実行せずにループが終了します。

continue と break

次に、ステージに穴を作る感覚でやってみましょう。左から数えて4の倍数の場所には穴(空白)を作ってみます。完成図は↓の感じ。

___ ___ ___ ___ ___ ___ ___ ___ ___ ___

《コード7》

#include <iostream>

int main(){
    int i = 0;

	while(true){
        if(i % 4 == 3){
            std::cout << " ";
            i++;
            continue;
        }
        
		std::cout << "_";
        i++;
  
        if(i >= 40)break;
    }
}

 while文についておさらいしましょう。while文の()内の条件が真である限り、{}内を実行し続けるんでしたね。しかし、ここでは()内はtrue、すなわち真であり、このままでは永遠に{}内を実行し続けてしまいます...
ここでbreakの出番です!

ループ文でのbreakの役割

breakがループ文内で実行されると、それぞれの文から即座に抜け出します。

上の例だとif(i >= 40)break;という形で使用していますね。
ifの条件(iが40以上)を満たすとき、breakを実行してwhile文の処理を終了しています。

次はcontinueを見ていきましょう。

continueの役割

continueがループ文内で実行されると、continueより下の処理は実行せずに次のループへ移行します。

上の例だと、i % 4 == 3である場合、continueが実行されて次のループへ以降します。つまり、この場合はcontinueより下にあるstd::cout << _;が実行されず、_を出力しません!

二重ループ

ゲーム内では、巨大なブロックの塊もよく見られると思います。次は5 x 5のブロックを作ってみましょう!
イメージはこんな感じ↓

#####
#####
#####
#####
#####

《コード8》

#include <iostream>

int main(){

    for(int i = 0;i < 5;i++){
	    for(int j = 0;j < 5;j++){
		    std::cout << "#";
	    }
        std::cout << std::endl;//改行
    }
	
	return 0;
}

この例では、for文の中にfor文を書いています。このような書き方を二重ループと呼びます。

まず、《コード8》の↓部分は、一列分の#####を出力します。《コード6》とほとんど同じですね。

for(int j = 0;j < 5;j++){//#####を出力
		std::cout << "#";
}                     
std::cout << std::endl;//改行

外側のfor文(iを用いているもの)は↑を{}で覆っています。つまり、#####を出力する処理を5回ループしているということです。

for(int i = 0;i < 5;i++){//   ↓を5回ループ
	for(int j = 0;j < 5;j++){//#####を出力
		std::cout << "#";
	}
    std::cout << std::endl;//改行
}

練習問題

(1)以下の形を出力してみよう。

#####
####
###
##
#
ヒント
  • for文の「条件式」を変えてみよう
  • 《コード8》を1か所変えるだけでよい
解答
#include <iostream>

int main(){

    for(int i = 0;i < 5;i++){
	    for(int j = 0;j < 5 - i;j++){//コード8と違う点
		    std::cout << "#";
	    }
        std::cout << std::endl;//改行
    }
	
	return 0;
}

(2)マリオのゴール前によく配置されている、以下の形を出力してみよう。

       #
      ##
     ###
    ####
   #####
  ######
 #######
########
解答
#include <iostream>

int main(){

    for(int i = 0;i < 8;i++){
	    for(int j = 0;j < 8 - (i + 1);j++){
		    std::cout << " ";
	    }
        for(int j = 0;j < (i + 1);j++){
		    std::cout << "#";
	    }
        std::cout << std::endl;//改行
    }
	
	return 0;
}

Extra

数字あてゲームを作ってみよう!

以下の三行で乱数を生成できます。

std::random_device seed_gen;
std::mt19937 random(seed_gen());
int number = random() % 100; 

100の部分を変えると乱数の範囲を変えられます
以下のインクルードをファイルの先頭に追加する必要があるので注意してください。

#include <random>

EX問題の入出力例

数字を入力してね: 50
もっと小さいよ!
数字を入力してね: 25
もっと小さいよ!
数字を入力してね: 10
もっと大きいよ!
数字を入力してね: 17
もっと小さいよ!
数字を入力してね: 13
正解!!
ヒント
  • ループ文とbreakを使おう
答え
#include <iostream>
#include <random>

int main(){
    // 数あてゲーム
    std::random_device seed_gen;
    std::mt19937 random(seed_gen());
    int number = random() % 100; 
    // この3行で0~99の乱数を生成できます

    // while文の中身を各自考えてみましょう
    while (true) {
        int guess;
        std::cout << "数字を入力してね: ";
        std::cin >> guess;

        if (guess == number) {
            std::cout << "正解!!" << std::endl;
            break;
        } else if (guess < number) {
            std::cout << "もっと大きいよ!" << std::endl;
        } else if (guess > number){
            std::cout << "もっと小さいよ!" << std::endl;
        }
    }
    
    return 0;
}


0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?