CRPL coding guide

Started by J, December 20, 2012, 11:31:15 AM

Previous topic - Next topic

J

I'm creating this thread to help people out who want to try out CRPL or want things explained in a different way. Like the CW2 guide about fields, this completely based on my own experiences. I'll add more stuff on this thread as soon as I learn more. This guide is meant to be moved to a public board once the game has been published (to non-betas).




In this guide:
[post 1]
What do I need to know before I start coding CRPL towers?
Starting with CRPL
Warp notation
Using the tracelog and further introduction of a stack language
Comparing numbers and conditions
More functions
Variables and loops
Attributes
FAQ
Changelog
[post 2]
Defining your own functions
Making you script more useful
Debugging your script and common copile errors
Efficiency tips
Useful links
FAQ
Changelog




What do I need to know before I start coding CRPL towers:

The most important thing is that you already know the basics of CW3 gameplay and CW3 editor. It is a good thing if you have programmed before but if that wasn't a stack language it sometimes is a disadvantage. You don't need to have any programming skills to start with CRPL.




Starting with CRPL:

Spoiler

First of all, if you have programmed before, try to forget that while reading. CRPL is (unlike many others) a stack based language, that means you put (push) all numers on a big stack of numbers and functions (like SetCreeper or QueueMove) get (pop) the last number(s) from the stack and do something with it (create a spore, emit creeper). Think of a stack of plates in the cafeteria. People take the top plate. Then someone brings more plates and they go on top and people start taking from the top again. The whole script will be executed once per frame. In scripts, you can use '#' skip the rest of the line. Think of it as a comment in you script.
#this is a comment and will not be executed
Time to create your first CRPL script! Try to use the functions GetCoords and SetCreeper. CurrentCoords puts two numbers on the stack (the X and Y coordinates of the CRPL tower) and SetCreeper pops 3 numbers from the stack (X and Y coords the creeper will be set on and the amount of creeper). The following code sets the creeper height to 5 every frame (there are 30 frames in an in-game second):
#Set the creeper to height 5 on the current position
CurrentCoords 5 SetCreeper

Instead of '5' you could pick another number or a variable (explained later) and instead of CurrentCoords you could pick two number to indicate the cell where the creeper has to be set. Here are 2 more examples of what you could write:
0 0 1 AddCreeper
AddCreeper: instead of setting the creeper to a specific height, this adds creeper.
#Set the creeper to 1 on a random position
RandCoords 1 SetCreeper

RandCoords: This picks a random X and Y coordinate somewhere on the map.
One more useful functions before I move on to the next section, you can use TIME Delay to stop the execution of the script for the set amount of time. So if you want to add 20 creeper every 3 seconds, you could write:
#first we add the creeper
CurrentCoords 20 AddCreeper
#then wait 3 seconds
90 Delay

This of course gives much more possibilitys check out this one:
CurrentCoords -10 AddCreeper 30 Delay
RandCoords 5 AddCreeper 30 Delay
CurrentCoords 15 AddCreeper 60 Delay

[close]




Using the tracelog and further introduction of a stack language:

Spoiler

There's a built-in function to show numbers that are currently on the stack. To enable the trace log, you must call the function ShowTraceLog. After that you can use the function Trace to pop an item from the stack and show it on the trace log (removes the item from the stack!). Use Trace2, Trace3, Trace4, Trace5 and TraceStack to pop and show 2, 3, 4, 5 or the whole stack on the trace log (TraceStack doesn't pop anything from the list).
The notation of the stack: We use OPERATION (BEFORE -- AFTER), try to use this if possible. So here are a few examples:
Add (99 33 -- 132)
once ShowTraceLog 99 33 Add Trace endonce
Sub (12 7 -- 5)
Mod (15 6 -- 3)
You can use multiple operators, but remember that they only pop the last added items from the stack. So where we normally would write (1+2)*(3+4), you now have to write 1 2 add 3 4 add mul
The most important thing here is that you keep in mind that what you've put on the stack last, will be the first you take off (Last In First Out). So '8 5 4 add' will result in '8 9' because 5+4=9. If you add another 'add' the sum of 9 and 8 will be calculated since that are the last two items on the stack.
If you want a challenge, read the following piece of code:
once ShowTraceLog
8 9 5 sub 3 8 mul add 5 7 8 add
add mod div
Trace endonce

