ターニングポイントさん!?

サルにはわからない上に無駄にめんどくせぇプログラミング入門

ひさしぶりだよね!プロプロ人ことプロレタリアプログラマー人間だよ!オッスオッス!元気してた!?🤗

…うん…そっか…ゴメンね😥

アタイ?アタイは元気してたよ!ふへへ!

◆はじめに

こんな記事開いちゃったってことは、キミはプログラミングしてみたいって思ってるのかね?

あはは。なんで?わけわかんない😟

…まぁいいや。じゃあプログラミングの世界に入門してみよっか!ごく一般的な方法で!

(以下、書くの疲れるのでプロプロ人は登場しません。)

◆この入門を完遂したときに何が出来るようになるか

なにも。なにもできるようにはならない。

「お前が何をできるようになるか」は俺の知るところではねぇぜ。お前さん次第です。

楽しいかどうかも、知らん。俺は書いていて楽しいけど。

いま世間で仕事してるプログラマが以下の七面倒くさい作業をしているってことでもない。大抵のプログラマはもっと楽してて、無知で、自分が書いたプログラムがどういう経緯で動いているのかって事を詳しくは知らずに仕事してる。だからガチれば半年でまくれる。

うん。じゃあやっていく。

◆Linux環境を手に入れろ

21世紀を手に入れろ。

プログラミングに入門するんであればWindowsで十分やれる。やれるんだけど、よりミニマムかつ根源的に入門させたいのでLinuxでやっていく。「まったくの未経験に対して好きなようにプログラミング教えてみろ」ってなったらLinuxでやらせると思う。UIなんか使わせない。
WSLを使う理由のひとつは、WSLの廃棄が簡単だから。Windowsを汚染しないから。いらなくなったら環境をポイ捨てして再構築すりゃいい。

最初からLinux使ってるんならそのまま進め。WindowsならWSLってのをインストールしろ。Macは知らん。

WSLのインストールの仕方は以下参照。多少面倒だけど頑張れ。「Ubuntu 20.04 LTS」的なものを実行できれば勝ち。Ubuntuを動かせ。

Windows 10 用 Windows Subsystem for Linux のインストール ガイド

基本的には

  • Windowsキー押して
  • powershellとか入力して
  • PowerShellを起動して
  • wsl –installってコマンドをたたく

だけでいいんじゃねぇかな。少し前はセットアップもそれなりに作業ありましたけど。PowerShellを起動するときに「管理者として実行」する必要あるかも?

よくわからんなら、この記事のコメントに書いていけ。
初期設定パスワードは、とりあえずユーザー名と同じにしちゃうのが無難。クソほど忘れるし、WSLでプログラミングの勉強するだけであればセキュリティを考える必要はない。
GPUアクセラレーションやらに対応し始めたので、詳しい人は以下を読むといいかも。いや、詳しい人はこの記事読まんと思うけど。
WSL 開発環境を設定する | Microsoft Docs

◆ソースをつくる

無事にWSLの起動を成功させたようですねおめでとうございますイェイイェイ。じゃあいよいよソースを作っていきましょう。

ソースって言うのは、お料理にかけておいしいやつのこと。それを作ると美味しいのでパソコンが動いてくれる。

▼ターミナルさん

「ターミナル」というなにがしにコマンドを打ち込むことによってPC
の操作をしていく。WindowsのWSLであれば、起動したそれがターミナル。もとからLinuxとか使っててターミナルの開き方がわからないならググれ。

Windows Terminalってのもあってカッコいいから、それを使ってUbuntuやら操作するのもおかし。

Windows Terminal

▼ファイルつくる

Linuxのターミナルを開くことに成功したならば、まずフォルダ作って、そこにプログラムのソースとなるファイルを作りたい気持ちが腹の底から湧いてくるはずだ。

ターミナルでは、マウスを一切使わずにPCを操作していく。フォルダを作ったりファイルを作ったりフォルダを開いたりってのも、マウスなし。全部コマンドを打ち込むことにより達成せねばならない。また、フォルダのことをLinuxではディレクトリって呼ぶ。

「mkdir」コマンドで「tinpo」っていう名前のディレクトリをつくる。mkdirはすなわちmake directoryの略。tinpoはちんぽ。

今後「$」が先頭にきてるアレがあったら、コマンドラインでの操作だと思ってくれ。「$ 」は入力せんでいい。下の例でいうと「mkdir」から入力しる。入力し終わったらEnter押せ。

$ mkdir tinpo

「cd」コマンドにより「tinpo」フォルダ内に移動する。change directory。

$ cd tinpo

「touch」コマンドで「prac1.c」ってファイル作成。touchがどういう意味なのかは知らん。

$ touch prac1.c

「tinpo」っていうフォルダ名とか「prac1.c」ってファイル名は別に何でも構わんよ。prac1ってのはpracticeのprac。

「ls」ってコマンドを叩けば、フォルダの中身を一覧できます。一個上のフォルダにあがりたいなら「cd ..」ってコマンドを叩け。

▼viとかいうエディタ

作ったファイルにソースコードを書き込もう。

ターミナルには入力補完機能がありまして、いちいちファイル名とか全部入力することもない。下記コマンドを例にとると、viって入力して半角スペースいれたら、Tabキーを押せば入力補完してくれる。Tabをポチポチと押し続けてお目当てのファイルを出せ。あと、「p」って入力した後にTabキー押せばファイルの先頭が「p」から始まるファイルを出してくれるよ。

$ vi prac1.c

「vi」コマンドはvimっていうテキストエディタを起動してくれる。なかなか操作がむずいので気を付ける事。慣れればいいものだらしいんだけど研鑽が必要。

Ubuntuにデフォで入っていると思われる「nano」っていうエディタがあって、そっちのほうが実際のところ操作が簡単ではある。「nano prac1.c」で起動できる。でも、しばらくvim使って苦しもう。

vimの起動に成功しただろうか。成功したっぽいなら「i」キーでINSERTモードに入るがよいぞ。vimではINSERTモードに入らねば書き込みはできない。あと「i」って入力しようとして全角で「い」とか入れてもINSERTモードには入れないんで、入力しちゃった文字をBackspaceで全部消してから半角モードにしてね。

INSERTモードに入ったら、以下を書きこめ。Ctrlキーを押すと迷宮入りする羽目になるかもしれないので気を付けろ。全部手で打ち込め。今はとりあえずコピペに頼らないで頑張れ。

#include <stdio.h>

int main(int argc, char *args[])
{
    printf("Hello, world!\n");
    return 0;
}

入力し終えたら「Ctrl + c」でINSERTモードを離れる。そいで「:wq」と入力し、Enterキーで保存して終了する。

ドザであれば「Ctrl + c」でコピーを連想するだろうけども、ターミナルの操作では「終了」を連想した方が良い。vimではCtrl + cでINSERTモードを終了だし、ターミナルから実行したコマンドを終わらせたい時もCtrl + c。困ったらCtrl + c。

vimの操作むずいかもしれないけど、vimを使わずにターミナルでテキストファイル編集するのはもっとつらたん。catやらsedのコマンド使わねばならん。vimに感謝しよう。

◆の~みそこねこね

コンパイラ。

人間に読めるプログラムを「prac1.c」として保存したわけだが、それをパソコンの読めるプログラムに変換したらねば動かすことは叶わない。

「ソース」「ソースコード」って言ってたのは、つまり「パソコンを動かすことのできるプログラム」がプログラムであって、その元ネタになるからソースコードと言う。

どうやってソースコードをプログラムに変換するのか?そりゃ変換用のプログラムを書いてやらねばならない。ソースコードを変換する用のプログラムのソースコードを書いて、それを変換する用のプログラムを変換する用の…みたいな話になってくるから、もうあるやつを使おう。

余談:セルフホスティング – Wikipedia

変換する用のプログラムは、大まかに「コンパイラ」と呼称される。

お前はまだコンパイラを持っていないので、欲しい。コンパイラはどこにあるのか。それはインターネットに落ちています。取得するには「apt-get」というプログラムを使えばよい。まずapt-getのバージョンアップをしよう。

$ sudo apt-get update

sudo(super do)っていうのを、実行したいコマンドの前に入れている。このsudoによって「管理者権限で実行」的なアレになる。パスワードを聞かれるかもしれないので、入力してEnterキーを押せ。

