百億のマインクラフトと千億のタートル

つまり、マイクラワールド1つあたりタートルが10匹生息しているわけですね。

【動画投稿】マイクラでプログラミングして遊びましょう #3を投稿しました

マイクラでプログラミングして遊びましょう #3 ~コードを書こう編~

f:id:hevohevo:20200523220414p:plain

短いプログラムを書いて、プログラミングを体験しましょう。 まずは、タートルを自由に動かすプログラムについてです。

ニコニコ動画はこちら

www.nicovideo.jp

Youtubeはこちら

www.youtube.com

動画中で使ったPPTファイル(PDF変換済み)はこちら

Dropbox - マイクラでプログラミングして遊ぼう#3.pdf - Simplify your life

【動画投稿】マイクラでプログラミングして遊びましょう #1 #2を投稿しました

はじめに

f:id:hevohevo:20200518203005p:plain

とりあえずはブログのネタも含めて動画撮り、公開しました。 このご時世に、ゲーム実況スタイルではなくガチパワーポイント資料併用スタイル。

ComputerCraft導入編

プログラミングという遊びの魅力説明、ComputerCraftの紹介、Twitchの力を借りたマイクラJEとCCのインストールまで

動画

www.nicovideo.jp

動画内で使ったPPT(PDF変換済み)

www.dropbox.com

タートルに命令編

タートルの準備から、コマンド入力、タートルを自由に動かすまで

動画

www.nicovideo.jp

動画内で使ったPPT(PDF変換済み)

www.dropbox.com

手続きに名前をつけてやると自作関数が作りやすいのです

はじめに

hevo2.hatenablog.com

前回からだいぶ間があきましたが、床ブロック敷設プログラムは役立っていますか?

コマンド入力時にコマンドライン引数を与えることで長さを指定できるの便利ですよね。

今回はこのプログラムの内容を再検討してみましょう。

床ブロック敷設プログラム

前回のプログラムの再掲。

なかなか長いプログラムですが、やっていること自体は意外とシンプルなのはこれまでのブログ記事を読んでいるとわかりますよね。

でもこんなことを思いませんか?

「なんだか、同じようなことを何度も繰り返して書いてない?」

以前、繰り返しfor文を紹介したとき(過去記事)に、「同じ手続きを何度も書くのは面倒なので、代わりにfor文使って回数指定して繰り返す」方法について説明しました。for文を使うことで冗長で無駄な記述が減って、全体がすっきりしました。

今回は、また別の、すっきり書く方法を説明します。

冗長な部分で何をやっているのか一言で説明しよう

まず特徴的なのは、以下のような手続きがコード中に二回出現していることです。

-- アイテムが入っているスロットを選択する
for slot=1,16 do
  local kosuu = turtle.getItemCount( slot )
  if (kosuu > 0) then
    turtle.select( slot )
    break
  end
end

行番号で言うと、19〜26行目と42〜49行目の二箇所です。同じ内容を2回書いているのでなんだか無駄な気がしますね。

ではこの手続きは何をやっているのでしょうか?

一言で要約するならば、「アイテムが入っているスロットを選択する」手続きですよね。

この手続きに注目してみましょう。

手続きに名前をつけてやる

この「アイテムが入っているスロットを選択する」手続きですが、コード全体では「アイテムが入っているスロットを選択する」手続きが二箇所あるわけで、なんとかこの「アイテムが入っているスロットを選択する」手続きを省略して書く方法を・・・。

・・・長いよ!一言じゃないよ! この手続きにはもっと短い名前をつけなおしましょう。

たとえばちょっとだけかっこつけて英語で名前をつけましょうか。

「selectItemSlot」(≒ select (an) item slot ≒ アイテムスロットを選択せよ)

とかどうでしょう。

新しく関数を定義する

プログラム言語では、何度も使う決まり切った手続きを、関数(サブルーチンと呼ぶこともある)として定義できます。一度関数として定義してしまえば、その関数は使い放題となります。

自分で新しく関数を定義するには、以下の表記方法を使います。

function 関数名()
  やって欲しい手続きを書く
end

この表記方法を使って、以下のように「selectItemSlot」関数を定義しましょう。

function selectItemSlot()
-- アイテムが入っているスロットを選択する
for slot=1,16 do
  local kosuu = turtle.getItemCount( slot )
  if (kosuu > 0) then
    turtle.select( slot )
    break
  end
end
end

今回は上記のように、「function 関数名()」〜「end」の間に、「selectItemSlot」の具体的な手続きをコピペしました。

ただこのままだと、インデント(行頭の空白スペース)が不揃いで不格好なので、以下のように整えました。

function selectItemSlot()
  -- アイテムが入っているスロットを選択する
  for slot=1,16 do
    local kosuu = turtle.getItemCount( slot )
    if (kosuu > 0) then
      turtle.select( slot )
      break
    end
  end
end

以下のようにシンプルに

このように新しい関数「selectItemSlot」を定義できたわけですので、この新関数を使って冗長な手続きを置き換えるとこんな感じになります。

