配列 9. 地形を作る

これまたややこしいコードですが、indexという変数を増やしていくことで、heights配列に入れた適当な数字を順番に先頭から使っていきます。ここではわかりやすくする為に、3,4,5と3つだけの数字をセットしておきます。つまり、heights[0]が3、heights[1]が4、heights[2]が5ということになります。この数字だけ12行目のfor文で繰り返されるので、ブロックの積まれる高さが、3個、4個、5個と増えていき、3つ使い切ると、9〜11行目のif文でindexが0に戻り、また3個になり以降、3,4,5,3,4,5,3,4,5…と繰り返されます。

完成写真はこちら。

1行目の3,4,5を変えてみると出来上がる景色もかわります。数が3つでなくても大丈夫です。9行目でheights.countで個数を調べて上限値を決めているからです。これがもし、

だったら最初の3つしか使われないことになります。3,4,5なら問題ありませんが、もし7,3,1,4にしても4が使われないままになります。5,1にしてしまったら数が足りなくてエラーになります。heights.countでその都度調べているので、1行目にいくつの数字をセットしても柔軟に対応できるわけです。

[2,5,1,7,4]に変えてみるとこんな形になります。

配列 8. 配列の範囲外エラーを直す

配列の真骨頂、番号によるアクセスについて説明されています。今までも複数のキャラクターを作る時、名前にexpert1、expert2と通し番号をつけたことはありましたが、配列を使ってその番号を指定すると、for文などで繰り返すのに非常に便利になります(このステージではまだその恩恵はありませんが)。
今回は、for文で1〜9繰り返して.appendメソッドでteamBlu配列の中にBlu型のインスタンスを誕生させています。ここで非常に重要なのは、配列の通し番号は0から数えるという点です。for文に1〜9と書いているので、[1]〜[9]となると思いがちですがそうではありません。このコードだと9つのBlu型インスタンスがteamBlu配列内にできますが、その指定方法はteamBlu[0]〜teamBlu[8]となります。
それを踏まえて19行目以降をみると、最後の24行目でteamBlu[10]という背番号を指定しています。存在しない番号を読んでしまうと、そのプログラムは即クラッシュしてしまう深刻なバグとなります。行ごと削除してしますか、[0]〜[8]の範囲内に数字を変更してやればエラーは消えます。

ちなみに、今までエラーがあった場所はコードの行頭に赤丸がついて教えてくれました。しかしここではそうならず、実行してその行を処理しようとした瞬間にエラーとなりコードの実行が停止します。これは配列の中身の数が実際に実行してその行を処理する瞬間になってみないと10あるかないかわからないからです。まぁ、この例ではわからなくもないですが、例えばユーザがボタンを押す度に新しいキャラクターが生まれるようなプログラムを作った場合、いつどのタイミングでキャラクター数がいくつになるかは事前には予測できません。

という一行そのものは文法としてなにもおかしいところはないので、書いただけの時点でエラー判定して赤丸で教えることはできないのです。こういう実行してみないとわからないエラーは非常に発見が難しく、色々なパターンで検証しなければならず、それが足りないと、リリースした後でクラッシュしてしまうアプリとして問題になるのです。

配列 7. 取り除いた値を追加する

world.allPossibleCoordinates(ステージの全てのマスの座標)の代わりに、指定した行の全マスを取得するメソッドが紹介されます。それが3行目です。inRows:パラメーターで整数を指定するとその行の全マスの座標が返るので、それを配列に入れて使用します。

出題文には手順しか書かれておらず、結局ステージがどうなればクリアなのかがわからないままですが、とりあえず//で始まるコメント文に指示してある内容を埋めていくとこんな風になります。

先に実行結果を見てみましょう。シュールな光景ですね(笑)。

まず9行目のfor〜in文では、2列目の全てのマスの座標が配列に入っているので、それをtmpcdnという名前で1つずつ取り出してループ内を実行します。ちなみにtmpというのは「一時的な」という意味のtemporaryを略したもの、cdnはcoodinateを略したもので、あわせてtmpcdnです。深い意味はありませんので、自分で憶えやすい名前にしてください。

で、それらの全マスにword.place()メソッドが実行されるので、端から端までブロックが1段積み上がります。

次に14行目の

ですが、まず()内のallCoordinates.removeFirst()で、全ての2列目の座標の配列から、最初の1つを削除します。つまり(0,2)〜(19,2)の20個の配列から先頭の(0,2)が抜かれ、(1,2)〜(19,2)の19個が残ります。配列内での背番号は詰められていきますが、実際の座標自体が詰まるわけではないので注意してください。allCoordinates[0]が(0,2)から(1,2)になります。抜いた(0,2)の座標が()の外側にあるcdns(最初に用意しておいた空の配列)に.append(追加)されます。ちょっと特殊な書き方ですが、removeFirst()などのメソッドを使うと、そこに削除したオブジェクトがその行の中に一時的に残ると考えてください。なので、.append()のパラメータとして引き継がれます。
これが8行目のfor i in 1…12によって12回繰り返されるわけですが、2回目はallCordinatesの中が(1,2)〜(19,2)になっているので、ひとつ奥の(1,2)から(19,2)の19座標に対してworld.place()が実行されます。結果としてさっきよりも1列短い形で2段目が形成されます。そのあとまた.removeされるので、3周目は(2,2)〜(19,2)となり、、結果として1段上がるごとに1列少ないブロックが12段積み上がっていきます。
8〜15行目のループが終わった後は、19行目からのループです。これには1行上がるごとに削られた左端の座標が1つずつ入っているので、それらすべてにキャラクターを置いていくと、写真のようになるわけです。
ややこしいですが順を追ってじっくり考えてみましょう。

