diff --git a/commands.lua b/commands.lua index 5d2595d..4d095fa 100644 --- a/commands.lua +++ b/commands.lua @@ -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 diff --git a/init.lua b/init.lua index aeaae4b..cc8a395 100644 --- a/init.lua +++ b/init.lua @@ -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 diff --git a/robogui.lua b/robogui.lua index a015c5f..2c2f11b 100644 --- a/robogui.lua +++ b/robogui.lua @@ -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", }, diff --git a/scripts/games/battle_minesweeper_game.lua b/scripts/games/battle_minesweeper_game.lua index 3066e29..6397d3c 100644 --- a/scripts/games/battle_minesweeper_game.lua +++ b/scripts/games/battle_minesweeper_game.lua @@ -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() diff --git a/scripts/games/blackbox_game.lua b/scripts/games/blackbox_game.lua index 9ac9753..fe20678 100644 --- a/scripts/games/blackbox_game.lua +++ b/scripts/games/blackbox_game.lua @@ -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 diff --git a/scripts/games/checkers.lua b/scripts/games/checkers.lua index 4125441..5daad71 100644 --- a/scripts/games/checkers.lua +++ b/scripts/games/checkers.lua @@ -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() diff --git a/scripts/games/connect4.lua b/scripts/games/connect4.lua index ae4c22a..415e7d8 100644 --- a/scripts/games/connect4.lua +++ b/scripts/games/connect4.lua @@ -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 = {}; diff --git a/scripts/games/cyberpunk_2077_puzzle_gen.lua b/scripts/games/cyberpunk_2077_puzzle_gen.lua new file mode 100644 index 0000000..e357caf --- /dev/null +++ b/scripts/games/cyberpunk_2077_puzzle_gen.lua @@ -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 + + diff --git a/scripts/games/go.lua b/scripts/games/go.lua index 9381a83..eb4eb3a 100644 --- a/scripts/games/go.lua +++ b/scripts/games/go.lua @@ -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 = { diff --git a/scripts/games/mensch_argere_dich_nicht.lua b/scripts/games/mensch_argere_dich_nicht.lua index d665c58..1a7db3a 100644 --- a/scripts/games/mensch_argere_dich_nicht.lua +++ b/scripts/games/mensch_argere_dich_nicht.lua @@ -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 diff --git a/scripts/games/minesweeper_game.lua b/scripts/games/minesweeper_game.lua index adcd27c..86f68f1 100644 --- a/scripts/games/minesweeper_game.lua +++ b/scripts/games/minesweeper_game.lua @@ -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; diff --git a/scripts/games/nonogram.lua b/scripts/games/nonogram.lua index 8605f81..688b00e 100644 --- a/scripts/games/nonogram.lua +++ b/scripts/games/nonogram.lua @@ -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; diff --git a/scripts/games/sliding_puzzle_game.lua b/scripts/games/sliding_puzzle_game.lua index 46f37a9..fd5142b 100644 --- a/scripts/games/sliding_puzzle_game.lua +++ b/scripts/games/sliding_puzzle_game.lua @@ -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(); diff --git a/scripts/games/sokoban_game.lua b/scripts/games/sokoban_game.lua index 09d2c94..c176f28 100644 --- a/scripts/games/sokoban_game.lua +++ b/scripts/games/sokoban_game.lua @@ -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 diff --git a/scripts/games/switching_game.lua b/scripts/games/switching_game.lua index 64c95d3..fa34051 100644 --- a/scripts/games/switching_game.lua +++ b/scripts/games/switching_game.lua @@ -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 diff --git a/scripts/misc/all_unicode_chars.lua b/scripts/misc/all_unicode_chars.lua new file mode 100644 index 0000000..4dfb6ab --- /dev/null +++ b/scripts/misc/all_unicode_chars.lua @@ -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 \ No newline at end of file diff --git a/scripts/misc/charcoal_maker.lua b/scripts/misc/charcoal_maker.lua new file mode 100644 index 0000000..fc8e411 --- /dev/null +++ b/scripts/misc/charcoal_maker.lua @@ -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() \ No newline at end of file diff --git a/scripts/programming/natural language.lua b/scripts/programming/natural language.lua new file mode 100644 index 0000000..5b2539f --- /dev/null +++ b/scripts/programming/natural language.lua @@ -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 \ No newline at end of file diff --git a/scripts/programming/natural language1.lua b/scripts/programming/natural language1.lua new file mode 100644 index 0000000..84192fd --- /dev/null +++ b/scripts/programming/natural language1.lua @@ -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 '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 \ No newline at end of file diff --git a/scripts/programming/parser - lines - words.lua b/scripts/programming/parser - lines - words.lua new file mode 100644 index 0000000..e18da72 --- /dev/null +++ b/scripts/programming/parser - lines - words.lua @@ -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() \ No newline at end of file diff --git a/scripts/utils/chatlog.lua b/scripts/utils/chatlog.lua index ee2eaa4..f55548e 100644 --- a/scripts/utils/chatlog.lua +++ b/scripts/utils/chatlog.lua @@ -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 \ No newline at end of file + --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 \ No newline at end of file