본문 바로가기

마인크래프트(Minecraft)/Computer Craft

[Computer Craft] [시연영상] 나무베기 터틀 1.5.2 이후

예전에 뼛가루 전성기 시절 1.4.7 포함 이전 버전에서는 뼛가루를 1개만 넣어주면 나무가 자라났기 때문에 뼛가루 판정을 하지 않아도 됬었죠.
그러나 1.5 이후에는 뼛가루가 너프 되면서 여러번 뿌려야하는 문제점에 봉착하게 됬습니다.
그래서 1.4.7 때 작성한 제 터틀 알고리즘이 쓸모가 없어지게 됬었죠.
그래서 다시 터틀을 알고리즘을 설계하고 조촐하게나마 만들어 봤습니다.
아직 약간의 문제점이 남아 있지만, 나무 베는데는 지장이 없을정도입니다.

문제점 : 뼛가루 사용시 상자에 뼛가루가 없을때 뼛가루를 찾아 빙글빙글 도는 현상 정작 나무가 자라나면 베어버린다는...
문제점2 : config 시스템 처리가 되어있지 않음. database 파일[아이템 슬롯위치, 뼛가루 사용 on/off 설정 정보]을직접 수정해야함. (귀찮아서 아직 작성안한 프로그래밍)
문제점3 :  상자위치가 정해져 있어 유저만의 커스텀 배치가 쉽지 않다. ( 추후 머리좀더 굴려서 해결 봐야겠네요. )

본론으로 들어가기에 앞서 파일 구조에 대해 간단히 설명하자면,
파일은 기본적으로 4개 구조로 나뉘어 있습니다.


1. startup
터틀이 처음 설치되거나 청크 리로드시 자동으로 수행하는 프로그램.
파일 작성하지 않아도 무방합니다.

2. database
터틀 프로그램 실행시 자동으로 생성되는 파일입니다.
처음 실행될때 파일 구조는 1 1 2 3 4 5 0 0 으로 저장되며, 숫자가 각각 의미하는 바는 아래와 같습니다. (순서는 왼쪽부터 설명합니다)
1 : 뼛가루 on/off 스위치 - 1 일때 on 이며 그외의 숫자는 보통 off 처리됩니다.
1 : 목재 슬롯위치 : 베어낼 나무의 슬롯을 정하는 곳입니다. 1로 되어 있으므로 첫번째 슬롯에 목재를 넣으면됩니다.
2 : 뼛가루 위치 뼛가루의 위치를 정하는곳입니다. 2로 되어 있으므로 두번쨰 슬롯에 넣으면 됩니다.
3 : 연료 위치를 정하는곳입니다. 세번째 슬롯이 기본값입니다.
4 : 묘목의 위치를 정하는곳입니다. 네번째 슬롯이 기본값입니다.
5 ; 상자위치를 정하는곳입니다. 터틀이 나무를 담아낼때 아래 상자가 없으면 자동으로 멈추게 하는 장치라 볼 수 있습니다.
0 : 터틀이 보는 방향입니다. (터틀기준 - 0: 나무위치 1:왼쪽상자 2: 뒤쪽상자 3: 우측상자)
0 : 터틀의 높이 입니다. 한칸 올라가면 1씩 증가하며 내려올떄마다 0씩 증가합니다. 악의적인 테러로 인해.. 1까지 쭉 내려가는것을 막기위한 장치라 볼수 있습니다.

3. lnclude.lua
터틀이 가져올 헤더 함수가 든 파일입니다.

4. main.lua
터틀 메인 알고리즘입니다. 실행기라고 보면됩니다.


파일 구조에 대한 간단한 소개를 했으니 그러면 시연 영상을 선 감상후. 후 소스를 보도록 하죠

시연영상




소스파일

1. startup

if turtle.getItemCount(1) > 0 then -- 첫번째 슬롯이 비었거나 처음 터틀이 설치 될때 메인 함수가 실행되는것을 막기위한 장치
  shell.run('main.lua') -- 첫번째 슬롯에 아이템이 있을경우 실행
else
  print("do not run lumberjack Sorry") -- 첫번째 슬롯에 아이템이 있을경우 메시지 출력
end


2. include.lua


function explode(div,str) -- 문자열을 나눠주는 함수 php의 explode를 구현하는것인데 만들기 귀찮아서 lua위키에서 가져왔음.
  if (div == '') then return false end
  local pos, arr = 0, {}
  for st,sp in function() return string.find(str,div,pos,true) end do
    table.insert(arr,string.sub(str,pos,st-1))
    pos = sp + 1
  end
  table.insert(arr,string.sub(str,pos))
  return arr
end