配列 6. 島を作る

前ステージの応用編です。allCoordinatesに全マスの座標を入れておき、ある条件に沿うものを配列Aに.append()、そうでないものを配列Bに.append()して、最後に配列Aと配列Bそれぞれについて異なる処理をします。
異なる処理というのは、「陸地ブロックを積む」と「海にする」なので、それぞれ、islandArrayとseaArrayという名前にしてみます。いいかげんcoordinate、coordinates、Coordinateなど似たようなのが入り乱れててわかりづいらいですよね。ので、配列には「Array」とつけてみました。配列を空の状態でつくる時は、将来的にどんな型のオブジェクトが入るかあらかじめ宣言しておく必要があります。今回はどちらもCoordinate型です(4,5行目)。

7行目からのfor〜inループは基本的に前ステージと同じです。Coordinate型オブジェクトcoordinateのメンバー変数であるcolumnとrowをチェックしてある範囲のものだったらislandArrayに追加。そして今回elseブロックでそれ以外の時はseaArrayに入れて行きます。これで全てのコマはislandArrayかseaArrayのどちらかに別れてコピーされたことになります。ちなみに、islandとは島(アイランド)の意味であって、isで始まるからといってBool型を意味してるわけではないです。

このステージでも実行結果を写真で載せておきます。真ん中に4×4マスの島ができ、後の陸地は全て海に変化してれば正常です。

coordinate、coordinates、Coordinateの区別

上の方で書いた似たような名前の見分け方について触れておきます。これはコードを書く人の好き好きで決められる部分でもあるので必ずではないのですが、一般的にこういうルールで使い分けてる人が多いですよ、という話です。逆にいえばこういうルールで書いておけば誰か他の人がみても理解しやすいでしょう、ということでもあります。

まず最初が大文字になっているものは、一般に型(クラス)を意味しています。実際のプログラミングでは型自体を自分で用意することもありますが、当面Swift Playgroundsの中では最初から用意されているなんらかの型を指していると考えて良いでしょう。

次に、同じ名前でcoordinateと小文字で書いたものは、大文字の型から作られたインスタンスであることが多いです。letやvarでTaiyaki型から作った1つのたい焼きがtaiyakiです。ひとつだけ作る場合はそれでいいですが、複数個の場合はtaiyaki1とtaiyaki2としたりもするかも知れません。

そして複数形coordinatesとなっていたら、Coodinate型のオブジェクトが複数入った配列であると考えられます。配列は中身が複数入るので複数形なのです。わかりやすいでしょ?たくさんあったらtaiyaki
[0]、taiyaki[1]というように[]内の通し番号で呼び分けます。

そしてfor〜in文でループする時は、

のように、「for 単数形 in 複数形 {}」とし、ループの中では単数形を使う形にするのが直観的でしょう。
例えばwatersみたいに「実際の英語では単複同形なのでsつけるのおかしくね?」というような場合もありますが、まぁあんまり英語としての正しさは二の次で、「配列ならs」をつけるというルールにしてしまった方がわかりやすかったりします。どうしても気持ち悪ければ上の例のように「Array(=配列)」をつけるとか自分で独自のルールを決めても良いと思います。

配列 5. 配列に追加する

8×8の64マスの座標をallCoordinates配列に入れて、for〜in文で1つ1つについてチェックをして、条件に当てはまるものを別のblockSetという配列に追加し、最後にblockSetの中身1つ1つについてまたfor〜in文でループしてブロックを積み上げていきます。

このステージでの目新しいテクニックは、2つ。
1つは、coodinate.columnとかcoodinate.rowでCoodinate型オブジェクトがもっているcolumnやrowといった値(メンバー変数と呼んだりします)を取り出せる点です。ドット記法は色々な意味があってややこしいですね。一般に、

のように最後に()がついている場合は、そのオブジェクトになんらかの処理(メソッド)を実行させる意味で、

のように()がついていないものは、オブジェクトが中にもっている変数(メンバー変数)を指していると思えばよいでしょう。英語なのが難点ですがその意味からなんとなくイメージすることもできることが多いです。

2つめは、配列に対して要素を追加する.append()メソッドです。以前、.insert()というのも出てきましたが、それと違い、単純に最後に付け足すというものです。

今配列内に要素がいくつあるか気にしなくて良いので、うっかり範囲外エラーを出してしまう心配もなくお手軽ですね。配列の中での順番が重要ではない場合は、.insert()より.append()がオススメです。

その2点を踏まえて正解例をご覧ください。

いまいち何をしているのかわかりづらいので、正解例コードを実行した時の結果も画像で載せておきます。