【Minecraft】タートルの一人で建築できるもん!【CCの道も一行から#2】

YouTube とニコニコ動画で投稿中の動画シリーズで、動画に収まりきらなかったこぼれ話や詳細解説を掲載しています。

動画はこちらから

作成したプログラム

clock.lua

簡単なデジタル時計のプログラム。

pastebin get qcGCwqXc build.luaでゲーム内からダウンロードできる。

コメント付き版

-- const

-- コマンドライン引数を取得
local args = {...}

-- 建築サイズを定義
local X_SIZE, Y_SIZE, Z_SIZE = 16, 4 * (args[1] or 1), 16

-- 使用ブロックを指定
local BLOCKS = {
    corner   = 'minecraft:polished_deepslate',
    wall     = 'minecraft:stone_bricks',
    floorEdge= 'minecraft:smooth_stone',
    floor    = 'minecraft:smooth_stone_slab',
    window   = 'minecraft:glass_pane',
}

-- 窓ガラスの位置を指定
local WINDOWS_I = {2, 3, 5, 6, 9, 10, 12, 13}


-- function

-- テーブルがある値を含むか
table.includes = function (t, x)
    for k, v in pairs(t) do
        if v == x then
            return true
        end
    end
    return false
end

-- 壁に窓をつけるか
function isWindow(i)
    -- i: 端を 1 として、端から何ブロック目か
    return table.includes(WINDOWS_I, i)
end

-- 建築家位置からの相対座標から、設置すべきブロックを返す
function getBlock(x, y, z)
    -- X軸・Z軸 に平行な壁面であるか
    local isEdgeX, isEdgeZ = x == 0 or x == X_SIZE - 1, z == 0 or z == Z_SIZE - 1
    
    -- X軸・Z軸 両方の壁面 ⇔ 四隅の柱
    if isEdgeX and isEdgeZ then
        return BLOCKS.corner
    -- 四ブロックごとに床を張る
    elseif y % 4 == 3 then
        if isEdgeX or isEdgeZ then
            return BLOCKS.floorEdge
        else
            return BLOCKS.floor
        end
    elseif isEdgeX or isEdgeZ then
        local i -- 端から何ブロック目か
        if isEdgeX then
            i = z
        else
            i = x
        end
        if y % 4 == 1 and isWindow(i) then
            return BLOCKS.window
        else
            return BLOCKS.wall
        end
    end
    return nil
end

-- タートルが持っているアイテムを数える
function countItems()
    local counts = {}
    local detail
    for slot = 1, 16 do
        detail = turtle.getItemDetail(slot)
        if detail ~= nil then
            counts[detail.name] = (counts[detail.name] or 0) + detail.count
        end
    end
    return counts
end

-- 必要な建材の量を数える
function countMaterials()
    local counts = {}
    for y = 0, Y_SIZE - 1 do
        for z = 0, Z_SIZE - 1 do
            for x = 0, X_SIZE - 1 do
                block = getBlock(x, y, z)
                if block ~= nil then
                    counts[block] = (counts[block] or 0) + 1
                end
            end
        end
    end
    return counts
end

-- 総移動回数を求める
function countMove()
    return (X_SIZE - 1) * 2 * Z_SIZE * Y_SIZE + (Z_SIZE - 1) * 2 * Y_SIZE + Y_SIZE
end

-- 建築を実行可能か確かめる
function canBuild()
    local itemCounts = countItems()
    local materials = countMaterials()
    
    -- すべての建材が必要数用意されているかをチェックする
    for name, needs in pairs(materials) do
        local extra = (itemCounts[name] or 0) - needs
        if extra < 0 then
            -- 何か一つでも足りなかったら、false を返す
            return false, ("%s need more %d."):format(name, -extra)
        end
    end

    local moveCount = countMove()
    local fuelLevel = turtle.getFuelLevel()
    if fuelLevel ~= 'unlimited' and moveCount > fuelLevel then
        -- 燃料が足りなければ、false を返す
        return false, ("need more fuel. (+%d)"):format(moveCount - fuelLevel)
    end
    return true
end

-- あるアイテムを選択する
function selectItem(name)
    for slot = 1, 16 do
        detail = turtle.getItemDetail(slot)
        if detail ~= nil then
            if detail.name == name then
                turtle.select(slot)
                return true
            end
        end
    end
    return false
end

-- 建築する
function build()
    for y = 0, Y_SIZE - 1 do
        turtle.up()
        for z = 0, Z_SIZE - 1 do
            for x = 0, X_SIZE - 1 do
                block = getBlock(x, y, z)
                if block ~= nil then
                    selectItem(block)
                    turtle.placeDown()
                end
                if x ~= X_SIZE - 1 then
                    turtle.forward()
                end
            end
            for i = 1, X_SIZE - 1 do
                turtle.back()
            end
            if z ~= Z_SIZE - 1 then
                turtle.turnRight()
                turtle.forward()
                turtle.turnLeft()
            end
        end
        turtle.turnLeft()
        for i = 1, Z_SIZE - 1 do
            turtle.forward()
        end
        turtle.turnRight()
    end
