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

[Critique Request] Please tear apart my code!

Started by OutlawBlue9, 21 April 2012 - 06:06 AM
OutlawBlue9 #1
Posted 21 April 2012 - 08:06 AM
EDIT: Updated code down below! or http://pastebin.com/0ENkZaH1


The below post occurred in a separate [Question] thread but was OT a bit so I moved it to a new one. I started playing around with CC and Lua two days ago to re-teach me some coding basics. I first started coding 15 years ago when I made TI-83 games and utilities in middle school and then took two C++ courses in high school. Since then I haven't touched a compiler so I'm virtually starting from scratch. I decided to try my hand at creating what I intend to make a fairly indepth program that uses a wide variety of different programming techniques and functions in order to learn along the way. The program will act primarily as a user database with passwords and permissions unique to each user. Once logged in based on the permissions the user will eventually have access to a menu of options (open a specific door, send items through a different pipe, create new users, delete users, change password, etc etc). Currently it is in the fairly early stages but since OminousPenguin stepped up and gave me some advice in another thread I felt now was a good time as any to open the floor up to the professional's critiques and suggestions to make my code more efficient and quell any bad habits! One obvious thing: I know I am not defining any of my variables as local and this is generally bad practice but I'm using it as sort of a debug technique so once the program as crashed I can look behind the scenes using the lua program in CC to check variables / tables etc. If you have a better way of doing this let me know though but I do understand that unless necessary a variable should be defined as local! :)/>/> Also, I'm not necessarily asking you to just make my code better…. I'd prefer it if you started out with theory and pointing out an example of where I could improve and maybe some vague recommendations so I can try and figure the specifics on my own.

Mods: This MAY be the wrong forum to post in? If so sorry and please feel free to move it if you feel necessary! Maybe it should be in the programs section I don't know but I figured most of the Pro's who I want to critique this would be here.

Here is the original post (of which I asked to be continued in here) that started this:

@OminousPenguin -

As this program is MOSTLY (although having a neat little user database on my server is also useful) to teach me to program after a 15 year hiatus I welcome any and all suggestions / tips to improve efficiency or what have you! I was going to actually create a thread asking for just pure and simple recommendations such as this as I'm always a fan of limiting the focus of individual threads but thank you for your input none the less. But while we're here lets go down your suggestions:

1) I see your point and calling a function too often with no actual change or results is never a good idea and it definitely seems that way now, such as here:
Spoiler

function home()
homescreen()
if #userlist == 0 and status == "Logged Out"
  then
   admin = true
   function newuser()
   homescreen()
	if admin == true
	 then
	  print("Please register Admin:")
	  write("Enter Username:")
	 else
	  print("Please register User:")
	  write("Enter Username:")
	end
Obviously homescreen() has just been called at the beginning of home() and then immediately after in newuser() without any screen changes, however I plan to add in functionality that will call newuser() (and other such functions) at other times which will require a homescreen() flush so to speak. Granted I may have missed an occurrance that is currently and will forever be superfluous so feel free to cite specific example! Or maybe I just missed your point entirely :)/>/> Aside: It may look strange because it wasn't till after I wrote most of this code that I realized you do not have to call a function immediately after ending it (derp!) which is how every example worked in the tutorial I looked at so the function organization is a bit funky. If I were to re write this code (and I probably will do this at some point) I would have functions such as newuser() and login() defined outside of any other functions.

2) Thank's for the heads up! I had not noticed that (mostly because the code as it was wouldn't even run that far and I was preoccupied with the previous error!) but it is now fixed! login() is now called after the nil check and it works as intended (for the most part; see earlier section to MysticT about new bug…)

3) Valid point. changed the call to x in line 85 to 3-a which is all x is anyways as you point out.

4) I'm guessing you mean that some of my if statements could be made shorter with a for or while loop? Correct me if I'm wrong. If that is the case, in my mind the if statement seems simpler I suppose? But lets challenge that. Give me an example of where you would use a loop instead of what I have but don't say anything about how you would implement and I will challenge myself to make it more efficient by using a loop (best way for me to learn eh?)