あと「Yes/No」を聞かれたら「yes」あるいは「y」って入力してEnterしておけ。あるいは[Y/n]とか書いてあったら、Enter押すだけでYesを選んでくれる。大文字があればそっちがデフォになるんだな。

暫く待ってアプデが終わったらば、以下のコマンドを叩く。

$ sudo apt install gcc

この「gcc」ってやつがGNUコンパイラコレクションっつって、色々なコンパイラ入ってるプログラムです。ありがとう、GCCを作ってくれたどこかの誰かさん達。

◆コンパイる

コンパイラ使ってソースコードをコンパイルする。「コンパイルする」ことを「コンパイる」っていう。嘘。言わない。

以下のコマンドを叩く。

$ gcc -o prac1 prac1.c

「-o prac1」ってのは、「『prac1』っていうファイル名でコンパイル結果を保存してね」っていう意味。べつにprac1じゃのうて「hoge」でも「fuga」でもなんでもいい。好きにしろ。この「-o prac1」を省略した場合、「a.out」っていうファイル名として自動的にコンパイル結果が保存される。

とくに何も表示されなかったらコンパイルに成功している。「ls」コマンドを叩けば「prac1」というファイルが生成されていることが確認できることと思う。なんらかの真っ赤なエラーが表示されたんであれば、prac1.cに書いてある内容が壊れているはず。見比べろ。あるいはファイル名が間違ってるとか。

prac1.cを捨てたくなったら「rm prac1.c」ってコマンド打てば削除される。rmはremoveって意味。

◆動かす

やっと動かせるぜ。イェイイェイ。

$ ./prac1

これでコンパイル結果を実行できる。「./」の「.」は、いまいるディレクトリ。「./」が無いとPCにインストールされているプログラムを探しに行ってしまって動かないので「./prac1」って指定してあげてね。

動かしたら、ターミナルに「Hello, world!」って表示されたことと思う。よかったね。

◆prac1.cの解説

なんのこっちゃわからんだろうから、解説を試みる。

▼#include <stdio.h>

実行したら「Hello, world!」って文字列がターミナルに出てきたじゃん?その「ターミナルに出す」っていう動作ですら、本来は自分でコーディングせねばならんのですね実は。

でもそれって辛いから、C言語の標準ライブラリ集から拝借してきたわけです。ライブラリ、すなわち部品、すなわち他のプログラムを、このコードにincludeしたわけです。stdio.hってのが標準入出力、すなわち「Standard I/O」を意味している。えっちだ…w

この「stdio.h」ってのが無いと、prac1.cにあった「printf」っていう機能が動かなくなっちゃう。printfはstdio.hにいる。printfのコードが気になるんならこの辺を見てみてね。

▼int main(int argc, char *args[])

ソースコードをファイルに書いてプログラムにして実行しようと思ったとき、「どっからプログラムを開始すればいいのか問題」が発生する。よくわからんだろうけど。

だから「mainという名前の関数から開始しよう」っていう話になった。

関数って言うのは、処理のまとまりのこと。あとで説明する。

関数であるところのmainの右隣にある丸カッコと、その内側に書かれているもろもろは、引数とよばれる奴ら。とりあえず考えなくていい。あとで使う。

細かく説明しちゃうと、例えばさっきgccを動かしたときに「gcc -o prac1 prac1.c」とか書いてたじゃん?その「-o prac1」やら「prac1.c」っていう情報を受け取るための口がプログラム上に必要となるとは思わんかね。mainという関数は特別で、argcとかargsって形でそれら「コマンドライン引数」を受け取ることができますです。argcはコマンドライン引数の総数。argsは「配列」なるデータ構造であり、なんかかんかすればコマンドライン引数の内容を読める。

このmainはエントリーポイントとも呼ばれたりする。実行開始の目印みたいな意味合い。

▼printf(“Hello, world!\n”)

printfの後ろに丸カッコがあるでしょう。つまりprintfも「関数」と呼ばれるアレであるような気がする。

でもコレは…mainとは何やら違うな。俺が関数を書いているっていうか、もう既に用意されている関数を使っているように見える。うん。

そして、丸カッコの中に書かれている「”Hello, world!\n”」は「引数」だったな確か。

以上を整理するに、printfという関数に引数を渡すと、その渡した引数の内容がターミナルに表示されるような気がする。

…ん。いや、「”」と「\n」は無視されておるな。「”」で囲まれた内側を表示しているんだろうけど、「\n」って何だろ。文字列の終端を意味しているんだろうか?vimで「\n」を削除してコンパイルして実行してみるか。

…なるほど。この「\n」ってやつは「改行」を表しているらしいな。なるほどなるほど…。

~完~

▼return 0

「ここで関数がおわるんだよ」っていうマーク。今回のmainでは「0」をreturnしている。

0ってのは、mainという関数が「int」を返却せねばならないから0。

おまえ「intってなに?」

俺「さぁ…」

▼;

セミコロンを行末に置くと、そこまでで1文あつかいになる。セミコロンが無いと、コンパイラくんが「1文」を解釈できなくなって死んじゃう。

◆prac2 ~俺に挨拶しろ~

お辞儀をしろこのポッター野郎。

▼前回までのたますじ

prac1で Hello, world! 処女を散らしたお前であるが、そのHello, world!を「Hello, 警報!」とか「Hello, project!」に書き換えてたりして遊んでたら3年が経過していた。

※「vimでファイルを開く」「INSERTモードにする」「書き換える」「Ctrl + cでINSERTモードから離脱する」「:wqからのEnterで保存」「gccでコンパイる」「実行する」っていうルーチンを何回か練習してもいいかもね。

3年もそんなこと続けてたから飽きてきました。プログラムってもっと機能豊富な気がするんだけど、いまんとこ文字を出力することしかできてないじゃん。例えば…ゲームみたいに自分で何かを入力したら、それに反応するようなプログラムは作れないの?

▼scanf

printfは「標準出力」。かたやstdio.hは「標準入出力」。つまり「標準入力」が存在しているような気がする。いや存在しているに決まってる。

というので「scanf」と言うものを使えばいい。

▼コピー、編集

prac1.cからprac2.cを生み出し、そのprac2.cを編集しよう。

tinpoディレクトリを開いているだろうか。そうでもないなら「cd」コマンドで移動してこい。tinpoディレクトリを開いたら以下のコマンドでprac1.cをprac2.cという名前でコピーする。

$ cp prac1.c prac2.c

そいで、「vi prac2.c」で編集しよう。以下を写経。

#include <stdio.h>

int main(int argc, char *args[])
{
    char name[255];
    scanf("%s", name);
    printf("Hello, %s!\n", name);
    return 0;
}

何だこのコードは。何が起きていやがる。理解できん。もう嫌だ。とにかく「gcc prac2.c」でコンパイルし「./a.out」で実行してみよう。

…なんだ?何が起きた。どういう状況だ。とりあえず「サイババ」って入力してEnterキーを押してみるか。

サイババ
Hello, サイババ!

何だコイツ。俺は…俺はサイババじゃない!違う!俺は違う!ああああああああ!!!!!もう嫌だあああああああああ!!!!!!!!!!!!!!11111

▼prac2.cの解説

・char name[255]

「変数」なるものを作っている。変数には値を記憶させておくことが出来る。値を記憶させておいて後で使うためのホニャララが変数。

例えばchar name[255]という書き方によって宣言できる変数は

  • nameという名前
  • charを255個格納できる

って感じ。

charが意味するものは「文字」。まとめると「文字を255個格納できるnameっていう名前の変数を宣言した」ことになる。良く分からんけど。

ちなみに、1文字のcharが格納できればいいってんなら

$ char name;

でいい。

・scanf(“%s”, name);

でたわね標準入力。

さっき宣言したnameっていう変数に対して、この書き方でなにやら読み込める。「%s」ってのが「string」を意味していて、stringは「文字列」を意味する。変数nameは文字を255個格納できるんで、つまり255文字までの文字列を格納できるんじゃあるまいか?

このscanfという関数は、入力を待ち受けてくれる機能を持ってる。お前がターミナルに何かを入力してEnterキーを押すまで待ってくれる。

このscanfって関数には、実用上色々と問題があったりする。取り敢えず触れないけど。

・printf(“Hello, %s!\n”, name);

また「%s」がおるな。

それよりまず、関数printfが2つの引数を受け取っている。さっきのscanfもだけど。みたところ、関数には複数の値を渡すことが出来るらしい。カンマで区切れば。

