コース目次 / 第3章

成果物 — 数当てゲーム

1から100までの数を当てるゲームを作ります。入力の罠を避ける定型つき。

第3章 / 全5章目安 約60分この章のゴール: 自分で作ったゲームが遊べて、友だちにも渡せる
お使いのパソコン
手順が切り替わります
お使いのエディタ
ファイル操作が切り替わります

いよいよゲームを作ります。1から100までの数を当てる、あの定番です。

今日のゴール

こうなります
1から100までの数を当ててください。
> 50
もっと小さい
> 25
もっと大きい
> 37
もっと大きい
> 43
当たり! 答えは 43 でした。

1. ミニ解説: 今日使う道具

書き方 何をするか
std::mt19937 でたらめな数を作る機械。種(たね)を渡すと、そこから決まった順に数が出る
std::getline(std::cin, gyou) 1行まるごと文字として受け取る
std::atoi(gyou.c_str()) 文字を数に変える
while (true) { ... break; } 当たるまでくり返す

入力の受け取り方に、C++でいちばん有名な罠があります。 std::cin >> kazu; と数を直接受け取る書き方は短いのですが、数字でないものを打たれると、そこから先ずっと動きが止まります(読めなかった文字が入り口に残り続けるため)。
だからこのコースでは「1行を文字として受け取ってから、自分で数に変える」という遠回りをします。これがC++の定石で、遠回りに見えて結局いちばん楽です。

2. 書く

kazuate.cpp を作って、そのまま書き写します。

kazuate.cpp
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <random>
#include <string>

#ifdef _WIN32
#include <windows.h>
#endif

// 文字列が数字だけでできているか。
// cin に数を直接流し込むと、数字でない入力が来たときに動きが固まってしまう。
// だから、いったん1行の文字として受け取ってから、自分で確かめる。
bool suuji_ka(const std::string& moji) {
    if (moji.empty()) {
        return false;
    }

    for (char c : moji) {
        if (c < '0' || c > '9') {
            return false;
        }
    }

    return true;
}

int main() {
#ifdef _WIN32
    // Windowsのコンソールに日本語をそのまま出すための1行。Macでは使われない。
    SetConsoleOutputCP(CP_UTF8);
#endif

    // 種を決めると、同じ答えが何度でも出せる(答え合わせのしかたを揃えるため)。
    unsigned int tane = static_cast<unsigned int>(std::time(nullptr));
    const char* shitei = std::getenv("SEED");
    if (shitei != nullptr) {
        tane = static_cast<unsigned int>(std::atoi(shitei));
    }

    std::mt19937 saikoro(tane);
    int kotae = static_cast<int>(saikoro() % 100) + 1;

    std::cout << "1から100までの数を当ててください。" << std::endl;

    std::string gyou;
    while (true) {
        std::cout << "> ";

        if (!std::getline(std::cin, gyou)) {
            break;
        }

        if (!suuji_ka(gyou)) {
            std::cout << "1から100までの数字を打ってください。" << std::endl;
            continue;
        }

        int kotae_kouho = std::atoi(gyou.c_str());

        if (kotae_kouho < kotae) {
            std::cout << "もっと大きい" << std::endl;
        } else if (kotae_kouho > kotae) {
            std::cout << "もっと小さい" << std::endl;
        } else {
            std::cout << "当たり! 答えは " << kotae << " でした。" << std::endl;
            break;
        }
    }

    return 0;
}

読みどころが3つあります

(1) 先頭の #ifdef _WIN32

Windowsのときだけ、日本語が化けないようにする行です。Macでは丸ごと無視されます。 #ifdef は「この環境のときだけ、この部分を使う」という指示で、C++が複数のOSで同じソースを使うための仕組みです。意味が分からなくても、そのまま写してください。

(2) suuji_ka 関数

文字が数字だけでできているかを、1文字ずつ確かめています。これがあるから、abc と打たれても壊れません。

(3) SEED の3行

ふだんは時計から種を作るので、毎回違う答えになります。SEED を決めて実行したときだけ、答えが固定されます。

std::mt19937 はC++の規格で計算のしかたが完全に決まっているので、同じ種なら、どのパソコンでも、どのコンパイラでも同じ数が出ます。だから「友だちと同じ問題を解く」ができます。
この教材の例が毎回同じ答えを出せるのも、公開前に自動で検査できるのも、この3行のおかげです。

3. コンパイルして遊ぶ

Mac をお使いの方

VS Codeのターミナルターミナル
clang++ -std=c++17 -Wall -Wextra -o kazuate kazuate.cpp

行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。

Windows をお使いの方

VS Codeのターミナルターミナル
g++ -std=c++17 -Wall -Wextra -o kazuate kazuate.cpp

行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。

VS Codeのターミナルターミナル
./kazuate

行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。

遊んでみてください。まん中(50)から始めて、半分ずつ絞るのがいちばん速く、100までなら7回以内で必ず当たります。

4. 同じ問題をもう一度

種を決めて実行すると、答えが固定されます。

Mac をお使いの方

VS Codeのターミナルターミナル
SEED=42 ./kazuate

行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。

Windows をお使いの方

VS Codeのターミナルターミナル
SEED=42 ./kazuate

行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。

答えは 43 です。何度やっても同じなので、手順を試すときに便利です。

こわしてみる

数字でないものを打ってください。

やってみる
> abc
こう出る
1から100までの数字を打ってください。

止まりません。そのまま次の入力を待ちます。suuji_ka が効いています。

もし std::cin >> kazu; で書いていたら、ここで無限ループに入っていました。「>」が延々と流れ続けて、Ctrl + C でしか止められません。入力を扱うときのC++の第一の落とし穴で、今日それを避ける形を身につけました。

ハマったら

「‘std::mt19937’ has not been declared」と出る

#include <random> を書き忘れています。C++は、使う道具を最初に宣言しておく必要があります。

警告がたくさん出る

-Wall -Wextra を付けているので、気になるところは全部言ってくれます。教材のコードをそのまま写していれば、警告は1つも出ません。出るなら、写し間違いを疑ってください。

いつまでも当たらない

まん中から半分ずつです。1〜100なら、50 → 25か75 → …と絞れば7回以内で必ず当たります。これは二分探索という考え方で、プログラミングのあちこちで出てきます。

ZIPで戻したい

このページの下のZIPは、この章を終えた状態のフォルダです。解凍して、その中で作業を続けられます。自分で書き換えた部分は消えてしまうので、先に自分のファイルを別の場所へ待避してから使ってください。

こうなっていればOK

うまくいかないときは、この章の完成ファイル一式(ZIP)をダウンロードして続きから進めても大丈夫です。

卒業まであと1章です。

この章はまだ完了していません。