Again thanks for your suggestions! I always welcome constructive criticism! Especially early on to help defeat long term bad habits. I might make this into a separate "Tear my code apart!!" thread in order to avoid OT discussions so reply there if you could!

And here is my code in all it's ugly glory:
Spoiler

version = "v.01"
userlist = {}
status = "Logged Out"
currentuser = "New User"
a=0
function homescreen()
  term.clear()
  term.setCursorPos(1,0)
  print(string.rep("*", 50))
  print("*Running OutSec "..version.."					  ".. textutils.formatTime( os.time(), false)) --currently displays Minecraft time, not real time
  print("*Status: "..status)
  if status == "Logged In"
   then
	term.setCursorPos(20,3)
	print("User: " ..currentuser)
  end
  print(string.rep("*", 50))
  term.setCursorPos(1,10)
end
homescreen()

function home()
homescreen()
if #userlist == 0 and status == "Logged Out"
  then
   admin = true
   function newuser()
   homescreen()
	if admin == true
	 then
	  print("Please register Admin:")
	  write("Enter Username:")
	 else
	  print("Please register User:")
	  write("Enter Username:")
	end
  
	newusername = io.read()
	write("Enter New Password:")
	newpassword = io.read()
  
	homescreen()
	print("Username: "..newusername)
	print("Password: "..newpassword)
	print("Confirm?[Y/N]")
	local event, k1 = os.pullEvent("key")
  
	if k1 == 21 --y
	 then
	  table.insert(userlist, newusername)
	  userlist[newusername] = {}
	  table.insert(userlist[newusername], "password")
	  userlist[newusername].password = newpassword
	  table.insert(userlist[newusername], "adminstatus")
	  userlist[newusername].adminstatus = admin
	  home()
	 elseif k1 == 49 then --n
	  newuser()
	end
   end
   newuser()

  elseif #userlist > 0 and status == "Logged Out" then
   function login()
	homescreen()
	write("Please enter Username:")
	loginid = io.read()
  
	if userlist[loginid] == nil
	 then
	  print("Invalid Username. Please try again.")
	  sleep(2)
	  login()
	end
   end
   login()
  
   function password()
	homescreen()
	if a>0
	 then
	  term.setCursorPos(1,9)
	  print("Incorrect Password. ("..3-a.."/3 attempts remaining.)")
	end
	write("Please enter password:")
	loginpass = io.read()
	if loginpass == userlist[loginid].password
	 then
	  status = "Logged In"
	  currentuser = loginid
	  print("Entry Authorized.")
	  sleep(2)
	  homescreen()
	 else
	  a = a+1
	  if a < 3
	   then
		password()
	   else
		a=0
		homescreen()
		print("Too many failed attempts. Please wait.")
		sleep(2)
		login()
	  end
	end
   end
   password()
end
  
end
home()

Some things that I know are wrong / not working as intended that maybe can get the ball rolling. I haven't really tried working / searching for a solution to these as they are secondary or tertiary to me vis a vi functionality so I've held off in favor of larger problems so far so I'm sure a solution wouldn't be THAT difficult to search for and find but I'm adding them here in case you have any ideas

1) in homescreen() lines 8-21: I intend to make a border at the top of the screen made out of *. There has to be a better way of the awkward and manual way I go about it here.

2) in homescreen() line 12: I call os.time() and then print it. This however is Minecraft time and I would prefer it to call the realworld time. I've seen and tried some examples (such as one that accesses a php website and takes the time from there) that didn't work so well .

3) In home() beginning at line 48: I ask it for a key input for a Y/N menu which works, but when it goes to the next io.read() whichever key I pressed is there in the command prompt. Not sure why this is??

I'm more looking for big picture things (Some of OminousPenguin's are great examples; use more loops!!!) that fix bad habits but I just put these up here to spur discussion. Thanks in advance!!
Advert #2
Posted 21 April 2012 - 01:21 PM
My general suggestions:
Use local variables when you don't need globals.
Move the homescreen() call down above your home() call, since having random code floating around doesn't help much.
You don't need to call homescreen(), since it's called at home().

