robathome icon

MC gridmaker

robathome | PRO | 11/12/25 07:31:41 PM UTC | 0 ⭐ | 253 👁️ | Never ⏰ | []
text |

10.78 KB

|

None

|

0 👍

/

0 👎

-- perimeter_grid.lua
-- CC:Tweaked turtle script
-- 1) Move forward until a wall, then right-hand-rule follow to map perimeter.
-- 2) After full loop, find a 9x9 square grid (10x10 nodes) fully inside polygon,
--    with the starting position centered in one grid square.
-- 3) Place blocks DOWN at each node, then return to start pose.
 ------------------------------
-- Pose & Movement Utilities --
------------------------------
local POS = {x=0, y=0, z=0, d=0} -- d: 0=N,1=E,2=S,3=W; x east+, z south+, y up+
local START = {x=0,y=0,z=0,d=0}
 local function dirToVec(d)
  if d==0 then return 0,0,-1
  elseif d==1 then return 1,0,0
  elseif d==2 then return 0,0,1
  else return -1,0,0 end
end
 local function updateForward()
  local dx,dy,dz = dirToVec(POS.d)
  POS.x, POS.y, POS.z = POS.x+dx, POS.y+dy, POS.z+dz
end
local function updateBack()
  local dx,dy,dz = dirToVec(POS.d)
  POS.x, POS.y, POS.z = POS.x-dx, POS.y-dy, POS.z-dz
end
 local function turnRight()
  turtle.turnRight()
  POS.d = (POS.d+1)%4
end
local function turnLeft()
  turtle.turnLeft()
  POS.d = (POS.d+3)%4
end
local function turnAround()
  turtle.turnRight(); turtle.turnRight()
  POS.d = (POS.d+2)%4
end
 local function forward()
  while not turtle.forward() do
    if turtle.detect() then return false end
    -- blocked by entity? try dig then forward
    turtle.dig()
    sleep(0.15)
  end
  updateForward()
  return true
end
 local function back()
  if turtle.back() then
    updateBack()
    return true
  else
    turnAround()
    local ok = forward()
    turnAround()
    return ok
  end
end
 local function up()
  while not turtle.up() do
    if turtle.detectUp() then turtle.digUp() end
    sleep(0.15)
  end
  POS.y = POS.y+1
end
 local function down()
  while not turtle.down() do
    if turtle.detectDown() then turtle.digDown() end
    sleep(0.15)
  end
  POS.y = POS.y-1
end
 local function faceDir(targetD)
  local diff = (targetD - POS.d) % 4
  if diff==1 then turnRight()
  elseif diff==2 then turnAround()
  elseif diff==3 then turnLeft() end
end
 local function gotoPose(x,y,z,d)
  -- Simple Manhattan navigator at single Y level first; will adjust Y as needed.
  -- Step 1: adjust Y
  while POS.y < y do up() end
  while POS.y > y do down() end
  -- Step 2: adjust X
  if POS.x ~= x then
    faceDir(POS.x < x and 1 or 3)
    while POS.x ~= x do assert(forward(), "Blocked en route (X).") end
  end
  -- Step 3: adjust Z
  if POS.z ~= z then
    faceDir(POS.z < z and 2 or 0)
    while POS.z ~= z do assert(forward(), "Blocked en route (Z).") end
  end
  -- Face desired direction
  if d~=nil then faceDir(d) end
end
 ------------------
-- Fuel Routine --
------------------
local function refuelMax()
  for i=1,16 do
    local detail = turtle.getItemDetail(i)
    if detail and turtle.refuel(0) then
      turtle.select(i)
      while turtle.refuel(1) do end
    end
  end
  turtle.select(1)
end
 -------------------------
-- Perimeter Following --
-------------------------
-- Right-hand rule helper: check relative blocks
local function lookForward() return turtle.detect() end
local function lookRight()
  turnRight()
  local ok = turtle.detect()
  turnLeft()
  return ok
end
local function lookLeft()
  turnLeft()
  local ok = turtle.detect()
  turnRight()
  return ok
