class: title, smokescreen, shelf, no-footer # C言語入学式<br>じゃんけんプログラム <div class=footnote> <small> 再入学式が正しい? まず<A HREF="/slides/lang/clang/caution/">諸注意</A>からみてください </small> </div> --- class: compact # 変数とデジタル表現 <div class=footnote> <small> (脚注) 数字であれば、どう決めてもよいので、 グー1、チョキ2、パー3とか グー1、チョキ2、パー4など、どう決めてもOKです。 ただ、0、1、2にすると後で便利なことがあります </small> </div> - コンピュータは数字しか理解できません - ジャンケンは、グー、チョキ、パーですよね? どうすればよいでしょうか? - 答えは「**プログラムを書く人が約束事を(人為的に)決める**」です - この課題では、 **グーは0、チョキは1、パーは2**と決めます | 表現 | じゃんけん | 備考 | |------|------------|--------------------------------| | 0 | グー | C言語では0から数え始める習わし | | 1 | チョキ | | | 2 | パー | | --- class: col-2,compact # 課題010 <div class=footnote> <small> ポイント: デジタル表現 変数 出力 </small> </div> - **変数に値を代入し、その変数の値を出力してください** (これではゲームっぽく無いですが、まずはソースコードに手を直接書きこんでみましょう) - ここでは、**コンピュータはチョキ、自分はパー**を出したとします (グーは0、チョキは1、パーは2) - 変数名は分かりやすくローマ字を使いましょう。 コンピュータの手は変数 aite に代入、自分の手は変数 jibun です <wbr> ``` // (1) 最初の行に必ず書く呪文 int main () { // (2) 整数変数 aite jibun を宣言 // (3) 代入: aite に 1、jibun に 2 を代入 // (4) 出力: 変数の値を出力してください // 出力例: jibun = 2, aite = 1 return(0); } ``` --- class: col-2,compact # 課題020 <div class=footnote> <small> ポイント: 入力 </small> </div> - 課題010を改造し、キーボードから自分の手を入力できるようにしてください <wbr> ``` // (1) 最初の行に必ず書く呪文 int main () { // (2) 整数変数 aite jibun を宣言 // (3) 代入: aite に 1 // (4) 入力: キーボードから jibun に 2 を代入 // (5) 出力: 変数の値を出力してください // 出力例: jibun = 2, aite = 1 return(0); } ``` --- class: col-2,compact # 課題030 <div class=footnote> <small> ポイント: 数値演算 </small> </div> - じゃんけんの判定は次の式で計算できます。ためしてみてください ``` 3 + 自分の手 - 相手の手 ``` 0がアイコ、1と4が(jibunつまりユーザの)負け、2と5が勝ちです。 例: 自分がパー、相手がチョキの場合 3 + 2 - 1 = 4 - 判定結果を変数 kekka に入れ、最後に出力してください <wbr> ``` // (1) 最初の行に必ず書く呪文 int main () { // (2) 整数変数 aite jibun kekka を宣言 // (3) 代入: aite に 1 // (4) 入力: キーボードから jibun に 2 を代入 // (5) 判定: 変数 kekka に判定結果を代入 // (6) 出力: 変数群の値を出力 // 出力例: jibun = 2, aite = 1, kekka = 4 return(0); } ``` --- class: col-2,compact # 課題040 <div class=footnote> <small> ポイント: 数値演算 剰余 </small> </div> - 次の式 ``` 3 + 自分の手 - 相手の手 ``` の**値を3で割った余り**で判定すれば、 0がアイコ、1が(jibunつまりユーザの)負け、2が勝ちになります。 試しに計算してみてください。 例: 自分がパー、相手がチョキの場合 3 + 2 - 1 = 4 -> 1 (4を3で割った余りは1) - 判定結果を変数 kekka に入れ、最後に出力してください <wbr> ``` // (1) 最初の3行に書く呪文 int main () { // (2) 整数変数 aite jibun kekka を宣言 // (3) 代入: aite に 1 // (4) 入力: キーボードから jibun に 2 を代入 // (5) 判定: 変数 kekka に判定結果を代入 // (6) 出力: 変数群の値を出力 // 出力例: jibun = 2, aite = 1, kekka = 1 return(0); } ``` --- class: compact # 乱数 <div class=footnote> <small> (脚注) 裏側では、ある数式を実行しています。 これは、乱数のようにみえる数字の列を生み出す数式です。 初期値が同じ場合、毎回おなじ数字の列を生成します。 そのため初期化のおまじないが必要です </small> </div> - こういう使い方をするものです。コピー&ペーストしてok ``` // ヘッダファイルが必要です #include <stdio.h> #include <stdlib.h> // 乱数を使うため #include <time.h> // 時間の関数を使うため // 以下は main 内 srand(time(NULL)); // 乱数の初期化のため一度だけ実行してください。引数には現在時刻(の秒数)を使っています int x; x = rand(); // rand関数を実行するたび x には違う値が入る // 値の範囲は 0〜約21.5億 (2,147,483,647) ``` --- class: col-2,compact # 課題090 <div class=footnote> <small> ポイント: 乱数 剰余 </small> </div> - 乱数を使い、コンピュータの手はコンピュータに考えさせてください。 これでゲームらしくなってきました - 判定結果を変数 kekka に入れ、最後に出力してください - ヒント: `rand()`の返す値は0〜約21.5億(前頁を参照)ですが、 aiteの値は 0 1 2 のいずれかです。 さて、どうすれば`rand()`を使ってaiteの値を決めることが出来るでしょうか? <wbr> ``` // (1) 最初の3行に書く呪文 int main () { // (2) 整数変数 aite jibun kekka を宣言 // (3) 乱数の初期化 // (4) 乱数で aite の値を決める // (4) 入力: キーボードから jibun に 2 を代入 // (5) 判定: 変数 kekka に判定結果を代入 // (6) 出力: 変数群の値を出力 // 出力例: jibun = 2, aite = 1, kekka = 1 return(0); } ``` --- class: compact # 条件文 (if) <div class=footnote> <small> (脚注) 最初の if 部分は必須なので if だけの条件文(上の例なら最初の3行だけの場合)がありえます。 つまり else if と else は無い条件文もあります。 なお else if は何個あってもかまいません。 else if が多くなるようなら switch 構文を使う書き方もありますが、 C言語の switch 構文は罠があるので注意が必要です </small> </div> ``` if (条件1) { 条件1を満たしたとき実行するコード } else if (条件2) { 条件2を満たしたとき実行するコード } else if (条件3) { 条件3を満たしたとき実行するコード } else { 条件1,2,3を満たさないときに実行するコード } ``` --- class: col-2,compact # 課題110 <div class=footnote> <small> ポイント: 条件文 </small> </div> - 出力を読みやすくしてください。具体的には、 課題090を改造し、結果(kekka)を 0 1 2 ではなく、 kachi make aiko とローマ字で表示してみてください <wbr> ``` // (1) 最初の行に必ず書く呪文 int main () { // (2) 整数変数 aite jibun kekka を宣言 // (3) 乱数の初期化 // (4) 乱数で aite の値を決める // (4) 入力: キーボードから jibun に 2 を代入 // (5) 判定: 変数 kekka に判定結果を代入 // (6) 出力: 変数群の値を出力 // 条件文を使い kachi make aiko を表示 // 出力例: jibun = 2, aite = 1, kekka = make return(0); } ``` --- class: col-2,compact # 課題210 <div class=footnote> <small> ポイント: 繰り返し文 </small> </div> - いよいよジャンケンがひとおおり完成します。 課題110を改造し、3回ジャンケンをくりかえしてください - まず、課題110の(1)〜(6)のうち、1回だけ実行する部分と繰り返す部分がどこか考えましょう <wbr> ``` // (1) 最初の行に必ず書く呪文 int main () { // (A) 一回だけ実行する部分 for ... 省略 ... { // 繰り返し文 // (B) くりかえし実行する部分 } return(0); } ``` --- class: title, smokescreen, shelf, no-footer # じゃんけんプログラム発展編 <div class=footnote> <small> </small> </div> --- class: compact # 発展課題INDEX - 281: じゃんけんを何回できるか?を指定できるようにしてください - 282: ジャンケンの回数に上限をなくしてください。 ただし数字の9を入れたらジャンケンを終了です (ヒント:無限ループ,ループを停止させる(ループから抜ける)には?) - 283: 終了時に、何回勝ったか?も表示してください - 284: 終了時に、何勝、何敗、あいこが何回か?を表示してください - 285: 課題210をwhile文で書き直してみよう - 286: [やや難] ユーザからの入力を guu choki paa で受け付けるようにしてください <br> (ヒント: 標準ライブラリ関数にある文字列操作関数と条件文のくみあわせ) - 287: [やや難] 勝敗の結果をファイルに記録してください。 ジャンケンプログラムの最後に、全対戦をつうじての勝敗結果も表示してください <br> (ヒント: 標準ライブラリ関数のファイル操作を使ってください) - 288: [難] ユーザの癖(0 1 2 に偏りがあるはず)を学習し、 コンピュータがより勝てる手を出せるようにしてみましょう --- class: compact # 発展課題281: じゃんけんを何回できるか?指定 - **課題210が書けることは大前提です** - (A.1) 変数宣言: 既存の変数に追加して、新変数 jougen を定義してください。 <br> ループできる回数(ループの上限値)を代入する変数です - (A.2) キーボードから何回できるか?(数字)を入力し jougen に代入 - (A.3) 乱数の初期化(課題210と同じ) - くりかえし部分で jougen 回じゃんけんを行います (B.1〜B.5の部分は課題210と同じ) - (C.1)(C.2) とくに無し --- class: compact # 発展課題282: じゃんけんの上限なし - **課題210が書けることは大前提です** - くりかえし部分 - 「無限ループ」にする - (B.1) aite を適当に決める(乱数) - (B.2) ユーザの手を変数 jibun に読み込む - (B.3) jibun の値が 9 ならループを終了する - (B.4)(B.5)は課題210と同じです - (C.1)(C.2) とくに無し --- class: compact # 発展課題283: 終了時に何勝を表示 - (A.1) 変数宣言: 回数を数える変数 kaisuu_kachi を追加 - (B.5) kachi,make,aikoを表示する条件文の中で、条件「勝ち」のとき kaisuu_kachi を +1 していく - (C.1) 変数 kaisuu_kachi の値を表示する - (C.2) 無し --- class: compact # 発展課題284: 終了時に何勝/何敗/あいこの回数を表示 - ここは、ほぼノーヒントとしましょう。前問の 283 と、ほぼ同じですから - いずれの箇所も3倍の長さになりますね - 変数は3つ必要;勝ち、負け、アイコの回数を記録する - 条件文も3ヶ所修正 - 最後に3つの値を表示する --- class: compact # 発展課題285: 課題210のwhile版 - ヒント: 書き方のバリエーションがありえますが、**一番短い書き方をした場合、修正箇所は2行だけ**です --- class: compact # 発展課題286: [やや難] ユーザからの入力は guu choki paa - **文字列**を扱う**標準ライブラリ関数**を使います - 課題210をいかし、**最小の手間で文字列対応するには?を考えましょう** - 以下ヒント - 文字列関数を使うため、`#include <string.h>`行も追加してください - (A.1) ユーザの手を読み込む一時領域(文字列 buf)を宣言してください - (B.2) - まず、一度ユーザの手(文字列)を buf に読み込みます - bufの値を文字列としてguu,choki,paaと比較していきます。 条件文でマッチしたとき、 jibun にそれぞれの値(0 or 1 or 2; guu なら jibun は 0 など)を代入していきます - 他の場所は課題 210 と同じです --- class: compact # 発展課題287: [やや難] 勝敗の結果をファイルに記録... - **ファイル操作**の**標準ライブラリ関数**を使う課題です - これも、なるたけ課題284をいかして最小の手間で対応するには?を考えます - (A.1)変数宣言(A.2)乱数の初期化は同じでいいよね? - (A.3)ここで、ファイルから保存しておいた勝敗の履歴を変数群へ読み込みます。 初回はファイルが存在しないので条件分岐してください (ファイルが無い場合は履歴の読み込みはないという条件文が必要) - 繰り返し部分は同じだよね? - (C.2) - ファイルに履歴を保存します - 初回はファイルが存在しないので、ファイルを新規作成する必要があります。 <br> 初回はファイルを作成(ファイル書き込みモードでファイルを開いてください) --- class: compact # 発展課題288: [難] ユーザのクセを学習し対策(1) <div class=footnote> <small> (脚注1) jibun の値 0 1 2 (グー、チョキ、パー)が 4:3:2 なら、 aite の値は 3:2:4 になるようにすれば勝てる確率があがりそうですね? (脚注2) 乱数の解釈ですが、0〜10のサイコロと考えてもいいよね? </small> </div class=footnote> - ここまで来られたらなら少ないヒントでも大丈夫でしょう:-) - クセを学習するには過去の履歴(過去の対戦履歴を全部みて、グー、チョキ、パー、それぞれ何回ずつ出したか?)が必要だと思うので課題287がベースです - jibun の統計を参考に aite を微修正 1. まず、ユーザの手つまり jibun の履歴をとるようにしておきます 1. 本来 jibun の値(0 or 1 or 2)は1/3に近い値のはずですが、 実際には1/3からずれるでしょう。 そのずれを計算してください 1. ずれを参考に aite の値を勝てるように微修正してください。 もちろん乱数の応用です。 ただ「乱数を使い同じ確率で値を選べ」は簡単ですが、 「微妙に偏らせる」には、どう書けばいいでしょうね? --- class: compact # 発展課題288: [難] ユーザのクセを学習し対策(2) - jibun の履歴をとる際に、どういう順番で手を出してくるか?も考えましょう。 たとえば、 ユーザは 0 1 2 2 0 1 のように均等に1/3ずつ手をくりだしてくるわけではなく、 (a) 0 0 2 1 0 1 のように 0 に偏よる、 (b) 0 0 と連続で出すクセがあるのか? こういったクセがあるか?を探します - まず履歴をいれる配列が必要ですよね? - クセのパターンを探すのは大変ですが、 (a)連続で出す傾向がある (b)0 のあとには 2 を出すことが多い といった決め打ちのパターンを探すなら、そこそこの手間でできそうです。 それでもけっこう長いコードになると思いますけど... --- class: compact # 発展課題288: [難] ユーザのクセを学習し対策(3) - より汎用的なクセを探すには?と言うと、 やっぱり機械学習させるの? でも、それをするにはデータが足らないんじゃないかなぁ??? - すでに、どっかに学習済みのデータとかないんですかね? あれば、それを呼び出して... - でも、そのデータを知らない ... - C言語から呼ぶには?自分でがんばってください:-) - Cythonを使えばPythonで学習させたデータをC言語側から呼び出せるらしい - あとはGoogleのTensorflowはC++だから、 学習済みのTSデータを拾ってきて、 C言語からC++ライブラリをなんとかして呼び出してとか... あたりが有望? - ぐぐるとTensorflow.jsでジャンケンとか引っかかって来ますが...使えるかな?