Try to guess what's in the trace log
Spoiler

8 ( -- 8)
9 (8 -- 8 9)
5 (8 9 -- 8 9 5)
sub (8 9 5 -- 8 4)
3 (8 4 -- 8 4 3)
8 (8 4 3 -- 8 4 3 8)
mul (8 4 3 8 -- 8 4 24)
add (8 4 24 -- 8 28)
5 (8 28 -- 8 28 5)
7 (8 28 5 -- 8 28 5 7)
8 (8 28 5 7 -- 8 28 5 7 8)
add (8 28 5 7 8 -- 8 28 5 15)
add (8 28 5 15 -- 8 28 20)
mod (8 28 20 -- 8 8)
div (8 8 -- 1)
Trace (1 -- )
It will show you the last item on the stack and that is 1.
[close]

Strings:
Anything between two quotes is a string, use this to put text messages in the trace log or when you need it for a function.
#Shows in the tracelog if the script is running
"This script works!" Trace


Now you should know how the stack and trace and stack system works. You should probably never use the trace functions in custom maps, use it as debug tool.
[close]

If you still need more examples, here's a spoiler with that kind of stuff in it:
Spoiler

3+9= becomes: 3 9 add
4*5= becomes: 4 5 mul
4+2*4= becomes: 4 2 4 mul add
(4+2)*4= becomes: 4 2 add 4 mul
(1+2)*(3+4)= becomes: 1 2 add 3 4 add mul
(7-4)*(2+3+4)= becomes: 7 4 sub 2 3 add 4 add mul
(16/4)/2= becomes: 16 4 div 2 div
16/(4/2)= becomes: 16 4 2 div div
[close]

If you have problems to visualise it, read the next section about the warp notation or put the same code in a comment and put brackets around it:
# Send a spore to the top part of the map
# CurrentCoords (RandCoords 2 div) (5 4 sub) (2 3 mul) CreateSpore
CurrentCoords RandCoords 2 div 5 4 sub 2 3 mul CreateSpore

# Has a lot of conditions before deciding if the code following must be executed or not
# ((((0 32 80 GetUnitCountInRange) 1 gte) (<-Eaten 20 gt) and) (GetRunnerCount 20 lt) and) if
0 32 80 GetUnitCountInRange 1 gte <-Eaten 20 gt and GetRunnerCount 20 lt and if





Warp notation:

Many people are used to languages like C++ or Java, first the command then the arguments. CRPL works in reversed order so the warp notation was made to make it possible. The warp notation is very simple, but can be a great help when trying to visualize your code. When compiling, the function before the brackets will be put directly after it:
add (1 4)
# is the same as
1 4 add

This doesn't have any impact on the performance since it is done while compiling, not while playing the map. Some people still prefer the normal notation. Neither of them are wrong or the best, both are good. Choose your own style.
Some more examples:
#These are all the same:
CurrentCoords 5 SetCreeper #normal notation without warp notation
SetCreeper (CurrentCoords 5) #correct notation with warp notation
CurrentCoords SetCreeper (5) #correct, but shouldn't be used
SetCreeper (5 (CurrentCoords)) #also correct, but shouldn't be used





Comparing numbers and conditions:

Sometimes you want a condition to check if code must be executed or not. Like in many other languages, this is possible with the 'if' function. Since we can't use brackets to show what piece of code must be skipped if the condition is not true, we must use 'endif'. 'true' is indicated by the number 1, and 'false' is indicated by the number 0. The 'if' function pops one number from the stack and if it is not equal to 0, the code will be executed. If that number is 0, the program skips the code until the next endif. The words 'true' and 'false' simply push a 1 (true) or 0 (false) on the stack. There are a lot of functions to compare numbers. Most these functions pop two items from the stack and push 0 (false) or 1 (true) back on the stack. You can find the full list in the wiki. I'll highlight some:

