-- 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