ARC is very similar to C and Java. It's a great help to know one of those languages before starting to learn ARC; it can be even beneficial to learn basic Java first since this tutorial is unlikely to be as good as a dedicated Java tutorial.
Compared to mobprogs:
Everything in ARC is an object. An object is a collection of data and code. For example, a character has data like name and her current location. Together with this data is code that only affects character - e.g. a function to calculate the character's weight including all his object.
When you code in ARC, you create classes. A class is a template for an object, like what you edit with e.g. MEDIT becomes the template for the mobile. An ARC class is stored in a file which ends with .arc. The class is then loaded and becomes an actual object.
A central concept within OOP (Object Orientated Programming) is inheritance. Let us say someone has created a generic shopkeeper class, which contains functions for creating an inventory list, buying and selling objects. If you wanted to create your own shopkeeper which was just like this shopkeeper, but bought things from certain races, you would do that by inheriting the old shopkeeper code (expressed in an ARC program by typing inherit oldShopkeeper. This will let you use all the code in the old shopkeeper, without copying it. You then override the function which calculates the price a character will get for an object, and insert an if-check for your race, doubling the price.
If there's a bug in the original shopkeeper, whoever created will fix it, and the fix will automatically carry over to your code; you don't have to do anything.
Let us start with creating our own command on AR. Switch directory to your home directory (cd ~). Create the cmd directory, if it doesn't already exist (mkdir cmd) and cd into it (cd cmd). Then create the file test.arc: edit -c test.arc. Type in the following:
// Show the string the character typed back to her
void execute (Character ch, string args) {
ch.print ("You typed: " + args + "\n");
}
|
Exit the editor. Now type test, followed by some string. If you typed in the above right, you will now see something like:
You typed: <whatever you typed> |
The command interpreter, after trying the MUD created commands, will try to find a command in your personal home directory. This would be e.g. /home/drylock/cmd/test.arc if your name was Drylock, and the command you typed was "test".
The .arc file contains a class definition. When you type in the "test" command, the MUD looks for the above mentioned file and tries to compile the code. Compilation is the process in which the raw text is turned into actual machine code. At that point, all syntax errors are found. Once the class has been compiled once, it does not have to be compiled again until it's changed.
After the class is compiled, the MUD creates an object of that class. Just like a CREAT trigger, it is possible to supply code which will be execute then - there is no need to do that in this case. This is in this case done only once, just after the command is first compiled.
Finally, the command is executed. To do that, the MUD looks for an execute function within the class. The execute function is in fact all we have within the class:
void execute (Character ch, string args) |
A function definition consists of several portions:
When called, a function is given certain arguments. The function is then executed and returns a value. In this case, the execute function is given the character that executes the command and a string - which is what was typed after the command name. This function has the return type "void" - which means it does not return anything.
Let us look close at what basic types ARC makes available. Unlike mobprogs, a variable cannot contain both a number and a string and character: it can only contain one specific type of data. The basic types are:
Now back to the function. The first word on the line is the type of data this function returns. In case of commands, commands never return anything, so the execute function returns nothing.
Then we have the function name. The name is a sequence of letters (and underscores) of any length. You should choose a descriptive name: if you have multiple words, either capitalize them (e.g. destroyObject) or put _ between them (e.g. destroy_object). Don't put words like "the" nor "a" in the names; typical names contain a verb and sometimes a noun (e.g. calculateCost).
In parens, we have the argument list. Each argument is separated by a comma. The argument has two words: the type of this argument, and the name of the argument. The name is internal to this function and is what you will use to refer to the data that is contained within this argument.
Good names are typically nouns. A number of conventions is used for often used variable names: A single Character passed is typical shortened to "ch". If there are multiple characters involved, you should use a mob descriptive name (e.g. transferGold(Character giver, Character taker)). Objects are named "obj". Strings vary: if there are several arguments you should use a short descriptive noun (e.g. "args" in this case). Sometimes you can get away with naming the string "s".
After arguments we have the code. Code is surrounded by { and } which make up a block of code.
ch.print ("You typed: " + args + "\n");
|
This line will call some code which is inside the "ch" - the Character that typed in this command. The function called is "print" which just prints out the string it is given. The dot between ch and print signifies that "print" should be called within this "ch" Character.
After that, we have a single argument surrounded by parens. We have a literal string, "You typed: ". Then we have a plus sign: when you add two strings together, you concatenate them. Then we have "args" which is the argument which was passed to the function (it contains the arguments given to this command).
When a variable is used like that, it is replaced by its current value.
Finally is another string, which contains "\n". The backslash is a special character, an escape code. \n is the escape code for a newline.
When this function runs, whatever is within the parens will get calculated first. "You typed: " will have the arguments added to it, and then a newline is added. This string is then sent to the function "print" within the character.
This causes the string to be printed to the character.
Finally, the statement is ended with a semicolon. Note that we did not end the comment nor the line which declared the function with a semicolon: only actual statements should be terminated with the semicolon.
As mentioned before, you have to declare variables before you use them in ARC. There are two kinds of variables: local and member. Local variables are only valid as long as a function runs: they are created when the function starts, keep their value through the function and disappear entirely when the function ends.
Member variables are like the current mobprogs variables: they keep their value as long as the object that contains them exists.
Let us expand our example: we will take whatever the player types, assume it's a number and return twice the value:
void execute (Character ch, string args) {
int val;
|
You can type in this code replacing test.arc, or you can create a test2.arc.
We start in the same way as before, by declaring an execute function. It is important to remember that each class has its own list of functions, so there can be an execute function with the test.arc class and there can be another one within the test2.arc class. If you call a function and do not specify what object to call it within, it will be called within the current object.
This time we start off the function with a variable declaration (int val). int is one of the types that I mentioned before, and val is the name of the variable. Variables can be named in the same way as arguments and functions: just letters and underscore. Of course, you may not name the local variable the same as another local variable in the same function nor another function. However, if you had another function called say, execute2, within this object, you could have a variable name the same without any problem.
The variable is of type int, so when the execute function starts, enough memory will be allocate to hold one number. This memory will be referred to as "val".
The next line is an empty line - you can put those in anywhere in ARC. Use them to seperate logical processes within a function (in this case we seperate the function in three parts with them: variable declaration, we work on the variable and finally print out the value of the variable).
val = atoi(args); |
An expression can be a number, a variable, a calculation, the result of a function call or just about any combination of those. There are no strange limits not requirements to put calculations within parens like with mobprogs. In this case, we call the function atoi with args as argument. Whatever that function returns is then taken and replaces the value of val.
The function atoi (see the Reference) takes a string (an ASCII string, that's where the 'a' comes from) and converts it to an integer. Unlike in mobprogs, a string is not the same as a number. You could not for example say:
val = args * 2; |
Even if the args variable contained the string "2". You have to convert the string to a number first, and that's what the atoi function will do for you.
val = val * 2; |
Once val contains the numerical value of the argument, we multiply it by two. First what is on the right side of the the equal sign is calculated, then assigned to whatever is on the left side - which is typically a variable.
If you just declare a variable, it will have the value 0 (a string variable would contain the empty string, ""). But you can also initialize the variable with an expression. The first 3 lines of the code in the example could be reduced to:
int val = atoi(args) * 2;
|
You can declare variables at any point within the function. If you declare the variable and assign something to it, that assignment will be executed at the point that the code reaches your declaration line.
Note that unlike in C, local declarations have scope that extends to the whole function (currently anyway).
You can multiply, divide, substract and add numbers as much as you want in ARC. In addition, there are a number of numeric functions which allow you to roll dice, pick random numbers, do something dependent on a percentage chance etc.
There are also two operators that come from C that are very hand: ++ and --. These can be applied to a variable and increment or decrement the value of that variable with one. They also return the changed value. Put them after the variable name e.g.:
int n = 42;
int x = n++; // x is 43
|
You can also put them before the variable. In that case, the value is still increased or decreased by one, but the value of the expression is whatever it was before you applied the operator. I.e. in the above example, if we used ++n instead n++, the value of n would be first assigned to the variable x, then it would be increased by one.
It may be cumbersome to have separate strings and variables and have to + them all together. Therefore ARC offers a system, that looks much like mobprogs and Perl, and allows you to put the variable values directly into strings. Here it what it looks like:
"Twice what you typed is: $val\n" |
The above is equivalent to the code presented before - in fact, it gets translated to exactly that code by the compiler. You don't have to put in the variables bare - you can also supply formatting code. For example, you could put commas in the number by specifhing $,val instead. There are several other formatting codes like that - look at Formatting section of the reference for a comprehensive list.
if (a > 4) {
ch.print ("A is bigger than 4.\n");
} else {
ch.print ("A is not bigger than 4.\n");
}
|
The if check consists of the word if followed by an expression in paranthesis. If this expression is true, the code after if gets executed. If false, the code after else (if any - it's optional) is executed.
Note that the placement of the { and } is arbitrary - this is the preferred style, but you could put them on a new line by itself if you wished so.
Also note that the { and } are not necessary if you want to execute only a single statement. Therefore, the above could have been written as:
if (a > 4)
ch.print ("A is bigger than 4.\n");
else
ch.print ("A is not bigger than 4.\n");
|
As you can see, we use > - a comparison operator. > is true if the thing on the left side of it is bigger than whatever is on the right. There is a number of similar operators, < which is the reverse, == which is true only when the two things are equal, and <= and >= which are, respectively, equal or lesser than and greater or equal.
The operators produce a boolean expression: while as a is an integer and 4 is an integer, a > 4 is of the boolean type: it can be either true or false.
Like int for numbers, there is a data type for strings: string. String variables hold an unlimited number of characters - including newlines, color code and whatever you can imagine.
You can add strings like numbers: they will be concatenad. E.g.:
string a = "This is ";
a = a + "test"; // a now holds "This is a test"
|
You can compare strings just like comparing numbers, using ==. Note that this is case insensitive so that string "Test" and "test" will be equal.
There is a wealth of functions that can be applied to strings.
A for loop iterates over a set of values, executing your code for each value. A for loop is rather complicated: there are three expressions within the control block, and a block of code:
int i;
for (i = 0; i < 5; i++) {
ch.print("Line $i\n");
}
|
First we declare the variable we will use as control variable. Then we have the for statement: there are three parts within the set of parens, delimitered by semicolons.
For loops are useful for e.g. doing something a set amount of times.
A list is 0 or more of certain kind of other data. For example, the people in a room are a list of Characters: there could be 0 or more of them.
A list of something is a distinct type. To specify it, put [] (left and right square brackets) after the type name. For example, to declare a local variable which is a list of strings:
string[] strings;
|
Just like numbers and strings are initialized to be empty, the strings list in the above example starts out with no elements in it. There is a wealth of List Functions that can be used to manipulate any kind of list. If you look at the reference, you will see that they all operate on mixed[] which is a list of any type of elements.
Lists can be accessed as array, using [] after the variable name, similarily to mobprogs. However, you will rarely need to access e.g. the 5th element of a list.
Let's make a small command that will remember the strings you feed to it, and list them if you don't supply anything:
string[] remembered;
|
First thing you may notice is that we declared remembered variable outside of the execute function. This means that the variable will start existing as soon as the command is compiled, and will not be reinitialized each time the command is run. This is a so-called member variable.
Now, we check if args is non-empty, then we call the add function of the list. This simply adds a new element at the end of the list. It does not check if the list has this element already, so you can add the same string to the list twice.
If args is empty, we declare a string s, and use the foreach keyword to set s to every value within the list in order and to execute some code. foreach works much like in mobprogs: you give it a variable it should modify, and a list it should run through.