Spoiler













andtrue if last 2 items are true
ortrue if one of the last 2 items are true
xortrue if last 2 items are false
nottrue if the last item is false
gt'greater than', pops 2 items from the stack, if the one pushed on the stack first is greater than the last one, it results in true
gte'greater than or equal'
lt'lower than'
lte'lower than or equal'
eq'equal' true if the last 2 item have the same value
neq'not equal', true if the last 2 items are not the same
eq0true if the last item on the stack is equal to 0
neq0true if the last item is not equal to 0

#These will result in true:
true false or
true true and
true true or
false false xor
false not
5 5 eq
1 2 add 3 eq
4 7 neq
9 neq0
9 2 gt
6 7 lt
2 2 gte
9 0 mul eq0
#These will result in false:
false false and
true false xor
true not
false false or
false true and
9 9 gt
6 4 lte
5 5 lt
4 5 9 add eq
2 eq0
1 8 8 div sub neq0
[close]




More functions:

Time to learn some more useful commands, I'll take the pre-made towers as example. A function is shown by the function name followed by bracket with the arguments in the order they must be put on the stack, seperated by a ','. Empty brackets mean that you don't need to give any arguments. With the new warp notation you could also copy it to your code and replace the arguments with values.

Spoiler

Emitter (creeper):
AddCreeper (x, y, amount), adds [amount] creeper to the cell on [x, y].
SetCreeper (x, y, amount), sets creeper to [amount] height on cell [x, y].
SetCreeperNoLower (x, y, amount), sets creeper to [amount] height on cell [x, y], but it won't remove creeper if there is more creeper than [amount].
GetCreeper (x, y), gets the creeper of cell [x, y] and puts it on the stack.
# this:
CurrentCoords 5 SetCreeperNoLower
# does exactly the same as this:
CurrentCoords GetCreeper 5 lt if
CurrentCoords 5 SetCreeper
endif
#or this:
if (GetCreeper (CurrentCoords) 5 lt)
SetCreeper (CurrentCoords 5)
endif
#lt means lower than and if decides if the code until endif must be executed or not


Spore Tower:
CreateSpore (x start, y start, x destination, y destination, health, payload), Creates a spore at cell [x start, y start] that moves towards cell [x destination, y destination]. The spore has a health of [health] ('normal' spores have 1) and if it lands it will drop [payload] creeper.
# Send a spore to a random unit
CurrentCoords RandUnitCoords 1 10 CreateSpore
# RandUnitCoords pick the coordinates of a random player unit


Timing functions:
SetTimer0 (time), set the timer of timer 0 to [time], the timers decrease with 1 each game frame. SetTimer1, SetTimer2 and SetTimer3 work the same.
GetTimer0 (), pushes the current value of timer 0 on the stack, same thing for GetTimer1, GetTimer2 and GetTimer3
# Send a spore to a random unit every second
once 30 SetTimer0 endonce
GetTimer0 eq0 if
CurrentCoords RandUnitCoords 1 10 CreateSpore
30 SetTimer0
endif
# anything between once and endonce is only executed once per game
# eq0 pops a number from the stack and checks if it is equal to 0


Runner Nest:
In case you don't know, D stands for digitalis
GetDigitalis (x, y), checks for D on cell [x, y] and pushes the health of the D back on the stack (0 - no D, 1 - full health).
CreateRunner (x, y, move, health, payload), creates a runner on position [x, y], the runner has [health] health and moves
pixels per frame. Once the runner is killed [payload] creeper is deposited.
GetRunnerCount (), pushes the amount of runner currently on the map created by this CRPLT on the stack (exactly the same as GetGlobalRunnerCount, except this is only for runners created by this tower).
GetGlobalRunnerCount (), pushes the total amount of runners currently on the map on the stack
# Create a weak runner every frame if D is present and less than 20 runners are on the map
CurrentCoords GetDigitalis neq0 GetGlobalRunnerCount 20 lt and if
CurrentCoords 2 5 5 CreateRunner endif