function fileread(name,patten) -- db에서 파일을 읽어오는 시스템
  if patten == nil then
    patten = 'r'
  end
  local filename = fs.open(name,patten)
  local str = filename.readAll()
  filename.close()
  return str
end

function dbupdate(dbname, dbTable) -- 바뀐 정보를 업데이트 시스템(database파일 값이 업데이트됨)
  dbfile = fs.open(dbname,'w')
  dbfile.write(tostring(dbTable['switch'])..' '..tostring(dbTable['slotWood'])..' '..tostring(dbTable['slotBone'])..' '..tostring(dbTable['slotFuel'])..' '..tostring(dbTable['slotSap'])..' '..tostring(dbTable['slotChest'])..' '..tostring(dbTable['degree'])..' '..tostring(dbTable['height']))
  dbfile.close()
end

function tError(code) -- 에러 메시지 출력을 위한 용도
  if code == 1 then print("Stop : not enough fuel item")
  elseif code == 2 then print("Stop : cannot find chest")
  end
end

function tMove(command) -- 터틀 이동 제어 함수
  if command == 'forward' then return turtle.forward()
  elseif command == 'back' then return turtle.back()
  elseif command == 'up' then return turtle.up()
  elseif command == 'down' then return turtle.down()
  else return false end
end

function tTurn(command) -- 터틀 방향 제어 함수
  if command == 'left' then return turtle.turnLeft()
  elseif command == 'right' then return turtle.turnRight()
  else return false end
end

function tAttack(command) -- 터틀 공격 제어 함수
  if command == 'front' then return turtle.attack()
  elseif command == 'up' then return turtle.attackUp()
  elseif command == 'down' then return turtle.attackDown()
  else return false end
end

function tDig(command) -- 터틀 채취 제어 함수
  if command == 'front' then return turtle.dig()
  elseif command == 'up' then return turtle.digUp()
  elseif command == 'down' then return turtle.digDown()
  else return false end
end

function tPlace(command) -- 터틀 블럭 놓기 제어 함수
  if command == 'front' then return turtle.place()
  elseif command == 'up' then return turtle.placeUp()
  elseif command == 'down' then return turtle.placeDown()
  else return false end
end

function tDetect(command) -- 터틀 아이템 감지 제어 함수
  if command == 'front' then return turtle.detect()
  elseif command == 'up' then return turtle.detectUp()
  elseif command == 'down' then return turtle.detectDown()
  else return false end
end

function tCompare(command) -- 터틀이 놓인 아이템과 현재 선택된 아이템과 비교 함수
  if command == 'front' then return turtle.compare()
  elseif command == 'up' then return turtle.compareUp()
  elseif command == 'down' then return turtle.compareDown()
  else return false end
end

function tDrop(command,count) -- 터틀이 아이템을 버리는 함수(상자가 있을떄는 넣습니다)
  if command == 'front' then return turtle.drop(count)
  elseif command == 'up' then return turtle.dropUp(count)
  elseif command == 'down' then return turtle.dropDown(count)
  else return false end
end

function tSuck(command) -- 터틀이 아이템을 줍는 함수(상자가 있을때는 1칸 단위씩 아이템을 가져옵니다)
  if command == 'front' then return turtle.suck()
  elseif command == 'up' then return turtle.suckUp()
  elseif command == 'down' then return turtle.suckDown()
  else return false end
end

function tCraft(count) -- 터틀이 조합해주는 함수
  if count > 0 then return turtle.craft(count)
  else return false end
end

function tCompareto(slot) -- 선택슬롯과 다른 슬롯과의 아이템 비교함수
  if slot >= 1 and slot <= 16 then return turtle.compareTo(slot)
  else return false end
end

function tSelect(slot) -- 터틀 아이템 슬롯 선택 함수
  if tonumber(slot) >= 1 and tonumber(slot) <= 16 then return turtle.select(tonumber(slot))
  else return false end
end

function tTransferTo(slot, count) -- 터틀 아이템 교환 함수
  if slot >= 1 and slot <= 16 and count >= 1 and count <= 64 then return turtle.transferTo(slot, count)
  else return false end
end

function tItemCount(slot) -- 선택된 슬롯의 아이템 갯수 세어주는 함수
  if slot >= 1 and slot <= 16 then return turtle.getItemCount(slot)
  else return false end
end

function tItemSpace(slot) -- 선택된 슬롯의 남은 아이템을 세어주는 함수(최대스택64 일때 현재 가진수를 뺀 나머지 값)
  if slot >= 1 and slot <= 16 then return turtle.getItemSpace(slot)
  else return false end
  return result
end

