3章 やりたいことをプログラムにする
第2部までで、CHaserのプログラムを組むための道具(移動・繰り返し・条件分岐・探査・変数・関数)は一通りそろいました。ところが、いざ「こう動かしたい」というアイデアが浮かんでも、それをどうやってブロックにすればよいのかが分からず、手が止まってしまうことがあります。
この章では、アイデアを、動くプログラムにするまでの考え方の手順を学びます。教材には、CHaserの対戦で使える動き(隣の敵を攻撃する、斜めにいる敵への対応、遠くの様子を先に見る、など)を取り上げます。ただ、ねらいは動きそのものではなく、「どうやってブロックにしたか」を順を追って見ていくことです。第2部の内容(特に9章の変数と関数)を読んでから進んでください。
この章のブロックについての注意
- この章のブロックは、方向を「値」として扱える【上級】のブロックで組んでいます(プログラミング画面の【上級】)。「上に移動する」のように方向が決まっているブロックではなく、「〔 〕に移動する」の〔 〕に、変数やリストの中身を入れられるブロックです。第2部9章の7-1で「移動方向」の変数に使ったものと同じ種類です。
- この章のブロックでは、相手のプレイヤーは「プレイヤー」と表示されます。
- 図に出てくる関数や変数の名前は、自分で決めたものです。自分のプログラムでは、好きな名前を付けて構いません。
また、この章は、答えを1つに決めるものではありません。同じ「やりたいこと」でも、ブロックにする方法は何通りもあります。どれを採用するかは、自分のプログラムで試しながら決めてください。
考え方:やりたいことを、プログラムにする4ステップ
「こう動かしたい」というアイデアは、日本語で浮かびます。ところが、コンピューターは日本語をそのままでは理解してくれません。日本語から、動くブロックまでの間には、次の4つのステップがあります。この章のどの例でも、この順番で考えています。
-
やりたいことを1文で書く
例:「隣に敵がいたら、攻撃したい」
-
あいまいな言葉を、数えられるものに直す
「近い」は何マス離れているか、「最近」は何ターン前か、というように、数えたり比べたりできるものに置き換えます。
-
日本語のまま、小さな手順に分ける
「①上下左右を1つずつ見る」「②敵がいるか確かめる」「③いたらブロックを置く」…と、1つの手順が1〜数個のブロックになる大きさまで分けます。
-
手順を1つずつ、ブロックに置き換える
下の表のように、手順の形ごとに使うブロックがだいたい決まっています。
| 手順の形 | 使うブロック(【上級】) |
|---|---|
| 候補を全部、順番に調べる | 「リスト〔 〕の各項目〔方向〕について」。リストは「以下を使ってリストを作成」ブロックに方向を並べて作る |
| 条件に合うものだけ選ぶ | 「もし〔条件〕なら実行」 |
| 行動できたら、そこで終わる | 「もし〔true〕 返す〔true〕」(条件付きの「返す」ブロックです。条件にtrueを入れると、必ず返します) |
関数の判断がtrueなら、そこで終わる |
「もし〔関数の呼び出し〕 返す」(値を持たない関数で使う形です) |
| 「最近」「近い」を判定する | 「−」の演算ブロックと、「≤」「≥」の比較ブロック |
| 含まれているか調べる | 「リスト〔 〕で以下のアイテムの最初の出現箇所を検索」が0でないか(第2部6章と同じ) |
うまく作るコツは、最初から全部を作ろうとせず、手順①だけを組んで動かし、確かめてから次の手順を足していくことです。第2部の総合演習と同じ進め方です。
やってみよう:「隣に敵がいたら攻撃する」
いちばん簡単な例で、4つのステップを一通りやってみましょう。
ステップ1:やりたいことを1文で書く
隣に敵がいたら、ブロックを置いて攻撃したい。
ステップ2:あいまいな言葉を、数えられるものに直す
- 「隣」→ 上・下・左・右のどれか
- 「敵がいる」→ 自分のその方向のマスが「プレイヤー」(第2部3章で学んだ、周辺情報の調べ方です)
- 「攻撃する」→ その方向に「ブロックを置く」
ステップ3:日本語のまま、小さな手順に分ける
- 上・下・左・右を、1つずつ見る
- その方向に敵がいるか、確かめる
- いたら、その方向にブロックを置く
- 置いたら、この仕事は終わり(あとで使うので、
trueを返す) - どの方向にもいなければ、何もしない(
falseを返す)
ステップ4:手順を1つずつ、ブロックに置き換える
手順1は「候補を全部、順番に調べる」なので「リスト〔 〕の各項目〔方向〕について」、手順2は「条件に合うものだけ選ぶ」なので「もし」、手順4と5は「終わったことを知らせる」ので「返す」です。
関数「隣の敵を攻撃する」のブロックです。手順1は「リスト〔上・下・左・右〕の各項目〔方向〕について」、手順2は「もし〔自分の〔方向〕マスがプレイヤーなら〕」、手順3は「〔方向〕にブロックを置く」、手順4は「もし〔true〕 返す〔true〕」、手順5は関数の最後の「返す〔false〕」です。
手順の番号とブロックが、1対1で対応していることを確かめてください。日本語の手順が細かく書けていれば、ブロックは自然に決まります。反対に、ブロックが組めなくて手が止まったときは、手順の分け方が足りていないことがほとんどです。手順を、もっと細かく分けてみましょう。
この章の以降の例は、どれも、この4ステップで組み立てていきます。
部品を組み合わせる:判断を優先順位で並べる
CHaserでは、1ターンに行動できるのは1回だけです。ある行動をしたら、そのターンはもう終わりです。この性質を使うと、前の節で作ったような関数(「やりたいこと」1つぶんの部品)を、次のような形で組み合わせられます。
- 「こういう状況なら、こう動く」という判断を、関数として1つずつ用意する。
- それぞれの関数は、行動したら
true、当てはまらなくて何もしなかったらfalseを返す。 - 優先したい順に上から呼び出し、
trueが返ってきたらそのターンを終える。
「もし〔関数の呼び出し〕 返す」というブロックを、優先したい順に上から並べます。「返す」ブロックは、実行されるとその関数をそこで終わらせるので、trueが返ってきた関数より下の判断は、実行されません。つまり、上に並べた判断ほど優先されるということです。値を返さない関数「1ターンの行動」の中でこの形にして、ずっと繰り返すブロックの中では、「自分のターンを待つ」のあとにこの関数を呼び出します。
プログラム全体の動きは、この「並べる順番」と「各関数の中身」で決まります。
「隣の敵を攻撃する」は前の節の図のとおりです。残りの2つも、同じ4ステップで作れます。「見えているアイテムを取る」は、敵を調べる代わりにアイテムを調べ、置く代わりに移動するだけで、形は「隣の敵を攻撃する」と同じです。
「ランダムに歩く」では、まず「空いている方向」という空のリストを作り、ブロックでない方向を「リスト〔 〕挿入位置:最後 値:〔方向〕」で1つずつ加えていきます。最後に「リスト〔 〕取得 ランダム」で1つ選んで移動します(第2部9章で学んだ、ランダムに選ぶブロックです)。「近隣探査する」は、四方がふさがったときの保険です。
ここで覚えておきたい約束:1つの関数の中で「移動する」「ブロックを置く」「近隣探査する」「遠方探査する」を実行するのは、多くても1回にしましょう。1ターンに行動できるのは1回だけなので、2回実行すると2回目は次のターンにずれ込み、思ったとおりに動かなくなります。
以降の節では、「1ターンの行動」の中に並べる部品を、「やりたいこと」ごとに1つずつ作っていきます。
例1 同じ「やりたいこと」を、3通りのプログラムにする(斜めに敵がいたら)
この例で身につく考え方:同じ「やりたいこと」でも、プログラムにする方法は1つとは限りません。複数の組み方を作って、比べて選びます。
周辺情報を見ると、敵が斜め(左上・右上・左下・右下)にいることがあります。このときの動き方は、勝敗に直結する重要な場面です。まず、何が問題になるのかを整理しましょう。
敵が斜めにいるとき、次に縦か横に隣り合う形を作った側は、その直後に相手の手番が来ます。相手は隣にいる自分にブロックを置いてアタックできるので、自分から隣り合いを作るのは危険です。逆に、相手が隣り合いを作ってくれたなら、次は自分の手番なので、こちらがアタックできます。
ここから、斜めに敵がいるときの動き方が3通り考えられます。どれも「自分から隣り合いを作らない」という点は共通です。
手順に分けてみよう
やりたいこと:敵が斜めにいるときに、自分から隣り合いを作らないように動く。
この文を、次の手順に分けると、プログラムにできます。3つの選択肢は、④だけが違い、ほかの手順は共通です。
| 番号 | 手順 |
|---|---|
| ① | 斜めの4方向を1つずつ調べる |
| ② | その方向に敵がいるか確かめる |
| ③ | 隣り合いができてしまう2マスの方向(または離れる方向)を求める |
| ④ | 選択肢ごとの行動をする(A:その側を近隣探査、B:反対に歩く、C:通れるほうを塞ぐ) |
| ⑤ | 行動したらtrue、斜めに敵がいなければfalseを返す |
③の「2マスの方向」は、たとえば右上に敵がいるなら、敵が隣り合うために入ってくるマス、つまり自分の上と、自分の右です。この2マスは、自分にも敵にも隣り合っています。ほかの斜めも同じ考え方で決まります。
選択肢A:動かずに待つ
相手が近づいてくるのを待つ作戦です。ただし、CHaserに「何もしない」という命令はありません。動かずに1ターンを使える行動は、近隣探査・遠方探査・ブロックを置くのいずれかです。ここでは、敵のいる側を近隣探査して待つことにします。近隣探査なら情報も手に入り、なおかつ何も壊しません。
斜めの4方向それぞれに「もし〔自分の〔斜め〕マスがプレイヤーなら〕」のブロックを1つずつ並べ、その中で敵のいる側を「近隣探査する」ようにします。たとえば右上に敵がいれば、上を近隣探査します。ブロックの中に、探査する方向を直接書き込みます。
選択肢B:反対側に離れる
隣り合いを作らないように、敵から遠ざかる作戦です。
斜めの4方向それぞれに「もし〔敵がいる〕」を並べるところは、選択肢Aと同じです。ただし今度は、その中で「離れる2つの方向」のリストを作り、「リストの各項目〔方向〕について」で、ブロックでない方向を探して移動します。離れる方向が2つあるのは、片方がブロックで塞がっていることもあるからです。図が縦に長いのは、この形を4方向ぶん並べているからです。
選択肢C:敵の近づく道を塞ぐ
敵が近づくために通るマスにブロックを置いて、隣り合えなくする作戦です。
選択肢Bとほとんど同じ形です。リストには「敵が近づく2つの方向」を入れ、移動する代わりに「〔方向〕にブロックを置く」を使います。1回に塞げるのは、2つの接近路のうち1マスだけです。もう1マスは空いたままなので、敵がそちらから隣り合ってくる可能性は残ります。
3つを比べて、選ぶ
3つとも、「1ターンの行動」に並べる位置は同じ(「隣の敵を攻撃する」の直後)で、呼び出す関数を差し替えるだけで切り替えられます。
それぞれに長所と短所があります。
| 選択肢 | 長所 | 気をつけたい点 |
|---|---|---|
| A 待つ | 自分は隣り合いを作らない。近隣探査で敵の周りの様子も分かる | 相手も待つと、お互いに動かなくなることがある |
| B 離れる | 敵との距離をあけられる | 逃げた先で別の敵接近や行き止まりに当たることがある |
| C 塞ぐ | 敵の接近路を1つ消せる | 自分の隣にブロックが増え、自分の動ける方向も減る |
どれが向いているかは、相手のプログラムや、マップの形によっても変わります。3つを全部組んで、CPU対戦などで試してみましょう。
例2 あいまいな言葉を、数字にする(いつ近隣探査するか)
この例で身につく考え方:「近く」「最近」のようなあいまいな言葉は、何かを数えて、数字を比べる式に直せば、プログラムにできます。
前の節では、近隣探査を「動かずに1ターンを使う行動」として使いました。近隣探査のもう1つの使い方は、遠くの様子を先に知ることです。
周辺情報で分かるのは、自分のすぐ周りの9マスだけです。その外側にいる敵は見えません。そこで、近隣探査を使って、進む先の様子を先に確かめます。
近隣探査は、指定した方向の2マス先を中心とする周囲9マスを教えてくれます。たとえば「上方向に近隣探査する」を実行すると、上方向の1〜3マス先の範囲が、次の形で「近隣情報リスト」に入ります。
| 左 | 中 | 右 | |
|---|---|---|---|
| 3マス先 | 左上 | 上 | 右上 |
| 2マス先 | 左 | 中央 | 右 |
| 1マス先 | 左下 | 下 | 右下 |
| 自分 | ★ |
(表の中は、「近隣情報リスト」から取り出すときの方角名です。「中央」が、2マス先のマスです。)
近隣探査を使うと、ちょうど周辺情報の外側にいる敵を先に見つけられます。ただし、近隣探査は1ターンを使うので、その間は移動もアイテム取得もできません。むやみに使うと、アイテムを集める速さが落ちます。そこで、「いつ使うか」が考えどころになります。
ここでは、敵を見た直後の数ターンだけ、進行方向を近隣探査するという作戦を作ります。敵がすぐ近くにいたなら、また近くにいる可能性が高いからです。
やりたいこと:敵が近くにいそうなときだけ、進行方向を近隣探査する。
このままでは、プログラムにできません。「近くにいそう」「使いすぎない」といったあいまいな言葉を、コンピューターが計算できる形に直す必要があるからです。コツは、「何を数えれば言えるか」を考えることです。
| やりたいこと(言葉) | 数えるもの | 式 |
|---|---|---|
| 敵が近くにいそう | 敵を最後に見てから、何ターンたったか | ターン数 − 最後に敵を見たターン ≦ 4 |
| 使いすぎない | 最後に近隣探査してから、何ターンたったか | ターン数 − 最後に近隣探査したターン ≧ 3 |
| どこを見るか | 最後に進んだ方向 | 進行方向 |
「4ターン以内」「3ターン以上」の数字は、この作戦の調整つまみです。そして、この式を計算するために、次の3つを覚えておく必要があります。
- いま何ターン目か(変数「ターン数」)
- 最後に敵を見たのは何ターン目か(変数「最後に敵を見たターン」)
- 最後に近隣探査したのは何ターン目か(変数「最後にlookしたターン」。図の変数名ではlookと書いてあります)
さらに、「進行方向」を覚えるために、「移動する」ブロックを包む関数「移動する」を作ります。
最初に、3つの数字と進行方向を「セット」します。−999は「まだ一度も見ていない」という意味です(理由は後の表で説明します)。
「移動する」は、方向を引数に受け取る関数です。歩いたあとで、その方向を「進行方向」にセットします。「先を見る」も引数つきの関数で、近隣探査したターンを覚え、敵が見えたら「最後に敵を見たターン」も更新します。「敵が見えたか」は、「近隣情報リスト」の中に「プレイヤー」が含まれているか、つまり検索の結果が0でないか、で調べます。
条件を数式にした「敵が近いなら先を見る」は、次のとおりです。「最近敵を見た」と「前回のlookから間があいた」という2つの変数に、それぞれの式の結果(成り立つか、成り立たないか)を入れて、「かつ」でつなげています。
Blocklyの変数は、関数の外で作ったものを、そのまま関数の中で読み書きできます。
条件の計算例
「敵が近いなら先を見る」の2つの条件は、ターン数の引き算だけで決まります。いま「ターン数」が10だとします。
| 最後に敵を見たターン | 最後にlookしたターン | 10 − 最後に敵を見たターン ≦ 4 | 10 − 最後にlookしたターン ≧ 3 | 先を見る? |
|---|---|---|---|---|
| 8 | 5 | 2 ≦ 4 → 成り立つ | 5 ≧ 3 → 成り立つ | する |
| 8 | 9 | 2 ≦ 4 → 成り立つ | 1 ≧ 3 → 成り立たない | しない(さっき探査した) |
| 3 | 5 | 7 ≦ 4 → 成り立たない | 5 ≧ 3 → 成り立つ | しない(敵を見てから時間がたった) |
| −999 | −999 | 1009 ≦ 4 → 成り立たない | 1009 ≧ 3 → 成り立つ | しない(敵をまだ見ていない) |
「最後に敵を見たターン」の最初の値を-999にしておくと、「まだ一度も敵を見ていない」ときは引き算の結果が大きくなり、1つ目の条件が成り立ちません。2つの条件が両方成り立ったときだけ、近隣探査します。
メインループでは、ターンごとに「ターン数」を1つ増やし、周辺情報に敵がいたら「最後に敵を見たターン」を更新します。ずっと繰り返すブロックの中で、「ターン数を1増やす」と、「周辺情報リスト」の中に「プレイヤー」がいたら「最後に敵を見たターン」を「ターン数」にセットする処理を、関数「1ターンの行動」の呼び出しの前に置きます。
「1ターンの行動」の中には、アイテム取得より下に「敵が近いなら先を見る」を置きます。アイテムを取れるときは取り、取れなくて敵が近くにいそうなときだけ先を見る、という順番です。
なお、「移動する」を使うときは、これまでの「〔方向〕に移動する」をすべて関数「移動する」に置き換えておきましょう。そうしないと、「進行方向」が更新されず、進行方向が古いままになります。
全部をつなげると
ここまで作った部品を、優先順位の連鎖に並べ直すと、次のようになります。
上から順に、「隣の敵を攻撃する」→「斜めの敵に備えて待つ」→「見えているアイテムを取る」→「敵が近いなら先を見る」の順に判断し、どれも当てはまらなかったときは、いちばん最後に「ランダムに歩く」です。
順番を入れ替えたり、一部を抜いたりして、動きがどう変わるかを見てみましょう。優先順位の連鎖は、「部品を足す」「順番を変える」「条件の数字を変える」という3つの操作だけで、プログラムの動きを組み替えられる形になっています。
もっと先へ進みたくなったら
この章のブロックで作れるのは、「いま見えているもの」から判断する動きです。もっと強いプログラムにするには、たとえば次のような動きが必要になります。
- 見たことのあるマスを覚えておいて、ブロックを避けた道順を考える
- 「まだ見ていないマスのうち、いちばん近いところ」を、近い順に調べて見つける
- 覚えているアイテムのうち、近いものを追いかける
- アイテムを取ると閉じ込められそうかどうか、出口の数を数えて判断する
これらは、座標をキーにした「辞書」や、調べる順番を管理する「順番待ちの列」を使うので、ブロックでは組めません。文字のコード(Python)で書くと、同じ4ステップの考え方でプログラムにできます。続編の応用編(Python編)の第3部3章で、これらを1つずつ作っていきます。
注意したいこと
- 「やりたいこと」が大きすぎて、手が止まる:無理にブロックを組もうとせず、日本語の手順を、1つが数個のブロックになる大きさまで細かく分けましょう。分けた手順を、1つずつ組んで動かして確かめると、途中でどこが違うかも見つけやすくなります。
- あいまいな言葉のまま、ブロックにしようとする:「近い」「最近」「多い」などは、何を数えて、いくつと比べるのかを決めてから組みます。
- 方向が決まっているブロックを使ってしまう:「上に移動する」のようなブロックには、変数やリストの中身を入れられません。この章のように方向を変数で扱うときは、【上級】の「〔 〕に移動する」「〔 〕にブロックを置く」などを使います。
- 「返す」ブロックの条件を、入れ忘れる:「もし〔 〕 返す〔 〕」は条件つきのブロックです。必ず返したいときは、条件に
trueを入れます。 - 「移動する」の関数を使わずに、直接移動してしまう:進行方向を覚えるには、歩くときは必ず関数「移動する」を通します。途中で直接「〔方向〕に移動する」を使うと、「進行方向」が実際の動きとずれてしまいます。
- 1つの関数で行動を2回実行する:「移動する」「ブロックを置く」「近隣探査する」「遠方探査する」は、1ターンに1回だけです。行動したら「返す〔true〕」で終わらせましょう。
- 周辺情報を、過去のものと取り違える:「周辺情報リスト」は毎ターン新しくなります。前のターンの情報を残したいときは、変数に書き込んでおく必要があります。
演習問題
- 「斜めに敵がいたら」の3つの関数(「斜めの敵に備えて待つ」「斜めの敵から離れる」「斜めの敵の通り道を塞ぐ」)を、すべて自分のプログラムに組んでみましょう。1つずつ「1ターンの行動」に差し替え、CPU対戦や友達との対戦で、結果や動き方にどんな違いが出るか記録してみましょう。
- 選択肢A(待つ)では、相手も待つタイプだった場合、お互いに動かなくなる可能性があります。「同じ場所で連続して待った回数を数え、上限を超えたら別の行動に切り替える」という工夫を、変数を使って組んでみましょう。
- 「敵が近いなら先を見る」の
≦ 4と≧ 3の数字を変えると、近隣探査を使う頻度がどう変わるか、考えてみましょう。使いすぎると、どんな困ったことが起きるでしょうか。 - 自分で「やりたいこと」を1つ考え、この章の4ステップ(1文で書く → あいまいな言葉を数えられるものに → 手順に分ける → ブロックにする)で、プログラムにしてみましょう。(例:直前に進んだ方向とは、反対には進まないようにしたい)
第3部のまとめ、そして本書の締めくくり
これで、CHaserのルールを理解し(第1部)、Blocklyでチュートリアルをクリアする力を身につけ(第2部)、実際の対戦の準備の仕方と、やりたいことをプログラムにする考え方(第3部)まで、ひととおり学び終えました。
CHaserは、覚える命令の種類こそ多くありませんが、「今の状況をどう読み取り、どう判断するか」を突き詰めていくと、驚くほど奥の深いゲームです。本書で学んだ移動・繰り返し・条件分岐・情報の取得・変数・関数という道具は、CHaserだけでなく、これからあなたが出会う他のプログラミングの課題でも、きっと役に立つはずです。
ブロックでは組みにくい動き(地図を覚える、道順を探索するなど)に挑戦したくなったら、続編の応用編でPythonを学んでみてください。Blocklyで身につけた「考え方」は、そのまま使えます。
第3部 確認クイズ
1章 プログラミング画面の使い方
- ワークスペースの一番上に必ず置く必要があるブロックは何ですか。
- 実行結果がうまくいかないとき、まずどこを確認するとよいでしょうか。
2章 対戦ルームの使い方
- 対戦を始める前に選べる2種類の対戦方法は何ですか。
- 「ゲームスタート」ボタンは、どんな状態になると表示されますか。
3章 やりたいことをプログラムにする
- 「やりたいこと」をプログラムにするときの、4つのステップを順に答えてください。
- 「近くにいる」のようなあいまいな言葉は、プログラムにするために、どのように直しますか。
- 「1ターンの行動」の中で、「もし〔関数の呼び出し〕 返す」を並べて判断を書くと、上に並べた判断ほど優先されるのはなぜですか。
- 「上方向に近隣探査する」を実行したとき、「近隣情報リスト」の「中央」は、自分から見てどのマスの情報を表しますか。
- 前のターンに進んだ方向のように、あとのターンで使うために覚えておきたい情報は、何に入れておけばよいですか。
(解答は付録1「確認クイズ・演習問題の解答」を参照してください。)
まずは自分のプログラムを実際に動かし、勝ったり負けたりしながら、次はどう直せばもっと強くなるかを考えてみてください。そうやって試行錯誤を重ねることこそが、プログラミングの一番の練習方法です。