lwscratch/program.lua
2021-03-22 23:34:14 +10:00

1142 lines
22 KiB
Lua

local utils = ...
local function cmdstr (cmd)
if not cmd or not cmd.command then
return ""
end
return cmd.command
end
------------------------------------------------------------------------
local function check_condition_detect (program)
local cmd = program:next_cell (false)
if not utils.is_command_item (cmdstr (cmd)) then
return true
end
return false, "item or blank must follow detect"
end
local function check_condition_fits (program)
local cmd = program:next_cell (false)
if not utils.is_command_item (cmdstr (cmd)) then
return true
end
return false, "item or blank must follow item fits"
end
local function check_condition_contains (program)
local cmd = program:next_cell (false)
if not utils.is_command_item (cmdstr (cmd)) then
return true
end
return false, "item or blank must follow contains item"
end
local function check_condition_counter (program)
local cmd = program:next_cell (false)
if utils.is_number_item (cmdstr (cmd)) then
return true
end
return false, "number must follow counter"
end
local function check_condition_number (program)
return false, "out of place number"
end
local check_condition_table =
{
["lwscratch:cmd_cond_detect_front"] = check_condition_detect,
["lwscratch:cmd_cond_detect_front_down"] = check_condition_detect,
["lwscratch:cmd_cond_detect_front_up"] = check_condition_detect,
["lwscratch:cmd_cond_detect_back"] = check_condition_detect,
["lwscratch:cmd_cond_detect_back_down"] = check_condition_detect,
["lwscratch:cmd_cond_detect_back_up"] = check_condition_detect,
["lwscratch:cmd_cond_detect_down"] = check_condition_detect,
["lwscratch:cmd_cond_detect_up"] = check_condition_detect,
["lwscratch:cmd_cond_fits"] = check_condition_fits,
["lwscratch:cmd_cond_contains"] = check_condition_contains,
["lwscratch:cmd_cond_counter_equal"] = check_condition_counter,
["lwscratch:cmd_cond_counter_greater"] = check_condition_counter,
["lwscratch:cmd_cond_counter_less"] = check_condition_counter,
["lwscratch:cmd_number"] = check_condition_number,
}
local function check_condition (program, last_condition)
local must_follow = true
local can_and_not = false
while true do
local cmd = program:next_cell (false)
if not utils.is_condition_item (cmdstr (cmd)) and
not utils.is_operator_item (cmdstr (cmd)) then
if must_follow then
return false, "condition must follow "..last_condition
end
while cmd do
if cmdstr (cmd) ~= "" then
return false, "out of place command/item"
end
cmd = program:next_cell (false)
end
return true
end
if cmd.command == "lwscratch:cmd_op_not" then
must_follow = true
can_and_not = false
last_condition = "not"
elseif cmd.command == "lwscratch:cmd_op_and" then
if not can_and_not then
return false, "out of place and"
end
must_follow = true
can_and_not = false
last_condition = "and"
elseif cmd.command == "lwscratch:cmd_op_or" then
if not can_and_not then
return false, "out of place or"
end
must_follow = true
can_and_not = false
last_condition = "or"
else
local result, msg = check_condition_table[cmd.command] (program)
if not result then
return result, msg
end
must_follow = false
can_and_not = true
end
end
end
local function check_dig (program)
return true
end
local function check_move (program)
return true
end
local function check_turn (program)
return true
end
local function check_pull (program)
local cmd = program:next_cell (false)
if not utils.is_command_item (cmdstr (cmd)) then
return true
end
return false, "item or blank must follow pull"
end
local function check_put (program)
local cmd = program:next_cell (false)
if not utils.is_command_item (cmdstr (cmd)) then
return true
end
return false, "item or blank must follow put"
end
local function check_drop (program)
local cmd = program:next_cell (false)
if not utils.is_command_item (cmdstr (cmd)) then
return true
end
return false, "item or blank must follow drop"
end
local function check_trash (program)
local cmd = program:next_cell (false)
if not utils.is_command_item (cmdstr (cmd)) then
return true
end
return false, "item or blank must follow trash"
end
local function check_place (program)
local cmd = program:next_cell (false)
if cmdstr (cmd):len () > 0 and not utils.is_command_item (cmd.command) then
return true
end
return false, "item must follow place"
end
local function check_craft (program)
local cmd = program:next_cell (false)
if cmdstr (cmd):len () > 0 and not utils.is_command_item (cmd.command) then
return true
end
return false, "item must follow craft"
end
local function check_detect (program)
return false, "out of place detect"
end
local function check_fits (program)
return false, "out of place item fits"
end
local function check_contains (program)
return false, "out of place contain item"
end
local function check_counter (program)
return false, "out of place counter"
end
local function check_number (program)
return false, "out of place number"
end
local function check_or (program)
return false, "out of place or"
end
local function check_not (program)
return false, "out of place not"
end
local function check_and (program)
return false, "out of place and"
end
local function check_stop (program)
return true
end
local function check_wait (program)
local cmd = program:next_cell (false)
if utils.is_number_item (cmdstr (cmd)) then
return true
end
return false, "number must follow wait"
end
local function check_if (program)
return check_condition (program, "if")
end
local function check_loop (program)
return check_condition (program, "loop")
end
local check_table =
{
["lwscratch:cmd_act_dig_front"] = check_dig,
["lwscratch:cmd_act_dig_front_down"] = check_dig,
["lwscratch:cmd_act_dig_front_up"] = check_dig,
["lwscratch:cmd_act_dig_back"] = check_dig,
["lwscratch:cmd_act_dig_back_down"] = check_dig,
["lwscratch:cmd_act_dig_back_up"] = check_dig,
["lwscratch:cmd_act_dig_down"] = check_dig,
["lwscratch:cmd_act_dig_up"] = check_dig,
["lwscratch:cmd_act_move_back"] = check_move,
["lwscratch:cmd_act_move_down"] = check_move,
["lwscratch:cmd_act_move_front"] = check_move,
["lwscratch:cmd_act_move_up"] = check_move,
["lwscratch:cmd_act_turn_left"] = check_turn,
["lwscratch:cmd_act_turn_right"] = check_turn,
["lwscratch:cmd_act_pull"] = check_pull,
["lwscratch:cmd_act_put"] = check_put,
["lwscratch:cmd_act_drop"] = check_drop,
["lwscratch:cmd_act_trash"] = check_trash,
["lwscratch:cmd_act_place_front"] = check_place,
["lwscratch:cmd_act_place_front_down"] = check_place,
["lwscratch:cmd_act_place_front_up"] = check_place,
["lwscratch:cmd_act_place_back"] = check_place,
["lwscratch:cmd_act_place_back_down"] = check_place,
["lwscratch:cmd_act_place_back_up"] = check_place,
["lwscratch:cmd_act_place_down"] = check_place,
["lwscratch:cmd_act_place_up"] = check_place,
["lwscratch:cmd_act_craft"] = check_craft,
["lwscratch:cmd_number"] = check_number,
["lwscratch:cmd_act_stop"] = check_stop,
["lwscratch:cmd_act_wait"] = check_wait,
["lwscratch:cmd_stat_if"] = check_if,
["lwscratch:cmd_stat_loop"] = check_loop,
["lwscratch:cmd_op_or"] = check_or,
["lwscratch:cmd_op_not"] = check_not,
["lwscratch:cmd_op_and"] = check_and,
}
------------------------------------------------------------------------
local function run_condition_detect (program, robot_pos)
local cmd = program:cur_command ()
local item = program:next_cell ()
local side = "front" -- assume lwscratch:cmd_cond_detect_front
if cmd.command == "lwscratch:cmd_cond_detect_back" then
side = "back"
elseif cmd.command == "lwscratch:cmd_cond_detect_back_down" then
side = "back down"
elseif cmd.command == "lwscratch:cmd_cond_detect_back_up" then
side = "back up"
elseif cmd.command == "lwscratch:cmd_cond_detect_down" then
side = "down"
elseif cmd.command == "lwscratch:cmd_cond_detect_up" then
side = "up"
elseif cmd.command == "lwscratch:cmd_cond_detect_front_down" then
side = "front down"
elseif cmd.command == "lwscratch:cmd_cond_detect_front_up" then
side = "front up"
end
local node = utils.robot_detect (robot_pos, side)
if not item or not item.command then
return node ~= nil
end
return node == item.command
end
local function run_condition_fits (program, robot_pos)
local cmd = program:cur_command ()
local item = program:next_cell ()
return utils.robot_room_for (robot_pos, (item and item.command) or nil)
end
local function run_condition_contains (program, robot_pos)
local cmd = program:cur_command ()
local item = program:next_cell ()
return utils.robot_contains (robot_pos, (item and item.command) or nil)
end
local function run_condition_counter (program, robot_pos)
local cmd = program:cur_command ()
local value = program:next_cell ()
if cmd.command == "lwscratch:cmd_cond_counter_less" then
return program:loop_counter () < value.value
elseif cmd.command == "lwscratch:cmd_cond_counter_greater" then
return program:loop_counter () > value.value
else -- assume lwscratch:cmd_cond_counter_equal
return program:loop_counter () == value.value
end
end
local run_condition_table =
{
["lwscratch:cmd_cond_detect_front"] = run_condition_detect,
["lwscratch:cmd_cond_detect_front_down"] = run_condition_detect,
["lwscratch:cmd_cond_detect_front_up"] = run_condition_detect,
["lwscratch:cmd_cond_detect_back"] = run_condition_detect,
["lwscratch:cmd_cond_detect_back_down"] = run_condition_detect,
["lwscratch:cmd_cond_detect_back_up"] = run_condition_detect,
["lwscratch:cmd_cond_detect_down"] = run_condition_detect,
["lwscratch:cmd_cond_detect_up"] = run_condition_detect,
["lwscratch:cmd_cond_fits"] = run_condition_fits,
["lwscratch:cmd_cond_contains"] = run_condition_contains,
["lwscratch:cmd_cond_counter_equal"] = run_condition_counter,
["lwscratch:cmd_cond_counter_greater"] = run_condition_counter,
["lwscratch:cmd_cond_counter_less"] = run_condition_counter,
}
local function run_condition (program, robot_pos)
local result = false
local not_next = false
local or_next = false
local and_next = false
while true do
local cmd = program:next_cell (true)
if not utils.is_condition_item (cmdstr (cmd)) and
not utils.is_operator_item (cmdstr (cmd)) then
return result
end
program:next_cell (false)
if cmd.command == "lwscratch:cmd_op_not" then
not_next = true
elseif cmd.command == "lwscratch:cmd_op_and" then
and_next = true
elseif cmd.command == "lwscratch:cmd_op_or" then
or_next = true
else
local op_result = run_condition_table[cmd.command] (program, robot_pos)
if not_next then
op_result = not op_result
end
if or_next then
result = result or op_result
elseif and_next then
result = result and op_result
else
result = op_result
end
not_next = false
or_next = false
and_next = false
end
end
end
local function run_dig (program, robot_pos)
local cmd = program:cur_command ()
local side = "front" -- assume lwscratch:cmd_act_dig_front
if cmd.command == "lwscratch:cmd_act_dig_back" then
side = "back"
elseif cmd.command == "lwscratch:cmd_act_dig_back_down" then
side = "back down"
elseif cmd.command == "lwscratch:cmd_act_dig_back_up" then
side = "back up"
elseif cmd.command == "lwscratch:cmd_act_dig_down" then
side = "down"
elseif cmd.command == "lwscratch:cmd_act_dig_up" then
side = "up"
elseif cmd.command == "lwscratch:cmd_act_dig_front_down" then
side = "front down"
elseif cmd.command == "lwscratch:cmd_act_dig_front_up" then
side = "front up"
end
utils.robot_dig (robot_pos, side)
return true
end
local function run_move (program, robot_pos)
local cmd = program:cur_command ()
local side = "front" -- assume lwscratch:cmd_act_move_front
if cmd.command == "lwscratch:cmd_act_move_back" then
side = "back"
elseif cmd.command == "lwscratch:cmd_act_move_down" then
side = "down"
elseif cmd.command == "lwscratch:cmd_act_move_up" then
side = "up"
end
local result, pos = utils.robot_move (robot_pos, side)
if result then
program.pos = pos
utils.add_robot_to_list (program.id, program.pos)
end
return true
end
local function run_turn (program, robot_pos)
local cmd = program:cur_command ()
local side = "left" -- assume lwscratch:cmd_act_turn_left
if cmd.command == "lwscratch:cmd_act_turn_right" then
side = "right"
end
utils.robot_turn (robot_pos, side)
return true
end
local function run_pull (program, robot_pos)
local item = cmdstr (program:next_cell ())
utils.robot_pull (robot_pos, "front", (item:len () > 0 and item) or nil)
return true
end
local function run_put (program, robot_pos)
local item = cmdstr (program:next_cell ())
utils.robot_put (robot_pos, "front", (item:len () > 0 and item) or nil)
return true
end
local function run_drop (program, robot_pos)
local item = cmdstr (program:next_cell ())
utils.robot_remove_item (robot_pos, (item:len () > 0 and item) or nil, true)
return true
end
local function run_trash (program, robot_pos)
local item = cmdstr (program:next_cell ())
utils.robot_remove_item (robot_pos, (item:len () > 0 and item) or nil, false)
return true
end
local function run_place (program, robot_pos)
local cmd = program:cur_command ()
local item = program:next_cell ()
local side = "front" -- assume lwscratch:cmd_act_place_front
if cmd.command == "lwscratch:cmd_act_place_back" then
side = "back"
elseif cmd.command == "lwscratch:cmd_act_place_back_down" then
side = "back down"
elseif cmd.command == "lwscratch:cmd_act_place_back_up" then
side = "back up"
elseif cmd.command == "lwscratch:cmd_act_place_down" then
side = "down"
elseif cmd.command == "lwscratch:cmd_act_place_up" then
side = "up"
elseif cmd.command == "lwscratch:cmd_act_place_front_down" then
side = "front down"
elseif cmd.command == "lwscratch:cmd_act_place_front_up" then
side = "front up"
end
utils.robot_place (robot_pos, side, item.command)
return true
end
local function run_craft (program, robot_pos)
local item = program:next_cell ()
utils.robot_craft (robot_pos, item.command)
return true
end
local function run_stop (program, robot_pos)
program.stopped = true
return true
end
local function run_wait (program, robot_pos)
local value = program:next_cell ()
local meta = minetest.get_meta (robot_pos)
if meta then
meta:set_int ("delay_counter",
math.ceil ((value.value / 10) / utils.settings.running_tick))
end
return true
end
local function run_if (program, robot_pos)
local indent = program:line_indent ()
local result = run_condition (program, robot_pos)
if not result then
program:advance_to_indent (indent)
end
return false
end
local function run_loop (program, robot_pos)
local indent = program:line_indent ()
local line = program:line ()
program:push_loop (line, indent)
local result = run_condition (program, robot_pos)
if not result then
program:pop_loop (line)
program:advance_to_indent (indent)
end
return false
end
local run_table =
{
["lwscratch:cmd_act_dig_front"] = run_dig,
["lwscratch:cmd_act_dig_front_down"] = run_dig,
["lwscratch:cmd_act_dig_front_up"] = run_dig,
["lwscratch:cmd_act_dig_back"] = run_dig,
["lwscratch:cmd_act_dig_back_down"] = run_dig,
["lwscratch:cmd_act_dig_back_up"] = run_dig,
["lwscratch:cmd_act_dig_down"] = run_dig,
["lwscratch:cmd_act_dig_up"] = run_dig,
["lwscratch:cmd_act_move_back"] = run_move,
["lwscratch:cmd_act_move_down"] = run_move,
["lwscratch:cmd_act_move_front"] = run_move,
["lwscratch:cmd_act_move_up"] = run_move,
["lwscratch:cmd_act_turn_left"] = run_turn,
["lwscratch:cmd_act_turn_right"] = run_turn,
["lwscratch:cmd_act_pull"] = run_pull,
["lwscratch:cmd_act_put"] = run_put,
["lwscratch:cmd_act_drop"] = run_drop,
["lwscratch:cmd_act_trash"] = run_trash,
["lwscratch:cmd_act_place_front"] = run_place,
["lwscratch:cmd_act_place_front_down"] = run_place,
["lwscratch:cmd_act_place_front_up"] = run_place,
["lwscratch:cmd_act_place_back"] = run_place,
["lwscratch:cmd_act_place_back_down"] = run_place,
["lwscratch:cmd_act_place_back_up"] = run_place,
["lwscratch:cmd_act_place_down"] = run_place,
["lwscratch:cmd_act_place_up"] = run_place,
["lwscratch:cmd_act_craft"] = run_craft,
["lwscratch:cmd_act_stop"] = run_stop,
["lwscratch:cmd_act_wait"] = run_wait,
["lwscratch:cmd_stat_if"] = run_if,
["lwscratch:cmd_stat_loop"] = run_loop,
}
------------------------------------------------------------------------
local program_obj = { }
function program_obj:new (program, pos)
local obj = { }
setmetatable(obj, self)
self.__index = self
obj.program = program
obj.stopped = false
obj.pos = { x = (pos and pos.x) or 0, y = (pos and pos.y) or 0, z = (pos and pos.z) or 0 }
if program then
program_obj.init (obj)
end
if pos then
local meta = minetest.get_meta (pos)
if meta then
obj.id = meta:get_int ("lwscratch_id")
utils.add_robot_to_list (obj.id, obj.pos)
end
end
return obj
end
function program_obj:serialize ()
if not self.program then
return nil
end
local meta = minetest.get_meta (self.pos)
if not meta then
return false
end
meta:set_string ("program", minetest.serialize (self.program))
return true
end
function program_obj:deserialize (pos)
local meta = minetest.get_meta (pos)
if not meta then
return false
end
self.id = meta:get_int ("lwscratch_id")
self.pos = { x = pos.x, y = pos.y, z = pos.z }
self.program = minetest.deserialize (meta:get_string ("program"))
utils.add_robot_to_list (self.id, self.pos)
return true
end
function program_obj:init ()
if not self.program then
return nil
end
self.program.cur_line = 1
self.program.cur_cell = 0
self.program.loops = { }
return true
end
function program_obj:cur_command ()
if not self.program then
return nil
end
local line = self.program.cur_line
local cell = self.program.cur_cell
if line > 0 and line <= 50 and cell > 0 and cell <= 10 then
return self.program[line][cell]
end
return nil
end
function program_obj:line ()
if not self.program then
return nil
end
return self.program.cur_line
end
function program_obj:cell ()
if not self.program then
return nil
end
return self.program.cur_cell
end
function program_obj:lines ()
if not self.program then
return nil
end
return #self.program
end
function program_obj:cells ()
if not self.program then
return nil
end
return #self.program[1]
end
function program_obj:line_indent ()
if not self.program then
return nil
end
local line = self.program[self.program.cur_line]
for c = 1, #line do
if cmdstr (line[c]):len () > 0 then
return c
end
end
return self:cells ()
end
function program_obj:next_line ()
if not self.program then
return false
end
if self.program.cur_line < 50 then
self.program.cur_line = self.program.cur_line + 1
self.program.cur_cell = 0
return true
end
self.program.cur_cell = self:cells ()
return false
end
function program_obj:next_cell (query)
if not self.program then
return nil
end
local cell = self.program.cur_cell
if cell < 10 then
cell = cell + 1
if not query then
self.program.cur_cell = cell
end
return self.program[self.program.cur_line][cell]
end
return nil
end
function program_obj:advance_to_indent (indent)
while self:next_line () do
if self:line_indent () <= indent then
return true
end
end
return false
end
function program_obj:jump_to_line (line)
self.program.cur_line = line
self.program.cur_cell = 0
end
function program_obj:next_command ()
if not self.program then
return nil
end
local cmd = self:next_cell (false)
while not cmd or not cmd.command do
if not self:next_line () then
return nil
end
cmd = self:next_cell (false)
end
return cmd
end
function program_obj:push_loop (line, indent)
if #self.program.loops < 1 or
self.program.loops[#self.program.loops].line ~= line then
self.program.loops[#self.program.loops + 1] =
{
line = line,
indent = indent,
counter = 0
}
else
self.program.loops[#self.program.loops].counter =
self.program.loops[#self.program.loops].counter + 1
end
end
function program_obj:pop_loop (line)
if #self.program.loops > 0 and
self.program.loops[#self.program.loops].line == line then
table.remove (self.program.loops, #self.program.loops)
end
end
function program_obj:loop_counter ()
if not self.program then
return nil
end
if #self.program.loops > 0 then
return self.program.loops[#self.program.loops].counter
end
return 0
end
function program_obj:loop_indent ()
if not self.program then
return 0
end
if #self.program.loops > 0 then
return self.program.loops[#self.program.loops].indent
end
return 0
end
function program_obj:loop_line ()
if not self.program then
return 0
end
if #self.program.loops > 0 then
return self.program.loops[#self.program.loops].line
end
return self:lines ()
end
function program_obj:check ()
if not self.program then
return nil
end
self:init ()
for l = 1, self:lines () do
self:next_line ()
local indent = self:line_indent ()
if indent then
for c = indent, self:cells () do
local cmd = self:next_cell ()
if cmdstr (cmd):len () > 0 then
if not check_table[cmd.command] then
return false, "out of place item"
end
local result, msg = check_table[cmd.command] (self)
if not result then
return result, msg
end
end
end
end
end
self:init ()
return true
end
function program_obj:run ()
local cmd = self:next_command ()
if not cmd and self:loop_indent () > 0 then
self:jump_to_line (self:loop_line ())
cmd = self:next_command ()
end
while cmd do
local indent = self:line_indent ()
if indent <= self:loop_indent () and self:line () > self:loop_line () then
self:jump_to_line (self:loop_line ())
else
if run_table[cmd.command] then
if run_table[cmd.command] (self, self.pos) then
self:serialize ()
return not self.stopped
end
end
end
cmd = self:next_command ()
if not cmd and self:loop_indent () > 0 then
self:jump_to_line (self:loop_line ())
cmd = self:next_command ()
end
end
self:serialize ()
return false
end
utils.program = program_obj
--