More Digitalis stuff:
A CRPLT allways 'activates' D, that means once the D is connected to a CRPLT, the D will grow. ***this might be changed in meantime***
GetDigitalisGrowth (x, y), checks for digitalis growth area at cell [x, y], if D can grow there, 1 is pushed back on the stack, if not, 0 is pushed back on the stack.
SetDigitalis (x, y, health), sets the health of the digitalis on cell [x, y] to [health], 0 removes the D while 1 sets the D to full health.
SetDigitalisGrowth (x, y, present), sets if D can grow on cell [x, y]. Present must be 0 (no D growth area) or 1 (creates growth area).
#this code is mostly used in moving towers
#leave a trail of D growth area and set the digitalis to full health
CurrentCoords true SetDigitalisGrowth
CurrentCoords 1 SetDigitalis

[close]



Variables and loops:

Spoiler

A variable: a way to store numbers without using the stack once stored.
You can use ->VARNAME to pop the last item from the stack and store it as variable and you can use <-VARNAME to push the value of the variable on the stack (doesn't remove the variable). Please not that instead of VARNAME you can use any word. Examples:16 ->mynumb
2 ->n2
4 ->endnumb
while <-mynumb <-endnumb neq repeat
<-mynumb <-n2 div endwhile


As you may have noticed, I used 'while', 'repeat' and 'endwhile'. These functions form a 'loop'. There are different loops you can create in CRPL. Lets start with a 'do' loop:
A do loop has 2 functions and has the following form:
do (limit, index) loop
'do' pops 2 items from the stack, if the index is bigger or equal to the limit, the execution skips to 'loop', else the loop runs with 'index', when 'loop' is read, the execution returns to 'do', the index is raised with 1 and everything starts again.
#Add 5 creeper to 5 random locations every 5 seconds
5 ->times
5 ->creeper
150 ->wait
<-times 0 do RandCoords <-creeper AddCreeper loop <-wait Delay


There's also a while loop. A while loop has 3 functions and has the following form:
while repeat (execute) endwhile
When while is read, the code between while and repeat is executed (and should push true or false on the stack). If true is read, the code will be executed until endwhile and execution return to while. If false is read, the code between repeat and endwhile is skipped and the execution continues at endwhile.
#Add 5 creeper to 5 random locations every 5 seconds
5 ->times
5 ->creeper
150 ->wait
0 ->numb
while <-numb 4 lte repeat RandCoords <-creeper AddCreeper <-numb 1 add ->numb endwhile <-wait Delay
#lte means lower than or equal


In a do or while loop, you can use 'break' to stop the loop immediatly and continue at loop or endwhile
#Add 5 creeper to 5 random locations every 5 seconds
5 ->times
5 ->creeper
150 ->wait
0 ->numb
while true repeat RandCoords <-creeper AddCreeper <-numb 1 add <-numb 4 gt if break endif ->numb endwhile <-wait Delay
#'true' pushes 1 (true) on the stack, this means repeat allways reads true
#and the loop will keep going until break is read
#this can easily make your game crash if you don't add the break command!
#gt means greater than

[close]




Attributes:

Spoiler

Unit attributes may look very complicated but they're actually pretty simple: You give the unit UID (identifier), the attribute you want to get/set and GetUnitAttribute or SetUnitAttribute. Here's the list:
Self (), Pushes the UID of the current unit on the stack
GetUnitAttribute (unit UID, attribute), Get the value of the attribute from the unit with the given UID
SetUnitAttribute (unit UID, attribute, value), Instead of reading the attribute it sets it

Here's the list of attributes you can use (they don't take any arguments)(remember that you should never put quotes around them):
CONST_COORDX, The x coordinate of the unit.
CONST_COORDY, The y coordinate of the unit.
CONST_ISDESTROYED, If the unit is destroyed. Can only be read, not set.
CONST_HEALTH, The unit's health, Floating point value.
CONST_MAXHEALTH, The unit's max health. Floating point value.
CONST_AMMO, The unit's ammo. Floating point value. (very fun to use on player units :D)
CONST_MAXAMMO, The unit's max ammo. Floating point value.
CONST_AMMOAC, The unit's AntiCreeper ammo. Floating point value.
CONST_MAXAMMOAC, The unit's max AntiCreeper ammo. Floating point value.
CONST_CREATEPZ, If the unit creates a PZ or not when destroyed, doesn't work on player units.
CONST_TAKEMAPSPACE, If the player can build over the tower
CONST_SUPPORDIGITALIS, If the tower makes digi grow, only when connected to digi
CONST_COUNTSFORVICTORY, If you must destroy the tower to complete the mission
CONST_TAKENULLIFIERDAMAGES, If nullifiers can damage the tower, doesn't work on player units.