I'll edit this and add a few more suggestions in an hour or two, but this is what I saw from a glance.
OminousPenguin #3
Posted 21 April 2012 - 03:41 PM
1) in homescreen() lines 8-21: I intend to make a border at the top of the screen made out of *. There has to be a better way of the awkward and manual way I go about it here.

2) in homescreen() line 12: I call os.time() and then print it. This however is Minecraft time and I would prefer it to call the realworld time. I've seen and tried some examples (such as one that accesses a php website and takes the time from there) that didn't work so well .

3) In home() beginning at line 48: I ask it for a key input for a Y/N menu which works, but when it goes to the next io.read() whichever key I pressed is there in the command prompt. Not sure why this is??

I'm more looking for big picture things (Some of OminousPenguin's are great examples; use more loops!!!) that fix bad habits but I just put these up here to spur discussion. Thanks in advance!!

1. Not really. If you want to write 50 asterisks then write(("*"):rep(50)) is what you need. Nothing superfluous about that.

2. That's the only way I know of. How didn't it work?

3. That's because pressing any key fires a 'key' event, but pressing an alphanumeric key also fires a 'char' event. You are capturing the key event but not the char event.


It may look strange because it wasn't till after I wrote most of this code that I realized you do not have to call a function immediately after ending it (derp!) which is how every example worked in the tutorial I looked at so the function organization is a bit funky. If I were to re write this code (and I probably will do this at some point) I would have functions such as newuser() and login() defined outside of any other functions.

I'd suggest doing that before proceeding. It'll mostly just be cutting and pasting your function definitions.
Once you've done that it'll probably be easier to see the flow of the program.

Obviously homescreen() has just been called at the beginning of home() and then immediately after in newuser() without any screen changes, however I plan to add in functionality that will call newuser() (and other such functions) at other times which will require a homescreen() flush so to speak. Granted I may have missed an occurrance that is currently and will forever be superfluous so feel free to cite specific example!

Line 20 and 23, and two of the three calls in password can be removed, if you turn it into a loop.

A few others I would argue are not user friendly in that they prevent the user from seeing their previous incorrect input.

As for loops. The password function would be a good one to tackle first. You asked for high level theory rather than specifics so here goes:
Loops are obviously good for when you're repeating the same thing several times.
So think about what it is you want to keep doing and what the conditions are for stopping repeating it.
Extra hint:
SpoilerYou want to keep asking for a password until they give the correct one or they run out of tries.
OutlawBlue9 #4
Posted 21 April 2012 - 07:34 PM
@Advert Thanks for the suggestions! I'm aware of the local vs global but I've been using local variables in this specific program just because I like being able to pop open lua.lua in CC and still have access to my variables for debugging / learning purposes. But yes; definitely for any real program I would use local whenever possible. Thanks for the heads up on that homescreen() call; I have deleted it. I look forward to any other suggestions you have!

@OminousPenguin

1) Well that sucks. Even the left and right borders? I just feel that adding two extra asterisks and 50 spaces to every line is just so…… inelegant and inefficient…. but oh well. I guess that's why we have higher level programming languages?

2) Well I mean it "works" just fine…but the only issue is I would prefer it to display the real world time as opposed to the time it is in Minecraft. eventually I want to use this info to also create error and login logs and having real world time is much more useful in that aspect than having what time it is in Minecraft since a day is only 10 minutes or so…..

3) Ah I see. So would something like this solve the problem?


local event, k1 = os.pullEvent("char")
if k1 == "y" then
do whatever
end

4(?) ) Fair enough. I definitely see the benefit of that and I'll go ahead and re-organize it all (not now because its 1:30 in the AM and I'm a lil bit drunk from homemade beer)

5) Excellent. Tomorrow I'll go and look at my code and see if I can make do with what you suggest and hint at here and later I'll post the results.

Cheers and thanks both! Anyone else out there? This is great!
OminousPenguin #5
Posted 21 April 2012 - 10:40 PM
1) Well that sucks. Even the left and right borders? I just feel that adding two extra asterisks and 50 spaces to every line is just so…… inelegant and inefficient…. but oh well. I guess that's why we have higher level programming languages?
use term.setCursorPos(x,y)