-- 床にブロックを敷き詰めながら指定した歩数だけ前に進む
for i=1,hosuu do
    -- アイテムが入っているスロットを選択する
  for slot=1,16 do
    local kosuu = turtle.getItemCount( slot )
    if (kosuu > 0) then
      turtle.select( slot )
      break
    end
  end
 
  turtle.placeDown()
  turtle.forward()
end

上記の冗長な表記から

以下のような、関数を使ったシンプルな表記へ

-- 床にブロックを敷き詰めながら指定した歩数だけ前に進む
for i=1,hosuu do
  selectItemSlot()
 
  turtle.placeDown()
  turtle.forward()
end

ここでのポイントは、作った関数「selectItemCount」を実行するためには、末尾に「()」をつけて、selectItemCount()のように書くことです。「()」を忘れると実行できないので要注意。

自作関数を利用した完成版プログラム

最終的に以下のようなプログラムになります。

このプログラムには3つの重要なポイントがあります。

まずコメントの------- Config ------のような区切り線に注目しましょう。

注目することで、プログラム全体の構造が以下のようになっていることがわかります。

  • Config 部: プログラム全体の設定パラメーターを記述する部分。挙動を変えるために書き換えても良い。
  • Functions 部: 全体をすっきりと表記するために、新しく関数を定義する部分。
  • Main 部: 実際にコンピュータが実行する手続きを表記する部分。

このように3部構成になっていることに気づくことが最初のポイントです。

2つ目のポイントとして、 初めて見るプログラムを読み解く時には、このMain部分から始めることが重要です。

Main部分を読んでいくうちに、謎の(知らない)関数が出てきたら、それはプログラム作成者が作った新しい関数である可能性があります。Function部に戻ってその関数を探し、何をする関数なのか解析しましょう。

なお、自作関数の名前を「selectItemSlot」のようにわかりやすいものにしておけば、「アイテムのスロットを選んでくれる関数」だとある程度推測がつきますので、Function部を読まなくても全体の動きが掴めるようになります。

関数の名前は、その挙動を説明するわかりやすいものにすることを心がけましょう。

つまり、「関数の名前つけは重要!」

3つ目のポイントとして、関数の定義は実際にその関数を実行するよりも前で行いましょう。つまり、関数を定義するFunction部は、実際にそれを実行するMain部よりも前に置くことがポイントです。

読者への宿題

このプログラムは、他にも冗長な記述がたくさんあります。

たとえば、以下のような関数をFunction部で定義して、その関数を使ってシンプルに書き換えてみましょう。

  • spreadBlockAndForward() : 真下にブロックを設置して前に1歩進む関数
  • goRight() : 右に移動する(右を向いて前に1歩進む)関数

おまけ

スピッツのこの曲。いいよね。 (アフィ広告ではなくAmazon商品へのただのリンクです)

名前をつけてやる

名前をつけてやる

わしのインベントリは16スロットまであるぞ

はじめに

hevo2.hatenablog.com

前回までに床ブロック敷設プログラムを改造してきましたが、 実はあえて、致命的な弱点に触れていなかったんですよね。

・・・これ、タートルの1スロットの容量である64個までしかブロックを敷設できないんです。

つまり、

> yuka4 64

より大きな数は無理なんですね。 しかもこの実行では、片道64歩分だけ敷設して、復路は何もせず帰ってきちゃいます。

タートルはインベントリを16スロットも持っているのですから、これらをうまく活用したいものです。

タートルのインベントリについての基礎知識

まずはタートルのインベントリに注目しましょう。

f:id:hevohevo:20191009142626p:plain
タートルのインベントリ

タートルのインベントリには16個のスロットがありそれぞれに番号がついています。最初タートルは一番左上のスロット番号1に注目しています。スロット1が少しだけ太い枠で囲まれているのがその証になります。

このようなタートルが注目しているスロットを「選択スロット」と呼びます。タートルがブロックを採掘したりブロックを設置するときは原則としてこの選択スロットを使います。1

タートルが自分のインベントリを操作する関数はたくさん用意されているのですが、今回はその中でもよく使う関数を抜粋して紹介しましょう。

サンプルプログラム1

以下が今回のサンプルプログラム「countSlots.lua」になります。

これは、タートルがスロット1から16まで順に選択スロットを変えていき、そのスロットにあるアイテムの個数を順に表示するプログラムです。

それではこのサンプルプログラムを解説しましょう。

まずはプログラム中の2つの新しい関数を紹介します。

関数名 機能
turtle.select(slotNum) 選択スロットを、指定したスロット番号slotNumに変更する。
turtle.getItemCount(slotNum) 指定したスロット番号slotNumにあるアイテムの個数を返す。なお、スロット番号を省略したら選択スロットのアイテム個数を返す。

プログラムの基本的な構造は、2行目のfor slot=1,16 doから6行目のendで挟んだ部分を、カウンター変数slotの値を1から16まで順に増やしながら、合計16回繰り返すf形になります。

