sandbox bugfixes

master
ac-minetest 2021-06-28 08:49:33 +02:00
parent c3a021d1fc
commit 2020477700
21 changed files with 667 additions and 135 deletions

View File

@ -595,6 +595,19 @@ local write_keyevent = function(data,pos, puncher,type)
end
local button_punched = function(pos, node, player,type)
local name = player:get_player_name(); if name==nil then return end
local round = math.floor;
local r = basic_robot.radius; local ry = 2*r;
local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; -- just on top of basic_protect:protector!
local hppos = minetest.hash_node_position(ppos)
local rname = basic_robot.data.punchareas[hppos];
local data = basic_robot.data[rname];
if data then
write_keyevent(data,pos, player:get_player_name(),type)
end
end
local register_robot_button = function(R,G,B,type)
minetest.register_node("basic_robot:button"..R..G..B,
@ -605,20 +618,8 @@ local register_robot_button = function(R,G,B,type)
wield_image = "robot_button.png^[colorize:#"..R..G..B..":180",
is_ground_content = false,
groups = {cracky=3},
on_punch = function(pos, node, player)
local name = player:get_player_name(); if name==nil then return end
local round = math.floor;
local r = basic_robot.radius; local ry = 2*r; -- note: this is skyblock adjusted
local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; -- just on top of basic_protect:protector!
local meta = minetest.get_meta(ppos);
local name = meta:get_string("name");
local data = basic_robot.data[name];
if data then
write_keyevent(data,pos, player:get_player_name(),type)
end
end
groups = {cracky=3,not_in_craft_guide = 1},
on_punch = function(pos, node,player) button_punched(pos, node,player,type) end
})
end
@ -632,19 +633,8 @@ minetest.register_node("basic_robot:button"..number,
paramtype2 = "facedir",
is_ground_content = false,
groups = {cracky=3},
on_punch = function(pos, node, player)
local name = player:get_player_name(); if name==nil then return end
local round = math.floor;
local r = basic_robot.radius; local ry = 2*r;
local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r};
local meta = minetest.get_meta(ppos);
local name = meta:get_string("name");
local data = basic_robot.data[name];
if data then
write_keyevent(data,pos, player:get_player_name(),type)
end
end
groups = {cracky=3,not_in_craft_guide = 1},
on_punch = function(pos, node,player) button_punched(pos, node,player,type) end
})
end
@ -657,21 +647,9 @@ minetest.register_node("basic_robot:button_"..number,
inventory_image = string.format("%03d",number).. ".png",
wield_image = string.format("%03d",number).. ".png",
is_ground_content = false,
groups = {cracky=3},
groups = {cracky=3,not_in_craft_guide = 1},
paramtype2 = "facedir",
on_punch = function(pos, node, player)
local name = player:get_player_name(); if name==nil then return end
local round = math.floor;
local r = basic_robot.radius; local ry = 2*r;
local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r};
local meta = minetest.get_meta(ppos);
local name = meta:get_string("name");
local data = basic_robot.data[name];
if data then
write_keyevent(data,pos, player:get_player_name(),type)
--data.keyboard = {x=pos.x,y=pos.y,z=pos.z, puncher = player:get_player_name(), type = type}
end
end
on_punch = function(pos, node,player) button_punched(pos, node,player,type) end
})
end
@ -683,20 +661,8 @@ minetest.register_node("basic_robot:button_"..number,
inventory_image = texture .. ".png",
wield_image = texture .. ".png",
is_ground_content = false,
groups = {cracky=3},
on_punch = function(pos, node, player)
local name = player:get_player_name(); if name==nil then return end
local round = math.floor;
local r = basic_robot.radius; local ry = 2*r;
local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r};
local meta = minetest.get_meta(ppos);
local name = meta:get_string("name");
local data = basic_robot.data[name];
if data then
write_keyevent(data,pos, player:get_player_name(),type)
--data.keyboard = {x=pos.x,y=pos.y,z=pos.z, puncher = player:get_player_name(), type = number}
end
end
groups = {cracky=3,not_in_craft_guide = 1},
on_punch = function(pos, node,player) button_punched(pos, node,player,type) end
})
end

View File