for y=1,18
  term.setCursorPos(50,y)
  term.write("*")
end

But I'd recommend against having borders. You're already limited on screen space, why use two columns up with asterisks which don't provide any increase in usability or anything?
Also you'd have to keep adding them as the console got scrolled down with use.

2) Well I mean it "works" just fine…but the only issue is I would prefer it to display the real world time as opposed to the time it is in Minecraft. eventually I want to use this info to also create error and login logs and having real world time is much more useful in that aspect than having what time it is in Minecraft since a day is only 10 minutes or so…..

You said "that didn't work so well" regarding getting the time from a PHP script.

3) Ah I see. So would something like this solve the problem?


local event, k1 = os.pullEvent("char")
if k1 == "y" then
do whatever
end

Yes.
Advert #6
Posted 22 April 2012 - 03:13 AM
Alright, I've had a chance to look over it again now.

I see that you're duplicating a lot of code, to get input. Making a function to do this would make your code clearer.

In your home function, you have a lot of other functions, that don't really serve a purpose, I'd move them out of home(), and send arguments to them instead of using upvalues.

When you're taking a password, I'd recommend using read("*"), which will asterisk out the password, then having the user confirm by typing in the password again.

When logging in, take a username and password regardless. Error at the end with 'Unknown username or password' instead of telling them that the user doesn't exist right away.

At some places, (12-13, 24-25, 48-49, …), you're using 'if <condition> <newline>then<newline>', but on other places you're not including the newline for the 'then'.

To recap: Turn smaller tasks into functions, especially the ones you do often (receiving username or password; you can make a function that takes one input, but you tell it what text to display, e.g local user, pass = getInput("Username: "), getInput("Password: ", "*").

And try using function arguments. You can pass a table around to simplify the arguments, and store related stuff in that table, like username, what the user can do, etc.
OutlawBlue9 #7
Posted 22 April 2012 - 07:44 PM
Whew! Ok I did a lot of work on the program and did most of your homework :)/>/>

@OminousPenguin:

1) I see your point regarding the borders but I do feel it necessary to differentiate between the top info bar and the actual UI. Got rid of most of the border but left in the bottom line of asterisks.

2) Ah! Sorry I misunderstood you. I for the life of me can't remember what the exact code I used was and where I found it and this was before I started seriously doing version control so I can't tell you a) how it looked and :)/>/> what the error that caused it was Sorry : If you have an easy remedy off hand that you know feel free to tell me otherwise this can wait as its a low priority atm.


@MysticT

1) Good idea. Here is what I created based on your tips and it works pretty good

Spoiler

function getInput(intype, character)
write(intype)
input = read(character)
return input
end

Usage example: username = getInput("Username:")

2) Same thing thing OminousPenguin mentioned. I had a poor excuse for it but its moot now. As you'll see later I've re-organized my entire code to be more "functional" (#iseewhatyoudidthere)

3) Although I don't really see a reason to ask for a confirmed password so much as I ask later to confirm both username and password I didn't implement that part of the suggestion but I definitely implemented the read("*") part. Thanks!

4) I suppose that would be more secure. Haven't implemented yet but added it to my todo list as it would be fairly easy.

5) By the time I got to this one I had already re-organized so the lines you gave were pretty useless at that point but I searched through what I have now and couldn't find any. I do have my elseif's with then on the same line but that is because in my mind I organize that as attached to the original if statement and it looked and made more sense in my head. One of those personal quirks I suppose.

Thanks for the suggestions so far!


@All

Here is a pastebin of where I'm at so far: http://pastebin.com/0ENkZaH1

I've taken the suggestion for more loops so I no longer call a function inside of itself (except for in newuser() which I just caught and will change up later). I've also made a massive re-organization and added more functionality. Thank you so much for the feedback so far and of course, further feedback on v.2 is encouraged and requested!

One specific issue: I have a function to display the users on the list but to do this I had to create a separate table along side userlist[] that contained the list of users as a string as opposed to a table full of tables because textutils.tabulate() does not work for a table filled with tables. Do you know any way to display the names of all the tables inside a table?