引数を何個うけとれるのかってのは関数によってそれぞれ定められているし、引数の順番を守らないとちゃんと動いてくれない。printfは1~2個の引数を受け取ることができるように観察される。

納得しなくていい。いずれ腑に落ちる。

ほいでな?さっき「サイババ」って入力したじゃん?それが「Hello, サイババ!」と言う風にprintfされたわな。

printfの「%s」に変数nameの中身が設定されつつ出力されているっぽいね。楽しいね。(説明放棄)

printfもscanfもだけど、末尾の「f」は「format」を意味している。scanfでは「%s」に値を受け取ってnameに横流ししているし、printfでは%sに変数の値をあてがっている。それがformat。理解できなくてもいい。Cに於いて「%」は特別な意味を持った文字。「%」とだけコンソールに表示したい場合は「%%」と重ねて書いてやれば%がひとつ表示される。

▼改善しる

prac2って何か実感わかないし面白くないってのもわかるんだが、段階踏まないとお前みたいなポンコツブレインクソビッチポケモンはすぐに置いて行かれてメソメソ泣きながら失禁しちゃうし仕方ない。もうちょいお付き合い願いたい。

prac2というプログラムを、ユーザーにとって分かりやすいものにしてみようよ折角だし。

ターミナルにカーソルがある状態で「↑」を入力すると、コマンドの履歴を呼び出せる。prac2.cを編集してコンパイラ通して実行してっていうのは既に一度叩いたコマンドだから、そっから引き出して動かすのが楽ちんちん。

どうやったら分かりやすくなるのかって、printfで補強してやりゃいいんだよ。以下を写経しろ。

#include <stdio.h>

int main(int argc, char *args[])
{
    char name[255];
    printf("名前を入力してください:");
    scanf("%s", name);
    printf("へぇ、あんたも%sって言うんだ。\n", name);
    return 0;
}

なんでコピペでなく写経させるのかって、「動かない経験」を積ませるためです。動かないコードを動くようにできると理解が少し深まる。今後、書いたコードが動かないってことは一億回くらい発生する。

どや?

ちょっとした事だけど、それっぽくなるもんでしょう?なにかワクワクしたものになるよな。

◆prac3 ~おさかな~

prac2を作ったお前はプログラムマスターになった。プログラムマスターになったので、以下のプログラムを開発した。

※prac3.c とゆう ふぁいるめいで しゃきょう して 、こんぱいらに くわせたのち、ケツをぶったたいて うごかして みよう!

#include <stdio.h>

int main(int argc, char *args[])
{
        printf("さかな博士「あなたの好きな魚はなんですか?」\n");

        char fish[255];
        scanf("%s", fish);

        printf("さかな博士「%s…ですか。私はサバのほうが美味しいと思いますがね。では。」\n", fish);

        return 0;
}

動かしてみればわかるんだが、なんとも素敵なプログラムである。

だが、重大な欠陥がある。それすなわち「サバ」と入力されたときだ。サバが好きだって言ってんのに、さかな博士は頓珍漢なことを言い始める。

これはよくない。

▼if else

もしもAであればBという処理をする。AでなければCという処理をする。

的な場合分けできればいいんじゃあるまいか。

それが「if文」というやつです。

#include <stdio.h>
#include <string.h>

int main(int argc, char *args[])
{
        printf("さかな博士「あなたの好きな魚はなんですか?」\n");

        char fish[255];
        scanf("%s", fish);

        if (strcmp(fish, "サバ") == 0) {
                printf("さかな博士「結婚してください。」\n");
        } else if (strcmp(fish, "うなぎ") == 0) {
                printf("うなぎ将軍「よくも俺の仲間を!!」\n");
        } else {
                printf("さかな博士「%s…ですか。私はサバのほうが美味しいと思いますがね。では。」\n", fish);
        }

        return 0;
}

おまえ「いや、これ何が起きてるかわからん。」

おれ「うん。じゃあ説明するか。」

おまえ「いや、やだこれ。むずかしい。」

おれ「は。ん?うん。そうか。でもこれ複雑なわけではなくて、簡単な部品の集まりなん

おまえ「やだ」

おれ「あのっ」

おまえ「やだ!!」

おれ「よしじゃあケツだせ!!!!」

◆C#

※この段落は別に読まんでよいし、理解できなくていい。

じゃあオラオラ来いよオラァ!

というので、C言語だと今後もなかなか大変。コマンドラインでやるのも大変。カスタマイズすればもうちょい楽になるが。

便利に開発したいので、昔の人はターミナルでの開発をやめた。C言語も具体的過ぎて大変っちゃ大変なので別のプログラミング言語を使うようになった。

世の中にプログラミング言語はいろいろある。あるんだけど今日のところはC#でやってみる。RustかTypeScriptかで迷ったけど。

C#はコンパイル時に一度「.NET」の言語に変換される。なので.NETって言葉が出てきたら「C#の根っこにあるアレ」みたいな感情を心に発生させておけばいい。.NETの中間言語を経由するのはC#だけではなく、VB.NETとかF#とかJ#とかあるらしいよ。

◆導入

環境構築なんか全部コマンドラインでやるのがいいんです。

▼Windowsに導入するやつ

・VS Codeっていう開発ツール取得

VS Codeはエディタである。さっきはvimってエディタを使ってたけど、僕は今日からVS Codeを使う。

Visual Studio Code

あるいはMicrosoft Storeで「Visual Studio Code」って検索して導入しろ。(Storeのリンクを張ろうと思ったんだけど、なぜかVS Codeだけ共有リンク作れなかった。)

Insiderビルドっていうのも選択できるんだけど、今後利用する「code」ってコマンドはInsiderビルドでは使用できない。代わりに「code-insiders」ってコマンドになる。

さっきまではUbuntuになんやかんや導入していたんだけど、VS CodeだけはWindowsに導入する。WSLのUbuntuに入らない。いや入るし起動もできるんだけどメリットが少ない。

・VS Codeをアレする

拡張機能を入れようぞ。VS Codeをインストールしたことによって「code」っていうコマンドを使えるようになりました。「–install-extension」で拡張機能をインストールできる。

だから「日本語化パック」「WSLにつなぐやつ」入れる。WSLじゃなくてPowerShellから入れる。

code --install-extension ms-ceintl.vscode-language-pack-ja
code --install-extension ms-vscode-remote.remote-wsl

終わったら日本語を有効化するためにウィンドウを閉じてもっかいVS Codeを起動しろ。

・.NET SDKをUbuntuに入れる

プログラム言語によっては、それ用の開発キットが提供されていることがある。それ使えば開発が楽ちんぽ。開発キットは横文字でSDK(Software Development Kit)って呼ばれている。かっこいい。須田恭也かな。

じゃあSDKをWSLのターミナル開いてインストールしよう。

Ubuntu に .NET SDK または .NET ランタイムをインストールする 21.10

みっつコマンドあると思うんで、コピーしてターミナルに張り付けて実行しろ。「署名キー追加」「SDKのインストール」「ランタイムのインストール」のみっつ。

・WSLの設定

WindowsからVS Codeを起動して「Ctrl + Shift + P」を入力し、検索窓に「>wsl new」って入力してEnterすればWSLとVS Codeがつながります。たぶん。

以下のエラーコードが出たら「Windowsキー」を押して「Ubuntu」って入力してUbuntuを立ち上げたら「sudo code」ってコマンドを入力してみ。そのあとでもう一度「>wsl new」すればつながるかも。

root/.vscode-server/bin/899d46d82c4c95423fb7e10e68eba52050e30ba3/server.sh: line 12: /root/.vscode-server/bin/899d46d82c4c95423fb7e10e68eba52050e30ba3/node: not found

Ubuntuのウィンドウは閉じてもいいよ。閉じても終了されるわけじゃない。

▼MacとLinux

Visual Studio Codeをインストールし、.NET 6 のSDKをインストールしろ。難しいこっちゃない。がんばれ。

◆プロジェクト作成

VS Codeを立ち上げた状態で「Ctrl + @」を押すと、画面が上下に分割されて下がターミナル(bash)になる。そこはWSLの世界につながったターミナルである。コマンドを入力できまする。

現在位置は「/home/[ユーザー名]」であるはず。「~」で表されるのがユーザのホームディレクトリ。

じゃあ練習用のフォルダ作って移動しろ。

mkdir programs
cd programs

