Part 1:
http://forums.achaea.com/discussion/4584/javascript-for-nexus-newbies-part-1Part 2:
http://forums.achaea.com/discussion/4596/javascript-for-nexus-newbies-part-2-my-first-not-so-simple-aliasToday, we're going to take the Oocly package we made last week and we're going to rearrange things. We're not going to add any new functionality - instead we're going to focus on organising it into a more sensible form (programmers call this
refactoring).
We're going to learn a basic framework for making packages that will make them significantly easier to write, modify, etc. Thus far we've been in that weird phase of learning where what we're doing technically works, but isn't actually how "the pros" do it. After this week, your packages will look basically like my packages. Edit: After the beginning of next week. We have one more thing to cover.
This is going to be a hard week. Take it step by step. This is where we hit the stuff that's going to be a little more tough to understand. Take it step by step.
Once we've gotten our hands
dirty with all of this, we'll be well-positioned to get our hands
filthy next week on a more substantive problem involving GMCP and some actual combat utility.
Before we start, make sure you've finished Part 2. Everything we're going to do today uses Part 2. You should have three aliases done for OOC tells, OOC replies, and OOC retells.
Quick Tip
You may not know this, but your browser probably has a way for you to just type in JavaScript and see what happens without having to make and run aliases or whatever. This can be really useful for testing basic things about JavaScript that you're not sure about.
In Chrome, ctrl+shift+j will open the console (you may have to click the
Console tab at the top) and then you can just type things in and it'll act just like they were part of the page's JavaScript. Type 5+5, hit enter, and you'll get 10. If you want to print something to the console, just pass a string to the console.log function just like we did with send_command! Handy!
Variables
We're going to start today by talking about variables.
There are a lot of analogies for variables. None of them are perfect. We'll start ours a little lower than usual though and I think that might make it a little less imperfect.
First, we need to talk about
memory. When we talk about memory, we're not talking about your hard drive, we're talking about your RAM. Your computer's memory is a lot like a post office with a million little slots (actually, it has about a
billion little slots per gigabyte of RAM in your computer!).
Every one of those slots has an address, exactly like a post office box. If you want to get at the stuff in that box, you tell the postal worker that address. If you're going to need to get at the stuff in the box later, you need to remember that address (How do you remember the address? Why you put it in a post office box of course!).
But we have some problems. All the boxes are pretty small and some of our stuff is going to take up more than one box. We really don't want to deal with that. We also don't want to have to type or remember the addresses since they're really long and not very meaningful. We don't want to deal with phone numbers, we want our phone to have a named contact so we never have to think about whatever the random number the phone actually uses to call them is.
That's where the concept of a variable comes in. When we use a variable, we'll ask the post office to store something for us, tell it the name we want to use, and they can get it for us if we give them that name.
The way we tell the post office we want them to get ready to store something is by
declaring a variable. Declaring a variable is like signing up for this storage service and the only thing we're required to put in the form in JavaScript is the label we're going to use for the thing we're storing. We do this in JavaScript with
let and it looks like this:
let my_first_variable;
The main restriction is that our labels can't have spaces in them. You can write it like myFirstVariable instead of my_first_variable (or almost any other way you want that doesn't use spaces), but almost everyone agrees the underscore is easier to read. Variable names shouldn't be capitalised - you can technically capitalise them, but every programmer reading your code will be confused because capitalised names are conventionally used for only one very special kind of variable that we'll probably never talk about.
We can do a few at a time too, we just stick commas between them:
let my_first_variable, my_second_variable;
If you're following along in the console right now and you did both of these, JavaScript probably won't complain at you. It should. When you go to do the second line after the first, it should tell you "hey, you already told me about the my_first_variable label, you can't sign up with the same name twice!". Unfortunately right now it just
silently ignored that attempt to redeclare my_first_variable. One of the reasons we're reorganising things is that it's going to let us turn on a feature in JavaScript that makes it tell us when we're doing things wrong instead of silently pretending like we didn't do them (we'll see how to turn this on at the beginning of next week).
Once we've got our variable declared, we can tell the post office what to store for us under that label. We do this with a normal equals sign. Let's store the number 5 under the label my_first_variable and the string "say Hello!" in my_second_variable.
my_first_variable = 5;
my_second_variable = "say Hello!";
We can change what we've got stored too. Let's add 1 to our 5:
my_first_variable = my_first_variable + 1;
That says: I want to store something under the label my_first_variable and the thing I want to store is whatever was in my_first_variable + 1. We only declare a variable once, but we can
assign to it like this as many times as we want. This particular assignment - taking the value of a variable, adding something to it, and then storing that in the same spot under the same label is a pretty common thing to do, so JavaScript actually has a shortcut to save you some typing (there are a few similar shortcuts like this for the other math operators):
my_first_variable += 1;
That means the same thing as my_first_variable = my_first_variable + 1;
Before we continue, let's get one thing straight: those words are instructions, they aren't the variable. If you run your script, then delete that line, that doesn't undo what you've done. You're handing the post office worker a scrap of paper that says what the post office worker should do. Tearing up the piece of paper after you've given it to the post office worker doesn't tear up the thing you had the post office worker do. That line of text in your script is not your variable, it's just an instruction for what to do with your variable - the actual variable is invisible and lives inside your computer and the only way you can interact with it is to ask the post office worker to do something with it for you. Don't get confused and confuse a recipe for an apple pie with the actual apple pie it makes.
But we can store more fancy stuff too. Remember our function send_command? Well "send_command" isn't actually the function. The function itself is a list of things to do. "send_command" is just the
label on that function that lets us ask the post office worker to go get it for us. Let's ask the post office worker to go get that function for us and to store it under a new label. To save ourself some typing, we can actually do the first assignment to a variable at the same time as we declare it (this is called
initialising a variable):
let my_third_variable = send_command;
Now the function that was labelled "send_command" is
also labelled "my_third_variable". Don't get confused here, it's not a copy of the function, it's the same function! We just asked them to put a second label on the boxes where the function is stored.
And to show that there's no magic here, let's use this new variable just like we used send_command, and we'll use our other variable just like we've been using a regular string:
let my_third_variable(my_second_variable, true);
This is exactly the same thing as send_command("say Hello!", true);
Remember that we only ever use
let once to declare things. A common beginner mistake is confusing initialising variables and assigning to them. When we assign to them, we just use the variable name and an equals sign. We only use
let when we want to declare a variable for the first time (and initialising is just a shortcut for declaring and then immediately doing the first assignment).
Make sure this all makes sense before moving on.
Some Complications
Don't worry too much about this, but I want to forewarn you about some things you might run into in other people's code.
We're using
let here. let is relatively new to JavaScript and a lot of older code will have
var in it instead. var does some really strange things and let is better and more intuitive in every way. There is absolutely no reason in your scripting to use var. Just don't do it. If you see a var in someone else's code, just think of it as let. If it doesn't make sense, either just accept it and move on or google the horrifying truth about how var actually works and pity the poor souls who wrote code before you.
You might also see
variables declared with
const. const is also new and it means the same thing as
let except you're only allowed to assign to it one time. It's a
constant. So you might do something like const PI = 3.14; because you know pi will never change. If you declare something with const and try to change it, Nexus will yell and you because you told it you weren't going to change it and then you tried to change it. This is yet another safety you can choose to use to protect yourself from yourself. It's a good habit to get into. Due to some old conventions, many people write the names of constants in all caps so they can remember they're constants, though you don't have to. If you get deeper into programming, you'll discover a whole world of people who think you should only
ever use constants (never normal variables) called
functional programmers (spoiler alert: these people are absolutely right - but you'll be writing simple enough things in Nexus that you don't need to worry about it).
Cleaning up Oocly Step 1
Let's use variables to make our Oocly functions more readable.
The ideal situation is to have each line of our program do
one thing. That makes it a lot easier to understand what the hell is going on when we come back to this a year from now or send it to someone else.
Our OOC tell looks like this right now:
send_command("tell " + args[1] + " *achaean OOC: " + args[2][0].toUpperCase() + args[2].slice(1), true);
We're doing a lot here: we get the first letter of our message, we uppercase it, we slice the rest of the message, we glue those together, we get the recipient, and we glue it and some other stuff on. A capable JavaScript programmer can pretty easily tell what's going on here, but it's still a lot for one line. It would be nice to split it up a little:
let recipient = args[1]; // Now we don't have to remember that args[1] is the recipient, we can just use a name that makes sense and remember that instead
let message = args[2]; // Same for the message
let first_letter_of_message = message[0];
first_letter_of_message = first_letter_of_message.toUpperCase(); // There's no let here because we're just assigning, we already declared our variable (when we initialised it in the last line)
let rest_of_mesage = message.slice(1);
message = first_letter_of_message + rest_of_message; // These variables are strings, so the + between them concatenates them into one string
let to_send = "tell " + recipient + " *achaean OOC: " + message;
send_command(to_send, true);
Make sure you understand this.
That's a little extreme, but it shows how we can use variables to break things up. How much you want to break things up like this is up to you, but doing this lets us focus on doing
one thing at a time. When we designed this script, we broke our problem down into progressively smaller pieces: first we figured out how to send a string, then we figured out how to glue our little strings together into the whole string, then we figured out how to split off the first letter of something, then we learned to uppercase it, then we learned to split the rest of the letters off, etc. Using variables like this lets us write our code (and
read our code) in a way that reflects that.
Don't fall into the trap of thinking that putting it all on one line is better. This is the classic newbie mistake. You might worry that all those extra variables are a "waste", but that's like Bill Gates worrying that he's wasting pennies. It isn't worth his time to avoid wasting pennies. It isn't worth your time to avoid making "extra" variables. You might think you're gaining efficiency, but the comically tiny gain in efficiency will never amount to even a single
second spent looking at your code all crammed into one line and trying to understand what it's doing.
You're also likely to see another, similar style to this where all of the variables for some function or script get declared together at the top instead of declaring them just before you actually need them. Our code would look like this:
let recipient, message, first_letter_of_message, rest_of_message, to_send;
recipient = args[1];
message = args[2];
first_letter_of_message = message[0];
first_letter_of_message = first_letter_of_message.toUpperCase();
rest_of_mesage = message.slice(1);
message = first_letter_of_message + rest_of_message;
to_send = "tell " + recipient + " *achaean OOC: " + message;
send_command(to_send, true);
I'll leave it up to you which style you think is easier to read. Sometimes people do a combination where they declare
and initialise all their variables at the beginning, but always before doing anything with them. Any of these are totally valid options.
First Exercise
Do this same thing for OOC reply and OOC retell. Make them as easy to understand as you possibly can. Add some comments too - put them on any line where you think that six months from now you might not remember how it works.
Comments
Functions and Functions
You are now ready to wield true power.This is where you will learn the inmost mysteries of the most fundamental concept in all of programming: functions.
We've done a lot with functions so far. In fact, everything we've done has been calling functions! We've just been calling send_command and passing it some arguments. Remember that we call functions by putting some parentheses after their names and putting any arguments we want to pass them inside the parentheses, separating the arguments with commas. Remember also that we saw with variables that "send_command" is just a label for the function, it isn't the function itself, just like the words "Recipe for Apple Pie" are not the recipe for an apple pie, they're just a label for that recipe.
Our goal here is going to be to rewrite our scripts as functions and just have our aliases call those functions. That way if we want to work on our script, we don't have to go between all these different aliases - all our code will be in one place.
Ugh, "Functions"
Remember, our functions are like recipes. But before we can use them, we need to give them to the post office worker to store. We can't very well ask the post office worker to follow our recipe if we haven't given it to them yet!Our aliases run scripts when the aliases fire. What we need is a way to just run a script when Nexus starts up. That way when Nexus starts up, we'll hand the post office worker all of our recipes, then when our alias fires, we'll just tell the post office worker which recipe to use (and also hand them some ingredients).
The way we do this is with the terribly confusing "Functions" built into Nexus. These are not JavaScript functions. The "function" we want is onLoad. A "function" with this name magically runs its script when Nexus starts up. Don't think about how or why. Make a "function" (the same way you make a new Alias or Trigger) and name it onLoad. Our main script is going to go into this "function" and every package you make from now on will have an onLoad "function". This is the only kind of "function" you'll ever make. Now forget that you've ever heard of these "functions". The only thing you'll ever use these "functions" for is onLoad.
Yay, Functions!
Now we're going to move our OOC tell to the onLoad.I'm going to use the last version we developed, and our function is going to look like this:
Let's break down what we did here.
First, we told the post office worker that we're filing this recipe away under the label ooc_tell.
Next, we said the thing we're filing away is a function. The word function here is a special word like let (these special words are called "keywords" or "reserved words"), but instead of declaring a variable, it defines a function.
After that, the first part of a function definition is a list of ingredients separated by commas. Whenever want to send an ooc_tell, we need to know who the recipient is and what the message is. These are called parameters and you can think of them like the opposite to arguments (you'll often see people just call these arguments too, we'll avoid that since it's confusing). When we call a function and pass it arguments, those arguments are passed in with the names we gave the parameters. So when we call ooc_tell, the first argument we pass it is going to get labelled "recipient".
So when we call ooc_tell("Sarapis", "Hello!"); we're saying "follow the ooc_tell recipe using "Sarapis" as your recipient and "Hello!" as your message". Any time it sees recipient or message in the body of the function, it'll use those. If you think about our variables, this should make sense - our function just puts some labels on the arguments it gets passed (according to its parameters) so we have a way of refering to them inside our recipe. We don't have to declare them - parameters are automatically declared for us.
That label only exists inside our function call. In fact, none of the variables inside our function exist outside our function. Imagine we do this:
Nexus will tell us it has no idea what the heck "message" is. And the same thing will happen if you use to_send instead of message there.
This is because the variables we declare with let are what are called local variables. And that's the only kind of variables we'll ever use. There's another kind called global that are less like not using the safety and more like pointing the gun directly at your head.
Think of that ending brace, the } that ends our function body, as an instruction to the post office to get rid of any labels we used in the recipe. That } at the end of the recipe means to put everything we used for our recipe back away so our counter is back to the way it was before we put out all our bowls and our rolling pins and all that.
And that goes for the parameters too. They're just regular local variables too. The difference is that they're special because they get their value when the function is called instead of when we write down the recipe. They're what let us use the same recipe with green apples and with red apples.
So after our parameters (our ingredients), we have a some braces and a bunch of stuff inside the braces. The stuff inside should look familiar. The only difference is that we don't need to declare or assign to recipient or message at the beginning anymore because that happens automatically since they're parameters.
Important note: Pay attention to the spacing and indentation there. The customary intendation is four characters (Nexus will automatically do this if you hit tab and will even do a lot of auto-indenting for you). JavaScript doesn't require any indentation. In fact, it doesn't require any line breaks at all - nothing is technically stopping you from writing that entire function out as a single line. The only thing stopping you is that you're not an idiot. You're always going to use indentation exactly like that. You'll always use spaces around an = or a + too, even though you don't actually need to. You'll do these things because without them, anyone else who sees your code will hate you (including future-you) and because without indentation, you're not going to have any clue what's going on once you start putting functions inside functions and other things like that, which is a thing you will eventually want to do (yo dawg I heard you like functions).
The reason you like indenting in this way so much is that it makes it really clear where variables are "visible". Code can always make use of any variable declared with the same or less indentation (of course you still can't use things before you've said what they are!). So we can do this, for instance:
Since our language variable is declared at a the same or a lower level of indentation than the stuff in our function, we can use it inside our function. Now it's way easier to change what language our OOC tells are in.
Now we need to change our OOC Tell alias to actually use this function. We'll change it to just call this function and pass it the right arguments. All it'll need to do now is: ooc_tell(args[1], args[2]);
When it fires, it'll call the ooc_tell function, pass it args[1] as the first argument which our first parameter in the function definition says should be labelled recipient, and pass it args[2] as the second argument which our second paremeter in the function defintion says should be labelled message.
Oops
That won't work. Now we're running into another Nexus complication.Different scripts have different "namespaces" (think of this like different post offices). So our alias's script actually has no idea what "ooc_tell" is.
The way around this is going to seem sort of mysterious for now, but we'll start next week by working out how and why this works.
For now, we're going to change our script to look like this:
Every post office has access to a post office called "client". We're asking them to open a new branch of client called the "oocly" branch and store all of our stuff there. Notice how we don't have to do any of this nonsense for the stuff inside our function - that stuff only exists temporarily when the function gets called anyway. Don't worry too much about the fact that there's no let here. We'll explain how and why this works next week.
We'll have to change our alias script accordingly too: client.oocly.ooc_tell(args[1], args[2]);
And there we go!
These exercises are especially important. If you're the kind of person who usually skips exercises, don't skip these. If you've understood everything here, these will all be very easy and fast. The last one in particular is absolutely crucial. If you can't do it, you are not ready to move on. It will teach you something that you absolutely need to know going forward.
Second Exercise
Put OOC Reply and OOC Retell into this onLoad too. Throw a few linebreaks between the function definitions so it's easy to read everything. Now all our functions are in the same place and it's really easy to deal with the whole package at once!Third Exercise
Remember how we decided that the language of our tell should be separated out of OOC Tell instead of having it hardcoded in? Now it's a lot easier to change in case we ever needed to for some reason. Take out the hardcoded language in reply and retell too. Now we can change the language for all three of them at once just by changing the language variable! Nice!Fourth Exercise.
Capitalising the first letter of a string is something you might want to do often. It's sort of silly that we've written that out three times here for instance. Really, what we ought to do is turn that into its own function and just call it in each of our other functions. So we'll have a function that takes one parameter (which will be a string) and returns us the same string with the first letter capitalised. So instead of doing all this stuff to "message" in our ooc_tell function, we'll just do: message = client.oocly.capitalise_first_letter(message);To do this you'll need to know how to return something from a function. That's easy, just use the keyword return. A function that takes two numbers and returns their sum looks like this:
(I could also have just done return first_number + second_number;)
Make sure you can do this exercise. If you can't, this is the time to ask for help.
Next Week
It looks like we've got one more organisational thing to talk about next week (the "advanced" thing I promised last week) and we'll also get started on a project that's actually more useful than just saving you some typing in your tells.Near the end of the first section of Variables, there is this line:
It should say:
Sorry for the confusion.