diff --git a/commands.lua b/commands.lua index 0da6d8c..8e4fc48 100644 --- a/commands.lua +++ b/commands.lua @@ -97,6 +97,29 @@ basic_robot.commands.dig = function(name,dir) local spos = obj:get_luaentity().spawnpos; local inv = minetest.get_meta(spos):get_inventory(); + + --require coal to dig + if nodename == "default:stone" and basic_robot.use_coal then + local meta = minetest.get_meta(spos); + local fuel = meta:get_int("fuel")-1; + if fuel<0 then -- attempt to refuel + local stack = ItemStack("default:coal_lump 10"); + if inv:contains_item("main", stack) then + meta:set_int("fuel",50) -- 50 digs with 10 coal + inv:remove_item("main", stack) + else + error("#OUT OF FUEL: please insert 10 coal lumps to dig") + basic_robot.data[name].obj:remove(); + basic_robot.data[name].obj=nil; + return + end + else + meta:set_int("fuel",fuel) + end + end + + + if not inv then return end --inv:add_item("main",ItemStack( nodename )); @@ -225,7 +248,6 @@ basic_robot.no_teleport_table = { ["robot"] = true, } --- BUG : doesnt return list! basic_robot.commands.pickup = function(r,name) @@ -285,7 +307,7 @@ basic_robot.commands.write_text = function(name,dir,text) return true end -basic_robot.commands.place = function(name,nodename, dir) +basic_robot.commands.place = function(name,nodename, param2,dir) local obj = basic_robot.data[name].obj; local pos = pos_in_dir(obj, dir) local luaent = obj:get_luaentity(); @@ -315,8 +337,12 @@ basic_robot.commands.place = function(name,nodename, dir) if placename then minetest.set_node(pos,{name = placename}) tick(pos); -- needed for seeds to grow - else - minetest.set_node(pos,{name = nodename}) + else -- normal place + if param2 then + minetest.set_node(pos,{name = nodename, param2 = param2}) + else + minetest.set_node(pos,{name = nodename}) + end end return true @@ -480,24 +506,25 @@ basic_robot.commands.activate = function(name,mode, dir) local node = minetest.get_node(tpos); if node.name == "default:furnace" or node.name == "default:furnace_active" then if mode>0 then robot_activate_furnace(tpos) end - return + return true end local table = minetest.registered_nodes[node.name]; if table and table.mesecons and table.mesecons.effector then else - return + return false end -- error local effector=table.mesecons.effector; if mode > 0 then - if not effector.action_on then return end + if not effector.action_on then return false end effector.action_on(tpos,node,16) elseif mode<0 then - if not effector.action_off then return end + if not effector.action_off then return false end effector.action_off(tpos,node,16) end + return true end @@ -603,19 +630,22 @@ basic_robot.commands.craft = function(item, name) local cache = basic_robot.commands.craftcache[name]; if not cache then basic_robot.commands.craftcache[name] = {}; cache = basic_robot.commands.craftcache[name] end - local itemlist = {}; + local itemlist = {}; local output = ""; if cache.item == item then-- read cache itemlist = cache.itemlist; + output = cache.output; else local craft = minetest.get_craft_recipe(item); if craft and craft.type == "normal" and craft.items then else return end + output = craft.output; local items = craft.items; for _,item in pairs(items) do itemlist[item]=(itemlist[item] or 0)+1; end cache.item = item; cache.itemlist = itemlist; + cache.output = output; -- loop through robot inventory for those "group" items and see if anything in inventory matches group - then replace -- group name with that item @@ -659,7 +689,7 @@ basic_robot.commands.craft = function(item, name) inv:remove_item("main",stack); end - inv:add_item("main",ItemStack(item)) + inv:add_item("main",ItemStack(output)) return true end @@ -668,7 +698,6 @@ basic_robot.commands.show_form = function(name, playername, form) minetest.show_formspec(playername, "robot_form".. name, form) end - -- handle robots receiving fields minetest.register_on_player_receive_fields(function(player, formname, fields) if not string.sub(formname,1,10) == "robot_form" then return end @@ -676,4 +705,260 @@ minetest.register_on_player_receive_fields(function(player, formname, fields) if not basic_robot.data[name] then return end basic_robot.data[name].read_form = fields; basic_robot.data[name].form_sender = player:get_player_name() or ""; -end) \ No newline at end of file +end) + + +-- ROBOT TECHNIC +-- amount parameter in generate_power, smelt,... is determined by upgrade level +-- it specifies how much energy will be generated : + +basic_robot.technic = { -- data cache + fuels = {}, --[fuel] = value + smelts = {}, -- item = [cooktime, cookeditem, aftercookeditem] + + grinder_recipes = { --[in] ={fuel cost, out, quantity of material required for processing} + ["default:stone"] = {2,"default:sand",1}, + ["default:cobble"] = {1,"default:gravel",1}, + ["default:gravel"] = {0.5,"default:dirt",1}, + ["default:dirt"] = {0.5,"default:clay_lump 4",1}, + ["es:aikerum_crystal"] ={16,"es:aikerum_dust 2",1}, -- added for es mod + ["es:ruby_crystal"] = {16,"es:ruby_dust 2",1}, + ["es:emerald_crystal"] = {16,"es:emerald_dust 2",1}, + ["es:purpellium_lump"] = {16,"es:purpellium_dust 2",1}, + ["default:obsidian_shard"] = {199,"default:lava_source",1}, + ["gloopblocks:basalt"] = {1, "default:cobble",1}, -- enable coble farms with gloopblocks mod + ["default:ice"] = {1, "default:snow 4",1}, + ["darkage:silt_lump"]={1,"darkage:chalk_powder",1}, + ["default:diamond"] = {16, "basic_machines:diamond_dust_33 2", 1}, + ["default:ice"] = {1, "default:snow", 1}, + ["moreores:tin_lump"] = {4,"basic_machines:tin_dust_33 2",1}, + ["default:obsidian_shard"] = {199, "default:lava_source",1}, + ["default:mese_crystal"] = {8, "basic_machines:mese_dust_33 2",1}, + ["moreores:mithril_ingot"] = {16, "basic_machines:mithril_dust_33 2",1}, + ["moreores:silver_ingot"] = {5, "basic_machines:silver_dust_33 2",1}, + ["moreores:tin_ingot"] = {4,"basic_machines:tin_dust_33 2",1}, + ["moreores:mithril_lump"] = {16, "basic_machines:mithril_dust_33 2",1}, + ["default:steel_ingot"] = {4, "basic_machines:iron_dust_33 2",1}, + ["moreores:silver_lump"] = {5, "basic_machines:silver_dust_33 2",1}, + ["default:gold_ingot"] = {6, "basic_machines:gold_dust_33 2", 1}, + ["default:copper_ingot"] = {4, "basic_machines:copper_dust_33 2",1}, + ["default:gold_lump"] = {6, "basic_machines:gold_dust_33 2", 1}, + ["default:iron_lump"] = {4, "basic_machines:iron_dust_33 2",1}, + ["default:copper_lump"] = {4, "basic_machines:copper_dust_33 2",1}, + }, + + compressor_recipes = { --[in] ={fuel cost, out, quantity of material required for processing} + ["default:snow"] = {1,"default:ice"}, + ["default:coalblock"] = {16,"default:diamond"}, + }, +} + +local chk_machine_level = function(inv,level) -- does machine have upgrade to be classified with at least "level" + local upg = {"default:diamondblock","default:mese","default:goldblock"}; + for i = 1,#upg do + if not inv:contains_item("main",ItemStack(upg[i].. " " .. level)) then return false end + end + return true +end + +basic_robot.commands.machine = { + + -- convert fuel into energy + generate_power = function(name,input, amount) -- fuel used, if no fuel then amount specifies how much energy builtin generator should produce + + if amount and amount>0 then -- attempt to generate power from builtin generator + local pos = basic_robot.data[name].spawnpos; -- position of spawner block + local inv = minetest.get_meta(pos):get_inventory(); + local level = amount*40; -- to generate 1 unit ( coal lump per second ) we need at least upgrade 40 + if not chk_machine_level(inv, level) then error("generate_power : tried to generate " .. amount .. " energy requires upgrade level at least " .. level .. " (blocks of mese, diamond, gold )") return end + local data = basic_robot.data[name]; + local energy = (data.menergy or 0)+amount; + data.menergy = energy; + return energy; + end + + local energy = 0; -- can only do one step at a run time + if basic_robot.maxenergy~=0 then + local data = basic_robot.data[name]; + energy = data.energy; + if energy > 0 then data.energy = energy-1 else error("only one generate_power per run step allowed"); return end + end + + if string.find(input," ") then return nil, "1: can convert only one item at once" end + + local pos = basic_robot.data[name].spawnpos; -- position of spawner block + local inv = minetest.get_meta(pos):get_inventory(); + local stack = ItemStack(input); + if not inv:contains_item("main",stack) then return nil,"2: no input material" end + + -- read energy value of input + local add_energy = basic_robot.technic.fuels[input]; + if not add_energy then -- lookup fuel value + local fueladd, afterfuel = minetest.get_craft_result({method = "fuel", width = 1, items = {stack}}) + if fueladd.time > 0 then + add_energy = fueladd.time; + end + if add_energy>0 then basic_robot.technic.fuels[input] = add_energy/40 end + end + + inv:remove_item("main", stack); + + --add energy + local data = basic_robot.data[name]; energy = data.menergy or 0; + energy = energy+ add_energy;data.menergy = energy + return energy; + end, + + -- smelting + smelt = function(name,input,amount) -- input material, amount of energy used for smelt + + local energy = 0; -- can only do one step at a run time + if basic_robot.maxenergy~=0 then + local data = basic_robot.data[name]; + energy = data.energy; + if energy > 0 then + data.energy = energy-1 + else + error("only one smelt per run step allowed"); return + end + end + + if string.find(input," ") then return nil, "0: only one item per smelt" end + + local pos = basic_robot.data[name].spawnpos; -- position of spawner block + local meta = minetest.get_meta(pos); + local inv = minetest.get_meta(pos):get_inventory(); + + --read robot energy + local cost = 1/40; + local smelttimeboost = 1; + local level = 1; + if amount and amount>0 then + level = amount*10; -- 10 level required for 1 of amount + if not chk_machine_level(inv,level) then + error("3 smelting: need at least level " .. level .. " upgrade for required power " .. amount); + return + end + cost = cost*(1+amount); + smelttimeboost = smelttimeboost + amount; -- double speed with amount 1 + end + + local data = basic_robot.data[name] + energy = data.menergy or 0; -- machine energy + if energy<=cost then return nil,"1: not enough energy" end + + local stack = ItemStack(input); + if not inv:contains_item("main",stack) then return nil, "2: no input materials" end + + local src_time = (data.src_time or 0)+smelttimeboost; + + -- get smelting data + local smelts = basic_robot.technic.fuels[input]; + if not smelts then + local cooked, aftercooked; + cooked, aftercooked = minetest.get_craft_result({method = "cooking", width = 1, items = {stack}}) + if cooked.time>0 then + basic_robot.technic.fuels[input] = {cooked.time, cooked.item, aftercooked.items[1]}; + smelts = basic_robot.technic.fuels[input]; + end + end + local cooktime = smelts[1]; local cookeditem = smelts[2]; local aftercookeditem = smelts[3] + + -- is smelting done? + data.menergy = energy-cost; + if src_time >= cooktime then + inv:remove_item("main",stack); + inv:add_item("main", ItemStack(aftercookeditem)); + inv:add_item("main", ItemStack(cookeditem)); + data.src_time = 0 + return true + else + data.src_time = src_time + return math.floor(src_time/cooktime*100*100)/100 + end + end, + + -- grind + grind = function(name,input) + --[in] ={fuel cost, out, quantity of material required for processing} + local recipe = basic_robot.technic.grinder_recipes[input]; + if not recipe then return nil, "unknown recipe" end + local cost = recipe[1]; local output = recipe[2]; + + local pos = basic_robot.data[name].spawnpos; -- position of spawner block + local meta = minetest.get_meta(pos); + local inv = minetest.get_meta(pos):get_inventory(); + + --level requirement + local level = math.floor((cost-1)/3) + + if not chk_machine_level(inv,level) then error("0: tried to grind " .. input .. " requires upgrade level at least " .. level) return end + + local stack = ItemStack(input); + if not inv:contains_item("main",stack) then return nil, "1: missing input material" end + + local data = basic_robot.data[name]; + local energy = data.menergy or 0; + if energy 0 then + data.energy = energy-1 + else + error("only one transfer per run step allowed"); return + end + end + + energy = data.menergy or 0; + if amount>energy then return nil,"energy too low" end + + if not tdata.menergy then tdata.menergy = 0 end + tdata.menergy = tdata.menergy + amount + data.energy = energy - amount; + + end, +} \ No newline at end of file diff --git a/init.lua b/init.lua index 82d5047..2951dca 100644 --- a/init.lua +++ b/init.lua @@ -9,6 +9,7 @@ basic_robot.bad_inventory_blocks = { -- disallow taking from these nodes invento ["craft_guide:sign_wall"] = true, } basic_robot.maxenergy = 1; -- how much energy available per run, 0 = unlimited +basic_robot.use_coal = true; -- does robot require coal to dig stone? ---------------------- @@ -63,13 +64,13 @@ function getSandboxEnv (name) }, place = { - left = function(nodename) return commands.place(name,nodename, 1) end, - right = function(nodename) return commands.place(name,nodename, 2) end, - forward = function(nodename) return commands.place(name,nodename, 3) end, - backward = function(nodename) return commands.place(name,nodename, 4) end, - down = function(nodename) return commands.place(name,nodename, 6) end, - up = function(nodename) return commands.place(name,nodename, 5) end, - forward_down = function(nodename) return commands.place(name,nodename, 7) end, + left = function(nodename, param2) return commands.place(name,nodename, param2, 1) end, + right = function(nodename, param2) return commands.place(name,nodename, param2, 2) end, + forward = function(nodename, param2) return commands.place(name,nodename, param2, 3) end, + backward = function(nodename, param2) return commands.place(name,nodename, param2, 4) end, + down = function(nodename, param2) return commands.place(name,nodename, param2, 6) end, + up = function(nodename, param2) return commands.place(name,nodename, param2, 5) end, + forward_down = function(nodename, param2) return commands.place(name,nodename, param2, 7) end, }, insert = { -- insert item from robot inventory into another inventory @@ -127,6 +128,11 @@ function getSandboxEnv (name) name = function() return name end, viewdir = function() local yaw = basic_robot.data[name].obj:getyaw(); return {x=math.cos(yaw), y = 0, z=math.sin(yaw)} end, + skin = function(textures) + local obj = basic_robot.data[name].obj; + obj:set_properties({textures=textures}); + end, + listen = function (mode) if mode == 1 then basic_robot.data.listening[name] = true @@ -236,6 +242,15 @@ function getSandboxEnv (name) end, }, + + machine = {-- adds technic like functionality to robots: power generation, smelting, grinding, compressing + energy = function() return basic_robot.data[name].menergy or 0 end, + generate_power = function(input,amount) return commands.machine.generate_power(name,input, amount) end, + smelt = function(input,amount) return commands.machine.smelt(name,input, amount) end, + grind = function(input) return commands.machine.grind(name,input) end, + compress = function(input) return commands.machine.compress(name,input) end, + transfer_power = function(amount,target) return commands.machine.transfer_power(name,amount,target) end, + }, keyboard = { get = function() return commands.keyboard.get(name) end, @@ -1057,54 +1072,67 @@ local on_receive_robot_form = function(pos, formname, fields, sender) if fields.help then local text = "BASIC LUA SYNTAX\n \nif x==1 then A else B end".. - "\nfor i = 1, 5 do something end \nwhile i<6 do A; i=i+1; end\n".. - "\narrays: myTable1 = {1,2,3}, myTable2 = {[\"entry1\"]=5, [\"entry2\"]=1}\n".. - "access table entries with myTable1[1] or myTable2.entry1 or myTable2[\"entry1\"]\n \n".. + "\n for i = 1, 5 do something end \nwhile i<6 do A; i=i+1; end\n".. + "\n arrays: myTable1 = {1,2,3}, myTable2 = {[\"entry1\"]=5, [\"entry2\"]=1}\n".. + " access table entries with myTable1[1] or myTable2.entry1 or myTable2[\"entry1\"]\n \n".. "ROBOT COMMANDS\n \n".. - " **MOVEMENT,DIGGING, PLACING, INVENTORY TAKE/INSERT\nmove.direction(), where direction is forward, backward, left,right, up, down)\n".. - "forward_down direction only works with dig, place and read_node\n".. - "turn.left(), turn.right(), turn.angle(45)\n".. - "dig.direction()\nplace.direction(\"default:dirt\")\nread_node.direction() tells you names of nodes\n".. - "insert.direction(item, inventory) inserts item from robot inventory to target inventory\n".. - "check_inventory.direction(itemname, inventory,index) looks at node and returns false/true, direction can be self,\n".. - " if index>0 it returns itemname. if itemname == \"\" it checks if inventory empty\n".. - "activate.direction(mode) activates target block\n".. - "pickup(r) picks up all items around robot in radius r<8 and returns list or nil\n".. - "craft(item) crafts item if required materials are present in inventory\n".. - "take.direction(item, inventory) takes item from target inventory into robot inventory\n".. - "read_text.direction(stringname,mode) reads text of signs, chests and other blocks, optional stringname for other meta,\n mode 1 read number\n".. - "write_text.direction(text,mode) writes text to target block as infotext\n".. - " **BOOKS/CODE\ntitle,text=book.read(i) returns title,contents of book at i-th position in library \nbook.write(i,title,text) writes book at i-th position at spawner library\n".. - "code.run(text) compiles and runs the code in sandbox\n".. - "code.set(text) replaces current bytecode of robot\n".. - "find_nodes(\"default:dirt\",3) returns distance to node in radius 3 around robot, or false if none\n".. - " **PLAYERS\n".. - "find_player(3) finds players in radius 3 around robot and returns list, if none returns nil\n".. - "attack(target) attempts to attack target player if nearby \n".. - "grab(target) attempt to grab target player if nearby and returns true if succesful \n".. - "player.getpos(name) return position of player, player.connected() returns list of players\n".. - " **ROBOT\n".. - "say(\"hello\") will speak\n".. - "self.listen(0/1) (de)attaches chat listener to robot\n".. - "speaker, msg = self.listen_msg() retrieves last chat message if robot listens\n".. - "self.send_mail(target,mail) sends mail to target robot\n".. - "sender,mail = self.read_mail() reads mail, if any\n" .. - "self.pos() returns table {x=pos.x,y=pos.y,z=pos.z}\n".. - "self.name() returns robot name\n".. - "self.spam(0/1) (dis)enable message repeat to all\n".. - "self.remove() stops program and removes robot object\n".. - "self.reset() resets robot position\n".. - "self.spawnpos() returns position of spawner block\n".. - "self.viewdir() returns vector of view for robot\n".. - "self.fire(speed, pitch,gravity) fires a projectile from robot\n".. - "self.fire_pos() returns last hit position\n".. - "self.label(text) changes robot label\n".. - "self.display_text(text,linesize,size) displays text instead of robot face\n".. - "rom is aditional table that can store persistent data, like rom.x=1\n".. - " **KEYBOARD : place spawner at coordinates (20i,40j+1,20k) to monitor events\n".. - "keyboard.get() returns table {x=..,y=..,z=..,puncher = .. , type = .. } for keyboard event\n".. - "keyboard.set(pos,type) set key at pos of type 0=air, 1..6, limited to range 10 around\n".. - "keyboard.read(pos) return node name at pos\n"; + "**MOVEMENT,DIGGING, PLACING, INVENTORY TAKE/INSERT\n move.direction(), where direction is forward, backward, left,right, up, down)\n".. + " forward_down direction only works with dig, place and read_node\n".. + " turn.left(), turn.right(), turn.angle(45)\n".. + " dig.direction()\n".. + " place.direction(\"default:dirt\", optional orientation param)\n".. + " read_node.direction() tells you names of nodes\n".. + " insert.direction(item, inventory) inserts item from robot inventory to target inventory\n".. + " check_inventory.direction(itemname, inventory,index) looks at node and returns false/true, direction can be self,\n".. + " if index>0 it returns itemname. if itemname == \"\" it checks if inventory empty\n".. + " activate.direction(mode) activates target block\n".. + " pickup(r) picks up all items around robot in radius r<8 and returns list or nil\n".. + " craft(item) crafts item if required materials are present in inventory\n".. + " take.direction(item, inventory) takes item from target inventory into robot inventory\n".. + " read_text.direction(stringname,mode) reads text of signs, chests and other blocks, optional stringname for other meta,\n mode 1 read number\n".. + " write_text.direction(text,mode) writes text to target block as infotext\n".. + "**BOOKS/CODE\n title,text=book.read(i) returns title,contents of book at i-th position in library \n book.write(i,title,text) writes book at i-th position at spawner library\n".. + " code.run(text) compiles and runs the code in sandbox\n".. + " code.set(text) replaces current bytecode of robot\n".. + " find_nodes(\"default:dirt\",3) returns distance to node in radius 3 around robot, or false if none\n".. + "**PLAYERS\n".. + " find_player(3) finds players in radius 3 around robot and returns list, if none returns nil\n".. + " attack(target) attempts to attack target player if nearby \n".. + " grab(target) attempt to grab target player if nearby and returns true if succesful \n".. + " player.getpos(name) return position of player, player.connected() returns list of players\n".. + "**ROBOT\n".. + " say(\"hello\") will speak\n".. + " self.listen(0/1) (de)attaches chat listener to robot\n".. + " speaker, msg = self.listen_msg() retrieves last chat message if robot listens\n".. + " self.send_mail(target,mail) sends mail to target robot\n".. + " sender,mail = self.read_mail() reads mail, if any\n" .. + " self.pos() returns table {x=pos.x,y=pos.y,z=pos.z}\n".. + " self.name() returns robot name\n".. + " self.skin(textures) sets robot skin, textures is array of 6 textures\n".. + " self.spam(0/1) (dis)enable message repeat to all\n".. + " self.remove() stops program and removes robot object\n".. + " self.reset() resets robot position\n".. + " self.spawnpos() returns position of spawner block\n".. + " self.viewdir() returns vector of view for robot\n".. + " self.fire(speed, pitch,gravity) fires a projectile from robot\n".. + " self.fire_pos() returns last hit position\n".. + " self.label(text) changes robot label\n".. + " self.display_text(text,linesize,size) displays text instead of robot face\n".. + " rom is aditional table that can store persistent data, like rom.x=1\n".. + "**KEYBOARD : place spawner at coordinates (20i,40j+1,20k) to monitor events\n".. + " keyboard.get() returns table {x=..,y=..,z=..,puncher = .. , type = .. } for keyboard event\n".. + " keyboard.set(pos,type) set key at pos of type 0=air, 1..6, limited to range 10 around\n".. + " keyboard.read(pos) return node name at pos\n".. + "**TECHNIC FUNCTIONALITY: namespace 'machine'\n" .. + " energy() displays available energy\n".. + " generate_power(fuel, amount) attempt to generate power from fuel material, if\n" .. + " amount>0 try generate amount of power using builtin generator - this requires\n" .. + " 40 gold/mese/diamonblock upgrades for each 1 amount\n".. + " smelt(input,amount) works as a furnace, if amount>0 try to use power to smelt -\n" .. + " requires 10 upgrades for each 1 amount, energy cost is: 1/40*(1+amount)\n".. + " grind(input) - grinds input material, requires upgrades for harder material\n".. + " compress(input) requires upgrades - energy intensive process\n" .. + " transfer_power(amount,target_robot_name)\n"; text = minetest.formspec_escape(text); diff --git a/scripts/blackbox.lua b/scripts/blackbox.lua deleted file mode 100644 index d7f0d1c..0000000 --- a/scripts/blackbox.lua +++ /dev/null @@ -1,195 +0,0 @@ ---black box by rnd, 03/18/2017 ---https://en.wikipedia.org/wiki/Black_Box_(game) - -if not data then - m=8;n=8;turn = 0; - attempts = 1; - - self.spam(1);t0 = _G.minetest.get_gametime(); - spawnpos = self.spawnpos() - data = {}; - for i = 1,m do data[i]={}; for j = 1,n do data[i][j]=0 end end - - for i=1,4 do -- put in 4 atoms randomly - data[math.random(m)][math.random(n)] = 1 - end - - render_board = function(mode) -- mode 0 : render without solution, 1: render solution - for i = 1,m do for j = 1,n do -- render game - if mode == 0 or data[i][j] == 0 then - if keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j})~="basic_robot:button808080" then - keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},2) - end - else - keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j},3) - end - end end - end - - get_dirl = function(dir) - local dirl; -- direction left - if dir[1] > 0.5 then dirl = {0,-1} - elseif dir[1] < -0.5 then dirl = {0,1} - elseif dir[2] > 0.5 then dirl = {-1,0} - elseif dir[2] < -0.5 then dirl = {1,0} - end - return dirl - end - - read_pos = function(x,z) - if x<1 or x>m or z<1 or z>n then return nil end - return data[x][z] - end - - newdir = function(x,z,dir) -- where will ray go next - local retdir = {dir[1],dir[2]}; - local xf = x+dir[1]; local zf = z+dir[2] -- forward - local dirl = get_dirl(dir) - - local nodef = read_pos(xf,zf) - local nodel = read_pos(xf + dirl[1],zf + dirl[2]) - local noder = read_pos(xf - dirl[1],zf - dirl[2]) - if nodef == 1 then - retdir = {0,0} -- ray hit something - elseif nodel == 1 and noder ~= 1 then - retdir = {-dirl[1],-dirl[2]} - elseif nodel ~= 1 and noder == 1 then - retdir = {dirl[1],dirl[2]} - elseif nodel == 1 and noder == 1 then - retdir = {-dir[1],-dir[2]} - end - return retdir - end - - shootray = function(x,z,dir) - --say("ray starts " .. x .. " " .. z .. " dir " .. dir[1] .. " " .. dir[2]) - local xp = x; local zp = z; - local dirp = {dir[1],dir[2]}; - local maxstep = m*n; - - for i = 1,maxstep do - dirp = newdir(xp,zp,dirp); - if dirp[1]==0 and dirp[2]==0 then return -i end -- hit - xp=xp+dirp[1];zp=zp+dirp[2]; - if xp<1 or xp>m or zp<1 or zp>n then return i,{xp,zp} end -- out - end - return 0 -- hit - end - - count = 0; -- how many letters were used up - border_start_ray = function(x,z) - local rdir - if x==0 then rdir = {1,0} - elseif x == m+1 then rdir = {-1,0} - elseif z == 0 then rdir = {0,1} - elseif z == n+1 then rdir = {0,-1} - end - if rdir then - local result,out = shootray(x,z,rdir); - if result >= 0 then - - if out then - if out[1]==x and out[2]==z then -- got back where it originated, reflection - keyboard.set({x=spawnpos.x+out[1],y=spawnpos.y,z=spawnpos.z+out[2]},1); - else - if result<=1 then - keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},6); -- immediate bounce off - else - local nodename = "default:obsidian_letter_"..string.char(97+count) .. "u"; - _G.minetest.set_node( - {x=spawnpos.x+out[1],y=spawnpos.y+1,z=spawnpos.z+out[2]}, - {name = nodename, param2 = 1}) - _G.minetest.set_node( - {x=spawnpos.x+x,y=spawnpos.y+1,z=spawnpos.z+z}, - {name = nodename, param2 = 1}) - count = count + 1; - keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},4); - keyboard.set({x=spawnpos.x+out[1],y=spawnpos.y,z=spawnpos.z+out[2]},4); - end - end - end - elseif result<0 then - keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},3); -- hit - end - end - end - - -- initial border loop and marking - - --render blue border - for i = 1,m do keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+0},5) keyboard.set({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+n+1},5) end - for j = 1,n do keyboard.set({x=spawnpos.x+0,y=spawnpos.y,z=spawnpos.z+j},5) keyboard.set({x=spawnpos.x+m+1,y=spawnpos.y,z=spawnpos.z+j},5) end - - for i = 1,m do keyboard.set({x=spawnpos.x+i,y=spawnpos.y+1,z=spawnpos.z+0},0) keyboard.set({x=spawnpos.x+i,y=spawnpos.y+1,z=spawnpos.z+n+1},0) end - for j = 1,n do keyboard.set({x=spawnpos.x+0,y=spawnpos.y+1,z=spawnpos.z+j},0) keyboard.set({x=spawnpos.x+m+1,y=spawnpos.y+1,z=spawnpos.z+j},0) end - - - z=0 -- bottom - for x = 1,m do - if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button8080FF" then - border_start_ray(x,z) - end - end - - x=m+1 -- right - for z = 1,n do - if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button8080FF" then - border_start_ray(x,z) - end - end - - z=n+1 -- top - for x = m,1,-1 do - if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button8080FF" then - border_start_ray(x,z) - end - end - - x=0 -- left - for z = n,1,-1 do - if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button8080FF" then - border_start_ray(x,z) - end - end - - check_solution = function() - for i = 1,m do - for j = 1,n do - if keyboard.read({x=spawnpos.x+i,y=spawnpos.y,z=spawnpos.z+j}) == "basic_robot:buttonFF8080" then -- red - if data[i][j]~=1 then return false end - else - if data[i][j]~=0 then return false end - end - end - end - return true - end - - --render board - render_board(0) - keyboard.set({x=spawnpos.x,y=spawnpos.y,z=spawnpos.z-1},5) - -end - -event = keyboard.get(); -if event then - local x = event.x - spawnpos.x;local y = event.y - spawnpos.y;local z = event.z - spawnpos.z; - if x<1 or x>m or z<1 or z>n then - if event.type == 5 then - if check_solution() then - say("#BLACKBOX : CONGRATULATIONS! " .. event.puncher .. " found all atoms after " .. attempts .. " tries."); self.remove() - else - say("#BLACKBOX : " .. event.puncher .. " failed to detect atoms after " .. attempts .. " attempts.") - attempts = attempts+1 - end - end - else -- interior punch - nodetype = 2; - if keyboard.read({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z}) == "basic_robot:button808080" then - nodetype = 3 - end - keyboard.set({x=spawnpos.x+x,y=spawnpos.y,z=spawnpos.z+z},nodetype); - end - -end -::END:: \ No newline at end of file diff --git a/scripts/perm2cycles.lua b/scripts/perm2cycles.lua new file mode 100644 index 0000000..0e6b405 --- /dev/null +++ b/scripts/perm2cycles.lua @@ -0,0 +1,83 @@ +if not perm2cycles then + + perm2cycles = function(perm) + local n = #perm; + local i = 1; -- already reached number + local ret = {}; + local visited = {}; + local step = 0; + + while (true) do + local cycle = {i} + local j=i; + + while (true) do + step = step +1 + if step > 2*n then return {} end + j=perm[j]; + visited[j] = 1; + if j and j~=cycle[1] then cycle[#cycle+1]=j else break end + end + + i=n+1; + for k=1,n do -- find smallest non visited yet + if not visited[k] and k cycles = " .. arr2string(cycles) ) + +end \ No newline at end of file diff --git a/scripts/substring_search.lua b/scripts/substring_search.lua new file mode 100644 index 0000000..1650732 --- /dev/null +++ b/scripts/substring_search.lua @@ -0,0 +1,64 @@ +-- Rabin–Karp substring s search in string t +-- https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm + + +-- rnd 2017 +-- ALGORITHM: +-- 1.) loop compute hashes of all substrings of t of length |s| using rolling hash idea +-- 2.) if some hash matches hash of s check more closely ( waste |s| time here, so this should only occur with probability < O(1)/|s| so expected waste is O(1)) + +-- how to do 1) rolling hash: how does hash of string change if you remove first character and add new last character? ... dont need to recompute whole hash! + +-- summary: we end up using O(|t|+|s| + (number of needed hits)*O(1)) time (if you want more than 1 hit..) +-- this is big improvement compared to O(|t|*|s|) when doing naive substring search + +-- improvement: we could also precompute all substring hashes of length |s| and then compute hash of some string of same length and +-- do quick lookups for that hash ( hash of hash :) ) + +if not hash then + + hash = function(s,p) + local length = string.len(s); + local h = 0 ; + for i = 1, length do + h=(256*h + string.byte(s,i))%p + end + return h%p + end + + getpower = function(p,k) -- safe computation of power 256^k in mod p arithmetic + local r=1; for i = 1,k do r=(256*r) % p end; return r + end + + karpin_rabin = function(t,s) + local ls = string.len(s); + local lt = string.len(t); + if lt