「cd p」って入れた後にTabキーで入力補完使えば「cd programs/」的に出てきてくれる。

programsフォルダに移動出来たら、さっきのSDKで手に入れた「dotnet」コマンドでプロジェクトの下地を作る。

dotnet new console -o Prac4
  • new:新規プロジェクトを作る
  • console:コンソールアプリを作る
  • -o Prac4:「Prac4」って名前で作る

今作ったプロジェクトをVS Codeで開く。

code Prac4 -r

「code Prac4 -r」の「-r」は、ウィンドウをreuseすることを意味している。「-r」がなければ新しいウィンドウとして開く。

「作成者を信頼しますか?」とか聞かれたら「はい、作成者を信頼します」しとけ。

これで準備完了。

◆準備完了

あーっはっはっはっは!!!生きてるぅ^~!帰ってこれた^~!

とはいえ、もうここまで付き合ったやつもおらんだろう。俺でも無理だと思う。だけど構わない。もう戻れない。いけるとこまで行くしかない。死のうかな。

うん。「Ctrl + @」でターミナル開いたら以下のコマンドでプログラムを動かすことができる。

dotnet run

runによりHello World!と表示されたことと思う。

「run」は、ビルド(コンパイル)と実行を兼ねたコマンドになってる。「dotnet build」でビルドだけを実行することもできるよ。

あと、C#用の拡張機能を導入しろ。「Ctrl + @」から以下のコマンドを入力。

code --install-extension ms-dotnettools.csharp

◆prac4

VS Codeを開いた状態で「Ctrl + p」と入力し「program」と入力し、Enterして「Program.cs」を開く。あるいは、左のメニューで紙っぽいボタンを押して「Program.cs」ファイルを見つけてクリックして開く。

▼余談(読まんでよし)

.NET 6から、コンソールアプリをめちゃ簡単に書けるようになってしまった。1行でコンソールに表示できちゃうんすね。

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

「//」から始まる行はコメント。プログラムの動作に一切影響しない。

本来の書きっぷりで言えば以下の通りになる。

using System;

namespace Prac4
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

なんのこっちゃわからんことと思う。この状態からワカラン記述が消えて一行で書ければ便利だよね。

ただ、消えたからと言って不要なものであったわけではない。必要だから書かれていたんです。

例えばCのほうにあった「stdio.h」に代わるものは「using System;」だったりする。Systemをusingすることによって「Console.WriteLine」を使用できるようになる。だけど、一行で書けちゃう奴には「using」がない。それに代わる記述は、以下に存在している。

気になるなら眺めに行ってもいいかもね。

▼書き換えてみる

以下を書いて「Ctrl + s」で保存をしたら、Ctrl + @でターミナル開いて「dotnet run」しろ。

Console.WriteLine("さかな博士「あなたの好きな魚はなんですか?」");
string fish = Console.ReadLine();

if (fish == "サバ")
{
    Console.WriteLine("さかな博士「結婚してください。」");
} 
else if (fish == "うなぎ") 
{
    Console.WriteLine("うなぎ将軍「よくも俺の仲間を!!」");
}
else
{
    Console.WriteLine(@$"さかな博士「{fish}…ですか。私はサバのほうが美味しいと思いますがね。では。」");
}

実行したら好きな魚を聞かれると思うので、回答を入力してEnterを押せばいい。

どういうコードなのかって?説明してやろうじゃないか。

▼Console

Console.WriteLineで標準出力に一行文字列を表示することができる。Cの時にあった末尾の「\n」が無くなってんね。それはWriteLineが初めから「行」を出力してくれるから。Console.Writeっていう、Lineがついてないやつを使えばprintfと同じ動きをさせることもできる。「\n」で改行できるのはCと同じ。

▼代入

string fish = Console.ReadLine();

これは何なんだろうか。

動きを見るに、標準入力から受け取った文字をfishという変数に格納しているらしい。

というので、ここでは「代入」という操作をしている。代入はさっき使ってたC言語にもある操作。C#だけの機能ではなく、大抵のプログラミング言語に用意されているもの。代入。

代入は、変数に「なんかの値」を記録させることができる。単に右辺(=マークの右側)を左辺に保存しているってだけ。今回の例では「=」の記号により、「Console.ReadLine()」を変数fishに格納している。

Console.ReadLineは、右側にカッコがついているので関数だな。

関数ってものについてまだ俺は説明をちゃんとしていないわけだが、この記事では最後まで説明してないです。ノリでやれ。

Console.ReadLine()により標準入力から文字を受け取りつつ「fish」という変数に格納しておるんだな。

変数fishはつまり文字列を代入できる変数なんだろう。C言語では文字を意味する「char」の集まりが文字列だったんだけど、C#ではstringっていうので文字列を扱えるみたい。

▼if

大抵のプログラミング言語では、if文というものにより条件分岐を記述することができる。

ifに続く丸かっこに条件を書くことにより、それに適合したときだけブロック内の処理を動かすことができる。

ブロックってのは「{}」で囲まれた範囲のこと。「fish」という変数が「サバ」だった場合は結婚を申し込むコードになっている。

「=」はもう代入っていう機能に割り当てられてるもんだから、比較の機能は「==」で表現してる。同じ「=」という記号を使っているけど、比較と代入は関係がない。それぞれ別。

「else」ってのはifの条件にあてはまらなかったときに通る処理を書く場所。「else if」はそれまでの条件に当てはまらず、かつ右側の丸カッコ内に適合したやつ。ifとelseはそれぞれ一度に一個までしか書けないけど、else ifに個数制限はない。いくらでもelse ifを書いていい。

理解しろ。

あと、ifブロックの中にさらにifを入れ子で書くこともできるよ。このコードを研究してみてね。fishへの再代入、つまりfishの書き換えもしている。

Console.WriteLine("さかな博士「あなたの好きな魚はなんですか?」");
string fish = Console.ReadLine();

if (fish == "サバ")
{
    Console.WriteLine("さかな博士「本当に?」");
    fish = Console.ReadLine();
    if (fish == "はい")
    {
        Console.WriteLine("さかな博士「結婚してください。」");
    }
    else
    {
        Console.WriteLine("うそつき!");
    }
} 
else if (fish == "うなぎ") 
{
    Console.WriteLine("うなぎ将軍「よくも俺の仲間を!!」");
}
else
{
    Console.WriteLine(@$"さかな博士「{fish}…ですか。私はサバのほうが美味しいと思いますがね。では。」");
}

Console.WriteLine("~完~");

▼returnなくね?

省略した。

書いてもいいけど「return 0;」だとダメ。Mainが「void」ってのを返すように指定されているから。voidであるばあいは「return;」みたいに無を返却せねばならない。

▼文字列にfish埋め込んでね?

よく気付くな。えらいぞ。よしよししてあげよう。

ダブルコーテーション「””」の前に「$」ってついてるじゃん?それを使えば文字列の中に「{fish}」的に変数を埋め込むことができる。便利ね。$はC#固有。C言語には無い機能だよ。

以上!次行くぞ!

◆prac5 ~ループ~

相手に魚を選ばせるっていうか「サバ」と答えるまで粘りたい。サバを食わせたい。新しいプロジェクト作ろう。

Ctrl + @でターミナル開いて以下コマンドを叩く。

cd ..

※「cd ..」コマンドで、ひとつ上の階層に移動できる。

dotnet new console -o Prac5
code Prac5 -r

Program.csを開いて、以下を記述。省略形じゃなくて全部書いちゃおう。

using System;

namespace Prac5
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("さかな博士「あなたの好きな魚はなんですか?」");

            string fish = Console.ReadLine();
            while (fish != "サバ")
            {
                Console.WriteLine("さかな博士「ゑ?すみません。もう一度お願いします。」");
                fish = Console.ReadLine();
            }

            Console.WriteLine("さかな博士「やはり魚と言ったらサバに限りますよね。」");
        }
    }
}

写経が済んだらCtrl + @でターミナル開いて「dotnet run」しろ。

▼かつて省略されていた部分の説明(読まんでよし)

・namespace

名前空間。プログラムの有効範囲。Prac5という名前空間にこのプログラムは存在している。例えば別の名前空間からPrac5にあるプログラムを使用したい場合、usingによってその名前空間を参照せねばならない。

・class Program

C#ではclassっていう塊に対してプログラムを書いて実行していく。大きいプログラムを書くときは、例えば各機能ごとにclassを作ってその組み合わせにより大きい仕事を達成する。

