コース目次 / 第3章
成果物 — 数当てゲーム
1から100までの数を当てるゲームを作ります。入力の罠を避ける定型つき。
いよいよゲームを作ります。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 を作って、そのまま書き写します。
#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 をお使いの方
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
Windows をお使いの方
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
遊んでみてください。まん中(50)から始めて、半分ずつ絞るのがいちばん速く、100までなら7回以内で必ず当たります。
4. 同じ問題をもう一度
種を決めて実行すると、答えが固定されます。
Mac をお使いの方
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
Windows をお使いの方
行頭の $ は「ここからコマンド」という目印です。$ は打たないでください。
答えは 43 です。何度やっても同じなので、手順を試すときに便利です。
こわしてみる
数字でないものを打ってください。
> abc1から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章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。