@ -1,4 +1,4 @@
-- basic_robot by rnd, 2016
-- basic_robot by rnd, 2016-2021
basic_robot = {};
@ -26,7 +26,7 @@ basic_robot.bad_inventory_blocks = { -- disallow taking from these nodes invento
basic_robot.http_api = minetest.request_http_api();
basic_robot.version = "2021/03/02a";
basic_robot.version = "2021/06/28a";
basic_robot.gui = {}; local robogui = basic_robot.gui -- gui management
basic_robot.data = {}; -- stores all robot related data
@ -40,6 +40,7 @@ basic_robot.ids = {}; -- stores maxid for each player
basic_robot.virtual_players = {}; -- this way robot can interact with the world as "player" TODO
basic_robot.data.listening = {}; -- which robots listen to chat
basic_robot.data.punchareas = {}; -- where robots listen punch events, [hashes of 32 sized chunk] = robot name
dofile(minetest.get_modpath("basic_robot").."/robogui.lua") -- gui stuff
dofile(minetest.get_modpath("basic_robot").."/commands.lua")
@ -63,6 +64,7 @@ function getSandboxEnv (name)
if not basic_robot.data[name].rom then basic_robot.data[name].rom = {} end -- create rom if not yet existing
local env =
{
_Gerror = error,
pcall=pcall,
robot_version = function() return basic_robot.version end,
@ -113,7 +115,7 @@ function getSandboxEnv (name)
obj:set_animation({x=anim_start,y=anim_end}, anim_speed, anim_stand_start)
end,
listen = function (mode)
listen = function (mode) -- will robot listen to chat?
if mode == 1 then
basic_robot.data.listening[name] = true
else
@ -121,6 +123,24 @@ function getSandboxEnv (name)
end
end,
listen_punch = function(pos, is_remove) -- robot will listen to punch events in 32 sized chunk containing pos
local round = math.floor;
local r = basic_robot.radius; local ry = 2*r; -- note: this is skyblock adjusted
if not pos then return end
local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; -- just on top of basic_protect:protector!
local hppos = minetest.hash_node_position(ppos)
local rname = basic_robot.data.punchareas[hppos];
if is_remove then -- remove listener
basic_robot.data.punchareas[hppos] = nil
return
end
if rname then -- area already registered for listening
return rname
end
basic_robot.data.punchareas[hppos] = name; -- register robot name
end,
listen_msg = function()
local msg = basic_robot.data[name].listen_msg;
local speaker = basic_robot.data[name].listen_speaker;
@ -153,7 +173,7 @@ function getSandboxEnv (name)
reset = function()
local pos = basic_robot.data[name].spawnpos;
local obj = basic_robot.data[name].obj;
obj:setpos({x=pos.x,y=pos.y+1,z=pos.z}); obj:setyaw(0);
obj:set_pos({x=pos.x,y=pos.y+1,z=pos.z}); obj:setyaw(0);
end,
set_libpos = function(pos)
@ -575,7 +595,7 @@ end
check_code = function(code)
--"while ", "for ", "do ","goto ",
local bad_code = {"repeat", "until", "_c_", "_G", "while%(", "while{", "pcall","%.%.[^%.]"} --,"\\\"", "%[=*%[","--[["}
local bad_code = {"repeat", "until", "_G", "while%(", "while{", "pcall","%.%.[^%.]"} --,"\\\"", "%[=*%[","--[["}
for _, v in pairs(bad_code) do
if string.find(code, v) then
return v .. " is not allowed!";
@ -666,8 +686,8 @@ preprocess_code = function(script, call_limit) -- version 07/24/2018
script = script:gsub("%-%-%[%[.*%-%-%]%]",""):gsub("%-%-[^\n]*\n","\n") -- strip comments
-- process script to insert call counter in every function
local _increase_ccounter = " _c_ = _c_ + 1; if _c_ > " .. call_limit ..
" then _G.error(\"Execution count \".. _c_ .. \" exceeded ".. call_limit .. "\") end; "
local _increase_ccounter = " _Gc = _Gc + 1; if _Gc > " .. call_limit ..
" then _Gerror(\"Execution count \".. _Gc .. \" exceeded ".. call_limit .. "\") end; "
local i1=0; local i2 = 0;
local found = true;
@ -726,7 +746,7 @@ preprocess_code = function(script, call_limit) -- version 07/24/2018
-- must reset ccounter when paused, but user should not be able to force reset by modifying pause!
-- (suggestion about 'pause' by Kimapr, 09/26/2019)
return "_c_ = 0 local _pause_ = pause pause = function() _c_ = 0; _pause_() end " .. script;
return "_Gc = 0 local _Gpause = pause pause = function() _Gc = 0; _Gpause() end " .. script;
--return script:gsub("pause%(%)", "_c_ = 0; pause()") -- reset ccounter at pause
end
@ -843,15 +863,18 @@ local robot_spawner_update_form = function (pos, mode)
form =
"size[9.5,8]" .. -- width, height
"textarea[1.25,-0.25;8.75,10.25;code;;".. code.."]"..
"style_type[textarea;font_size=12;font=mono;bgcolor=#000000;textcolor=#00FF00;border=false]"..
"style_type[button;font_size=14;font=mono;bgcolor=#000000;border=false]"..
"style_type[button_exit;font_size=14;font=mono;bgcolor=#000000;border=false]"..
"textarea[1.25,-0.25;8.8,10.25;code;;".. code.."]"..
"button[-0.15,7.5;1.25,1;EDIT;EDIT]"..
"button[-0.15,-0.25;1.25,1;OK;"..minetest.colorize("yellow","SAVE").."]"..
"button_exit[-0.15, 0.75;1.25,1;spawn;"..minetest.colorize("green","START").."]"..
"button[-0.15, 1.75;1.25,1;despawn;"..minetest.colorize("red","STOP").."]"..
"field[0.15,3.;1.2,1;id;id;"..id.."]"..
"button[-0.15, 3.6;1.25,1;inventory;storage]"..
"button[-0.15, 4.6;1.25,1;library;library]"..
"button[-0.15, 5.6;1.25,1;help;help]";
"button[-0.15, 3.6;1.25,1;inventory;STORAGE]"..
"button[-0.15, 4.6;1.25,1;library;LIBRARY]"..
"button[-0.15, 5.6;1.25,1;help;HELP]";
else -- when robot clicked
form =
@ -1531,9 +1554,9 @@ minetest.register_on_player_receive_fields(
local title,text = basic_robot.commands.read_book(itemstack);
title = title or ""; text = text or "";
local dtitle = minetest.formspec_escape(title);
local form = "size [8,8] textarea[0.,0.;8.75,8.5;book; TITLE : " .. minetest.formspec_escape(title) .. ";" ..
local form = "size [8,8] textarea[0.,0.15;8.75,8.35;book; TITLE : " .. minetest.formspec_escape(title) .. ";" ..
minetest.formspec_escape(text) .. "] button_exit[-0.25,7.5;1.25,1;OK;SAVE] "..
"button_exit[1.,7.5;2.75,1;LOAD;USE AS PROGRAM] field[4,8;4.5,0.5;title;title;"..dtitle.."]";
"button_exit[0.9,7.5;3,1;LOAD;USE AS PROGRAM] field[4,8;4.5,0.5;title;title;"..dtitle.."]";
minetest.show_formspec(player:get_player_name(), "robot_book_".. sel.. ":".. minetest.pos_to_string(libpos), form);
end
@ -1746,6 +1769,11 @@ minetest.register_node("basic_robot:spawner", {
}
},
effector = {
action_on = spawn_robot,
action_off = despawn_robot
},
on_receive_fields = on_receive_robot_form,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
@ -1836,9 +1864,10 @@ minetest.register_craftitem("basic_robot:control", {
local pos = pointed_thing.under
if not pos then return end
local ppos = {x=round(pos.x/r+0.5)*r,y=round(pos.y/ry+0.5)*ry+1,z=round(pos.z/r+0.5)*r}; -- just on top of basic_protect:protector!
local meta = minetest.get_meta(ppos);
local name = meta:get_string("name");
local data = basic_robot.data[name];
local hppos = minetest.hash_node_position(ppos)
local rname = basic_robot.data.punchareas[hppos];
local data = basic_robot.data[rname];
if data then data.keyboard = {x=pos.x,y=pos.y,z=pos.z, puncher = owner, type = 0} end
return
end

View File

@ -244,12 +244,14 @@ local help_pages = {
["KEYBOARD AND USER INTERACTIONS"] = {
"back to [Commands reference]",
"KEYBOARD","",
" EVENTS : place spawner at coordinates (r*i,2*r*j+1,r*k) to monitor",
" events. value of r is ".. basic_robot.radius,
" EVENTS : first attach listener to robot with self.listen_punch",
" self.listen_punch(pos,is_remove) robot will listen to punch events in ",
" ".. basic_robot.radius .. " sized chunk containing position",
" pos = {x=.., y=.., z=..}. if is_remove==true then remove listener",
" keyboard.get() returns table {x=..,y=..,z=..,puncher = .. , type = .. }",
" for keyboard event",
" keyboard.set(pos,type) set key at pos of type 0=air,1-6,7-15,16-271,",
" limited to range 10 around spawner",
" if there was keyboard event, nil if there was none",
" keyboard.set(pos,type) set key as a node in world at pos of type",
" 0=air,1-6,7-15,16-271, limited to range 10 around spawner",
" keyboard.read(pos) return node name at pos",
},

View File

@ -15,6 +15,7 @@ if not data then
state = SIGNUP
t0 = _G.minetest.get_gametime();spawnpos = self.spawnpos() -- place mines
self.listen_punch(self.pos()); -- attach punch listener
data = {};
init_game = function()

View File

@ -2,14 +2,16 @@
--https://en.wikipedia.org/wiki/Black_Box_(game)
if not data then
m=16;n=16;
atoms = 32
-- novice: 8x8, 4
m=8;n=8;
atoms = 8
attempts = 1;turn = 0;
spawnpos = self.spawnpos(); spawnpos.x = spawnpos.x-m/2; spawnpos.y = spawnpos.y+2; spawnpos.z = spawnpos.z-n/2
local players = find_player(5,spawnpos);
if not player then self.remove() else pname = players[1] end
self.listen_punch(self.pos()) -- attach punch listener
self.spam(1);t0 = _G.minetest.get_gametime();
data = {};
for i = 1,m do data[i]={}; for j = 1,n do data[i][j]=0 end end

View File

@ -1,6 +1,7 @@
--checkers by rnd, 1.5 hr
if not init then init=true
spos = self.spawnpos()
self.listen_punch(self.pos()) -- attach punch listener
sizex = 8; sizez= 8
gamepieces = {
@ -81,7 +82,10 @@ if not init then init=true
build_game()
punchpos = nil; -- pos of last punched piece
step = 0;
self.label("checkers\npunch piece then punch board to move")
self.label("checkers\npunch piece then punch board to move\n"..
"RULES\n\n1. move only diagonal and forward. capture pieces by jumping over them.\nif you can capture you must\n"..
"2. once you reach end of board you get king. it can move backward too"
)
end
event = keyboard.get()

View File

@ -4,6 +4,7 @@ if not data then
-- m=3;n=3;turn = 0; num = 3;
self.spam(1);t0 = _G.minetest.get_gametime();
spawnpos = self.spawnpos() -- place mines
self.listen_punch(self.pos()) -- attach punch listener
state = 0; -- 0 signup 1 game
players = {};
data = {};

View File

@ -0,0 +1,64 @@
--cyberpunk 2077 'breach protocol puzzle' generator
--by rnd, 20 min
if not init then init = true
n=4; -- size of square
steps = n*n; -- length of sequence
tries = n*10; -- how many tries in current row/col before giving up
tb = {};
for i = 1,n do
tb[i] ={}; local tbi = tb[i]
for j = 1,n do
tbi[j] = (i-1)*n+j
end
end
--make random path col/row/col... starting at random position
row = true;
posi = 1; -- row
posj = 1; -- col
path = {}
used = {}; -- [num] = true, when taken
for i = 1, steps do
if row then
local tmp = posj;
local s = 0
while (tmp == posj or used[tb[posi][tmp]]) and s < tries do
tmp = math.random(n);
s=s+1
end
if s == tries then say("stuck at lenght " .. #path) break end
posj = tmp
else
local tmp = posi;
local s = 0
while (tmp == posi or used[tb[tmp][posj]]) and s < tries do
tmp = math.random(n);
s=s+1
end
if s == tries then say("stuck at lenght " .. #path) break end
posi = tmp
end
row = not row
path[#path+1] = tb[posi][posj];
used[path[#path]] = true
end
local ret = {};
for i = 1,n do
for j = 1,n do
ret[#ret+1] = string.format("%02d",(i-1)*n+j).." ";
end
ret[#ret+1] = "\n"
end
self.label(table.concat(path," ") .. "\n\n"..table.concat(ret))
end

View File

@ -1,6 +1,7 @@
--go by rnd
if not init then init=true
spos = self.spawnpos()
self.listen_punch(self.pos()) -- attach punch listener
sizex = 9; sizez = 9
gamepieces = {

View File

@ -19,6 +19,7 @@ if not init then
punchstate = 1; -- first punch
punchpos = {}
pos = self.spawnpos()
self.listen_punch(self.pos()) -- attach punch listener
dice = 0
spawns = {
{2,2,1,2,2,"basic_robot:buttonFF8080"}, -- xstart,zstart,ystart, dimx, dimz, nodename
@ -61,11 +62,12 @@ if not init then
local idx = msgs[1] or 1;
msgs[idx+1] = text;idx = idx+1; if idx>5 then idx = 1 end msgs[1] = idx
end
show_msgs = function() -- last message on top
local out = {}; local idx = msgs[1] or 1;
for i = idx,2,-1 do out[#out+1] = msgs[i] or "" end
for i = 6, idx+1,-1 do out[#out+1] = msgs[i] or "" end
self.label(table.concat(out,"\n"))
show_msgs = function()
local out = {};
local idx = msgs[1] or 1;
for i = idx,2,-1 do out[#out+1] = msgs[i] or "" end
for i = 6, idx+1,-1 do out[#out+1] = msgs[i] or "" end
self.label(table.concat(out,"\n"))
end
end

View File

@ -8,6 +8,8 @@ if not data then
self.spam(1)
t0 = _G.minetest.get_gametime();
data = {}; spawnpos = self.spawnpos() -- place mines
self.listen_punch(self.pos()); -- attach punch listener
for i = 1, minescount do local i = math.random(m); local j = math.random(n); if not data[i] then data[i] = {} end; data[i][j] = 1; end
if not data[1] then data[1] = {} end if not data[2] then data[2] = {} end -- create 2x2 safe area
data[1][1] = 0;data[1][2] = 0;data[2][1] = 0;data[2][2] = 0;

View File

@ -2,9 +2,10 @@
-- INIT
if not grid then
n=6 -- size
n=6
solved = false -- do we render solution or blank?
-- _G.math.randomseed(3)
self.spam(1)
@ -20,7 +21,7 @@ if not grid then
end
_,scores_string = book.read(1); scores = minetest.deserialize(scores_string)
if not scores then scores = init_score(5,5,-999) end -- 5 levels, 5 top records, smaller time is better (thats why - in top 5, there largest value counts)
if not scores then scores = init_score(5,5,-999) end -- 5 levels, 5 top records
t0 = _G.minetest.get_gametime()
local intro ="numbers at beginning of each row (coloumn) tell how many\nred blocks are together in each row ( coloumn )." ..
@ -168,6 +169,11 @@ if not grid then
if not players then error("nonogram: no players near") end
local pname = players[1];
self.listen_punch(self.pos()) -- attach punch listener
--self.label()
--self.label(string.gsub(_G.dump(read_field()),"\n","") )
difficulty = get_difficulty()
reward = 0; limit = 0;

View File

@ -71,7 +71,8 @@ if not init then
create_board(size)
render_board()
self.listen_punch(self.pos()) -- attach punch listener
end
event = keyboard.get();

View File

@ -34,6 +34,7 @@
self.spam(1)
self.listen_punch(self.pos()); -- attach punch listener
sokoban.push_time = 0
sokoban.blocks = 0;sokoban.level = 0; sokoban.moves=0;
imax = 0; jmax = 0

View File

@ -79,6 +79,7 @@ render_lights(); render_switches(true)
self.label("GOAL OF GAME: punch buttons with numbers in correct order to turn all blocks to 0")
self.listen_punch(self.pos()) -- attach punch listener
--self.label(serialize(switches))
end

View File

@ -0,0 +1,35 @@
--ALL UNICODE by rnd, 2021
-- range to 32-1550, then 7400-10000
--cyrillic 0410-042F: 1040-1071
--cyrilic 0430-044F: 1072-1103
if not init then init = true
function utf8(decimal)
local bytemarkers = { {0x7FF,192}, {0xFFFF,224}, {0x1FFFFF,240} }
if decimal<128 then return string.char(decimal) end
local charbytes = {}
for bytes,vals in ipairs(bytemarkers) do
if decimal<=vals[1] then
for b=bytes+1,2,-1 do
local mod = decimal%64
decimal = (decimal-mod)/64
charbytes[b] = string.char(128+mod)
end
charbytes[1] = string.char(vals[2]+decimal)
break
end
end
return table.concat(charbytes)
end
ret= {"ALL UNICODE\n0 = "}
for i = 1550, 4000 do
if i%25==0 then ret[#ret+1] = "\n"..(i).. " = " end
ret[#ret+1] = utf8(i)
end
self.label(table.concat(ret))
end

View File

@ -0,0 +1,27 @@
-- coal maker mod idea in 30 minutes by rnd
-- build dirt box around 3x3x3 area filled with wood, remove one boundary wood (lower one) and start fire there. start robot then!
check_firebox = function(pos)
local p = minetest.find_node_near(pos, 5, "fire:basic_flame") -- locate fire nearby!
if not p or minetest.get_node(p).name ~= "fire:basic_flame" then say("light fire first!"); self.remove() end
d=3; -- inner size of box, area filled with wood
local dirs = {{-1,0,0},{1,0,0},{0,0,-1},{0,0,1}};local dir1,dir2; -- position of vertices on dirt box
for i = 1,#dirs do
local dir = dirs[i];
if minetest.get_node({x=p.x+d*dir[1],y=p.y+d*dir[2],z=p.z+d*dir[3]}).name == "default:dirt" and
minetest.get_node({x=p.x+(d-1)*dir[1],y=p.y+(d-1)*dir[2],z=p.z+(d-1)*dir[3]}).name == "default:wood" then dir2 = dirs[i]; break end
end
if not dir2 then say("error, place fire in correct place in correctly built dirt box!") self.remove() end
dir1 = {dir2[3], dir2[2], -dir2[1]};
local v1 = {x=p.x-(d-1)*dir1[1]-dir2[1],y=p.y-1,z=p.z-(d-1)*dir1[3]-dir2[3]}
local v2 = {x=p.x+(d-1)*dir1[1]+(d)*dir2[1],y=p.y+d,z=p.z+(d-1)*dir1[3]+(d)*dir2[3]}
local res = minetest.find_nodes_in_area(v1,v2,{"default:wood","default:dirt"},true);
if (#(res["default:dirt"] or {})) == 97 and #(res["default:wood"] or {})==26 then
say("all ok. making charcoal now!")
minetest.swap_node(p,{name = "air"}) -- turn off fire!
else say("fail! check that you built dirt box/wood correctly!")
end
end
check_firebox(self.pos())
self.remove()

View File

@ -0,0 +1,187 @@
-- "natural language" programming demo by rnd, 2021
-- input is lines of 'natural' language, will be translated into lua code
--[[
move forward 3
turn left
dig left
if see dirt turn left and move forward
subroutine circle commands..
--> TRANSFORMED into lua :
for i = 1,3 do move.forward(); pause(); end
turn.left(); pause();
dig.left(); pause()
if read_node.forward()=="dirt" then turn.left();pause(); move.forward(); pause() end
TODO: integrate into robots, maybe command: code.natural(..)
--]]
if not init then init = true
prog = [[
subroutine walk
if see air move forward and move down
if see dirt move up
if see wood turn right
subroutine end
walk
]]
subroutines = {}; -- [subname] = true , so we know its subroutine
nodenames = -- translation of block names into minetest
{
["dirt"] = "default:dirt",
["cobble"] = "default:cobble",
["stone"] = "default:stone",
["wood"] = "default:wood",
["water"] = "default:water_source",
}
keywords = {
["quit"] = function() return "break;" end,
["move"] = {
["forward"] = function() return "if move.forward() then pause();paused=true; end;" end,
["backward"] = function() return "if move.backward() then pause();paused=true; end;" end,
["left"] = function() return "if move.left() then pause();paused=true; end;" end,
["right"] = function() return "if move.right() then pause();paused=true; end;" end,
["up"] = function() return "if move.up() then pause();paused=true; end;" end,
["down"] = function() return "if move.down() then pause();paused=true; end;" end,
},
["turn"] = {
["left"] = function() return "turn.left(); pause();paused=true;" end,
["right"] = function() return "turn.right(); pause();paused=true;" end,
["random"] = function() return "if math.random(2)==1 then turn.right() else turn.left() end; pause(); paused=true;" end
},
["dig"] = function() return "dig.forward(); pause();" end, --TODO: remember to set robot energy to large value at start
["place"] = function(line)
local pattern1 = "place";
local i = string.find(line,pattern1)+ string.len(pattern1)+1
local nodename = string.sub(line, i) -- what are we placing?
if nodenames[nodename] then nodename = nodenames[nodename] end -- translate name
return "place.forward('" .. nodename .. "'); pause();"
end,
["if"] = {
["see"] = function(line)
local pattern1 = "see";
local pattern2 = "and" .. " "; -- important, space after 'and'
local i = string.find(line,pattern1) + string.len(pattern1) + 1
--nodename command
local j = string.find(line," ",i)
local nodename, command
nodename = string.sub(line,i,j-1)
if nodenames[nodename] then nodename = nodenames[nodename] end -- translate name
-- maybe command has several parts separated by 'and' ?
--cmd1 and cmd2 and cmd3
j = j+1; local cmds = {}; local found = false
while true do
local k = string.find(line,pattern2,j+1)
if not k then-- no more AND
if found then
cmds[#cmds+1] = string.sub(line,j + string.len(pattern2)) break
else
cmds[#cmds+1] = string.sub(line,j) break
end
end
if found then
cmds[#cmds+1] = string.sub(line,j+string.len(pattern2),k-1)
else
cmds[#cmds+1] = string.sub(line,j,k-1)
end
found = true
j=k
end
for i = 1,#cmds do
cmds[i] = parse_line(cmds[i])
end
return "if read_node.forward()=='" ..nodename .. "' then " .. table.concat(cmds," ") .. " end"
end,
},
}
parse_line = function(line)
local struct = keywords;
for word in string.gmatch(line,"%S+") do
local matched = struct[word]
local issub = subroutines[word]
if matched or issub then
--say(word .. " = " .. type(matched))
if type(matched) == "table" then
struct = matched; -- climb deeper into structure
else
local instruction;
if issub then
instruction = word.."();"
else
instruction = matched(line)
end
-- do we have need to repeat instruction?
local i = string.find(line,word) + string.len(word) + 1
local snum = tonumber(string.sub(line,i)) or 1 -- repeating?
if snum>1 and snum<10 then
return "for i = 1,"..snum .." do " .. instruction .. " end;"
end
return instruction
end
else
say("error in line: " .. line .. ", unknown command " .. word) return ""
end
end
end
parse_prog = function(code)
local out = {};
local subdef = false; -- are we defining subroutine?
local subname;
local subcmds = {}
local pattern1 = "subroutine"
for line in string.gmatch(code,"[^\n]+") do -- line by line
local i = string.find(line,pattern1)
if i then -- do we define new subroutine command?
local j = i+string.len(pattern1)+1
local sname = string.sub(line,j)
if subdef and sname == "end" then -- end of subroutine
subdef = false
if not keywords[subname] then
out[#out+1 ] = "function " .. subname .. "()\n" .. table.concat(subcmds,"\n") .. "\nend"
subroutines[subname] = true;
else
-- error, subroutine name is reserved keyword
end
subcmds = {};
else
subdef = true -- all commands will now register with subroutine
subname = sname
end
else -- normal command
if subdef then
subcmds[#subcmds+1] = parse_line(line)
else
out[#out+1] = parse_line(line)
end
end
end
return "--coroutine NaturalLanguage autogenerated\nwhile true do paused = false; "..table.concat(out,"\n") .."if not paused then pause() end end"
end
parsed_prog = parse_prog(prog)
self.label(prog .. "\n\n==>\n\n" .. parsed_prog)
code.set(parsed_prog) -- actually run code by robot
end

View File

@ -0,0 +1,158 @@
-- Natural language compiler, outputs lua code
-- (C) rnd 2021
-- TODO: add IF: if condition arg1 ACTION1 else ACTION2 ?
-- ADD SUBROUTINE: sub NAME = enter subroutine definition mode, sub end = ends definition mode
if not init then init = true
dtext = {};
dout = function(text) dtext[#dtext+1] = text end
text =
[[
if cond1 value action1 and action2 and action3 else actiona1 and actiona2
]]
translate = { -- dictionary of used words
["forward"] = "forward",
["backward"] = "backward",
["left"] = "left",
["right"] = "right",
["random"] = "random",
["dirt"] = "default:dirt",
["wood"]= "default:wood",
["cobble"] = "default:cobble",
}
cmds = {
["if"] = function(code,ibeg,iend) -- if COND value ACTION1 and ACTION2 and ... ACTIONn else(optional) ACTION1 and ... and ACTIONm
-- COND: 'see' nodename (block right in front), 'var = value' value of variable?,...
local ELSE = " else"
local AND = " and"
local i,j,condtype,value;
condtype, j = get_next_word(code,ibeg,iend)
local out = {};
dout(condtype)
if condtype== "see" then
value, j = get_next_word(code,j,iend)
value = translate(value or "");
if not minetest.registered_nodename(value) then say("error: unknown block name " .. value .. "used in 'if'") return "" end
out[#out+1] = "if read_node.forward() == " .. value .. " then ";
else
say("error: unknown condition " .. condtype .. " used in 'if'") return "" end
end
-- now after j left: ACTION1 else(optional) ACTION2, ACTION can be multiple, separated by ' and '
-- parse and before else
local k = j;
lcoal cmds = {}
while k do
k = string.find(code,AND,j)
if k and k<ielse then
cmds[#cmds+1] = string.sub(code,j,k-1)
j=k+1;
else
break
end
end
dout(table.concat(cmds,"\n"))
--j=...
out[#out+1] = "end"
return table.concat(out," "),j
end,
["move"] = function(code,ibeg,iend)
-- move forward count
local i,direction,count
direction,i = get_next_word(code,ibeg,iend)
direction = translate[direction];
if not direction then say("error: unknown direction used in 'turn'") return "" end
return "move."..direction .."(); pause();",i;
end,
["turn"] = function(code,ibeg,iend)
-- move forward count
local i,direction,count
direction,i = get_next_word(code,ibeg,iend)
direction = translate[direction];
if not direction then say("error: unknown direction used in 'turn'") return "" end
local c;
if direction == "random" then
c = "if math.random(2) == 1 then turn.left() else turn.right() end;";
else
c = "turn."..direction .."();"
end
return c.." pause();",i
end,
["dig"] = function() return "dig.forward(); pause();" end,
["place"] = function(code,ibeg,iend)
-- place nodename
local nodename,i
nodename,i = get_next_word(code,ibeg,iend)
nodename = translate[nodename]
if not nodename then say("error: unknown nodename used in 'place'") return "" end
return "place.forward('" .. nodename .. "'); pause();",i
end,
}
-- given position ibeg in string find next word, return it and then return position immediately after word.
-- word is defined as a sequence of alphanumeric characters (%w)
-- example 'hello world', ibeg = 1. -> 'hello', 6
get_next_word = function(code, ibeg,iend) -- attempt to return next word, starting from position ibeg. returns word, index after word
if not ibeg or not iend then return end
local j = string.find(code,"%w",ibeg); -- where is start of word?
if not j or j>iend then return "", iend+1 end -- no words present
ibeg = j;
j = string.find(code,"%W",j);--where is end of word?
if not j or j>iend then return string.sub(code,ibeg,iend-1),iend+1 end
return string.sub(code,ibeg,j-1), j
end
parse_code = function(code)
local out = {};
local ibeg,iend,word;
local clen = string.len(code)
local step =0
iend = 1; ibeg = 1;
while step < 10 do
if ibeg>clen then break end
step = step+1
iend = string.find(code, "\n", ibeg)
if not iend then iend = clen end -- get out of loop, no more lines to process
word, ibeg = get_next_word(code,ibeg,iend)
--dout("rem " .. string.sub(code,ibeg,iend))
--dout("Dword '" .. word .. "' " .. ibeg .. " " .. iend)
local cmd = cmds[word];
if cmd then out[#out+1],ibeg = cmd(code,ibeg,iend) end
if not ibeg then ibeg = iend+1 end
if ibeg<=iend then -- still some space remaining in line, last parameter is repetition
local count,i
count,i = get_next_word(code,ibeg,iend);
count = tonumber(count) or 1
if count>9 then count = 9 elseif count<1 then count=1 end
if count > 1 then out[#out] = "for i=1,"..count.. " do " .. out[#out] .. " end" end
end
ibeg = iend +1 -- go new line
end
return table.concat(out,"\n")
end
self.label(parse_code(text) .. "\n\n" .. table.concat(dtext,"\n"))
end

View File

@ -0,0 +1,40 @@
-- given position ibeg in string find next word, return it and then return position immediately after word.
-- word is a sequence of alphanumeric characters
-- example 'hello world', ibeg = 1. -> 'hello', 6
get_next_word = function(code, ibeg,iend) -- attempt to return next word, starting from position ibeg. returns word, index after word
if not ibeg or not iend then return end
local j = string.find(code,"%w",ibeg); -- where is start of word?
if not j or j>iend then return "", iend+1 end -- no words present
ibeg = j;
j = string.find(code,"%W",j);--where is end of word?
if not j or j>iend then return string.sub(code,ibeg,iend-1),iend+1 end
return string.sub(code,ibeg,j-1), j
end
text = [[
hello world
today
day night
]]
ibeg = 1; iend = string.find(text,"\n",ibeg) or string.len(text) -- where is next new line
say("INIT LINE " .. ibeg .. " " .. iend .. " LINE '" .. string.sub(text,ibeg,iend-1) .."'")
for i = 1,10 do
word, ibeg = get_next_word(text,ibeg,iend)
say("word '" .. word.."', end " .. ibeg)
if ibeg>=iend then -- newline!
--say("newline")
local j = ibeg;
iend = string.find(text,"\n", iend+1) -- find next newline
if not iend then say("END") iend = string.len(text) break end -- end of text!
say("LINE " .. ibeg .. " " .. iend .. " LINE '" .. string.sub(text,ibeg,iend-1) .."'")
end
end
self.remove()

View File

@ -1,46 +1,48 @@
--rnd 2017
if not logdata then
self.label("chatlog bot");
_G.minetest.forceload_block(self.pos(),true)
n = 250;
idx = 1;
logdata = {};
insert = function(text) -- insert new message
idx = idx +1;
if idx > n then idx = 1 end
logdata[idx] = text;
end
last = function(k,filter) -- return last k messages
if k > n then k = 30 end
local i,j,ret;
i=idx;j=0; ret = ""
for j = 1,k do
if not logdata[i] then break end
if filter and not string.find(logdata[i], filter) then
else
ret = ret .. logdata[i] .. "\n";
end
i=i-1; if i < 1 then i = n end
end
return ret
end
self.listen(1)
end
speaker, msg = self.listen_msg()
if msg then
if string.sub(msg,1,4) == "?log" then
local j = string.find(msg," ",6);
local k = tonumber(string.sub(msg,6) or "") or n;
local text;
if j then text = last(k,string.sub(msg,j+1)) else text = last(k) end
local form = "size[8,8]".. "textarea[0.,0;11.,9.5;text;chatlog;".. text .. "]"
self.show_form(speaker, form)
else
insert(os.date("%X") .. " " .. speaker .. "> " .. msg)
end
end
--rnd 2017
if not logdata then
self.label("");--chatlog bot");
_G.minetest.forceload_block(self.pos(),true)
n = 500;
idx = 1;
authusers = {["rnd"] = 1} -- who is allowed to use ?log
logdata = {};
insert = function(text) -- insert new message
idx = idx +1;
if idx > n then idx = 1 end
logdata[idx] = text;
end
last = function(k,filter) -- return last k messages
if k > n then k = 30 end
local i,j,ret;
i=idx;j=0; ret = ""
for j = 1,k do
if not logdata[i] then break end
if filter and not string.find(logdata[i], filter) then
else
ret = ret .. logdata[i] .. "\n";
end
i=i-1; if i < 1 then i = n end
end
return ret
end
self.listen(1)
end
speaker, msg = self.listen_msg()
if msg then
if string.sub(msg,1,4) == "?log" and authusers[speaker] then
local j = string.find(msg," ",6);
local k = tonumber(string.sub(msg,6) or "") or n;
local text;
if j then text = last(k,string.sub(msg,j+1)) else text = last(k) end
local form = "size[8,8]".. "textarea[0.,0;11.,9.5;text;chatlog;".. minetest.formspec_escape(text) .. "]"
self.show_form(speaker, form)
else
insert(os.date("%X") .. " " .. speaker .. "> " .. msg)
end
end