ComputerCraftでシールド工法っぽいことをする

今更Minecraftをやっている。
自力で映える建築物を建てる気はなかったので、はじめから幻想的な地形になるamplifiedでプレイしているのだが、高低差があるせいかかなりの確率でブランチマイニング中に地下水にぶつかってしまう。~~そして対処しようとしてうっかり溺死する~~

もともとプログラミングの勉強を兼ねてスクリプトによる作業の自動化をしたかったので、これを期に[CC:Tweaked](https://tweaked.cc/)（ComputerCraftの派生）というModを導入し、シールド工法よろしく周囲を埋めながら穴を掘るスクリプトを書くことにした。

## やること

ComputerCraftでTurtleに以下のことをさせる。
- 指定した幅×高さの横穴をほる。
- 周りを任意のブロックで埋める。
    - 個人的には地形を生かしたい（というか自力で映える建築物を作るの面倒）ので、ガラスでやることを念頭に置いている。

## できないこと
- 有限である溶岩源の有効利用
    - 現状では壁で溶岩源をつぶしてしまう。
- トンネル内の液体ブロックの対処
    - 別途水抜きが必要
- たいまつの設置

## 作ったもの

デフォルトで存在するtunnel.luaをベースに、位置に応じて壁を埋める処理を追加したのが以下。

```lua:sheld.lua
if not turtle then
    printError("Requires a Turtle")
    return
end

local tArgs = { ... }
if #tArgs < 3  or #tArgs > 4 then
    local programName = arg[0] or fs.getName(shell.getRunningProgram())
    print("Usage: " .. programName .. " <material> <width> <height> [<length>]")
    return
end

local material = tostring(tArgs[1])

local function trySelectMaterial()
    local selectedItem = turtle.getItemDetail()
    if selectedItem ~= nil and selectedItem["name"] == material then
        return true
    else
        for i = 1, 12 do
            selectedItem = turtle.getItemDetail(i)
            if selectedItem ~= nil and selectedItem["name"] == material then
                turtle.select(i)
                print("Select slot: " .. tostring(i))
                return true
            end
        end
    end
    print("Material not found")
    return false
end

if not trySelectMaterial() then
    return
end

local width = tonumber(tArgs[2])
if width < 1 then
    print("Tunnel width must be positive")
    return
end

local height = tonumber(tArgs[3])
if height < 1 then
    print("Tunnel height must be positive")
    return
end
local length = 128;
if #tArgs == 4 then
    length = tonumber(tArgs[4])
    if length < 1 then
        print("Tunnel length must be positive")
        return
    end
end

local collected = 0

local function collect()
    collected = collected + 1
    if math.fmod(collected, 25) == 0 then
        print("Mined " .. collected .. " items.")
    end
end

local function tryDig()
    while turtle.detect() do
        if turtle.dig() then
            collect()
            sleep(0.1)
        else
            return false
        end
    end
    return true
end

local function tryDigUp()
    while turtle.detectUp() do
        if turtle.digUp() then
            collect()
            sleep(0.1)
        else
            return false
        end
    end
    return true
end

local function tryDigDown()
    while turtle.detectDown() do
        if turtle.digDown() then
            collect()
            sleep(0.1)
        else
            return false
        end
    end
    return true
end

local function tryPlace()
    trySelectMaterial()
    return turtle.place()
end

local function tryPlaceUp()
    trySelectMaterial()
    return turtle.placeUp()
end

local function tryPlaceDown()
    trySelectMaterial()
    return turtle.placeDown()
end

local function refuel()
    local fuelLevel = turtle.getFuelLevel()
    if fuelLevel == "unlimited" or fuelLevel > 0 then
        return
    end

    local function tryRefuel()
        for n = 1, 16 do
            if turtle.getItemCount(n) > 0 then
                turtle.select(n)
                if turtle.refuel(1) then
                    turtle.select(1)
                    return true
                end
            end
        end
        turtle.select(1)
        return false
    end

    if not tryRefuel() then
        print("Add more fuel to continue.")
        while not tryRefuel() do
            os.pullEvent("turtle_inventory")
        end
        print("Resuming Tunnel.")
    end
end

local function tryUp()
    refuel()
    while not turtle.up() do
        if turtle.detectUp() then
            if not tryDigUp() then
                return false
            end
        elseif turtle.attackUp() then
            collect()
        else
            sleep(0.5)
        end
    end
    return true
end

local function tryDown()
    refuel()
    while not turtle.down() do
        if turtle.detectDown() then
            if not tryDigDown() then
                return false
            end
        elseif turtle.attackDown() then
            collect()
        else
            sleep(0.5)
        end
    end
    return true
end

local function tryForward()
    refuel()
    while not turtle.forward() do
        if turtle.detect() then
            if not tryDig() then
                return false
            end
        elseif turtle.attack() then
            collect()
        else
            sleep(0.5)
        end
    end
    return true
end

local x = 1;
local y = 1;
local z = 1;
local i = 1
local j = 1;
local baseX = 1
local baseY = 1

local function isReverse()
  return (x%2) == 1
end

print("Tunnelling...")

local function tryPlaceShields()
  if x == 1 then
    turtle.turnLeft()
    tryPlace()
    turtle.turnRight()
  end
  if x == width then
    turtle.turnRight()
    tryPlace()
    turtle.turnLeft()
  end

  if y == 1 then
    tryPlaceDown()
  end
  if y == height then
    tryPlaceUp()
  end
end


local function tryMoveNextCell()
  if baseX == 1 then
    turtle.turnRight()
    tryForward()
    turtle.turnLeft()
    x = x + 1
  elseif baseX == width then
    turtle.turnLeft()
    tryForward()
    turtle.turnRight()
    x = x -1
  else
    print("Error wrong baseX")
  end 
end

local function tryMoveNextRow()
  if baseY == 1 then
    tryUp()
    y = y + 1
  elseif baseY == height then
    tryDown()
    y = y - 1
  else
    print("Error wrong baseY")
  end
end


local function tryMoveNext()
  local j = i % (width * height)
  if j == 0 then
    tryForward()
    z = z + 1
    baseX = x
    baseY = y
  elseif j % width == 0 then
    tryMoveNextRow()
    baseX = x
  else
    tryMoveNextCell()
  end
end

while z < length do
  tryPlaceShields()
  tryMoveNext()
  i = i + 1
end

print("Tunnel complete.")
print("Mined " .. collected .. " items total.")

```

## 結果

以下でガラスで壁を埋めつつ4×4のトンネルをほる。

```
shield.lua minecraft:grass 4 4 128
```

水中にトンネルを通した様子が以下

![2024-01-13_08.42.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/400984/06d9ab10-d59b-b557-58d5-d4ae337d1b23.png)


- 画像は水抜きをしたあと。
- 穴が空いているのは水草があってブロックを置けなかった箇所。
- 水抜きは必要だが、周囲を埋めることで作業中の水没の危険性は大幅に減らせた。
- 左右の端で壁を確認する都合上、幅１マスだと相対的に振り向く時間ロスが大きい  
  マイニング目当てでやるなら、幅をもっと広げたほうが効率がいいかもしれない。
- 単純に掘るだけなら人の手でやったほうが早い。
- タートルは小さいのでシールドマシン感は薄い。速度の向上も兼ねて複数のタートルを並べて連動させると風情もあっていいかもしれない。