ほいで、「Main」はC#の中で特別な意味を持ってる。そこからプログラムを始めるよっていう決め事がある。

Main() とコマンド ライン引数 | Microsoft Docs

▼ループ処理

「dotnet run」すれば、お前が「サバ」と答えるまで無限に聞き返してくるプログラムになったはずだ。

なんでかって?

それはね?お前を食べるためさ!!!!!🐺🐺🐺🐺

キャーッ!!!wwwww

大抵のプログラム言語にはループ(反復処理)という仕組みが用意されていて、繰り返すことができる。どんなプログラミング言語であっても「条件分岐(ifのこと)」と「反復処理」が九分九厘存在している。

C#のループ処理はまずwhile、そしてdo-whileforforeach。いっぱいあるんだけど、とりあえずどんなループであってもwhileで書けるからそれで攻めてみる。

dotnet runで走らせたプログラムを強制的に終わらせたい場合は、ターミナルで「Ctrl + c」を押すよろし。

▼while

while (fish != "サバ")

「fishがサバでなければもう一度聞きなおす」って動作をさせたかった気がしていて、動きをみるに上記while文がその役目を果たせているらしい。

fishがサバと等しい時はループの外に出てしまうわけだから、つまり「!=」ってのは「等しくない」を表現しているっぽい。

であれば、whileの右カッコの内側に書かれているのは、ifでもあったように「条件」みたいですね。条件に合致している間じゅうループし続ける能力がwhileであるはずだ。

そいで、whileのブロックの中でfishを書き換えているわけで、fishの中身が「サバ」になった瞬間にこのループから脱出することができるのかな。

神「いいえ。違います。whileのブロックに書かれた処理は上から下まで一通り流れ、最後の行を処理した後に条件判定が走ります。fishがサバになった瞬間ではありません。」

そうなんだ…ありがとう!神!

◆prac5.1 ~別解~

同じ処理を違う書き方してみる。

using System;

namespace Prac5
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("さかな博士「あなたの好きな魚はなんですか?」");

            while (true)
            {
                string fish = Console.ReadLine();
                if (fish == "サバ")
                {
                    break;
                }
                
                Console.WriteLine("さかな博士「ゑ?すみません。もう一度お願いします。」");
            }

            Console.WriteLine("さかな博士「やはり魚と言ったらサバに限りますよね。」");
        }
    }
}

▼true / false

trueはC#の予約語であって、「真」を意味している。真の逆、「偽」を意味するのが「false」。

ifやらwhileに書いていた条件式は、その式を評価した結果がtrueになるかfalseになるかを見ていたんです。

if (true)と書けばそのifブロックの中に必ず入るし、while (true)と書けば、そのブロックは無限ループになる。

無限ループがあるプログラムは即ち終わらないプログラムだ。困る。

でもこの例ではbreakしているから安心なんですね。

▼break

ジャンプ ステートメント – C# リファレンス | Microsoft Docs

自分がいまいるループを強制終了してブロックの外に脱出できる。それだけ。ifでfishの判定をし、サバであったらbreakしているな。

一行で書いてもいいよ。

if (fish == "サバ") break;

◆prac5.2 ~別解~

do-whileループは以下の感じでかける。

using System;

namespace Prac5
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("さかな博士「あなたの好きな魚はなんですか?」");

            string fish = "";
            do
            {
                fish = Console.ReadLine();
                if (fish != "サバ")
                {
                    Console.WriteLine("さかな博士「ゑ?すみません。もう一度お願いします。」");
                }
            }
            while(fish != "サバ");

            Console.WriteLine("さかな博士「やはり魚と言ったらサバに限りますよね。」");
        }
    }
}

whileの条件を下に書くことができるんですね。

今見たように、同じ動作をするプログラムであっても別解がなんだかんだある。このdo-whileの例はなんか前のふたつより汚いコードに思えるな。ループの書き方を変えたほうがいい。あるいは、セリフの出し方を変えてあげたりすればdo-whileのほうが適していたりもするのだ。

◆prac6 ~ゲーム開発~

飽きたからゲームを作ろうゲームを。

とはいえ、使える材料がほぼないので簡素なゲームになってしまう。でも何でか割と遊べるゲームだから作ってみろ。

  1. コンピュータくんが 0 ~ 100 の間で好きな数字を決める。
  2. プレイヤーが適当な数値を標準入力に食わせる。
  3. 間違っていた場合、その入力した数値が大きいか小さいかを教えてくれる。
  4. 正解するまでループ。

んん…作れる気がしない…

材料足りてないよね?

▼ランダム

コンピュータくんに適当な数値を決めてほしい。その願いをRandomがかなえてくれます。

とりあえずPrac6を作れ。Ctrl + @でターミナル開いて以下コマンドを叩く。

cd ..
dotnet new console -o Prac6
code Prac6 -r

そしたら以下を書いてみる。

var r = new Random();
while (true)
{
    Console.Write(r.Next(0, 101));
    Console.ReadLine();
}

このコードにより、Enterを押すたびに0~100の値が表示されるプログラムを記述できる。

終わりたくなったらCtrl + c。

・var r = new Random();

C#には、ランダムな値を生成してくれるclassがはじめから用意されている。それがRandomさ。

Random クラス (System) | Microsoft Docs

そのRandomは、なんていうか概念の状態でありますから、この世界に召喚してあげなければ使うことできません。「new Random()」っていう操作によって実体化させてあげる必要がある。

うつしよに顕現したRandomを「r」という名前の変数に格納して使えるようにしたのが先ほどのコードです。

rという変数を宣言しつつ、召喚したRandomで初期化している。

・varってなに?

以下、理解できなくていい。

varを使わないのが本来の書き方だったりする。以下の記述でRondomの実体(※実体のことを今後はインスタンスと呼ぶ)を変数rに格納できる。

Random r = new Random();

Rondomをnewによって実体化させたとき、そのインスタンスを格納できる変数もRondomという形になっていなければならない。

うん。わからんだろう。

変数には種類があって、ある変数にはあるインスタンスしか格納できない。「string fish」にはstringを、「Random r」にはRandomを、「int n」にはintしか格納することができない。

stringは文字列で、intは整数値。Randomはランダム。…いやランダムってなんだよ。

とにかく、以下の書き方により「rという名前の変数を作りながら」「インスタンス化したRandomを代入」している。

Random r = new Random();

そいで、「=」の右側、すなわち右辺がRandomのインスタンスであるってことはコンパイラくんが理解できるので、左側に書かれている「Random r」のRandomは「var」っていう風に省略できる。

「var fish = “”」であれば、右辺が文字列だってことをコンパイラくんが推論できるんでfishの型はおのずとstringって決定される。いちいち変数の型を書かなくていいので便利。

var – C# リファレンス | Microsoft Docs

・r.Next(0, 101)

Randomのインスタンスには「Next」っていうメソッドがある。

おまえ「メソッドってなに。もうわからん。いっそ殺して。」

いっぺん寝てこい。

メソッドってのは、命令みたいなものです。この世界に召喚したRandomに対して「0から100までの整数をおくれやす」って命令できなければRandomを召喚した意味なかろう?命令できなければただの置物だ。

Randomのインスタンスは、Nextという命令を聞くことができる。言い換えればNextというメソッドを持っていて、俺たちはそのメソッドを呼ぶことができる。

Consle.WriteLine()の「WriteLine」部分もメソッド。

Nextを呼ぶときには、一緒に色々と値を渡すことができる。今回は「0から100のランダムな整数」が欲しいんで、0と101を渡しておけばいい。

その他の用法は「Randomクラスのページ」を参照

おまえ「クラスってなに?」

なんかの概念って思ってればいいよ。概念であるところのクラスをインスタンス化したら、そのインスタンスを使って遊べる。

そいで、さっきも言ったけどRandom.Nextというメソッドはランダムな値を返却してくれる。それを受け取るにはintの変数を作ればよいよ。

var r = new Random();
var n = 0;

n = r.Next(0, 101);
Console.WriteLine(n);

n(numberのn。nじゃなくてtinpoでもなんでもいい。好きにしろ。)っていう変数にランダムな値を記憶させておくことができる。

Random.Nextって呼ぶたびに新しい値を返してくれちゃうわけだから、nに保存しておかないと再利用できなくて困るよね。

▼比較

数字とか文字列とか、わりと簡単に比較することできます。

比較演算子 – C# リファレンス | Microsoft Docs