つまり以下のように、変数slotが1から16まで変化しながら、16回繰り返すわけですね。

  • 変数slotが1: 選択スロットを1にして(3行目)、その選択スロットのアイテム個数を変数kosuuに代入(4行目)、その個数を画面表示(5行目)
  • 変数slotが2: 選択スロットを2にして(3行目)、その選択スロットのアイテム個数を変数kosuuに代入(4行目)、その個数を画面表示(5行目)
  • 変数slotが3: 選択スロットを3にして(3行目)、その選択スロットのアイテム個数を変数kosuuに代入(4行目)、その個数を画面表示(5行目)
  • 変数slotが16: 選択スロットを16にして(3行目)、その選択スロットのアイテム個数を変数kosuuに代入(4行目)、その個数を画面表示(5行目)

さあこれでこれら2つのインベントリ操作系関数の使い方が理解できましたね。それでは、実際に「アイテムが入っているスロットを選択するプログラム」を作りましょう。これまで学んできたfor文とif文を組み合わせて使います。

サンプルプログラム2

以下が、アイテムが入っているスロットを選択するプログラムである「selectItem.lua」です。

あたらしい記述が二つありますので順に解説しましょう。

比較演算子とは

まず注目して欲しいのは、5行目にあるif文の「条件式」です。 前回の記事では、if 「条件式」 then 「やって欲しいこと」 endというif文の基本構造を紹介し、「条件式」に「真(true)」または「偽(false)」が入るのだという解説をしました。

今回は条件式として(kosuu > 0)を記述しています。

「>」という不等号は、数学の世界では「5 > 3」のように使って、「5は3よりも大きい」という事実を表現するわけですが、プログラムの世界では違います。

現在使っているLua言語では、「A > B」と記述することで「AはBよりも大きいかどうかをチェック」します。もしこの記述(A > B)が正しいならば真(true)を返し、この記述が間違っていれば偽(false)を返します。

このように数学の世界とプログラムの世界で「>」の挙動が違うので注意しましょう。Lua言語では「>」のような記号を「比較演算子」(左側と右側を比較して演算する記号、の意味)と呼びます。

この不等号以外にも比較演算子はあります。他の比較演算子を知りたい方はココをクリック