Still this might be very difficult without any examples, so here's an example to help you:
# Finds the nearest unit in range.
# Returns the unit uid of the closest unit, or 0 if no unit is in range
# You can use this piece of code at the end of your scrip since it starts with a function
# If you use it, the variable range must be specified in your script
:GetClosestUnit
99999999 ->closestDistance
0 ->closestUnit
# GetUnitsInRange first pushes the UIDs of the units that are in range the then the amount of units
# If there are no units in range it pushes 0 on the stack
CurrentCoords <-range GetUnitsInRange ->unitCount
<-unitCount neq0 if
<-unitCount 0 do
->unit
CurrentCoords <-unit CONST_COORDX GetUnitAttribute <-unit CONST_COORDY GetUnitAttribute Distance ->d
<-d <-closestDistance lt if
<-d ->closestDistance
<-unit ->closestUnit
endif
loop
endif
<-closestUnit

[close]




FAQ:

Q: I have a question or an improvement for this guide
A: You could PM me or post it in this topic

Q: My code doesn't work! Help!
A: First, check if you wrote all functions correctly (they are case-sensitive) and if you actually put the arguments first on the stack (and in the right order). Second, write down (in word or notepad) the stack for every step and check if it actually is what you want. If that didn't help, put it somewhere on the forums so we can help.

Q: Are timers local to an unit or global?
A: Local

Q: After dividing, I get another number than I expected, why?
A: Make sure it's a floating number, integers can't have anything behind the decimal dot. If you have an integer, use 'AsFloat' to convert it into a floating number.

Q: Can you help me with the different value types?
A: Sure. There are three different types of values: integer, float and string. Anything between quotes ("string") is a string. Numbers with a decimal dot are floating point numbers. Integers are numbers without the decimal dot. 4 is an integer, 4.0 is a float, "4" is a string.




Changelog:

(23-1-2013): Added Attributes
(18-2-2013): Added warp notation (added Warp notation and changed Using the tracelog and further introduction of a stack language and More functions)
(9-4-2013): Changed Attributes (added new attributes to the list)
(24-4-2013): Changed Attributes and Using the tracelog and further introduction of a stack language (to include strings)
(2-5-2013): Added more questions to the FAQ
(12-5-2013): Added link to wiki in comparing numbers and conditions and fixed a mistake in the FAQ
(25-6-2013): Updated warp notation

@G&V: if there are any spelling mistakes, you can correct them without warning me

J

#1
Defining your own functions:
If you want to use the same piece of code multiple times or want a better overview, you can use functions. In the main code, use @FUNCTION to call the function. At the end of the code, use :FUNCTION to define the function. The function is the piece of code between :FUNCTION and the end of the script or another function. If you want to give arguments or return a value, use the stack. An example to help you:
@getnumb #pass the execution to :getnumb
@emit #pass the execution to :emit
#------------------ (end of main body, using a line helps visualizing it)
:emit #once @emit is read, execution continues here
->numb #pop a value from the stack and store it
CurrentCoords <-numb AddCreeper
:getnumb #start a new function here, the :emit function stops here
5 #push 5 on the stack
#end of the code, the :getnumb function stops here