C#に於ける「比較」はIComparable<T>インターフェースを…

いや。そうじゃないな。

とにかく、「==」とか「!=」で一致不一致を比較できるし、数値なら「<」とか「>」とか「<=」とか「>=」で大小の比較もできる。

たとえば「<」は「右辺が左辺よりも大きいかどうか」だし、イコールがついた「<=」なら「右辺が左辺以上であるかどうか」を判定することできます。

int n1 = -1;
int n2 = 2;

Console.WriteLine(n1 == n2);
Console.WriteLine(n1 != n2);
Console.WriteLine(n1 < n2);
Console.WriteLine(n1 > n2);
Console.WriteLine(n1 <= n2);
Console.WriteLine(n1 >= n2);

TrueやらFalseがターミナルに表示されたことと思う。

これをif文とかwhile文で使えますよね。

もう作れるよね?よぉし。作れ。考えろ。

・おまえ「作れねぇよ」

それなー。

さっきから「文字列」とか「数値」とか言ってたんだけど、文字と数値は本質的に違うから比較とかできないんですね。

数値の「0」と文字列の「”0″」を比較することは当然ながらできない。数値の「2」と「100」は当然 2 のほうが小さいけど、文字列の「”2″」と文字列の「”100″」であれば、最初の一文字が「1」である「100」のほうが小さい扱いです。

文字列を比較するためには「string.Compare(“100”, “2”)」みたいにする。「<」とかは通常使えない。そいで100のほうが小さいんで、このメソッドは「-1」を返却してくる。

標準入力からは数値を受け取ることができない。文字列で受け取ることしかできない。だから「文字列を数値に変換する」ことができないと困るような気がしてきませんかね。しない?しろ。

int32.TryParseっていうメソッドを使います。覚えること多いねぇ。

var moji = "1";
var r = int.TryParse(moji, out var kazu);
if (r) Console.WriteLine(100 > kazu);

Q:intって実体化されてないけど、なんでTryParseって命令を呼べるんですか。intのインスタンスが必要なんじゃないですか。

A:そういう日もある。概念が持ってるメソッドを使うことができたりする。超能力。

Q:「var r」には何が入ってるの。

A:変換に成功したかどうか。mojiの中に「”うんこ”」とか入ってたら、数値に変換できないよね。変換できなかったらfalseが入りますわよ。
メソッドの返却値を知るにはドキュメントを読むしかない。インターネットでググるか、あるいはメソッドにオンマウスすることによってドキュメントが表示されてくるかも。

Q:「out var kazu」ってどゆこと。

A:メソッドには「out」っていう「結果を出す場所」を引数として受け取るやつもいたりする。「var kazu」を出力場所に指定してあげることによって、Parseの結果をkazuに受け取ることができる。

TryParseの結果であるrがtrueだったら変換に成功したってことだから、if文を書いて使ってあげればいい。falseだったら「変換に失敗したよ」って扱いにして差し上げろ。

よーーしお疲れ様。じゃあ作れ。それそれ。

  1. コンピュータくんが 0 ~ 100 の間で好きな数字を決める。
  2. プレイヤーが適当な数値を標準入力に食わせる。
  3. 間違っていた場合、その入力した数値が上か下かを教えてくれる。
  4. 正解するまでループ。

書いてみて、俺の書いたコードと見比べてみような。

▼回答例

