This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
steven6282's profile picture

[RESOLVED] Start a program with variable arguments

Started by steven6282, 14 May 2014 - 12:10 PM
steven6282 #1
Posted 14 May 2014 - 02:10 PM
EDIT: I resolved this myself between posting this and the moderator approving it. I just needed to use the textutils.unserialize() function.
This is my new final working code:

args = {...}
sensorPosition="top"
xMin=-5
xMax=5
yMin=-5
yMax=5
zMin=-5
zMax=5
outputDirections={front=false}
directions={"front","back","left","right","top","bottom"}

if args[1] ~= nil then
		args[1]=textutils.unserialize(args[1])
		if args[1].xMin ~= nil then xMin=args[1].xMin end
		if args[1].xMax ~= nil then xMax=args[1].xMax end
		if args[1].yMin ~= nil then yMin=args[1].yMin end
		if args[1].yMax ~= nil then yMax=args[1].yMax end
		if args[1].zMin ~= nil then zMin=args[1].zMin end
		if args[1].zMax ~= nil then zMax=args[1].zMax end
		if args[1].output ~= nil then
				outputDirections={}
				n = 1
				while args[1].output[n] ~= nil do
						thisInvert=false
						if args[1].output[n]=="table" then
								if args[1].output[n].invert ~= nil then thisInvert=args[1].output[n].invert else thisInvert=false end
								outputDirections[args[1].output[n].dir]=thisInvert
						else
								outputDirections[args[1].output[n]]=thisInvert
						end
						n=n+1
				end
		end
		if args[1].sensor ~= nil then sensorPosition=args[1].sensor end
else
		print("No arguments specified")
end

p=peripheral.wrap(sensorPosition)

function setOutputs(toState)
		local out=1
		while directions[out] ~= nil do
				dir=directions[out]
				if outputDirections[dir] ~= nil and outputDirections[dir] == true then
						if toState==false then
								redstone.setOutput(dir,true)
						else
								redstone.setOutput(dir,false)
						end
				elseif outputDirections[dir]~=nil then
						redstone.setOutput(dir,toState)
				else
						redstone.setOutput(dir,false)
				end
				out=out+1
		end

end

function anyKeyExit()
		local runScript=true
		while runScript==true do
				local event,param1,param2,param3=os.pullEvent()

				if event=="key" then runScript=false break end
		end
end

function main()
		while true do
				if (p.getPlayerNames()[1] ~= nil) then
						players=p.getPlayerNames()
						playerFound=false
						n=1
						while players[n] ~=nil do
								playerd=p.getPlayerData(players[n])
								if playerd.position.y>=yMin and playerd.position.y<=yMax and playerd.position.x>=xMin and playerd.position.x<=xMax and playerd.position.z>=zMin and playerd.position.z<=zMax then
										playerFound=true
								else
										--print(playerd.position.x,",",playerd.position.y,",",playerd.position.z)
								end
								n=n+1
						end
						setOutputs(playerFound)
				else
						setOutputs(false)
				end
		end
end

setOutputs(false)
parallel.waitForAny(main,anyKeyExit)
setOutputs(false)








Hello everyone. Hoping I can get a little help with what I'm trying to do here that doesn't seem to be working. First off a little about my setup. I'm playing on a server running The Crack Pack from ATLauncher.com It has computercraft 1.58 and OpenPeripherals (among many other mods) atm. I am working on my first computercraft script and ran into a little bit of a problem. I know of another way I could potentially handle it,b but would rather not do it that way (will get to that part below). Also just want to mention, I've been a developer for 14 years, so I'm not a complete noob when it comes to programming, but I am a noob when it comes to CC programming :)/>

Now then, what I'm trying to do. So in my base I've got lots of doors, and I like to use double doors to make a wider space to walk through. So I've been going around trying to combine computercraft, with an openperipherals sensor, a few logic gates from BC and some structure pipes in order to make an automatic opening door when players are near. Right now I'm just doing this based on any player being near. The sensor has getPlayerNames() and getPlayerData(username) functions that allow me to do this, with getPlayerData returning a position relative to the sensor. What I'm working on is one door_control program for all of my doors instead of having to go in and change hard coded values every time I need to place a new one for another door. Also though, being that my base has multiple levels and such, I'm wanting to restrict the XYZ that opens each said door to make it more likely that the door is in fact going to be used and not opening when someone is underneath it. The route I went to accomplish this initially is after seeing the tables and such in CC, I figured I could just pass one in as an argument so that I could define only the variables I wanted to change from default at the start, however that does not seem to work. Here is my code:


args = {...}
sensorPosition="top"
xMin=-5
xMax=5
yMin=-5
yMax=5
zMin=-5
zMax=5
outputDirections={front=false}
if args[1] ~= nil then
--This isn't working... the arg isn't being passed in as a table
		if args[1].xMin ~= nil then xMin=args[1].xMin end
		if args[1].xMax ~= nil then xMax=args[1].xMax end
		if args[1].yMin ~= nil then yMin=args[1].yMin end
		if args[1].yMax ~= nil then yMax=args[1].yMax end
		if args[1].zMin ~= nil then zMin=args[1].zMin end
		if args[1].zMax ~= nil then zMax=args[1].zMax end
		if args[1].output ~= nil then
				outputDirections={}
				n = 1
				while args[1].output[n] ~= nil do
						thisInvert=false
						if args[1].output[n]=="table" then
								if args[1].output[n].invert ~= nil then thisInvert=args[1].output[n].invert else thisInvert=false end
								outputDirections[args[1].output[n].dir]=thisInvert
						else
								outputDirections[args[1].ouptut[n]]=thisInvert
						end
						n=n+1
				end
		end
		if args[1].sensor ~= nil then sensorPositions=args[1].sensor end
else
		print("No arguments specified")
end
directions={"front","back","left","right","top","bottom"}
p=peripheral.wrap(sensorPosition)
function setOutputs(toState)
		local out=1
		while directions[out] ~= nil do
				dir=directions[out]
				if outputDirections[dir] ~= nil and outputDirections[dir] == true then
						if toState==false then
								redstone.setOutput(dir,true)
						else
								redstone.setOutput(dir,false)
						end
				else
						redstone.setOutput(dir,toState)
				end
				out=out+1
		end
end
function anyKeyExit()
		local runScript=true
		while runScript==true do
				local event,param1,param2,param3=os.pullEvent()
				if event=="key" then runScript=false break end
		end
end
function main()
		while true do
				if (p.getPlayerNames()[1] ~= nil) then
						players=p.getPlayerNames()
						playerFound=false
						n=1
						while players[n] ~=nil do
								playerd=p.getPlayerData(players[n])
								if playerd.position.y>=yMin and playerd.position.y<=yMax and playerd.position.x>=xMin and playerd.position.x<=xMax and playerd.position.z>=zMin and playerd.position.z<=zMax then
										playerFound=true
								else
										--print(playerd.position.x,",",playerd.position.y,",",playerd.position.z)
								end
								n=n+1
						end
						setOutputs(playerFound)
				else
						setOutputs(false)
				end
		end
end
setOutputs(false)
parallel.waitForAny(main,anyKeyExit)
setOutputs(false)

And this is an example of how I would call it for the first door I was trying to set up (this would be in the startup file so that if the computer restarts it auto restarts with the correct parameters).


shell.run("door_control {yMin=-2,yMax=1,zMin=-3.5,zMax=-1.5,output={'back'}}")

Like I said, this isn't working, it treats the entire argument as a single string instead of as a table. Makes sense I guess, but I'm wondering if there is a way around this so that I can do what I'm trying to do and pass in a variable number of arguments.

The alternative I know I could do that I mentioned above would be to have the initial run of the program check for a file for saved settings and if it doesn't exist ask for the settings initially and then save them to the file so that it doesn't have to ask next time it starts up. I may still end up going that route, but for now I want o figure out if there is a way to achieve the above method. It's also partially because of the way I edit the programs. The terminal in game is unbearable to use for any real editing. I choose to instead use VIM and edit the files directly on my server, so it'd be nice to simply be able to copy my door_control program, set a startup script for the computer and then power on the computer and be done rather than have to mess with the in game terminal at all. Although I just had yet another idea, perhaps I can just edit that save file on VIM as well so that it's already there ….. hmm definite possibility, but I'm still interested in finding out if there is a way to do what I initially tried to do above.

Thanks for any help!
Edited on 14 May 2014 - 01:49 PM
CometWolf #2
Posted 14 May 2014 - 04:10 PM
That's… an interesting approach. I suppose it would work, since you're passing the table in as a string, thus unserializing it will indeed result in a proper table. However the normal way this is handled is simply using the numerically indexed args table.

shell.run"program arg1 arg2 arg3"