比較演算子 機能 使用例(と返す値)
> 左が右より大きいなら真(小さいなら偽)を返す 3 > 1 (→true
< 右が左より大きいなら真(小さいなら偽)を返す 3 < 1 (→false
>= 左が右以上なら真(以下なら偽)を返す 3 >= 1 (→true
<= 右が左以上なら真(以下なら偽)を返す 3 <= 1 (→false
== 左と右の値が同じならば真(違うなら偽)を返す 4 == 2 (→false
~= 左と右の値が違うならば真(同じなら偽)を返す 4 ~= 2 (→false

よく間違えるのが比較演算子「==」です。「=」だと変数に代入する記号(代入演算子)になるのでご注意ください。

比較演算子を使ったkosuu > 0という式は、変数kosuuが0より大きければ真(true)を返し、0より小さければ偽(false)を返します。

つまり今回は、「kosuu」の値が0より大きければ(つまり、アイテム個数が1個以上)ならば、then 〜 endの中を処理することになります。

break文とは

もう一つの新しい記述として7行目のbreakがあります。 この記述をコンピュータが処理するとfor文が処理途中であってもいきなりそのforの処理をやめてforの外に抜け出す(ブレークする)という挙動になります。

よってこのプログラムの挙動を詳細に追うと

  • 変数slotが1: スロット1のアイテム個数を変数kosuuに代入(3行目)、その個数が0より大きいかチェック(5行目)
  • 変数slotが2: スロット2のアイテム個数を変数kosuuに代入(3行目)、その個数が0より大きいかチェック(5行目)
  • 変数slotが3: スロット3のアイテム個数を変数kosuuに代入(3行目)、その個数が0より大きいかチェック(5行目)
  • 変数slotが16: スロット16のアイテム個数を変数kosuuに代入(3行目)、その個数が0より大きいかチェック(5行目)

という動作をするのですが、それぞれのスロットの個数チェック時(5行目)に0より大きいことがわかったら、タートルの選択スロットをそのスロットに変えた後、いきなりforの処理を抜けて外に飛び出します。 たとえslotが1であろうと10であろうと、ifの処理によって強制的forの処理を中断してforの外に飛び出すところがポイントです

選択スロットをそのスロットに変えてからforの外にブレークするので、このプログラムによって、「アイテムが入っているスロットを選択できる」わけなのです。

床ブロック敷設プログラムの改善版

それではこれまで学んだことを生かして、今回の課題である「yuka4」プログラムを改善し、以下のような「yuka5」プログラムを作りました。

元の「yuka4」プログラムとの変更点は2カ所です。

turtle.placeDown()というブロックを真下に設置する関数がありますが、その関数の実行直前に、これまで学んできた「アイテムを持っているスロットを選択する」処理を埋め込んでいます。

このturtle.placeDown()が2カ所あるので、埋め込んだのも2カ所ですね。

さあこれでインベントリの全てのスロットを使ってブロックを設置できるようになりました。16スロット全てに64個のブロックを入れておけば、合計1024個のブロックを設置できますね!


  1. ブロックを採掘する時にこのスロットが満タンだったり他のアイテムで埋まっている時には、その隣のスロットに入れようとします。逆にアイテムを設置しようとした時に、このスロットが空っぽだったり設置できないアイテムならば、設置に失敗します。

もし変数に何か入っているならば・・・え?無が入っているの?

はじめに

前回記事からしばらく時間がたちましたがいかがお過ごしでしょうか。

hevo2.hatenablog.com

前回紹介した「yuka3」プログラム活用してます?

シンプルで便利ですよね。10歩分の床ブロックを敷き詰めたい(往復で2列)なら、こう。

> yuka3 10

以下のように16歩分でも問題なし。ただしその分のブロック32個(= 16 × 2)をタートルに渡すの忘れないでくださいね。

> yuka3 16

でも、何度も使っているうちにこんなことやらかしませんでした?

> yuka3

f:id:hevohevo:20191004200407p:plain
エラーメッセージ「13行目:for文の終端値は数字でないといけません」

つまり、引数なしでこのプログラムを実行したら、「ソースコードの13行目:for文の終端値は数字でないといけません」とエラーメッセージが表示されたわけですね。

ちょっと13行目を確認しましょう。

for i=1,hosuu do

つまり、変数hosuuの値が数値ではないと。それは当たり前ですよね。6行目でlocal hosuu = ...のようにコマンドライン引数を変数hosuuに代入していますが、そもそも引数を入力してないので、その値を代入する変数hosuuの値が数値なわけがありません。

何も入ってないが入ってる

では何が変数hosuuに入っているのでしょうか。

結論から言うと何も入っていません。

いえ、正確に言うと「何もない」という情報がこの変数に入っています。

それをLua言語では「nil」(ニル)と呼びます。つまりLua言語では、「何もない(無)」をnilとして表現するわけですね。覚えておきましょう。1

「無」(nil)は当然ながら数値ではありません。そのためコードの13行目で、「for文の終端値は数字でないといけません」とエラーが出てしまったわけですね。

表示されるエラーメッセージはプログラムのミスを発見するのに役立ちます。特にエラーが発生した行番号の情報は重要です。その場所を中心に、なぜそのようなエラーが起きたのかミスを推測することができるからです。2

エラーが出ないように改造する

それではこのようなエラーがでないようにプログラムを改造しましょう。

改造のやり方はいろあるのですが、ここでは、「変数の初期値を決めておいて、引数が入力されたときだけその値に変更する」方向をオススメします。

つまり、次のようなイメージ。

  1. あらかじめ、変数hosuuの値を6に決めておく(数値は何でも好きにどうぞ)。
  2. もし引数に何か入力されたらそれを変数hosuuの値に代入する(つまり、何も入力されなかったら何もしない)。

という流れです。

if文の簡単な使い方

「もし◯◯◯ならば、△△△をする」

プログラムではこのような構文をよく使います。3 このような構文を表記するには、「条件分岐 if 文」を利用します。このif文自体は少々複雑なのですが、まずはシンプルなプログラムでif文の基本的な使い方を学びましょう。

以下のプログラムを「if0.lua」というプログラム名で保存して、様々な引数を入力して実行してみましょう。なお、--で始まる行はコメントですし、行の途中から--が入っているときはその--以降はコメントです。

以下、実行例。

> if0 10
10
> if0 20
20
> if0
6

このように「10」とか「20」とか引数を与えて実行すると、その引数を最終的に変数hosuuに代入してその値を表示します。また、引数を与えないと変数hosuuの初期値である6をそのまま表示するというプログラムです。

サンプルプログラムの詳細解説

まず3、4行目の変数定義部分は、ここまで記事を読み進めてきた人ならば何も問題なく理解できると思います。

(注意したいのは、3行目の表記によって「一番目の引数」が変数arg1に代入されることです。引数を複数入力しても、一番目のものだけ取り出します)

次にif文ですが、これは次のような構造をしています。

if 「条件式」 then
  「やって欲しいこと(複数も可)」
end

ifthenendの3つのキーワードが必須ですので位置も含めてしっかりと覚えましょう。

さて、この「やって欲しいこと」については、好きなこと書けばいいので問題ないとして、困ったのは「条件式」になります。これは何でしょうか?

真偽値

Lua言語における「条件式」は、結果として最終的に「真」または「偽」となる式を取り扱います。

・・・うん。難しいですね。つまりこの「条件式」の部分には真(true)または偽(false)のどちらかの値しか入らないのです。「真とは正しい」ということ、「偽とは間違っている」ということを意味します。

そしてこの真偽値によって、if文は挙動を変えるのですよ!

ちょっと例を示しましょう。

if true then
  print("shinndayo")
end

このプログラムでは、if文の「条件式」部分に「真(true)」がはめ込まれている。だからこのif文は続く「then 〜 end」の中を処理することになる。だから画面に"shinndayo"を表示する。

if false then
  print("shinndayo")
end

逆にこのプログラムでは、if文の「条件式」部分に「偽(false)」がはめ込まれている。だからこのif文は続く「then 〜 end」の中を読み飛ばして何もしない。画面表示もなし。

つまり、if 「条件式」 then 「やって欲しいこと」 endというif文は、「条件式」部分に「真」が入ると「やって欲しいこと」を実行し、「偽」が入ると読み飛ばしてしまうわけなのです。

さてここで、「真」「偽」というお話をしたわけですが、Lua言語ではもう少しこの真偽値を柔軟に取り扱っていて、truefalse以外にも真偽の分類を広く認めています。それを次の表で説明しましょう。

false, nil  ←この2つ以外全部

出ましたnil。これは「無いこと」を示す特殊な値でしたね。 Lua言語では、このnilfalseは「偽」とします。 そしてそれ以外の全部の値を「真」とします。4

単純明快!!シンプル!!

さあこれで全体を説明するためのパーツの説明が揃いました。まとめましょう。

サンプルプログラムの解説

サンプルプログラムを再掲します。

まず、3行目で変数arg1の中に引数の一番目を代入しています。 もしプログラム実行時に引数を入力していればここには「何か(真)」が代入されていることになります。逆に引数が入力されていなければ、「nil(偽)」が代入されてます。

4行目は、変数hosuuの初期値として6を設定して定義していますね。

さあ、6行目のif文とその条件式です。今回条件式にはarg1をはめこんでいるわけでこれがかでif文は条件分岐するわけですね。

【引数が入力された「真」の世界】もし引数に何かを入力していれば、arg1は真なので、「then 〜 end」部分が実行されます。

つまり、hosuu = arg1ですね。arg1の値をhosuuに代入します5。 その結果、初期値が6だったhosuuは、引数の値によって上書きされました。

10行目。最終的に画面には、入力した引数がそのまま表示されます。

【引数が入力されなかった「偽」の世界】さて逆に、引数が入力されなかったらarg1はnilであり偽なので「then 〜 end」は読み飛ばされて下の行に進みます。

その結果、初期値6だったhosuuは手付かずそのままです。

10行目。最終的に画面には、初期値6がそのまま表示されます。

プログラムの最終形

これまでの説明を反映して改造した「yuka4」プログラムです。

便利に使い倒しましょう。そして、不便なところを見つけましょう。

次回はもっと便利に改造します。


  1. プログラム言語によっては、何も代入していない変数(初期化してない変数)の中に何が入っているのか不定な場合があります。実際に中を見るまで何かわからないとか怖っ! Lua言語では、何も代入していない変数の中身は「無」(nil)と決まっています。

  2. 実際にエラーが起きたのはたしかにこの行なのですが、そのエラーの根本的な原因はそれよりも前にあることが多い、ということは覚えておきましょう。今回の例で言うならば、「13行目のエラーの原因」は「6行目で引数を変数に代入しているけれどそもそも引数を入力してなかった」です。

  3. コンピュータの世界では、コード中に「もし◯◯◯ならば△△△をする」としか書かれていなかった場合、「◯◯◯ではないとき」には「何もしません」。つまりコンピュータは、命令されない限り何もしないというのが原則動作となります。

  4. nil,false以外の全部の値を「真」とするのはとてもシンプルですが、意外とくせものです。たとえば数値10、文字列"abc"は当然ながら真として、文字列"false"も真となります("“で囲んでいるのがポイント)。また文字列右""も真です(これは中身が空っぽの文字列で空文字と呼びます)。いじわる!

  5. この等号、数学の世界における「等しい」の意味じゃないですから気をつけて!プログラム言語の世界では、「arg1の値をhosuuに代入する」の意味で使っています。紛らわしいですが、現実世界ってそんなものです。割り切って覚えましょう。

コマンドライン引数の便利さを万人が知るべき

はじめに

前回の記事では、プログラム内で変数を使う時の基本をお話しました。

hevo2.hatenablog.com

変数って便利ですよね。

プログラムの挙動を変えるためにソースコードを何箇所も書き換えるのはとても面倒ですが、変数を使うことで、ソースコードの先頭部分を一箇所書き換えるだけでよくなりました。

これは小さな一歩でありながら、大きな進歩でもあります。

しかし、そもそも毎回プログラムを書き換えるのって面倒じゃないですか?

そこで今回のお話です。まずは何がやりたいのか目標を示しましょう。

コマンド入力時に引数を与えたい

これまで、プログラムを実行するときは、そのプログラム名をコマンド(命令)として与えていました。

たとえば「yuka」というプログラムならば、ターミナル画面で次のようにコマンドを入力して実行していました。

> yuka

でも、こんな感じで実行できたら便利じゃないですか?

「16歩分の床ブロックを敷きなさい」

> yuka 16

あるいは、「100歩分の床ブロックを敷きなさい」

> yuka 100

このように、コマンド入力時にその時限りのパラメータ(値)を与え、プログラム内でそのパラメーターを利用するのです。

ちなみに、この「コマンド入力時にプログラムに渡すその場限りのパラメータ」を「コマンドライン引数」とここでは呼びます。

つまり、コマンドライン引数を使ったプログラムの書き方が今回のテーマです。

コマンドライン引数を使ったサンプルコード

使いこなしには少しだけコツが必要です。 できるだけシンプルに説明するために、次のようなコードを紹介しましょう。

-- hikisuu0
local arg = ...
print(arg)

これを理解するには、実際の挙動を確かめるのが一番。「hikisuu0」というプログラム名でファイル保存して、色々実行してみましょう。

たとえば、こんな引数を与えて数回実行してみます。

> hikisuu0 100
100
> hikisuu0 abc
abc
> hikisuu0 1 2 3 4
1
> hikisuu0 a b c d
a
> hikisuu0
 

以上からわかることは、このプログラムは与えたコマンドライン引数のうち一番目のものだけを画面に表示するということです。ただし引数を何も与えないと何も表示しません。

サンプルコードの詳細解説

上記のサンプルコードをそのままコピペすればそのままあなたのプログラムに使えるわけなのですが、「なんで?どういう理屈でそうなるの?」と思いませんか?その疑問に思う気持ちは重要です。私は尊重します。(興味ない人はこの章を読み飛ばしてもOK)

詳細に解説しましょう。

まず注目するのは1行目のlocal arg = ...です。

基本的な形が、local 変数名 = 値の形をしていることから、変数arg 1の定義をしていると推測できるはずです。では...とは何でしょうか。

Lua言語では...というピリオド3つの表記を「可変長引数式」と呼びます。その詳しい説明はここでは省略しますが、この3つの点だけでコマンド入力時に後ろに続けて入力したパラメータ(コマンドライン引数)が利用できるわけですね。

なお、...という表記によって、入力したコマンドライン引数の全部が利用できます。たとえば4つ入力したら4つともです。

しかし、local arg = ...という表記では、入力したコマンドライン引数のうち先頭の1個しか利用できません。今回はそれで問題ないのですが、もしあらかじめ4つの引数を入力することがわかっていて、その4つ全てを拾って使いたいのならば、次のような表記が必要です。

local arg1, arg2, arg3, arg4 = ...

つまり代入用の変数をあらかじめ4つ用意しておくわけですね。ちなみにこういうような複数の変数への代入を一括で行う表記のことを「多重代入」2と呼びます。

とはいえ先ほども言いましたが、今回は多重代入は使いません。だって今回は、引数は先頭の一つだけでよいのですから。

床ブロック設置プログラムにコマンドライン引数を利用

さて、コマンドライン引数を使って、前回のプログラム「yuka2」を次のように改造しました。

変更点わかります?

----------------------------------
-- yuka3: 床にブロックを設置する(1往復)
----------------------------------

------- Config -------
local hosuu = ...

------- Main -------
-- 地面に置いたタートルでは床にブロック置けないので、浮かせる
turtle.up()

-- 床にブロックを敷き詰めながら指定した歩数だけ前に進む
for i=1,hosuu do
  turtle.placeDown()
  turtle.forward()
end

-- 右の列に移動
turtle.turnRight()
turtle.forward()

-- 右の列のスタート地点に移動。こちら側を向いている。
turtle.turnRight()
turtle.forward()

-- 右の列に床ブロックを敷き詰めながら戻ってくる
for i=1,hosuu do
  turtle.placeDown()
  turtle.forward()
end

実は。前回の「yuka2」プログラムに比べると一箇所しか変わってないんです。

  • 「yuka2」の6行目: local hosuu = 6
  • 「yuka3」の6行目: local hosuu = ...

これだけ!

プログラム実行

次のようなコマンドを入力して挙動を確認しましょう。

> yuka3 6
> yuka3 16

f:id:hevohevo:20191008172750p:plain
足場がないの助けて

おわりに

これでタートルに、柔軟に床貼りをしてもらえますね! レッツ床貼り!(ネザーとかで)

次回は、このプログラムの問題点について触れ、その改善をします。

実際に使いはじめるとそのうち気づく問題点なのですが・・・。もしわかったら、コメントに書いてみてくださいね。


  1. 変数名argの由来は、英語のargument(引数)です。このようなコマンドライン引数を示す変数名としてよく使われるので覚えておくと色々捗ります。

  2. たとえばこんな表記で多重代入ができるのでたくさんの変数を一括で定義したいときは便利です。local x, y, z = 1, 2, 3

変数を使いこなしてプログラムを改善しよう

はじめに

CCプログラミングの環境の設定の話で記事を書き始めたらそちらへの脱線が長くなってしまいました。

今回から、本格的にCCプログラミングやっていきます。

まずは床にブロックを設置するプログラムを再掲しましょう。

床貼りプログラム(1往復)

----------------------------------
-- yuka1: 床にブロックを設置する(1往復)
----------------------------------

-- 地面に置いたタートルでは床にブロック置けないので、浮かせる
turtle.up()

-- 床にブロックを敷き詰めながら6歩分前に進む
for i=1,6 do
  turtle.placeDown()
  turtle.forward()
end

-- 右の列に移動
turtle.turnRight()
turtle.forward()

-- 右の列のスタート地点に移動。こちら側を向いている。
turtle.turnRight()
turtle.forward()

-- 右の列に床ブロックを敷き詰めながら戻ってくる
for i=1,6 do
  turtle.placeDown()
  turtle.forward()
end

外部エディタ(VSCode)使うとこのようなコード(ソースコードの略)が色分けされて見やすいですよね。 なお、せっかくなので1行目〜3行目のように、このプログラムの名前とその説明についても書いてみました。 「yuka1」だけでは何をするプログラムなのかわからなくなりますからこのようなコメントは重要です。

上記のコードをエディタ側にコピー&ペーストもできるので、書くのも楽になります。 でもできれば、上記コードを見ながらでかまわないので、タートルの行動を頭の中でシミュレーションしながら自分でコードを書いてみてくださいね。1

このプログラムの問題点

さて、このプログラムなかなか便利なのですが問題点もあります。 それは「6歩分のブロックを設置する」と決め打ちでコード中に書いていることです。

場合によっては、「10歩にしたい」「100歩くらい余裕」「13kmや」などと変更したいときもありますよね。 そういうときは、おもむろにこの「yuka1」プログラムを書き換える方法があります。

該当する部分は、9行目と23行目の二箇所です。繰り返しfor文のカウンタの終端値が「6」になっているのでこれを好きな値に書き換えれば良いわけです。

ただし気をつけなくてはならないのは、9行目と23行目の両方を同じ値にしないとタートルがおかしな挙動になってしまうことです。たとえば、100歩先に進んだにも関わらず、6歩しか戻ってこないのではおかしいですよね。

このプログラムを使っていると、「二箇所も書き換えるの面倒。しかも同じ数字にしないといけないし!」と思うかもしれません。

そこでコードの改善ですよ!

変数ってなんだっけ?

学校の数学の時間では、xやyのような変数を習ったことがあるかもしれません。 様々な値(数値など)を変数に代入できたり2、またあるいは特定の値の代わりに変数で置き換えることもできたり3と、変数は数学の世界ではとても強力な道具です。

プログラミングでも変数は利用できます。数学の時間と異なるのは、変数に自分で好きな名前をつけてよいことです。4 いえむしろ、x・y・zではなく、他人がみてわかりやすい変数名をつけることが積極的に推奨されています。

変数の使い方

以下はプログラム中に変数の使い方を説明するためのサンプルコードです。

local x = 3  -- ローカル変数xの値は3と定義します
print(x+5)  -- x+5の値を表示しなさい

1行目で 変数xの値は3であると「定義」しています。「local」ってなぁに?と思うかもしれませんが、これはローカル変数(今の段階では、ここだけで使う「一時的な変数」と説明しておきます。より詳しく知りたいならば、「変数のスコープ」「グローバル変数」あたりのキーワードで自分で調べてみましょう)を定義するための構文で、(ローカル)変数を使いたいときには、最初に必ずこう書いて定義しなくてはなりません。

ここで注意して欲しいのが、local x = 3の「=」は、数学の時間の「等号=」の意味で使っていないことです。これ、世界で最初にプログラム言語を発明した人の最大のミスだと個人的に思っています5

ここでの「=」は「is」くらいの意味だと思ってください。

つまり、「x = 3」は「x is 3(xは3とします)」くらいの意味。なぜこのような細かいことを説明するかと言うと、次のようなサンプルコードを見せると、数学が得意な人ほど混乱してしまうのですよ。

local x = 3  -- 変数xの値は3と定義します
print(x+5)  -- x+5の値を表示しなさい

x=1  -- 変数xの値を1にします(変更します)
print(x+5)  -- x+5の値を表示しなさい

数学得意マン「x=3なんでしょ? なんでx=1とか言い始めるのさ。おかしいでしょ!」

わかります。学校でプログラミング教育やっているそうだけど、算数・数学の授業との整合性(=は等号でしょ?)はどうやっているのか・・・。現場の苦労が偲ばれます。

算数・数学の時間では、「=」は等号であり「x=3」は「xは3と等しい」という意味だけど、プログラミングの世界では「=」は「is」くらいの意味であり「x=3」は「(ここでは)xは3とします」くらいの意味です。世界が変わると記号の意味も変わるのです。

このサンプルコードは、1行目から順に素直に解釈していきましょう。

  • 1行目は、localとついているので(ローカル)変数xを定義していますね。
  • 2行目でそのローカル変数xを使った計算結果を画面に表示します。
  • 4行目で変数xの値を1に変えます
  • 5行目でそのローカル変数xを使った計算結果を画面に表示します。

このプログラムの実行結果は次のように画面に表示されます。

8
6

変数を使ってプログラムを改善する

はい、長々とお話しました。

当初の目的は、歩数の値を二箇所も書き換えるのがめんどうなので、変数を使うことで少し楽ができるようにしようというお話でした。

以下に結論となるコードを紹介します。

----------------------------------
-- yuka2: 床にブロックを設置する(1往復)
----------------------------------
local hosuu = 6

-- 地面に置いたタートルでは床にブロック置けないので、浮かせる
turtle.up()

-- 床にブロックを敷き詰めながら6歩分前に進む
for i=1,hosuu do
  turtle.placeDown()
  turtle.forward()
end

-- 右の列に移動
turtle.turnRight()
turtle.forward()

-- 右の列のスタート地点に移動。こちら側を向いている。
turtle.turnRight()
turtle.forward()

-- 右の列に床ブロックを敷き詰めながら戻ってくる
for i=1,hosuu do
  turtle.placeDown()
  turtle.forward()
end

注目して欲しいのは、4行目のlocal hosuu = 6ですね。変数hosuuをコードの先頭で定義しておき、その変数hosuuを以降の繰り返しfor文の終端値として使っています。

そのため、6歩から10歩に変えたいのなら、local hosuu = 6local hosuu = 10のように書き換えるだけで完了となります。

コードの先頭の数字を変えるだけOKなので、ちょっと楽になりましたね。

このように、プログラムの挙動を変える値(たとえば今回の、移動する歩数など)のことをパラメーターと呼ぶ場合があります。このようなパラメーターは、使用する際に書き換えることが多いので、たいていはコードの先頭に書いておくことが多いです。

それを明示的にするため、私は次のような書き方をします。どうですか?コード自体は長くなったけど、みやすくなっていません?

----------------------------------
-- yuka2: 床にブロックを設置する(1往復)
----------------------------------

------- Config -------
local hosuu = 6

------- Main -------
-- 地面に置いたタートルでは床にブロック置けないので、浮かせる
turtle.up()

-- 床にブロックを敷き詰めながら6歩分前に進む
for i=1,hosuu do
  turtle.placeDown()
  turtle.forward()
end

-- 右の列に移動
turtle.turnRight()
turtle.forward()

-- 右の列のスタート地点に移動。こちら側を向いている。
turtle.turnRight()
turtle.forward()

-- 右の列に床ブロックを敷き詰めながら戻ってくる
for i=1,hosuu do
  turtle.placeDown()
  turtle.forward()
end

つまり、------- Config -------以降はパラメータを設定する部分です。挙動を変えるときは書き換えてくださいね。 ------- Main -------以降はプログラムのメイン部分ね。これ以降を書き換えるときは気をつけて。

今後もこの方針でサンプルコードを紹介していきますのでご承知ください。

f:id:hevohevo:20191002131603p:plain
床にブロック設置するプログラム

次回のお話

使うたびにコードを二箇所も書き換えることに辟易としていたhevohevoは、変数を使う方式に書き換えることで少し楽になるのだった。だが人間の欲望とは尽きないもの。

「毎回書き換えるの面倒になりますた(^p^)」

このようなめんどくさがりやがプログラミングチュートリアルを完結できるのか。次の更新日はいつにしよう。チュートリアルなのに文章多すぎかも。

次回「コマンドライン引数の使い方」 乞うご期待。


  1. すでにお気づきかもしれませんが、ComputerCraft内のeditコマンド(CC内エディタ)ではコピー&ペーストの機能が制限されています。これはCCの作者であるDan200氏による意図的なものです。おそらく、既存のコードを思考停止状態でコピー&ペーストするのでなくできるだけ考えながら自分でプログラミングして欲しいというDan200氏の思想(願い)でしょう。まぁ私たちは外部エディタを使うのでこの制限は意味がないのですけどね。しかし、Dan200氏の願いであろう「考えながら自分でプログラミング」については完全に同意します。このページを読んでる皆さんもがんばろう!

  2. 「xは3です。x+5はいくつになるでしょうか?」など

  3. 「(x+3)2+2(x+3)+1=0 の方程式で(x+3)をAとおくと、A2+2A+1=0となります。そうすると・・・(以下略)」

  4. 数学の途中計算で、変数名としてx/y/z/X/Y/Zだけでは物足りなくなって、□や△のような記号使ったことあります?僕はあります。出来心で「(x+3)を♂とする」と小テストでやらかしたけどしっかりとマルをもらえました。それをみた友人には笑われたけど。

  5. ちなみに、2つの値が等しいかどうかを調べるときは、Luaを含むたいていのプログラミング言語はx == 3という表記を使います(==を等号演算子と呼びます)。うん。小学校でこれ教えると絶対混乱すると思う。プログラミング言語によってはこのあたりの問題点に対応するためか、変数の定義にlocal x := 3のような表記が使える場合もあります。