HOME
ABOUT US
MEMBERS
LINKS
OUR STRATEGY
TIPS
GLOSSARY OF TW TERMS
FORMULAS
SCRIPTS
OTHER |
|
INTRODUCTION:
This
isn't a full blown tutorial. That would be too much work. My
goal with this article is to show you some of the capabilities of TWX (like arrays) by using
well documented examples of some of the scripts I've written for our
corp. These scripts are first generation scripts that I have updated
for version 2.X of TWX, and as such, they are somewhat primitive and
inefficient compared to the stuff I'm using now. But they are well
documented now, and should provide you with good examples and ideas for
writing your own scripts.
For
those of you classically trained programmers out there I should warn you
that I am NOT a programmer by trade. I am a Network Administrator
for a large healthcare organization (I support the Routers, LAN's and
Firewalls for them.), and so everything I learned about scripting I had to
pick up the hard way. If the way I do things bothers you, then you
should get a grip and remember that this is just a game :) I'd also
like to thank the following people: Xide for making TWX in the first
place. I am an accomplished REXX coder, (I have to use it for work
sometimes :) but I've always hated the language. TWX
rocks! Cherokee for bugging me about ports, and getting me off
my ass and rewriting the Haggle part of my SDT script. When I found
out his SDT script made about 3% to 5% more than my haggle routines,
(which made more than anyone else's...lol) I reverse engineered what he
did, saw where I could make improvements, and implemented them. I'm
still tweaking it, but a reliable and working version is posted
below. Sweet Little Girl for
her input on how to make this article more readable. And,
finally, I'd like to thank my corpies for all their hard work testing all
of scripts with me in live games and not killing me when they got podded
because I made a typo somewhere.
Now,
on to the fun part.
ARRAYS and WHILE loops in TWX
What is an array? What is a WHILE
loop? I'll get to WHILE loops in a minute, first I want to start
with arrays. If you are already familiar with variables, you can
think of an array as a super variable. It's a variable that can hold
more than one value. Each value has an index number associated with
it so the array can keep track of them all. Think of an array like
an egg carton. It's a single object that has 12 cups that hold
eggs. Now, number each of those cups from 1 to 12. Those
numbers would be the indexes. Now, you don't HAVE to put eggs in the
little cups, you could put pennies, marbles, whatever. If you wanted
to know what was in cup 12, you would just open up the egg carton, and
find cup # 12, and look inside. That's pretty much how arrays
work.
As an example, take a look at the following
piece of TWX code. It has nothing to do with TW, but it may help you
visualize how arrays work. I'll tie this example back to TW further
down.
1 setvar
$CartonSize 12 2
setvar $count 1 3
setarray $EggCarton $CartonSize 4 while ($count <=
$CartonSize) 5
getinput $EggCarton[$count] "How many Pennies in cup # " & $count
& " do you want?" 0 6 echo "*Cup Number " & $count
& " holds " & $EggCarton[$count] & "
pennies.*" 7 add
$count 1 8
end (The line numbers
are in grey. They are there for reference only) Line 1 creates the
variable $CartonSize to 12. This is how many cups (indexes) the
carton (array) has. Line 2
creates the variable $count which we
will use to check each cup (index) and add pennies (values) to
it. Line 3 creates the Array, $EggCarton. It creates the actual egg
carton (the array), called $EggCarton, and tells TWX that the egg carton
contains 12 cups (indexes). This is how you define a static array in
TWX. Line 4 gets a bit more tricky. Here I'm setting up a
WHILE loop to step through each cup (index). It's basically saying
that as long as $count is less than or equal too the maximum number of
cups (The maximum size of the Array), it should run all the commands
between the WHILE statement and the END statement. All WHILE loops
need an END at the end of them, so the loop knows when to go back to the
start of the WHILE loop and retest the conditions to see if it still needs
to run. So, the commands in this WHILE loop will execute in order
until it gets to the END command, and then go to the top and check to see
if $count is still less than or equal to $CartonSize. If this
condition is still true, it will run the loop again. That is what a
WHILE loop is in a nutshell. As long as the condition is true, it
will keep doing the loop. Line
5 is the first command in the WHILE
loop. It asks the user for manual input, in this case, it's asking
for the number of pennies you want in each cup (index). The user
just types in a number (or whatever, it could be text if you want) and
presses enter. This is the first usage of the Array.
$EggCarton[$count] is the actual array. See them []'s? The
contents of those braces is the cup number (index) So, what I'm
doing here is asking the user to put in the number of pennies into the
array in the cup number (index) equal to the value of $count. Notice how I
was able to mix text and the variables in the same line. You do that
with the & symbol. When you put an & symbol on a line, it
ties everything together into one line. This is very useful for
echos, like the one below, and for file
writes. Line
6 is just an echo that takes what you
entered and spits it back on the screen, so, if you entered 10
pennies in line 5, for cup 1 (index 1) it would output the
following: Cup Number 1 holds 10
pennies. What I'm doing here is
querying the array and asking it what the contents are of cup #1 (index
#1) and having it echo that to the screen. See how I used the &
symbol to string together text and the variables? Line 7 increments
$count. This is important, because if I don't do something to make
$count eventually become greater than $CartonSize, it will always meet the
conditions of the WHILE loop and go on forever. So I add one to the
value of $count, so after 12 loops, $count is equal to 13, and therefore
greater than the value of $CartonSize, making the test condition false and
causing the while loop to stop. You are not limited to numbers
either. You could setup a while loop to check for text just as
easily. (i.e. while $count = "a" and then as soon as the value of
$count becomes something besides a, it will run the loop) Line 8 is the ever
important END statement. If you don't put an END at the end of your
loop, it will continue processing all the commands that follow the WHILE
statement till it hits an END command. This can lead to undesirable
results. If I didn't include this line here, then what would happen
is it would run through the loop once, and then seeing no other commands,
it would terminate the script.
OK, egg cartons are cool and all, but how
about a REAL example of something useful? You need a reason to use
arrays you say? Well, if you made the array 5000 in size, as in the
number of sectors in a game, you could do a lot with it. If you let
each index represent a sector number, then you could do something like
pull a 'G' (fig list) and set the contents of each index equal to the
number of figs you have deployed in each sector. So, if you
wanted to know how many figs you have in sector 588, you would query index
588 in the array, and it would return the number of figs you had in that
sector. I'll go more into that a bit later.
The following script is our old auto furb
deployer script. It asks you how many sectors you want to put furbs
in, what the sector numbers are, and how many furbs you want in each
individual sector. It then buys the furbs and delivers them for
you. This is useful if your furber can't be on-line at the same time
as your reds, but you still want to furb them. It's not as turn
efficient as it could be (it should buy all the furbs at once), but hey,
it's a 1.x script. :)
Download it here: _01_deploy_furbs.ts
Then read through the boring text at the top of the script so you know how
to use it. Remember to turn off your helper, and turn off ANSI and
Animation (cn1 & 2 from the command menu) I should also mention
that ALL of my scripts require these things to be OFF. It may not be
pretty, but it's the only way to make them work.
Anyway, there are a few sections of the
script that I want to review here so you can see another feature of
arrays, the array within an array feature :)
Here is the first piece of code I want to
examine: 80 # ----====[ Get User Data ]====---- 81 # User enters data manually
here and I initialize the array. 82
getinput $arraysize "Enter the total number of
sectors you want to put furbs in: " 0 83
setarray $sectors $arraysize 84 setvar $count
1 85 while
($count <= $arraysize) 86 getinput $sectors[$count] "Enter Sector Number " &
$count & ": " 0 87 getinput $sectors[$count][1] "Enter Number of Furbs: "
0 88 echo
"*Sector Number: " & $sectors[$count] & " # of Furbs: " &
$sectors[$count][1] & "*" 89
add $count 1 90 end 91 goto
:getotherinfo (The line numbers are in grey. They are there for
reference only) On the surface, you will see that this looks kind of
like the last piece of code I showed you. But there are a few
additions and changes. Line
80 and 81 are just comments. They
are there so I know what this section is supposed to be doing.
ALWAYS comment your code, cause a year from now if you need to make
changes in your scripts, it's a heck of a lot easier to figure out where
you need to make the changes if you know where to start.
:-) Line 82 is where I ask how big the array
should be (how many indexes) I set that to be equal to whatever the
user enters, and that should be the TOTAL number of sectors that they are
planning on dropping furbs off in. Line 83 is where I actually
initialize the array. The array is named $sectors, and it has a
total of $arraysize indexes. This is a Static Array. Line 84 is where I
setup my counter. Line 85 is the WHILE loop. basically it says execute
the commands between the WHILE statement and the END statement over and
over as long as the value of $count is less than or equal to the total
number of indexes in the array. (it executes lines 86 through
89) Line 86 you may notice, looks a lot like line 5 in the above example,
except this time I'm asking for the sector number. But the principal
is the same. Line 87, heh, now I get tricky again. What the heck is
that second set of []'s for? What does that 1 in there mean?
Well, I'm using one of the less well documented features of arrays.
Each index can also have what I'll call sub-indexes for now. Imagine
10 egg cartons stacked on top of each other. Now you know that if
you want to see what is in cup 12 of the top carton, you just query the
array and ask it what's in index 12. With the 10 egg carton model,
if I wanted to know what was in the cup 12 of the second egg carton, you
would query the array and ask it what was the contents of index 12,
sub-index 1. That tells the array to go look at cup 12 in the second
carton. Confused? Well, think of the first carton as having a
sub-index number of zero. By default, you don't need to put in the
sub-index number for the top level of the array, TWX assumes that is
zero. So if you start the count at zero for the first carton,
then the bottom carton, number 10, would have a sub-index number of
9. If you wanted to know what was in cup 5 of the 10th egg carton,
you would query the array and ask it for the contents of index 5,
sub-index 9. What I'm doing here is asking the user for the number
of furbs that they want in the sector, and I'm telling the array to put
that value in sub-index 1. The first set of []'s is the index, and
the second set of []'s is the sub-index number. These have to be
numbers, or variables that contain numbers. So, when I request the
user to put in the number of furbs in the array at this index, I'm really
telling the array to use egg carton number two (remember carton number 2
is the FIRST sub-index), and to put the number of furbs they enter
in cup 12 of carton 2.
So, if you put in 588 for the first
sector number, and you wanted 10 furbs there, the array contents would
look like this: $sectors[1] = 588 $sectors[1][1] = 10 That format
is how TWX handles sub-indexes. They are very powerful. TWX
makes extensive use of them with it's GETSECTOR command, which I'll go
into later. Line 88 is where I echo back the things that you typed in,
so you can be sure that you didn't make a typo or something. First
I'm telling TWX to go look at what's in the array at the current index
(equal to $count) and echo that value to the screen (the sector number),
then I tell it to look at the same index, but sub-index 1 to get the
number of furbs for that sector. See how I again used the & to string together the variables and
text so it's all one line. Line
89 increments the value of $count just
like in line 7 of the first example. Line 90 is then end of the WHILE
loop. If I didn't put that in there, it would just skip on to line
91 without running through the loop the correct number of
times. Line 91 tells the script to go to another section of the
script. It references something called a Label. Labels are how
you get to different sections of your script. They are also how you
organize your script and how you define sub-routines. More on that
later.
If you read through the rest of the script,
you can see how I use more WHILE loops to step through the Array and how I
use sub-routines to actually deliver the furbs. Lets take a closer
look at that: 106 # ----====[ Main Routine ]====---- 107 # here is where all the work
gets done. The nested while loop. 108
:start 109
setvar $sectorcount 1 110 while ($sectorcount <=
$arraysize) 111
setvar $furbsector
$sectors[$sectorcount] 112
setvar $furbcount
$sectors[$sectorcount][1] 113
while ($furbcount > 0) 114 gosub :getfurb 115 gosub :deliverfurb 116
subtract $furbcount 1 117
end 118
add $sectorcount 1 119 end 120 echo ANSI_10 "***ALL
DONE!***" ANSI_7 121 halt (The line numbers are in grey.
They are there for reference only. I also put in some formatting to
make it a bit easier to look at. My scripts all use tabs and spaces
in them, but those don't convert well to html :) Line 106 and
107
are more comments. Line 108
is a label. When you want to jump
from one part of the script to another, you have to use labels. All
labels in TWX start with a leading ":", and each one has to have a unique
name. More on that later. Line
109 is where I setup another
counter. Why don't I use $count again? Because $count isn't
very descriptive, while $sectorcount tells me what it's counting.
Less confusing for me. Anyway, I set this counter equal to
one. This counter is how I keep track of what sector's I have
dropped furbs off in. Line
110 is another WHILE loop. This
time I'm testing to see if the counter $sectorcount is less than or equal
to the value of $arraysize, which is equal to the total number of indexes
in the array, which corresponds to the total number of sectors. As
long as that's true, it will run through all the commands between it and
the END command in line 119 till it becomes false. Wait, you
say? What about the END in line 117? Well, guess what?
That end command in line 117 belongs to yet ANOTHER WHILE command, the one
on line 113. You see, I'm sticking another WHILE loop in the middle
of my first WHILE loop. It should become more clear what I'm doing
as I get a bit further into this bit of code. Line 111 this is
where I create a new variable, $furbsector, and make it equal to the value
of the array $sectors index $sectorcount. That number if you recall,
is equal to the sector number you entered earlier. $furbsector is
the variable that I use later to tell you where to warp your ship
too. So, if you entered in 588 as one of your sectors, then the
corresponding index in the array will be 588, and $furbsector is also now
equal to 588. Line 112 creates another counter, a variable called
$furbcount. This is equal to the number of furbs you entered for the
corresponding sector number. So if you wanted 10 furbs in sector
588, the value of $furbcount would be set to 10. See,
it's querying the array $sectors at the index equal to the value of
$sectorcount and in the first sub-index of that index. The first
time it goes through the WHILE loop, the value of $sector count is equal
to 1, so it's doing the same thing as asking for the contents of
$sector[1][1]. As the value of $sectorcount increases, the index in
the array goes up, but the sub-index doesn't change, since I've hard coded
it to always look in the first sub-index of the corresponding index
number. So, the second time through the WHILE loop, the value
of $sectorcount is equal to 2 (because I added one to it in line
118), so the value of $furbcount gets set to $sector[2][1]. So, if I
entered 999 for the second sector and 5 for the number of furbs, then the
second time through the WHILE loop, it would set $furbsector equal to 999
in line 111, and $furbcount equal to 5. I hope this becomes more
clear as I go on. Line
113 is the second WHILE loop. If
your head is hurting now, wait till I try to explain the logic of this to
you :) Anyway, here is where I put in my second WHILE loop to do the
actual delivery of the furbs. Remember that counter I set in line
112? Here is where I use it. I'm testing $furbcount to see
that it's greater than zero. As long as $furbcount (the total number
of furbs for the sector equal to $furbsector) is greater than zero, it
will execute the commands between lines 114 and 117. Well, what
about the first WHILE loop? Why doesn't it use the END command in
line 117, and not the one in line 119? Because when TWX compiles a
script, it tries to match each WHILE loop with a corresponding END
statement. As you already know, when you have a WHILE loop, it will
execute all the commands below it until it hits an END statement, then it
will go back to the beginning of the WHILE loop and recheck the conditions
to see if they are still true. What TWX does when it's checking for
WHILE's and END's is start from the middle and work it's way out.
So, in the above example, the WHILE statement on line 113 is the innermost
WHILE statement. The compiler steps through the script looking for
the very NEXT END statement and when it finds one, it associates it with
that WHILE statement. So, the END in line 117 gets associated with
the WHILE in line 113. Now it works UP the script and looks at the
WHILE statement in line 110, and steps through the script until it finds
an END statement. It sees the one in line 117, but it knows that
117's END is being used by the WHILE statement in 113 already, so it skips
it and moves on to the next END statement in line 119. Line 114 tells the
script to go to a label called :getfurb. Notice I use the term
GOSUB, instead of GOTO. What's the difference? GOTO tells the
script to go to a label and forget about where it just was. GOSUB
tells the script to temporarily to go to the label, but to RETURN to this
spot when it's executed all the commands under that label. In other
words, it tells the script to go somewhere and return here when you are
done executing the commands at that location. In this case, the
GOSUB tells the script to go buy a furb, then return to this spot.
Since I have to buy a lot of furbs, it's a lot less typing to tell the
script once how to buy a furb, and just have it run that sub-routine every
time it needs a new furb. Line 115
tells the script to run the drop-off
furb sub-routine. Then return to this spot and go to line 116 once
it's dropped off the furb. Line 116
is where I change the value of
$furbcount for the WHILE loop in line 113. Notice that I'm
subtracting instead of adding here. I have to do this, since the
WHILE loop in 113 is checking to see if the value of $furbcount is greater
than zero. Once $furbcount equals zero, it will finish this WHILE
loop and move on to line 118. So, this is how the script keeps track
of the furbs it needs in a particular sector. The WHILE loop in line
113 runs (buying furbs and delivering them) until $furbcount gets to
0. So, if you entered in 10 for the number of furbs, it would run 10
times, and there would be 10 furbs in the sector. Line 117.
This is the END statement that goes with the WHILE in line
113. Line 118. OK, now that I've dumped off all the required furbs
in the sector, I can now move on to the next sector. I do that by
adding one to the variable $sectorcount. Now what happens is the
WHILE loop in line 110 gets checked again, and if it's still true,
it goes to line 111 where it increments the index of the array, to look at
the second sector number. That becomes the new value of $furbsector,
and then it checks for the number of furbs that the new sector needs in
line 112. Then it runs the second WHILE loop until it's delivered all the
furbs for that sector, then it goes back to this line (line 118) adds one
to $sector count, and so on, until it runs out of sectors. Once it
runs out of sectors, then it moves on to line 120. (Line 119 is an
END statement, so it is part of the WHILE statement in line 110.
Because of that, when the WHILE condition becomes false, the next line for
processing is line 120) Line
119. This is the END statement that goes
with the WHILE in line 110. Line
120 Now that you have delivered all the
furbs, I put an echo here to announce that the script has
finished. Line 121 tells the script to stop. The HALT command
always tells a script to terminate.
Wait a minute. There are a lot more
lines of code AFTER the HALT. What's going on? Do those get
processed or what? Yes they do. Remember the
GOSUBs? All that code at the bottom of the script, after the halt,
are my sub-routines. Basically, anytime you have to do the same
thing over and over in your script, you should make it a sub-routine and
have the script call it when it's needed. It makes for more
efficient scripts, and they are a lot easier to debug. I usually put
all my sub-routines at the bottom of my script, and reference them as they
are needed with GOSUB, or rarely GOTO.
OK, so why an autofurber? Well,
because it goes well with my SDT suite of scripts. Suite?
Yes. I have an SDT script for all occasions, being a red and all
:) But the ones that I'm going to show you are team SDT
scripts. They REQUIRE at least 2 corpies on-line at the same
time. Each corpie runs a different script, and those scripts talk to
each other to manage the SDT run. They are:
_01_3_primary_SDT.ts _01_3_secondary_SDT.ts _01_3_furber_SDT.ts
THE
SCRIPTS ARE NOT QUITE READY FOR PRIME TIME. USE THEM AT YOUR OWN
RISK AT THIS POINT!!!! THEY DO WORK, BUT I'M NOT DONE WITH THE
DOCUMENTATION IN THEM YET. MORE ON THIS LATER -Traitor
4/30/03
These scripts run 2 reds in a 3 sector SDT
area with bust clearing and optional furber support. I say OPTIONAL,
because if you furber can't be around, then he can run that first script I
showed you and drop off furbs for your reds in the SDT area. Anyway,
it runs 2 reds with 1000 turns in about 15 minutes or less. I have
revised their HAGGLE subroutines, and they now make a ton of profit.
The better the port, the more profit they make.
If you haven't already downloaded them, you
should do it now and give them a read. I've tried to document the
hell out of them...it's over 1761 lines of code between all three scripts,
but a lot of it is remarks, so it's really not all that big, all things
considered. Just remember to turn off the helper in your client, and
ANSI and animation must be off as well. 9 times out of 10, problems
with these scripts are a direct result of people forgetting to do those 3
steps. Since I use ZOC 99% of the time, the helper part has never
bothered me. TWX IS MY HELPER!
Anyway, back to Primary. Primary has
three things in it that I think are worth taking a closer look at.
The first is how it exchanges information between the other scripts, and
some of the things I put in there to help prevent spoofing by other
players. It's not perfect, but anytime you create a script that
talks to other scripts your corpies are running, you have to take security
into account. The second thing is how the script keeps track of the
busts, the ships, and the reds, so that you can take full advantage of
bust clearing. I never send out sector numbers, only the ship
numbers. Finally, there is the haggle routine, that takes advantage
of the fixed prices offered for planet negotiations.
Security is handled by using private
hails. Since I have ANSI turned off, you always get the full trader
name in incoming messages, like this:
Incoming transmission from Traitor
Hello World!
So my security looks for the message
"Incoming transmission from" and then checks to see what the
trader name is. If the trader name exactly matches with what it's
expecting, it will process the message, otherwise it will ignore it and
continue waiting. Since TWGS doesn't allow traders to have similar
names anymore, it's near impossible to spoof these messages. I don't
like using Sub-Space Channels in script communications, since there have
been documented cases of people figuring out your Sub-Space Channel and
taking advantage of your scripts. Now
for the logic it uses for keeping track of busts and ships. These
scripts take advantage of bust clearing. When someone busts at a
port, the port only keeps a record of the LAST person to bust there.
If someone new comes along and busts at that port, the first person's bust
is cleared from the ports memory, and the first person can start
robbing/stealing there again. So, if Traitor busts at a port, then
Roberts comes along and busts there 15 minutes later, Traitor is able to
rob there again, since Roberts was the last person to bust there. My
scripts take advantage of this. The script uses 3 ports, we'll call
them A, B, and C for now. The first red (Primary) runs ports A and
B, while the second red (Secondary) waits at port C. When Primary
busts at a port, lets say he busts at port A, he tells Secondary what port
he busted at, then he goes and waits in port B. Port B is clear for
Primary, since he didn't bust there, and port A was the last place he
robbed. Now, secondary runs ports A and C. Say that secondary
busts at port C. Now, Secondary waits in port A, and Primary runs
ports B and C. If Primary busts at port C, he waits in
port B, and Secondary runs ports A and C. Secondary can run
port C because Primary cleared his bust in port C. My script keeps
track of the ports by associating the ports with ship numbers. |