And how it looks without comments:
@getnumb @emit
#---------------
:emit
->numb CurrentCoords <-numb AddCreeper
:getnumb 5





Making your script more useful:

If you use $VARNAME:DEFAULT at the start of your code you can define the variable when adding scripts to units in-game. In other words, you can set these variables in-game and that can be different for other cores.
$amtToEmit:10
$interval:15

CurrentCoords <-amtToEmit SetCreeper
<-interval Delay

Take a look at '$interval:15'. When you attach the script to a core in-game you can choose a value for the variable 'interval'. If you don't input a number in-game, 15 is used.
This is a very powerful mechanism to use the same script over different cores or if you want to give the script to other map makers.




Debugging your script and common copile errors:

If you get a compile error, you your script isn't working correctly. Here's a list of common compile errors and how to fix them:

Spoiler
Error: Invalid token: [name]
What happened: The compiler doesn't know the command you used
Fix: fix the typo if it was a command, use quotes if it was a string, or add <-[name] ->[name] if it was a variable.

if you have more common compile errors, post in this topic or send me a PM
[close]

Sometimes your script doesn't work, while there were no compile errors, you don't get any information of what happened. Here are a few checks you should do before posting your script on the forums:

Spoiler
If you are using unit attributes, make sure you didn't put quotes around the constants.

Make sure you spelled all variable names correctly.

If you have any complex calculations, write down (in word or notepad) the stack for every step and check if it actually is what you want. Also make sure all commonds get the right arguments.

I'll try to add a working fix for every script posted on the forums
[close]




Efficiency tips:

CRPL is already very efficient, but sometimes it can take some time to calculate stuff like free locations to move to or a player unit to target. Here are some tips to make scripts more efficient.

Commands like GetUnitsInRange are very CPU-intensive, if there are also other tests to see if a piece of code must be executed or not, first check the 'easier' ones and nest the if's.
#checks if doit is true and if there are units on the map
0 0 9999 GetUnitsInRange <-doit and if
#first checks if doit is true, then checks if there are units on the map
<-doit if 0 0 9999 GetUnitsInRange if
The second one is much more efficient if doit is false, then it won't even check if there are units on the map (which is an expensive call).




Useful links:

A page filled with functions and examples (OLD):
http://knucklecracker.com/creeperworld3/CRPL/docs/crpldocs.html

The official KC wiki with a CRPL overview, reference and examples:
http://knucklecracker.com/wiki/doku.php?id=crpl:start

For if anyone copies the whole guide without looking at it, a link to the most-updated version on the KC-forums:
http://knucklecracker.com/forums/index.php?topic=12253.0




FAQ:
No questions to answer yet :)

Note there is also a FAQ in the first post of the topic




Changelog:
(3-5-2013): added debugging and common compile errors
(9-4-2013): added/changed some useful links

Grauniad

Good start.

It may be worth illustrating the architecture by putting in an example to show that addition is as follows:


# add 2 and 3 together.
3  2 add


to get the result of 5

Further, there is a specific notation that might be useful for those struggling to debug their code.

the stack trace notation shows what happens.

for the above example this is how it would appear:

3 2 -- 5
A goodnight to all and to all a good night - Goodnight Moon

knucracker

I'd look at sites like this for a descriptive reference:
http://www.forth.com/starting-forth/sf1/sf1.html
Scroll down the the arithmetic section, since the opening is about forth.  It describes a stack and postfix notation.

Wikipedia pages also have some description you can take inspiration from:
http://en.wikipedia.org/wiki/Stack-oriented_programming_language
http://en.wikipedia.org/wiki/RPL_(programming_language)

J

My inspiration comes from CW3 ;)

And the CRPL docs topic

Grauniad

The complex example seems *very* complex.

7 8 9 5 sub 3 8 mul add 5 7 3 5 add add
add mod div 3


7 8 9 5 -sub- 7 8 4 -- 7 8 4 3 8 -mul- 7 8 4 24 -add- 7 8 28 -- 7 8 28 5 7 3 5 -add- 7 8 28 5 7 8 -add- 7 8 28 5 15
7 8 28 5 15 -add- 7 8 28 20 -mod- 7 8 8 -div- 7 1 -- 7 1 3