タァーーーーーーイムアーーーーーーップ!(サルヂエ

こんなんでました。

var random = new Random();
var target = random.Next(0, 101);

Console.WriteLine("◆かずあてゲーム");
Console.WriteLine("・俺のいま考えてる数値を当てろ。");

while (true)
{
    var input = Console.ReadLine();
    var result = int.TryParse(input, out var n);
    if (result)
    {
        if (target == n) break;

        if (target < n)
        {
            Console.WriteLine("・おっきぃよぉ///");
        }
        else
        {
            Console.WriteLine("・ちいさい…");
        }

    }
    else
    {
        Console.WriteLine("・数値を入れろ数値を");
    }
}

Console.WriteLine("・正解だ。よくやった。");

回答のひとつがこんな感じ。お前の書いたコードはどうだっただろうか?

人の書いたコードと見比べてみるのも楽しかろう。

▼別解:notとcontinue

true/falseの前に「!」を入れると、true/falseを反転できる。

あとcontinueってのを使うと

  • ループを打ち切る。
  • ループの先頭からもっかい始める。

ができる。breakはループを打ち切ってループの外に出ちゃうけど、continueはループの先頭に戻れる。

つまり?つまり、うまくやればブロックの入れ子を減らすことができる。ifとかwhileとか、階層が深すぎるとコードが読みづらくなるんすね。同じ処理を書いたとしても読みづらいコードになる。読みやすいコードにもできる。

var random = new Random();
var target = random.Next(0, 101);

Console.WriteLine("◆かずあてゲーム");
Console.WriteLine("・俺のいま考えてる数値を当てろ。");

while (true)
{
    var input = Console.ReadLine();
    var result = int.TryParse(input, out var n);

    if (!result)
    {
        Console.WriteLine("・数値を入れろ数値を");
        continue;
    }

    if (target == n) break;

    if (target < n)
    {
        Console.WriteLine("・おっきぃよぉ///");
    }
    else
    {
        Console.WriteLine("・ちいさい…");
    }
}

Console.WriteLine("・正解だ。よくやった。");

「変換に失敗した場合」って、もう次のループに行っちゃえばいいじゃん。だからcontinueしちまえばいいんだよ。そうすれば入れ子の深さを減らせる。上から読んで理解できるコードにできる。

continueやらbreakやらでループを操れ。うまいことやればコードを簡潔にできる。一番初めに書いたコードは、たぶん読みづらいコードだ。書き直したりしろ。

▼スリーチャンス

ゲーム性がないわな。「当てられなかったら負け」じゃないとゲームとは言えねぇし、一発で当てられるような奇跡は祝ってほしい。ルールを変更してみる。

  • 3回で当てられないと負け。
  • 0 ~ 100 だと広すぎるんで、0 ~ 9 にする。
  • 一撃で当てたら祝う。

・四則演算

該当するプログラムを作るには「変数の内容を1ふやす」って操作が必要になる。けど、四則演算の方法を教えてなかったな。数値を単純に足したり引いたりすればいい。

int a = 0;
int b = 1;
int c = a + b;

ね。「a + b」の結果をcに代入している。

プログラム言語によるけども、加算は「+」減算は「-」乗算は「*」除算は「/」。乗除算が加減算より優先されるのも四則演算と同じだし、カッコで優先度を変更できる。

int a = (1 + 2) * 3 + 4 * 5;

aの中身は29ですよね。

あと、以下の書き方もできまする。

int a = 0;
a = a + 1;

右辺がまず処理されてそれが代入されるんだとしたら、aには1が入るよな。

でな?以下の結果は何だと思うね。

int a = 3 / 2;

Console.WriteLineすればわかるけど、「1.5」にはなってくれんのですね。なんでか「1」になる。

それはintが整数値だから。小数値は切り捨てされちゃう。

小数値を扱いたいのであれば、intじゃのうてdoubleを使えばいい。

double a = (double)3 / 2;

double型であったらaが1.5になってくれる。ミソなのが「(double)3」って書き方。3をdouble扱いすることができる。3がint扱いのままだと、小数点はやっぱり切り捨てられてしまう。

右辺をまず計算しきってから左辺に代入しようとするから、int / intで結果はintになるんすね。

以下の書き方でもいいよ。「3.0」って書けば「小数やね」って理解してくれる。

double a = 3.0 / 2;

・return

returnでその関数を終わらせることができる。breakはループを終わらせることができたけど、returnは関数を終わらせることができる。

while (true)
{
    var input = Console.ReadLine();

    if (input == "さば")
    {
        Console.WriteLine("「「サバ!!!」」");
        return;
    }
}

この場合、「さば」と入力され次第returnでプログラムを終了させている。

正解したらもうそこでメッセージ出して処理を打ち切ろうや。

・const

定数。「変数」は後から再代入して中身を変えることができるけど、定数には再代入できない。「もう値を変えたくない」って明示することによりコードの意図を明確化できる。

const int a = 1;

このaは変数みたいに値の読み直しができるけど、再代入はできない。

・インクリメント/デクリメント

代々、「++」とか「–」みたいなもんで数値を1増やしたり減らしたりできる。できないプログラミング言語もあるけどC#はできる。

int a = 1;
a++;
a--;

a++を通ると、aに1がプラスされる。a–を通ると、aから1がマイナスされる。インクリメントとかデクリメントとか言う。

何回目の挑戦であるかを数えるために、カウンターとなる変数を作ってインクリメントしていこう。

よーしじゃあ作れ。

▼新Prac6

できたかね。俺が書いたのが下記の通り。見比べてみろ。

const int LIMIT = 3;
const int MAX_NUM = 9;

var random = new Random();
var target = random.Next(0, MAX_NUM + 1);

Console.WriteLine("◆かずあてゲーム");
Console.WriteLine($"・俺のいま考えてる数値(0 ~ {MAX_NUM})を当てろ。 {LIMIT} 回以内に。");

var tryCount = 0;
while (tryCount < LIMIT)
{
    tryCount++;
    Console.WriteLine($"『{tryCount} 回目の挑戦』");

    var input = Console.ReadLine();
    var result = int.TryParse(input, out var n);

    if (!result)
    {
        Console.WriteLine("・数値を入れろ数値を");
        continue;
    }

    if (target == n)
    {
        if (tryCount == 1)
        {
            Console.WriteLine("・ホールインワン!ワンワンワンワンワンワン!");
            return;
        }
        Console.WriteLine("・正解だ。よくやった。");
        return;
    }

    if (target < n)
    {
        Console.WriteLine("・おっきぃよぉ///");
    }
    else
    {
        Console.WriteLine("・ちいさい…");
    }
}

Console.WriteLine($"・お前の負けだ。正解は {target} でした。");

こんな感じ。わかるかね。わからんかね。

ちょい難しかったかもしれんな。でも大体これで遊べるだろう。理解できるまで読みこんでくれ。

ゲームっぽいもの作りは一旦置いておいて、もうちょい簡単な例に戻る。

◆Prac7 ~FizzBazz~

代々受け継がれしプログラミングの課題。それがFizzBazz。

  • 1から100までの数値をループさせる。
  • 3で割り切れる場合は標準出力に「Fizz」と表示。
  • 5で割り切れる場合は標準出力に「Bazz」と表示。
  • 3でも5でも割り切れる場合は標準出力に「FizzBazz」と表示。
  • それ以外は元々の数値を表示。

▼割り切れるってなに?

あまりがないこと。剰余がゼロであること。

つまり、

  • 数値Aを数値Bで割った結果を整数値Cに代入
  • BとCを掛け合わせてAと比較
  • 一致しなければ割り切れない

だよね。

int使えば小数値を無視できるから、以下の書き方で判別可能。

int x = 9;
int y = 3;
int z = x / y;
if (y * z == x)
{
    Console.WriteLine($"{x} は {y} で割り切れる");
}
else
{
    Console.WriteLine($"{x} は {y} で割り切れない");
}

ね。

ただ、余りを求める演算子っていうのもある。「%」を使えばよろしい。

int x = 10;
int y = 3;
if (x % y == 0)
{
    Console.WriteLine($"{x} は {y} で割り切れる");
}
else
{
    Console.WriteLine($"{x} は {y} で割り切れない");
}

うわぁべんりぃ。ありがとーーーー。

算術演算子 – C# リファレンス | Microsoft Docs

今までの例を理解できたんなら、問題なくコードに落とし込めるだろう。書け。

この後に回答を載せるけど、また教えてない書き方をする。あはん。

▼回答例

こんなかんじ。

for (int i = 0; i <= 100; i++)
{
    if (i % 3 == 0 && i % 5 == 0)
    {
        Console.WriteLine("FizzBazz");
    }
    else if (i % 3 == 0)
    {
        Console.WriteLine("Fizz");
    }
    else if (i % 5 == 0)
    {
        Console.WriteLine("Bazz");
    }
    else
    {
        Console.WriteLine(i);
    }
}

▼forってなに

for文

  • iって変数を0で初期化&宣言しつつ
  • ループが終わるごとに「i++」しつつ
  • 「i <= 100」を満たす限りループ

みたいなのを、上記のノリで書けます。whileでカウンター作ってってのでも問題ないが。

▼and / or

真偽値「true」「false」の変数がふたつあったときに、その条件を混ぜ合わせたい。「条件1と条件2の両方がtrueであるとき」をandで、「条件1と条件2のどちらか片方がtrueであるとき」をorで引っ掛けられる。

andが「&&」でorが「||」です。

  • if (true && true):とおる
  • if (true && false):とおらない
  • if (true || false):とおる

以上。

▼ナベアツ

改造して、3で割り切れる数値と3を含む数値でアホになるプログラムに変更してみる。

「3を含む数値」ってのの判定が、intのままだと難しい。だから

  • intを文字列に変換して
  • “3”という文字を含むかどうか

で判定するのがはやい。

こうする。

int a = 1;
string b = a.ToString();

intのインスタンスには「ToString」ってメソッドがある。戻り値はstring。すなわち文字列。

そいで更に言えば、stringのインスタンスは「Contains」ってメソッドを持っていて、ある文字列が任意の文字列を持っているかどうかを判定し true / false で返してくれる。

じゃあこんな感じで書けるだろう。

for (int i = 0; i <= 100; i++)
{
    if (i % 3 == 0 || i.ToString().Contains("3"))
    {
        Console.WriteLine($"( ゜∀ 。)「{i}!!!!!!」");
    }
    else
    {
        Console.WriteLine($"( ・∀・)「{i}」");
    }
}

◆Prac8 ~じゃんけんマシン~

これで最後。じゃんけんマシン作ろう。

ほんとはコンソールじゃなくて画面が出てくるあれを作ろうと思ったんだけど、説明しきれん。一応「dotnet new wpf」で、画面が表示されるWPFアプリを作れる。

Prac8を作る。

cd ..
dotnet new console -o Prac8
code -r Prac8

とりあえず、段階的に作っていく。以下のプログラムでは標準入力に「グー」「チョキ」「パー」を入力したらじゃんけんできる。

Console.WriteLine("◆じゃんけんしようぜ!");

// 相手の手
var r = new Random();
var n = r.Next(0, 3);
var enemy = n switch
{
    0 => "グー",
    1 => "チョキ",
    _ => "パー",
};

// 自分の手
var me = Console.ReadLine();

if (me == enemy)
{
    Console.WriteLine("◆あいこでしょ?");
    return;
}

if ((me == "グー" && enemy == "チョキ")
    || (me == "チョキ" && enemy == "パー")
    || (me == "パー" && enemy == "グー"))
{
    Console.WriteLine("◆俺の負けだよ…");
    return;
}

Console.WriteLine("◆てめぇの負けだ。でなおしてまいれ。");

▼switch

じゃんけんの相手の手を決めるには、Randomを使うしかない。そのランダムの結果を、じゃんけんの手に変換してるな。

var enemy = n switch
{
    0 => "グー",
    1 => "チョキ",
    _ => "パー",
};

「0、1、2」をランダに生成して、それをグーチョキパーに変換して「enemy」という変数に格納している。最後の「_ => “パー”」ってのは、それまでの条件に一致しなかったパティーンを補足してるやつ。

この書き方では0でも1でもないやつを全てパーとして扱ってる。

▼野球拳

じゃあ、体力制にしてみよう。

自分のライフ、相手のライフを変数に入れて、whileで回せばいいだろ。

Console.WriteLine("◆じゃんけんしようぜ!");

const int MAX_LIFE = 5;

var life = MAX_LIFE;
var enemysLife = MAX_LIFE;

while (life != 0 && enemysLife != 0)
{
    // 相手の手
    var r = new Random();
    var n = r.Next(0, 3);
    var enemy = n switch
    {
        0 => "グー",
        1 => "チョキ",
        _ => "パー",
    };

    // 自分の手
    var me = Console.ReadLine();

    if (me == enemy)
    {
        Console.WriteLine("◆あいこでしょ?");
        continue;
    }

    if ((me == "グー" && enemy == "チョキ")
        || (me == "チョキ" && enemy == "パー")
        || (me == "パー" && enemy == "グー"))
    {
        Console.WriteLine("◆ぐはあぁ!!!");
        enemysLife--;
    }
    else
    {
        Console.WriteLine("◆オラアアア!!!");
        life--;
    }
    
    Console.WriteLine($"[自分の体力:{life} / {MAX_LIFE}] [敵の体力:{enemysLife} / {MAX_LIFE}]");
}

if (life == 0) Console.WriteLine("◆てめぇの負けだ。でなおしてまいれ。");
if (enemysLife == 0) Console.WriteLine("◆俺の負けだよ…死にます。");

うん。ええやん。ゲームやんこれ。

どちらかの体力がゼロになるまでループを回すのがポイントですね。

▼拡張:処理の切り出しとEnum

読まんでよし。

・関数(メソッド)

ある処理のまとまりを切り出すことができる。

using System;

namespace Prac8
{
    class Program
    {
        static double CalcPriceIncludingTax(int p)
        {
            return p * 1.08;
        }

        static void Main(string[] args)
        {
            var price = 100;
            var taxed = CalcPriceIncludingTax(price);
            Console.WriteLine($"税抜き{price}円は税込み{taxed}円です。");
        }
    }
}

Mainと同じ階層に「CalcPriceIncludingTax」って関数を定義している。intの変数を受け取り、doubleを返却している。その「CalcPriceIncludingTax」は、とりあえずMainの中で使うことができる。Mainの中でpriceを渡したら、doubleとして結果が返ってきてますわね。

「static」ってのは、インスタンス不要なメソッドであるという意味だよ。RandomのNextを使うにはRandomのインスタンスが必要だったけど、intのTryParseにはインスタンスが不要だったよな。

・Enum

Enumってのが用意されているプログラム言語が多い。Enumは列挙って言うか…数値に意味を持たせるための機構というか…なんというか…

「じゃんけんの手」とか「じゃんけんの勝敗」をEnumにすることができる。

enum Command
{
    Gu = 0,
    Choki = 1,
    Pa = 2
}

enum Result
{
    Win,
    Lose,
    Draw
}

また、このenumにもTryParseがある。ターミナルからReadLineで「0」とか受け取りつつenumに変換するのは以下の通り。

var r = Enum.TryParse(Console.ReadLine(), out Command me);

rには変換できたかどうかが入って、meにはCommandが入る。

例を書いてみる。もう多くは説明せんぞ。読み解け。標準入力に「0」「1」「2」を入力すればそれがグーチョキパーに対応する。「Gu」「Choki」「Pa」って入力しても動いてくれるよ。

using System;

namespace Prac8
{
    enum Command
    {
        Gu = 0,
        Choki = 1,
        Pa = 2
    }

    enum Result
    {
        Win,
        Lose,
        Draw
    }

    class Program
    {
        static Result Janken(Command me, Command enemy)
        {
            if (me == enemy) return Result.Draw;

            if ((me == Command.Gu && enemy == Command.Choki)
                || (me == Command.Choki && enemy == Command.Pa)
                || (me == Command.Pa && enemy == Command.Gu))
            {
                return Result.Win;
            }

            return Result.Lose;
        }

        static void Main(string[] args)
        {
            Console.WriteLine("◆じゃんけんしようぜ!");

            const int MAX_LIFE = 5;

            var life = MAX_LIFE;
            var enemysLife = MAX_LIFE;

            while (life != 0 && enemysLife != 0)
            {
                // 相手の手
                var random = new Random();
                var enemy = (Command)random.Next(0, 3);

                // 自分の手
                var parseResult = Enum.TryParse(Console.ReadLine(), out Command me);
                if (!parseResult)
                {
                    Console.WriteLine("◆そんな手はねぇ!");
                    continue;
                }

                var result = Janken(me, enemy);
                switch (result)
                {
                    case Result.Win:
                        Console.WriteLine("◆ぐはあぁ!!!");
                        enemysLife--;
                        break;
                    case Result.Lose:
                        Console.WriteLine("◆オラアアア!!!");
                        life--;
                        break;
                    default:
                        Console.WriteLine("◆あいこでしょ?");
                        break;
                }
                Console.WriteLine($"[自分の体力:{life} / {MAX_LIFE}] [敵のライフ:{enemysLife} / {MAX_LIFE}]");
            }

            if (life == 0) Console.WriteLine("◆てめぇの負けだ。でなおしてまいれ。");
            if (enemysLife == 0) Console.WriteLine("◆俺の負けだよ…死にます。");
        }
    }
}

▼拡張:画面のリライト

読まんでよし。

もっとゲームっぽくするには、画面のリライトをすればいい。まいど画面を書き直せばいい。

「Console.Clear()」ってのを書けばいいよ。

以下の感じになる。やったね(説明放棄)。説明しない。何とかググったりして読み解け。

using System;

namespace Prac8
{
    enum Command
    {
        Gu = 0,
        Choki = 1,
        Pa = 2
    }

    enum Result
    {
        Win,
        Lose,
        Draw
    }

    class Program
    {
        static Result Janken(Command me, Command enemy)
        {
            if (me == enemy) return Result.Draw;

            if ((me == Command.Gu && enemy == Command.Choki)
                || (me == Command.Choki && enemy == Command.Pa)
                || (me == Command.Pa && enemy == Command.Gu))
            {
                return Result.Win;
            }

            return Result.Lose;
        }

        static void Main(string[] args)
        {
            const int MAX_LIFE = 5;

            var life = MAX_LIFE;
            var enemysLife = MAX_LIFE;

            while (true)
            {
                Console.Clear();
                Console.WriteLine("◆じゃんけんしようぜ!");
                Console.WriteLine($"→自分の体力:{new string('●', life)}{new string('◯', MAX_LIFE - life)}");
                Console.WriteLine($"→相手の体力:{new string('●', enemysLife)}{new string('◯', MAX_LIFE - enemysLife)}");
                Console.WriteLine();
                Console.WriteLine("◆じゃーんけーん…");
                Console.WriteLine("[グー:0] [チョキ:1] [パー:2]");

                // 相手の手
                var random = new Random();
                var enemy = (Command)random.Next(0, 3);

                // 自分の手
                var parseResult = Enum.TryParse(Console.ReadLine(), out Command me);
                if (!parseResult)
                {
                    Console.WriteLine("◆そんな手はねぇ!");
                    Console.WriteLine();
                    Console.WriteLine("[Press Enter...]");
                    Console.ReadLine();
                    continue;
                }

                // 両者の手を表示
                Func<Command, string> toHand = (Command c) => c switch
                {
                    Command.Gu => "グー",
                    Command.Choki => "チョキ",
                    _ => "パー",
                };
                Console.WriteLine($"自分の手:{toHand(me)}");
                Console.WriteLine($"敵の手:{toHand(enemy)}");
                Console.WriteLine();

                // じゃんけん
                var result = Janken(me, enemy);
                switch (result)
                {
                    case Result.Win:
                        Console.WriteLine("◆ぐはあぁ!!!");
                        enemysLife--;
                        break;
                    case Result.Lose:
                        Console.WriteLine("◆オラアアア!!!");
                        life--;
                        break;
                    default:
                        Console.WriteLine("◆あいこでしょ?");
                        break;
                }

                // 体力が空になったらループ脱出
                if (life == 0 || enemysLife == 0) break;

                Console.WriteLine();
                Console.Write("[Press Enter...]");
                Console.ReadLine();
            }

            if (life == 0) Console.WriteLine("◆てめぇの負けだ。でなおしてまいれ。");
            if (enemysLife == 0) Console.WriteLine("◆俺の負けだよ…死にます。");
        }
    }
}

◆おわり

もう書くの疲れた。ふざけんな。だれが真面目にこなすんだこんなもん馬鹿野郎。

というので、こんな感じにプログラムを書くのよ。もっときれいに書くこともできるけど、またそれは別のお話…

第一コンソールアプリしか作ってないし。先は長いよね。でもまぁ入門できたと言えるんじゃないの?

…できてないか。せめて配列とファイル操作くらいはやったほうがいいわな。でも俺はもう疲れた。いずれパート2でやろうぜ。

そのあとはWebアプリのつくりでもやろうかね。ここでの学びは役に立つはずだぜ。基礎をすっ飛ばして入門することもできたけど、今回は基礎をやったのだよ。お疲れ様だったな。

そんじゃな。あばよ。またどこかで。

追記:2022/1/17

テストコード書いても解決にはならんよ

技術記事が読みづらいと思っているのはお前だけではないから安心しろ

1件のコメント

  1. 流しのプログラマ

    こういうのすきです

流しのプログラマ へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


Powered by WordPress & Theme by Anders Norén

%d人のブロガーが「いいね」をつけました。