local args = {...}
print(args[1],args[2],args[3]) --you get the idea

However, given your use of shell.run i can only assume you're calling this program from another program. Meaning that you should instead us loadfile, which allows you to pass it arguments like a regular function.

loadfile("filePath")({xMax = 2,yMax = 3}) -- the second set of () is used to directly invoke the function that loadfile returns. You could alternatively save this function to a variable, and call it later.
theoriginalbit #3
Posted 14 May 2014 - 04:15 PM
which allows you to pass it arguments like a regular function.
you know shell.run can do that too right?

shell.run("someProgram", "arg1", 2, {arg = 3}, true)
shell.run uses os.run, and os.run uses the technique you suggested with the major difference of actually running the program in the 'normal' global environment giving it access to the other APIs and the like.
CometWolf #4
Posted 14 May 2014 - 04:17 PM
Ah yes, my mind lapsed for a second there. You can indeed pass anything you want to shell.run. Just remove the quotes, OP. Personally i avoid shell.run like the plague :P/>
Edited on 14 May 2014 - 02:17 PM
theoriginalbit #5
Posted 14 May 2014 - 04:23 PM
Just remove the quotes, OP.
also not required… well for a table it is, but anything other than a table it isn't required you do that.


shell.run("program hello world")
is functionally the same as

shell.run("program", "hello world")
--# and
shell.run("program", "hello", "world")

Personally i avoid shell.run like the plague :P/>
It does come in quite handy due to the fact that it does resolve the program, so doing

shell.run("shutdown")
is the same as

shell.run("rom/programs/shutdown")
(assuming of course there's no other programs called 'shutdown')
steven6282 #6
Posted 14 May 2014 - 04:39 PM
That's… an interesting approach. I suppose it would work, since you're passing the table in as a string, thus unserializing it will indeed result in a proper table. However the normal way this is handled is simply using the numerically indexed args table.

shell.run"program arg1 arg2 arg3"

local args = {...}
print(args[1],args[2],args[3]) --you get the idea

However, given your use of shell.run i can only assume you're calling this program from another program. Meaning that you should instead us loadfile, which allows you to pass it arguments like a regular function.

loadfile("filePath")({xMax = 2,yMax = 3}) -- the second set of () is used to directly invoke the function that loadfile returns. You could alternatively save this function to a variable, and call it later.

I know the typical structure of an array of args like you mention, but I don't see how that would help in my case where I wanted to have variable args?

For example, on one door control I might want to pass in a yMin and yMax and let everything else be defaults. On another I might want to pass in a xMin and xMax only. If I did it as simply args[1], args[2], args[3], … then I'd either have to parse a string of variable=value to get the variable and value, or I'd have to have a to specify a parameter position for each variable and still be required to pass in values for all variables if I wanted to change only the last one.

I did it the way I did because it's easy to say if table.variable exists then use it, otherwise, don't change the default. Allowing me to pass in whatever args I want in whatever order I want.

As for the shell.run(), I'm using it because it's in my startup file. So yes, I guess the way CC works it seems startup is just a program that runs on start, so I could probably rig it up to use the loadFile as you suggest. I just started with CC so wasn't aware of that function, and when I did a search for how to start a program automatically shell.run() in startup was the prominent result.
Lyqyd #7
Posted 14 May 2014 - 05:43 PM
which allows you to pass it arguments like a regular function.
you know shell.run can do that too right?

shell.run("someProgram", "arg1", 2, {arg = 3}, true)
shell.run uses os.run, and os.run uses the technique you suggested with the major difference of actually running the program in the 'normal' global environment giving it access to the other APIs and the like.

Um. Wat. You should probably review what shell.run does. I could be wrong, but last I checked, table values didn't survive table.concat well, and certainly wouldn't be passed to the program as a table. This would work fine with os.run.
CometWolf #8
Posted 14 May 2014 - 08:43 PM
It appears shell.run even specifically checks the argument types. Specifically it throws this error when you attempt to pass a table.
bad argument: string expected, got table
Only thing i can take away from this, is don't ask me about shell.run lol.
theoriginalbit #9
Posted 15 May 2014 - 03:02 AM
Um. Wat. You should probably review what shell.run does. I could be wrong, but last I checked, table values didn't survive table.concat well, and certainly wouldn't be passed to the program as a table. This would work fine with os.run.
Oh true, I forgot it used table.concat again… pretty sure one of the beta versions dan had removed it, making it accept tables…