Seems a *little* bit simpler? Perhaps only to my jaundiced eye.

Maybe Virgil and others can weigh in and advise? This has to be  easy to understand.

A goodnight to all and to all a good night - Goodnight Moon

knucracker

Yeah, the complex example is a big jump, mostly because it requires people to visualize more than three things on the stack at a time.  It's of course perfectly valid, but from a learning perspective it would be better to guide people through a few more intermediate examples I think. The complex example also marries two different concepts that I describe below.

There are really two concepts that a student has to grasp in stack programming.  The first is the RPN notation (what every one calls 'backwards').  The best way to teach RPN notation is with math examples.  Stick to numbers, math operators, and a single number as a result.  Showing some more examples in prefix notation like you did with (1+2)*(3+4) and then how that comes out in postfix notation might be helpful.

1+2   becomes 1 2 add
1+2+3   becomes 1 2 3 add add
1+2-3   becomes 1 2 3 sub add
(2-1) + 1 becomes 2 1 sub 1 add
etc.

The hardest part for many people in the beginning is just the very concept that operations need to come after the arguments.  They are used to typing "2 + 3 =" on a calculator and never really imagine what the underlying machine has to do to support that syntax (it has to remember the operator, "+" in this case, till another number is entered).  In RPN notation only numbers are remembered by the underlying machine and operations act on stored numbers and never need to be remembered.  Once a person realizes this reason for the notation, they usually open up more to it and see it as less arbitrary.

Anyway, the second concept for new students is that the stack is a way to pass data into and get results from functions.  This is actually a different concept from RPN notation alone.  It just so happens that RPN notation is ideally suited for dealing with a stack data structure.  The stack becomes the way that all data is passed into functions and how all data can be returned.  So examples that focus on pushing things to the stack and getting results are usually in order.  Examples that leave items on the stack and ask the student to show the stack contents are also in order.

For instance:
# Push a string to the stack
# Call the Trace function
# The Trace function takes an item from the stack and shows it in the game.
"Hello World" Trace
etc.

That's my two cents on how to guide a student in the early moments of discovery.  I'll probably incorporate some of these things into examples I do, so you can take or leave them for your guide. 


Grauniad

Quote from: virgilw on December 21, 2012, 12:29:12 PM

For instance:
# Push a string to the stack
# Call the Trace function
# The Trace function takes an the most recent item from the stack and shows it in the game.
"Hello World" Trace
etc.


reinforce LIFO? "An item" seems so indeterminate as if Trace has a choice on which item to take... :P
A goodnight to all and to all a good night - Goodnight Moon

Grauniad

#8
J, on another note. You should probably point out the LIFO (Last In, First Out) nature of the stack.

Thus

5 6
4 add


Will have these results
5 6 4 -- 5 10

A good analogy (Virgil did you also mention it?) is to think of a stack of plates in the cafeteria. People take the top plate. Then someone brings more plates and they go on top and people start taking from the top again.


A goodnight to all and to all a good night - Goodnight Moon

lurkily

Quote from: virgilw on December 21, 2012, 12:29:12 PMThe hardest part for many people in the beginning is just the very concept that operations need to come after the arguments.
This is true for me, even with a grounding in coding and scripting - modern languages.  (C++, Lua, an abysmal misunderstanding of Javascript, and various other simpler scripting languages, like AutoHotkeys, and various game's modding interfaces/scripting.)

It helps me to, in complex situations, duplicate the line in a quote, and insert parens to help visually parse it.

For instance, this:

#((((0 32 80 GetUnitCountInRange) 1 gte) (<-Eaten 20 gt) and) (GetRunnerCount 20 lt) and) if
0 32 80 GetUnitCountInRange 1 gte <-Eaten 20 gt and GetRunnerCount 20 lt and if

Tricks like this help me parse the code more like the mathematical notation I'm used to using.