function tRefuel(slot) -- 연료 충전함수
  tSelect(slot)
  turtle.refuel(1)
end

3. main.lua

local dbname = 'database' -- 지역변수 db이름을 database 로 결정
local tShutdown = 0 -- 지역변수 초기화 목적 0은 에러가 없다는 의미
shell.run('include.lua') -- 헤더 파일 읽어오기
if not fs.exists(dbname) then -- db 파일이 없을때 생성 있을때는 생략
  dbfile = fs.open(dbname,'w')
  dbfile.write('1 1 2 3 4 5 0 0')
  dbfile.close()
end
local str = fileread(dbname) -- db에서 파일데이터 불러오기
local db = explode(' ',str) -- db에서 파일데이터를 ' '스페이스바 기준으로 배열화 처리
local database = {
  ['switch']    = tonumber(db[1]),
  ['slotWood']  = tonumber(db[2]),
  ['slotBone']  = tonumber(db[3]),
  ['slotFuel']  = tonumber(db[4]),
  ['slotSap']   = tonumber(db[5]),
  ['slotChest'] = tonumber(db[6]),
  ['degree']    = tonumber(db[7]),
  ['height']    = tonumber(db[8])
} -- 배열화된 값을 알아보기 쉽게 커스텀 테이블로 정의

tSelect(database['slotWood']) -- 오류 미연 방지를 위한 나무 선택(기본값 : 1번슬롯)


while true do
--[[
터틀이 공중에 있는가를 체크하는 부분 공중에 있을때
공중에 있을때 연료 체크하고, 연료가 없으면 바로 정지
연료가 충분할때는 한칸씩 내려가는데, 뭔가가 가로 막으면 두가지 판정을 통해 달리 수행
1. 블럭이 막을때. 블럭을 부순다.
2. 유저나 몹이 길을 막아 못내려갈때. 공격 해서 밀어낸다.
]]--
  while database['height'] > 0 do
    if turtle.getFuelLevel() <= 0 then
      if tItemCount(database['slotFuel']) > 1 then
        tRefuel(database['slotFuel'])
        tSelect(database['slotWood'])
      else
        tError(1)
        break
      end
    end
  if tDetect('down') then tDig('down') end
  if tMove('down') then
    database['height'] = database['height'] -1
    dbupdate(dbname, database)
  else tAttack('down') end
end

--[[
터틀 높이가 0(원래 높이가 될떄)
상자 슬롯을 선택후 아래에 상자가 있는가를 체크
상자가 없으면 종료
상자가 있으면 1개를 뺀 나머지를 상자에 담아냄.
]]--
if database['height'] == 0 then
  tSelect(database['slotChest'])
  if tCompare('down') then
    tSelect(database['slotWood'])
    tDrop('down',(tItemCount(database['slotWood'])-1))
  else
    tError(2)
    break
  end
end

--[[
아이템 교환 시스템.
현재 뼛가루가 1개 남을때 흩어진 뼛가루 슬롯을 모아주는 역할
]]--
if tItemCount(database['slotBone']) == 1 then
  tSelect(database['slotBone'])
  for i=5,16 do
    if tCompareto(i) then
      tSelect(i)
      tTransferTo(database['slotBone'],tItemCount(i))
      break
    end
  end
end

--[[
아이템 교환 시스템.
현재 연료가 1개 남을때 흩어진 연료 슬롯을 모아주는 역할
]]--
if tItemCount(database['slotFuel']) == 1 then
  tSelect(database['slotFuel'])
  for i=5,16 do
    if tCompareto(i) then
      tSelect(i)
      tTransferTo(database['slotFuel'],tItemCount(i))
      break
    end
  end
end

--[[
아이템 교환 시스템.
현재 모묙이 1개 남을때 흩어진 연료 슬롯을 모아주는 역할
]]--
if tItemCount(database['slotSap']) == 1 then
  tSelect(database['slotSap'])
  for i=5,16 do
    if tCompareto(i) then
      tSelect(i)
      tTransferTo(database['slotSap'],tItemCount(i))
      break
    end
  end
end

--[[
아이템 충전 시스템
모아둔 모묙이 1개 남을때 상자에서 꺼내오기 처리
]]--
while tItemCount(database['slotSap']) == 1 do
  if database['degree'] == 0 then
    tTurn("left")
    database['degree'] = 1
    dbupdate(dbname, database)
  elseif database['degree'] == 1 then
    tSuck("front")
    break
  elseif database['degree'] == 2 then
    tTurn("right")
    database['degree'] = 1
    dbupdate(dbname, database)
  elseif database['degree'] == 3 then
    if math.random(0,1) > 0 then
      tTurn('left')
      database['degree'] = 0
      dbupdate(dbname, database)
    else
      tTurn('right')
      database['degree'] = 2
      dbupdate(dbname, database)
    end
  end
