room_carver.lua

355 lines · 14.6 KB

Open raw

Room Carver (lean) Start position: place turtle at the room's bottom-left corner, on floor level, facing into the room. Goal: carve a rectangular room using efficient reach (minimal vertical moves), place torches, and auto-trash matching items when full. ]]-- local version = "2.2" -- UI helpers (minimal) local function ask_number_default(prompt, default_value, min_val, max_val) while true do term.clear(); term.setCursorPos(1,1) write("Room Carver v"..version.."\n\n") write(string.format("%s [default: %s] ", prompt, tostring(default_value))) local s = read(); if s == nil or s == "" then return default_value end local n = tonumber(s) if n and (not min_val or n >= min_val) and (not max_val or n <= max_val) then return n end write("\nInvalid input. Press Enter to try again."); read() end end local function ask_yes_no(prompt, default_yes) while true do term.clear(); term.setCursorPos(1,1) write("Room Carver v"..version.."\n\n") local def = default_yes and "Y" or "N" write(string.format("%s (y/n) [default: %s] ", prompt, def)) local s = read(); s = s and string.lower(s) or "" if s == "" then return default_yes end if s == "y" or s == "yes" then return true end if s == "n" or s == "no" then return false end

Copy & run

wget https://perlytiara.github.io/turtles.tips/raw/programs/perlytiara/room_carver.lua
1--[[
2 Room Carver (lean)
3 Start position: place turtle at the room's bottom-left corner, on floor level, facing into the room.
4 Goal: carve a rectangular room using efficient reach (minimal vertical moves), place torches, and auto-trash matching items when full.
5]]--
6
7local version = "2.2"
8
9-- UI helpers (minimal)
10local function ask_number_default(prompt, default_value, min_val, max_val)
11 while true do
12 term.clear(); term.setCursorPos(1,1)
13 write("Room Carver v"..version.."\n\n")
14 write(string.format("%s [default: %s] ", prompt, tostring(default_value)))
15 local s = read(); if s == nil or s == "" then return default_value end
16 local n = tonumber(s)
17 if n and (not min_val or n >= min_val) and (not max_val or n <= max_val) then return n end
18 write("\nInvalid input. Press Enter to try again."); read()
19 end
20end
21local function ask_yes_no(prompt, default_yes)
22 while true do
23 term.clear(); term.setCursorPos(1,1)
24 write("Room Carver v"..version.."\n\n")
25 local def = default_yes and "Y" or "N"
26 write(string.format("%s (y/n) [default: %s] ", prompt, def))
27 local s = read(); s = s and string.lower(s) or ""
28 if s == "" then return default_yes end
29 if s == "y" or s == "yes" then return true end
30 if s == "n" or s == "no" then return false end
31 write("\nInvalid input. Press Enter to try again."); read()
32 end
33end
34
35-- Movement/helpers
36local current_fuel_slot = 2
37local heading_index = 0 -- 0:+Z, 1:+X, 2:-Z, 3:-X
38local function turn_left() turtle.turnLeft(); heading_index = (heading_index + 3) % 4 end
39local function turn_right() turtle.turnRight(); heading_index = (heading_index + 1) % 4 end
40local function face_dir(target)
41 local diff = (target - heading_index) % 4
42 if diff == 1 then turn_right()
43 elseif diff == 2 then turn_right(); turn_right()
44 elseif diff == 3 then turn_left() end
45end
46local function ensure_fuel(minimum)
47 if turtle.getFuelLevel()=="unlimited" then return end
48 if turtle.getFuelLevel()>=minimum then return end
49 -- Prefer configured fuel slot first
50 if current_fuel_slot and current_fuel_slot>=1 and current_fuel_slot<=16 then
51 turtle.select(current_fuel_slot)
52 if turtle.refuel(0) then while turtle.getFuelLevel()<minimum and turtle.refuel(1) do end end
53 if turtle.getFuelLevel()>=minimum then return end
54 end
55 -- Fallback: try all slots
56 for i=1,16 do turtle.select(i); if turtle.refuel(0) then while turtle.getFuelLevel()<minimum and turtle.refuel(1) do end; if turtle.getFuelLevel()>=minimum then return end end end
57 term.clear(); term.setCursorPos(1,1); print("Out of fuel. Add fuel and press Enter."); read(); return ensure_fuel(minimum)
58end
59local function dig_forward() while turtle.detect() do if not turtle.dig() then turtle.attack(); sleep(0.05) end end end
60local function dig_up() while turtle.detectUp() do if not turtle.digUp() then turtle.attackUp(); sleep(0.05) end end end
61local function dig_down() while turtle.detectDown() do if not turtle.digDown() then turtle.attackDown(); sleep(0.05) end end end
62local function safe_forward() ensure_fuel(100); while not turtle.forward() do dig_forward(); sleep(0.02) end end
63local function safe_up() ensure_fuel(100); while not turtle.up() do dig_up(); sleep(0.02) end end
64local function safe_down() ensure_fuel(100); while not turtle.down() do dig_down(); sleep(0.02) end end
65local function step_right() turn_right(); dig_forward(); safe_forward(); turn_left() end
66local function step_left() turn_left(); dig_forward(); safe_forward(); turn_right() end
67
68-- Inventory helpers
69local function empty_slots()
70 local e=0; for i=1,16 do if turtle.getItemCount(i)==0 then e=e+1 end end; return e
71end
72local function any_match(sample_slot)
73 if not sample_slot or turtle.getItemCount(sample_slot)==0 then return false end
74 for i=1,16 do if i~=sample_slot and turtle.getItemCount(i)>0 then turtle.select(i); if turtle.compareTo(sample_slot) then return true end end end
75 return false
76end
77local function drop_matching_front(sample_slot)
78 if not sample_slot or turtle.getItemCount(sample_slot)==0 then return end
79 for i=1,16 do
80 if i~=sample_slot and turtle.getItemCount(i)>0 then
81 turtle.select(i)
82 if turtle.compareTo(sample_slot) then dig_forward(); turtle.drop() end
83 end
84 end
85end
86
87-- Persistent resume state
88local function state_path()
89 local dir = ".room_carver"; if not fs.exists(dir) then fs.makeDir(dir) end; return fs.combine(dir, "state")
90end
91local function save_state(st)
92 st.heading_index = heading_index
93 local p = state_path()
94 local h = fs.open(p, "w"); if not h then return end
95 h.write(textutils.serialize(st))
96 h.close()
97end
98local function load_state()
99 local p = state_path(); if not fs.exists(p) then return nil end
100 local h = fs.open(p, "r"); if not h then return nil end
101 local d = h.readAll(); h.close()
102 local ok, t = pcall(textutils.unserialize, d)
103 if ok and type(t)=="table" then return t end
104 return nil
105end
106local function clear_state()
107 local p = state_path(); if fs.exists(p) then fs.delete(p) end
108end
109
110-- Torch placement on side wall we are adjacent to (left if at x==1, right if at x==width)
111local function place_torch_on_wall(torch_slot, on_left)
112 if torch_slot<=0 or turtle.getItemCount(torch_slot)==0 then return end
113 turtle.select(torch_slot)
114 if on_left then turn_left() else turn_right() end
115 local ok=turtle.place()
116 if not ok then dig_forward(); ok=turtle.place() end
117 if on_left then turn_right() else turn_left() end
118end
119
120-- Traverse helpers
121local function lawn_step_side(to_right)
122 if to_right then step_right() else step_left() end
123end
124local function advance_row()
125 -- Ensure we move along current forward Z direction
126 dig_forward(); safe_forward()
127end
128
129-- Inventory + IO helpers for capacity and chest dump
130local function ensure_capacity(sample_slot)
131 if sample_slot and empty_slots()==0 and any_match(sample_slot) then drop_matching_front(sample_slot) end
132end
133local function turn_to_side(side) if side=="left" then turn_left() else turn_right() end end
134local function place_chest_in_wall(chest_slot, side)
135 if chest_slot<=0 or turtle.getItemCount(chest_slot)==0 then return false end
136 turn_to_side(side)
137 dig_forward()
138 turtle.select(chest_slot)
139 local ok=turtle.place()
140 if not ok then dig_forward(); ok=turtle.place() end
141 turn_to_side(side=="left" and "right" or "left")
142 return ok
143end
144local function deposit_into_front(reserved_slots)
145 for i=1,16 do
146 if not reserved_slots[i] and turtle.getItemCount(i)>0 then
147 turtle.select(i)
148 if not turtle.refuel(0) then turtle.drop() end
149 end
150 end
151end
152local function ensure_inventory_capacity(cfg, at_left)
153 if empty_slots()>0 then return end
154 -- First try throwing matches
155 if cfg.use_throw and any_match(cfg.throw_slot) then drop_matching_front(cfg.throw_slot); if empty_slots()>0 then return end end
156 -- Then try chests
157 if cfg.use_chests and turtle.getItemCount(cfg.chest_slot)>0 then
158 local side = at_left and "left" or "right"
159 if place_chest_in_wall(cfg.chest_slot, side) then
160 -- Face chest now; deposit everything except reserved slots
161 turn_to_side(side)
162 local reserved = {}
163 reserved[cfg.fuel_slot]=true; reserved[cfg.chest_slot]=true; reserved[cfg.torch_slot]=true; reserved[cfg.throw_slot]=true
164 deposit_into_front(reserved)
165 -- Keep exactly 1 item in throw sample slot if present
166 if cfg.use_throw and turtle.getItemCount(cfg.throw_slot)>1 then
167 turtle.select(cfg.throw_slot); turtle.drop(turtle.getItemCount(cfg.throw_slot)-1)
168 end
169 turn_to_side(side=="left" and "right" or "left")
170 end
171 end
172end
173
174-- Optimized row drilling: turn once per row, traverse along X, turn back to +Z
175local function advance_row_z(z_forward)
176 face_dir(z_forward and 0 or 2)
177 dig_forward(); safe_forward()
178end
179
180-- Returns whether we ended on the right edge
181local function line_snake_traverse(width, depth, per_cell_fn, on_row_start, start_from_right, z_forward)
182 local going_right = not start_from_right
183 for zi=1,depth do
184 local row_from_front = z_forward and zi or (depth - zi + 1)
185 if on_row_start then on_row_start(row_from_front, going_right) end
186 -- Face along X for this row
187 face_dir(going_right and 1 or 3) -- 1:+X, 3:-X
188 for x=1,width do
189 per_cell_fn(x, row_from_front, going_right)
190 if x<width then dig_forward(); safe_forward() end
191 end
192 -- Advance to next row along Z
193 if zi<depth then advance_row_z(z_forward) end
194 going_right = not going_right
195 end
196 -- Determine ending edge: after depth rows, if depth is odd we end opposite of start edge
197 local ended_right = (depth % 2 == 0) and start_from_right or (not start_from_right)
198 return ended_right
199end
200
201-- Multi-band carve to support arbitrary height
202local function carve_room(width, depth, height, cfg)
203 if height < 2 then height = 2 end
204 local bands
205 if height == 2 then bands = 1 else bands = math.ceil((height-1)/2) end
206 -- Move to working level for first band
207 if height >= 3 then safe_up() end
208 local start_from_right = cfg.resume_start_from_right or false -- band 1 starts at left edge by default
209 local z_forward = (cfg.resume_z_forward==false) and false or true -- default forward
210 local start_band = cfg.resume_band or 1
211 local start_row = cfg.resume_row or 1
212 for b=start_band,bands do
213 local skip_up = (height >= 3) and (b == bands) and ((2*b + 1) > height)
214 local function per_cell_fn(x, z, going_right)
215 -- Keep inventory flowing
216 ensure_inventory_capacity(cfg, going_right)
217 -- Clear vertical for this cell using reach
218 if height == 2 then
219 dig_up()
220 else
221 dig_down(); if not skip_up then dig_up() end
222 end
223 end
224 local function on_row_start(z, going_right)
225 -- Capacity check at row start
226 ensure_inventory_capacity(cfg, going_right)
227 -- Torches only on first band
228 if b==1 and cfg.use_torches and cfg.torch_spacing>0 and (z % cfg.torch_spacing == 0) then
229 place_torch_on_wall(cfg.torch_slot, going_right)
230 end
231 -- Fuel guard: small rolling reserve based on remaining rows and columns
232 local remaining_rows = z_forward and (depth - z + 1) or (z)
233 local reserve = math.max(40, remaining_rows + width + 10)
234 ensure_fuel(reserve)
235 -- Persist state at row boundary
236 save_state({width=width, depth=depth, height=height, band=b, row=z, start_from_right=start_from_right, z_forward=z_forward})
237 end
238 -- Traverse this band from the correct corner and along proper Z direction
239 local depth_remaining = (b==start_band) and (depth - (start_row-1)) or depth
240 local ended_right = line_snake_traverse(width, depth_remaining, per_cell_fn, on_row_start, start_from_right, z_forward)
241 -- Move up to next band
242 if b < bands then safe_up(); safe_up() end
243 -- Prepare next band start: flip Z direction and set start edge to where we ended
244 start_from_right = ended_right
245 z_forward = not z_forward
246 start_row = 1
247 end
248 -- Clear state when finished
249 clear_state()
250end
251
252local function main()
253 term.clear(); term.setCursorPos(1,1)
254 print("Room Carver v"..version)
255 print("Start at bottom-left, on floor, facing into room.")
256
257 -- Favorites
258 local function favorite_path() local dir = ".room_carver"; if not fs.exists(dir) then fs.makeDir(dir) end; return fs.combine(dir, "favorite") end
259 local function load_favorite()
260 local p=favorite_path(); if not fs.exists(p) then return nil end
261 local h=fs.open(p,"r"); if not h then return nil end
262 local d=h.readAll(); h.close(); local ok,t=pcall(textutils.unserialize,d); if ok and type(t)=="table" then return t end; return nil
263 end
264 local function save_favorite(tbl)
265 local p=favorite_path(); local h=fs.open(p,"w"); if not h then return end; h.write(textutils.serialize(tbl)); h.close()
266 end
267
268 local fav = load_favorite()
269 local use_fav = fav and ask_yes_no("Use favorite saved config?", true) or false
270
271 local width, depth, height
272 local use_torches, torch_spacing, torch_slot
273 local use_throw, throw_slot
274 local use_chests, chest_slot
275 local fuel_slot
276
277 if use_fav then
278 width=fav.width or 9; depth=fav.depth or 9; height=fav.height or 3
279 use_torches = fav.use_torches~=false; torch_spacing=fav.torch_spacing or 9; torch_slot=fav.torch_slot or 1
280 use_throw = fav.use_throw~=false; throw_slot=fav.throw_slot or 4
281 use_chests = fav.use_chests~=false; chest_slot=fav.chest_slot or 3
282 fuel_slot = fav.fuel_slot or 2
283 else
284 width = ask_number_default("Room width (X):", 9, 1, 199)
285 depth = ask_number_default("Room depth (Z):", 9, 1, 199)
286 height = ask_number_default("Room height (>=2):", 3, 2, 32)
287 use_torches = ask_yes_no("Place torches?", true)
288 if use_torches then
289 torch_spacing = ask_number_default("Torch spacing (rows):", 9, 1, 64)
290 torch_slot = ask_number_default("Torch slot:", 1, 1, 16)
291 else
292 torch_spacing = 0; torch_slot = 1
293 end
294 use_throw = ask_yes_no("Auto-throw matching items when full?", true)
295 throw_slot = ask_number_default("Sample slot to match:", 4, 1, 16)
296 use_chests = ask_yes_no("Place chest to dump when full?", true)
297 chest_slot = ask_number_default("Chest slot:", 3, 1, 16)
298 fuel_slot = ask_number_default("Fuel slot:", 2, 1, 16)
299 if ask_yes_no("Save as favorite?", true) then
300 save_favorite({width=width,depth=depth,height=height,use_torches=use_torches,torch_spacing=torch_spacing,torch_slot=torch_slot,use_throw=use_throw,throw_slot=throw_slot,use_chests=use_chests,chest_slot=chest_slot,fuel_slot=fuel_slot})
301 end
302 end
303
304 current_fuel_slot = fuel_slot or 2
305
306 -- Attempt to resume from saved state if present; ask before using it
307 local st = load_state()
308 if st and st.width==width and st.depth==depth and st.height==height then
309 local prompt = string.format("Found saved progress for this size: band %d, row %d. Restore?", st.band or 1, st.row or 1)
310 local restore = ask_yes_no(prompt, true)
311 if not restore then
312 clear_state()
313 st = nil
314 end
315 end
316
317 term.clear(); term.setCursorPos(1,1)
318 print("Room Carver v"..version)
319 print("Size:", width, "x", depth, " height:", height)
320 if use_torches then print("Torches every", torch_spacing, "rows from slot", torch_slot) else print("Torches: off") end
321 print("Fuel slot:", current_fuel_slot)
322 print("Chest dump:", use_chests and ("slot "..(chest_slot or 3)) or "off")
323 print("Auto-throw:", use_throw and ("sample slot "..(throw_slot or 4)) or "off")
324 print("Press Enter to start...")
325 read()
326
327 -- Estimate minimal fuel and ensure (soft check; we now check per-row too)
328 local est_moves = (width*depth) * math.max(1, math.ceil((height-1)/2)) * 2 + depth + width
329 ensure_fuel(math.min(est_moves + 50, 500))
330
331 -- State (if any) was already handled above
332
333 -- Carve with full configuration
334 carve_room(width, depth, height, {
335 use_torches=use_torches,
336 torch_spacing=torch_spacing or 0,
337 torch_slot=torch_slot or 1,
338 use_throw=use_throw,
339 throw_slot=throw_slot or 4,
340 use_chests=use_chests,
341 chest_slot=chest_slot or 3,
342 fuel_slot=current_fuel_slot,
343 -- resume hints
344 resume_band = st and st.band or 1,
345 resume_row = st and st.row or 1,
346 resume_start_from_right = st and st.start_from_right or false,
347 resume_z_forward = st and (st.z_forward~=false)
348 })
349
350 term.clear(); term.setCursorPos(1,1)
351 print("Done. Room completed.")
352end
353
354main()
355