The Cabal's Secret Hideout

Our TradeWars 2002 Homepage

Web Master: traitor@tw-cabal.com

CONTENTS

TWX SCRIPTING part 1

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.

Copyright 2002 - 2005, Chris Kent aka Traitor.  All rights reserved.  See About.html for more info.