end
 -- We assume a single-layer mapping at POS.y == START.y.
-- First: move forward until hit a wall to start hugging.
local function advanceUntilWall()
  while not turtle.detect() do
    if not forward() then break end
  end
end
 -- Right-hand rule step:
-- Prefer: turnRight & forward if open; else if forward open go; else if left open turnLeft & go; else turnAround & try.
local function rhsStep()
  -- try right
  turnRight()
  if not turtle.detect() then
    local ok = forward()
    if not ok then return false end
    return true
  else
    -- can't go right; face back forward
    turnLeft()
    if not turtle.detect() then
      local ok = forward()
      if not ok then return false end
      return true
    else
      -- try left
      turnLeft()
      if not turtle.detect() then
        local ok = forward()
        if not ok then return false end
        -- we are now facing left; keep that heading
        return true
      else
        -- dead end: turn around
        turnRight() -- return to original forward
        turnAround()
        if not turtle.detect() then
          local ok = forward()
          if not ok then return false end
          return true
        else
          -- completely boxed (rare)
          return false
        end
      end
    end
  end
end
 -- Follow the perimeter and record the 2D (x,z) vertex path at current y.
local function followPerimeterAndMap()
  local y0 = START.y
  -- Ensure we’re on the same plane
  while POS.y > y0 do down() end
  while POS.y < y0 do up() end
   advanceUntilWall()
   -- Start hugging with right-hand rule
  local path = {}  -- sequence of positions (x,z)
  local function rec()
    path[#path+1] = {x=POS.x, z=POS.z}
  end
  rec()
   local steps = 0
  local maxSteps = 20000
  local started = false
  local startSignature = string.format("%d,%d,%d", START.x, START.z, START.d)
   while steps < maxSteps do
    if not rhsStep() then
      error("Got stuck while following perimeter.")
    end
    steps = steps + 1
    rec()
     -- loop detection: returned to starting tile with starting heading, after moving at least some steps
    local sig = string.format("%d,%d,%d", POS.x, POS.z, POS.d)
    if sig == startSignature and steps > 10 then
      break
    end
  end
   if steps >= maxSteps then
    error("Perimeter too large or loop not detected within step budget.")
  end
   -- Simplify path by removing consecutive duplicates
  local simp = {}
  local last = nil
  for _,p in ipairs(path) do
    if not last or p.x~=last.x or p.z~=last.z then
      simp[#simp+1] = p
      last = p
    end
  end
   -- Close polygon if needed
  if simp[1].x ~= simp[#simp].x or simp[1].z ~= simp[#simp].z then
    simp[#simp+1] = {x=simp[1].x, z=simp[1].z}
  end
  return simp
end
 ---------------------------
-- Geometry / PIP / Grid --
---------------------------
local function pointInPolygon(px, pz, poly)
  -- Ray casting on XZ plane; polygon is { {x,z}, ... } closed
  local inside = false
  for i=1,#poly-1 do
    local x1,z1 = poly[i].x, poly[i].z
    local x2,z2 = poly[i+1].x, poly[i+1].z
    -- Check edges that cross horizontal ray to +X
    local zmin = math.min(z1,z2)
    local zmax = math.max(z1,z2)
    if (pz > zmin) and (pz <= zmax) and (z1 ~= z2) then
      local xint = (pz - z1) * (x2 - x1) / (z2 - z1) + x1
      if xint > px then inside = not inside end
    end
  end
  return inside
end
 local function bbox(poly)
  local xmin,xmax = math.huge,-math.huge
  local zmin,zmax = math.huge,-math.huge
  for _,p in ipairs(poly) do
    if p.x < xmin then xmin = p.x end
    if p.x > xmax then xmax = p.x end
    if p.z < zmin then zmin = p.z end
    if p.z > zmax then zmax = p.z end
  end
  return xmin,xmax,zmin,zmax
end
 -- Find a 9x9 grid (10x10 nodes) fully inside polygon, with start centered in a cell.
-- We define grid nodes at integer lattice points: (gx0 + i, gz0 + j) for i,j=0..9
-- The starting position must be the center of some grid square:
-- => start at (gx0+0.5 + c, gz0+0.5 + r) for some cell (c,r). We choose c=r=4 (middle-ish)
-- so enforce: START.x == gx0 + 4.5 and START.z == gz0 + 4.5  -> gx0 = START.x - 4.5, gz0 = START.z - 4.5
-- Then we try small integer offsets around that to fit the polygon.
local function findFittingGrid(poly)
  local targetSize = 9
  local nodes = targetSize + 1 -- 10
   -- base (floating) origin
  local baseGx0 = START.x - 4.5
  local baseGz0 = START.z - 4.5
   -- Try small integer shifts to keep all nodes inside polygon
  local search = {}
  local function push(dx,dz) search[#search+1] = {dx=dx,dz=dz} end
  for dz=-4,4 do
    for dx=-4,4 do
      push(dx,dz)
    end
  end
   for _,off in ipairs(search) do
    local gx0 = math.floor(baseGx0 + off.dx + 0.5)
    local gz0 = math.floor(baseGz0 + off.dz + 0.5)
     local allInside = true
    for j=0,targetSize do
      for i=0,targetSize do
        local nx, nz = gx0 + i, gz0 + j
        -- Require node strictly inside polygon (not on edge) for safety:
        if not pointInPolygon(nx + 1e-6, nz + 1e-6, poly) then
          allInside = false
          break
        end
      end
      if not allInside then break end
    end
    if allInside then
      return {gx0=gx0, gz0=gz0, nodes=nodes}
    end
  end
   return nil, "Could not fit 10x10 node lattice inside mapped perimeter near start."
end
 -------------------------
-- Block Placement API --
-------------------------
local function ensureBlockSelected()
  for i=1,16 do
    local d = turtle.getItemDetail(i)
    if d then turtle.select(i); return true end
  end
  return false
end
 local function placeDownAtNode(x,z,y)
  -- Go at y level to (x,z) and place down
  gotoPose(x,y,z,nil)
  if not turtle.detectDown() then
    -- ensure we have something to place
    if not ensureBlockSelected() then
      error("Out of blocks while placing grid nodes.")
    end
    turtle.placeDown()
  end
end
 -----------------------
-- Main Orchestration
-----------------------
local function main()
  print("Refueling to maximum...")
  refuelMax()
   print("Mapping perimeter with right-hand rule...")
  local poly = followPerimeterAndMap()
  print(("Perimeter mapped: %d vertices."):format(#poly))
   print("Searching for 9x9 grid (10x10 nodes) inside polygon...")
  local grid, err = findFittingGrid(poly)
  if not grid then
    printError(err)
    print("Returning to start pose.")
    gotoPose(START.x, START.y, START.z, START.d)
    return
  end
  print(("Grid origin (nodes): gx0=%d, gz0=%d"):format(grid.gx0, grid.gz0))
   -- Place nodes at y = START.y (down placement)
  local yLevel = START.y
  print("Placing blocks at grid nodes...")
  for j=0,grid.nodes-1 do
    -- Simple boustrophedon to reduce turning
    if j % 2 == 0 then
      for i=0,grid.nodes-1 do
        local nx, nz = grid.gx0 + i, grid.gz0 + j
        placeDownAtNode(nx, nz, yLevel)
      end
    else
      for i=grid.nodes-1,0,-1 do
        local nx, nz = grid.gx0 + i, grid.gz0 + j
        placeDownAtNode(nx, nz, yLevel)
      end
    end
  end
   print("Grid placement done. Returning to start...")
  gotoPose(START.x, START.y, START.z, START.d)
  print("Complete.")
end
 -----------------------
-- Execute
-----------------------
local ok, err = pcall(main)
if not ok then
  printError("Error: "..tostring(err))
  -- try to return to start safely
  pcall(function() gotoPose(START.x, START.y, START.z, START.d) end)
end
 

Comments