end

function main()
    local isPossible, reason = canBuild()

    -- 建築不可能なら理由を表示
    if not isPossible then
        print(reason)
        return false
    end

    print("OK")
    build()
    return true
end


-- main

main()
Lua
Expand

使い方

build [階数]で実行する。[階数]には 2 以下の非負整数、つまり 1 か 2 を入力する。(3 以上はタートルのインベントリが足らず、建築不可能である。getBlock(x, y, z)を書き換えることで、伸ばせるかもしれない。)省略すると、一階建てになる。フールプルーフ1は実装していない。

詳細解説

動画に登場した要素を詳しく解説します。

用語集

動画内に出てきた専門用語やアイテムなどの解説です。これ本当に専門用語か…?

ずんだもち

宮城県を中心とする、東北地方の郷土料理。四国めたん氏は東北ずん子・ずんだもんプロジェクトのキャラクターであるため作成した。

タートル

移動やブロックの設置が出来るようになったコンピューター。というかロボット。製作者曰く、その名前は LEGO の教育用ロボットに由来するらしい。

安息の地――メッカ――

元ネタは東北3姉妹の準公式4コマ「ずんちゃんといっしょ!」の第121話

コマンド

動画内で使用した、CC のコンピューターなどで実行可能なコマンドの解説です。

dance コマンド

タートルが踊る。それだけ。

refuel コマンド

タートルに燃料を補給する。かまど燃料1秒分で、1ブロック分移動できる。refuel allでタートルのインベントリ内すべての、refuel [個数]で指定個数分のアイテムを消費する。個数に0と入力することで、チャージ済み燃料の量の確認のみを行える。

プログラム関連

{}(テーブル)

配列とリストと連想配列とオブジェクトの性質を併せ持った型。添え字を書かずに生成すると配列のような振る舞いをし、明示的に添え字を与えると連想配列のような振る舞いをする。値へのアクセスは、[](各括弧)を使った一般的な記法のほかに、JavaScript のように添え字が文字列2のものに関しては.(ドット)でアクセスできる。

...(可変長引数)

...は可変長引数式を表し、今回の場合は{...}と記載することで、コマンドライン引数を順に要素に持つテーブルを生成している。単にコマンドライン引数にアクセスするには、グローバル変数argも使用可能である。

分割代入

細かく説明できないが、カンマで区切ることで一行のうちに複数の代入処理を書ける。

~=(不等価演算子)

二つのオペランドが等しくないとき、trueを返す演算子。ほかの言語では!=で表現されることも多い。

for

繰り返し変数の値を増減値分ずつ変化させながら、doendを繰り返す。増減値を省略すると、1ずつ変化する。

for 繰り返し変数 = 初期値, 終了値, 増減値 do
    繰り返す内容
end

for 繰り返し変数 = 初期値, 終了値 do
    繰り返す内容
end

ほかの一般的な言語に翻訳すると、以下のようなイメージになる。

for (繰り返し変数 = 初期値; 繰り返し変数 <= 終了値; 繰り返し変数 += 増減値) {
    繰り返す内容
}

for (繰り返し変数 = 初期値; 繰り返し変数 <= 終了値; 繰り返し変数++) {
    繰り返す内容
}

また、テーブルの要素を順に処理するには、for ... in文が有効である。

for 添え字, 値 in pairs(テーブル) do
    繰り返す内容
end

turtle.select(slot)

タートルの選択スロットをslotに変更する。左上が1番、その右が2番で、右下が16番。

turtle.getItemDetail([slot[, detailed]])

指定スロット(slot、省略時は選択中のスロット)のアイテムに関する情報を取得する。空の際はnilを、アイテムが取得できた際はtableを返す。

turtle.getFuelLevel()

現在チャージ済みの燃料の量を返す。

turtle.placeDown(text)

真下に選択中のスロットのブロックを設置する。一部右クリックで使用するアイテムもこれで使用する。正面や真上版のturtle.place()turtle.placeUp()も存在する。看板などを設置する際には、引数textの文字列が使用されるらしい(未検証)。

turtle.forward()/turtle.back()/turtle.up()/turtle.down()

それぞれの方向にタートルが移動する。障害物があると失敗する。

turtle.turnLeft()/turtle.turlRight()

それぞれの方向にタートルが回転する。

編集後記

このあたりから制作プログラムの規模が肥大化してきた気がする。あと、今思うとtable.includes()を用意するより、{[2] = true, [3] = true, ...}の方が賢い気がする。


  1. 誤った使い方や想定していない動作を回避するための工夫。
    例:内部圧力が高いままだと外せない圧力鍋の蓋 ↩︎
  2. 正確には、変数名として利用可能な文字列。当然、演算子として利用する記号を使った文字列などではこの方法は使えない。 ↩︎