end

--[[
아이템 충전 시스템
모아둔 뼛가루가 1개 남을때 상자에서 꺼내오기 처리
]]--
if database['switch'] ~= 0 then
  while tItemCount(database['slotBone']) == 1 do
    if database['degree'] == 0 then
      if math.random(0,1) > 0 then
        tTurn('left')
        database['degree'] = 1
        dbupdate(dbname, database)
      else
        tTurn('right')
        database['degree'] = 3
        dbupdate(dbname, database)
      end
    elseif database['degree'] == 1 then
      tTurn("left")
      database['degree'] = 2
      dbupdate(dbname, database)
    elseif database['degree'] == 2 then
      tSuck("front")
      break
    elseif database['degree'] == 3 then
      tTurn("right")
      database['degree'] = 2
      dbupdate(dbname, database)
    end
  end
end

--[[
아이템 충전 시스템
모아둔 연료가 1개 남을때 상자에서 꺼내오기 처리
]]--
while tItemCount(database['slotFuel']) == 1 do
  if database['degree'] == 0 then
    tTurn("right")
    database['degree'] = 3
    dbupdate(dbname, database)
  elseif database['degree'] == 1 then
    if math.random(0,1) > 0 then
      tTurn('left')
      database['degree'] = 2
      dbupdate(dbname, database)
    else
      tTurn('right')
      database['degree'] = 0
      dbupdate(dbname, database)
    end
  elseif database['degree'] == 2 then
    tTurn("left")
    database['degree'] = 3
    dbupdate(dbname, database)
  elseif database['degree'] == 3 then
    tSuck("front")
    break
  end
end

--[[
아이템을 다 꺼내 왔는데 나무를 바라보지 않을때 나무를 바라보게끔 처리
]]--
while database['degree'] ~= 0 do
  if database['degree'] == 1 then
    tTurn("right")
    database['degree'] = 0
    dbupdate(dbname, database)
  elseif database['degree'] == 2 then
    if math.random(0,1) > 0 then
      tTurn("left")
      database['degree'] = 3
      dbupdate(dbname, database)
    else
      tTurn("right")
      database['degree'] = 1
      dbupdate(dbname, database)
    end
  elseif database['degree'] == 3 then
    tTurn("left")
    database['degree'] = 0
    dbupdate(dbname, database)
  end
end

--[[
아이템 비교 시스템
전방에 아이템이 감지될때와 그렇지 않을때를 각각 수행
1. 전방에 아이템이 감지될때
1-1 전방이 나무 목재와 일치하면, 나무베기 시작.
1-2 전방이 나무와 같지 않다면 뼛가루on/off 체크후 (뼛가루가 on 일떄 나무가 자라날떄까지 뼛가루를 뿌리기 시작

2. 전방에 아이템이 감지 안될때
묘목을 심는다. 이때 앞에 걸리적거리면 공격 하게 했으나. 묘목 특성상 걸리적거리는게 없다는게 함정
]]--
if tDetect("front") then
  tSelect(database['slotWood'])
  if tCompare("front") then
    while tCompare("front") do
      tDig("front")
      tDig("up")
    if turtle.getFuelLevel() <= 0 then
      if tItemCount(database['slotFuel']) > 1 then
        tRefuel(database['slotFuel'])
        tSelect(database['slotWood'])
      else
        tError(1)
        tShutdown = 1
    break
      end
    end
      if tShutdown == 1 then break end
      if tMove("up") then
        database['height'] = database['height'] + 1
        dbupdate(dbname, database)
      end
    end

    while not tDetect("down") and database['height'] > 0 do
    if turtle.getFuelLevel() <= 0 then
      if tItemCount(database['slotFuel']) > 1 then
        tRefuel(database['slotFuel'])
        tSelect(database['slotWood'])
      else
        tError(1)
        tShutdown = 1
    break
      end
    end
      if tMove("down") then
        database['height'] = database['height'] - 1
    dbupdate(dbname, database)
      else tAttack("down") end
    end
    if tShutdown == 1 then break end
  else
    if database['switch'] ~= 0 then
      tSelect(database['slotWood'])
      while not tCompare("front") do
        tSelect(database['slotWood'])
        if tCompare("front") or not tDetect("front") or tItemCount(database['slotBone']) == 1 then
          break
        end
    tSelect(database['slotBone'])
        tPlace("front")
      end
    end
  end
else
  tSelect(database['slotSap'])
  if not tPlace("front") then
    tAttack("front")
  end
end
end