Analysis of a CRPL script

Started by Karsten75, December 17, 2012, 11:21:05 AM

Previous topic - Next topic

Karsten75

I'm working my way through Example 9 and it's complicated, so I thought I'd do it here and you guys can either follow along or correct me.

Here is the script:
# Create a wandering emitter.  
# The emitter leaves a Creeper trail as it moves around.
# Whenever it changes directions, it leaves a pool of creeper.
# When destroyed, it emits a bunch of Creeper and fires spores at player units.

# Setup an initial movement.  
# This is so we don't leave a pool
# of Creeper on the first movement.
once
  RandCoords swap 2 div swap 1 QueueMove
endonce

# If the current movement queue is empty, pick a new spot to move to.
GetQueuedMoveCount eq0 if
  # We will move randomly around on the left side of the map.
  # So we pick Random Coordinates, but then divide
  # the X coordinate by 2 so it is on the left side of the map.
  RandCoords swap 2 div swap 1 QueueMove
 
  # Leave a pool of Creeper since we are changing
  #directions and moving to a new spot.
  CurrentCoords 100 AddCreeper
endif

# We want to leave a trail of Creeper behind.  So we emit 2 Creeper every 10 frames
GetTimer0 eq0 if
  CurrentCoords 2 AddCreeper
  10 SetTimer0
endif

# When Destroyed, emit a bunch of Creeper and choose 5 units and fire spores at them.
:destroyed
CurrentCoords 1000 AddCreeper
5 0 do
  CurrentCoords RandUnitCoords 1 20 CreateSpore
loop


What I'm not sure about is the order things end up on the stack.  (edit:)

According to the Forth Emulator, 50 60 2 div will yield

50 30 on the stack.

So the Stack is LIFO (Last IN, First Out).

In this table I'm going to try and represent the stack:
































Executed:Stack contentsStack DiagramComment
once--
RandCoords60, 50 -- 60 50Assume X=60, Y=50
Swap50, 6060 50 -- 50 60
2 Div 50 60, 2 50 60 -- 50 60 2 -- 50 30Divide X-Coordinate by 2
swap30 5050 30 -- 30 50
1 QueueMove25, 30, 130 50 1 --X, Y,  Speed, for queued move
endonceStack will now be empty, since Queuemove consumed three values from the stack.
GetQueuedMoveCount1 -- 1Number of queued moves
eq00 1 -- 0 There was one queued move, so EQ0 returns False (0)
if0 0 -- 0 Since we already stacked a move, this is 0 (false) again and we won't execute this on the first pass.
RandCoords swap 2 div swap 1 QueueMoveSame as code bracketed by [once]
CurrentCoords45 55-- 45 55X and Y coordinate of tower.Let's assume at X=45, Y=55
10045, 55 100 45 55 -- 45 55 100
AddCreeper45 55 100 --Stack's empty again
GetTimer0Value of timer0 -- 0Should be zero on first pass
EQ010 -- 1Assuming timer was 0, then this returns True (1)
if1 --True
Currentcoords -- 44 40It's moving towards 50 30, X=44, y=40
244, 40 2 44 40 -- 44 40 2
Addcreeper44 40 2 --Add the creeper, stack's empty
10 settimer0 -- Pushed value 10, settimer0 consumed the value 10 as timing interval
Destroyed:
CurrentCoords 1000 AddCreeper-- destroyed at X=56, Y=28, 1000 units
5 0 do 5 0 -- Loop 5 times starting at 0
CurrentCoords-- 56 28
RandUnitCoords 56 28 -- 56 28 12 32Unit (at (Eg.) X=12, Y=32
1 20 56 28 12 32 -- 56 28 12 32 1 20
CreateSpore56 28 12 32 1 20  -- Stack's empty, spore on its way!

Edit: I reworked this a bit. Hopefully tomorrow I'll get some time to test it. :)

Karsten75

I am using, awkwardly, this Forth emulator to see what the results should be.

knucracker

The order of x and y are flipped in the first few lines of your assessment.  It's not big deal other than in your description.  The end result should be that the x coordinate is divided by two, not the y coordinate.

Many commands require an x and a y coordinate.  These commands expect the y coordinate to be higher on the stack than the x, or right most when reading from left to right.   So:

# X Y Amt SetCreeper
RandX RandY 1 SetCreeper


This can be simplified to:

RandCoords 1 SetCreeper


RandCoords just pushes two values to the stack, X first, then Y second.  So, U is on top of the stack.

Another way to visualize the stack is exactly like a physical stack of items.  Imagine a stack of plates or trays at a cafeteria.  You push plates down onto the stack one at a time.  If you take one, you take the one off the top of the stack.  So this is a LIFO structure.

You can also use stack effect diagrams to show stack changes ( http://en.wikipedia.org/wiki/Stack-oriented_programming_language ).  In these diagrams the right most elements indicates the top of the stack and the "--" separates the before and after stack views.

Another convenience when reading any RPL is to look for the action verbs, the read backwards left from there. 

1 2 3 4 5 add sub mul div


rewrite the 'verbs' or actions on different lines as the following.  I show the stack effect diagram to the right of each line. 

1 2 3 4 5    -- 1 2 3 4 5
add           1 2 3 4 5 -- 1 2 3 9
sub           1 2 3 9 -- 1 2 -6
mul           1 2 -6 -- 1 -12   
div            1 -12 -- 0


Note that 1 divided by -12 will yield 0 since these are all integers.  If I had pushed "1.0 2.0 3.0 4.0 5.0" to the stack, the the division would have used floating point arithmetic and the final entry on the stack would have been -0.08333333333.



Karsten75

Quote from: virgilw on December 17, 2012, 12:54:33 PM
The order of x and y are flipped in the first few lines of your assessment.  It's not big deal other than in your description.  The end result should be that the x coordinate is divided by two, not the y coordinate.

That *is* a big deal, It means I don't at all understand it yet. :(

knucracker

#4
RandCoords pushes two values to the stack, an X and a Y coordinate.  They are pushed in that order, so Y is at the top of the stack and X is under it.  It's the same as calling
RandX and RandY in that order.

The stack change diagram would be: [ -- X Y ].
This means nothing is on the stack, and the result is X and Y on the stack with Y being on the top (the right most elements represent the top most elements in the stack).

This is how it looks arranged vertically, with Y on top of the stack.

Top
___
| Y |
| X |
---

Bottom


So, on your first line in your analysis your comment should read: "Assume X=50, Y=60"

You clearly understand stack manipulation in your analysis.  The only thing you got wrong was the order that RandCoords pushes values to the stack.  I could have done them in either order when I implemented the guts of that command (it's arbitrary), but I chose to put them in the current order so that they read X and Y from left to right.

Karsten75

You made me LOL, I should change the code, not the comment! :P

knucracker

Instead of this:
RandCoords swap 2 div swap 1 QueueMove

You could do this:
RandX 2 div RandY 1 QueueMove

I guess this is a case where using the individual Rand functions is cleaner than using the monolithic RandCoords function.

Karsten75

OK, I *think* I fixed the comment and stack appearance in the "once" section of my analysis. If you agree (there was also another error) then I'll move on a bit later. Right now I need a tea break.

lurkily

#8
Okay.  I'm trying to make a link between two positions that transports creeper instantly.  Basically, it averages the two locations, and sets both locations to the average.

I'm fumbling here.  First, I built this.

CurrentCoords GetCreeper 55 31 GetCreeper add 2 div This should get the average.  Right?

To parse that a little, (((CurrentCoords GetCreeper) (55 31 GetCreeper) add) 2 div).  I have a gut feeling that I'm handling this the wrong way.

Next . . . I need to set creeper at TWO locations to be the average of both.  But the moment I set creeper in one location, the average changes.Will I need to offset the cell that transfers creeper from the tower by one cell?  Or am I thinking about this the wrong way?

EDIT: Needs to be capable of a two-way flow.

knucracker

That should calculate the average Creeper in the two cells you show (current and 55,31).  So that is correct... what isn't correct is that I am internally rounding down the Creeper to the nearest int.  I used to return Creeper as an integer, but switched that to floats yesterday... but forgot to change the return type on one function.  I'll correct this in about 15 minutes and post an updated build.

To see what is going on you can do this:


ShowTraceLog
ClearTraceLog
CurrentCoords GetCreeper Trace


Trace pops and item from the stack and puts it in a Trace log.  The ShowTraceLog command pops up a little window on the unit so you can view the log.  Calling this repeatedly isn't harmful.  The ClearTraceLog call clears the log.  This keeps it from just filling up with the same value over and over in this example.

lurkily

THAT'S what the trace log is . . . here I was thinking I'd have to dig up a text file or something.

EDIT: Any advice on the above issue?  It's the heisenberg uncertainty principle at work in code.

Grauniad

For unit movement, what is the speed? cells per frame?

If a unit has a move order, does it do nothing else during that period of time?

Let's say I want to move a unit 50 squares and drop 10 creeper and a digitalis network as it moves....
A goodnight to all and to all a good night - Goodnight Moon

knucracker

Quote from: lurkily on December 17, 2012, 07:36:34 PM
THAT'S what the trace log is . . . here I was thinking I'd have to dig up a text file or something.

EDIT: Any advice on the above issue?  It's the heisenberg uncertainty principle at work in code.

I'm not quite following you on the second part of your problem....

Note that a script executes atomically, if that helps at all.  So if you do something to different cells in the same script invocation, they happen in sequence in the script, but nothing else happens in the game while this script is running.  Each script is part of each CRPLTower's update that happens once per frame.

knucracker

Quote from: Grauniad on December 17, 2012, 07:38:21 PM
For unit movement, what is the speed? cells per frame?

If a unit has a move order, does it do nothing else during that period of time?

Let's say I want to move a unit 50 squares and drop 10 creeper and a digitalis network as it moves....

Speed is in pixels per frame, where a cell is 8x8 pixels.  You can use floating point values (like 0.1 for a very slow mover).

Queued movements happen automatically outside of each script.  Think of them like movement instructions you have given to the CRPLTower.  It will follow those instructions independently of what your script continues to do.  So you can do whatever you like in the script while the unit is moving, including issuing a Delay command, etc.

Take this example

once
  10 10 1 QueueMove
  50 10 1 QueueMove
  50 50 1 QueueMove
  10 50 1 QueueMove
  10 10 1 QueueMove
endonce

CurrentCoords 2 SetCreeper



This queues up 4 movement orders once when the unit starts.  They will move it in a square and that's it.  No more movement after these complete.  While these movements are taking place creeper gets dropped every frame.

Moving and dropping creeper and digitalis would be very similar to Ex 10 in the docs post.  Instead of picking random coordinates, you could toggle between two locations, or three, etc.

lurkily

#14
I need to set two different cells to the average of their own level of creeper.  So a bare cell of creeper and a cell at depth five should instantly change to both being at 2.5.  

But I was worried that once I used SetCreeper, the result of the average would also change.  I needed to set creeper in the same cells that I was averaging.  

It looks like it isn't an issue.  I have it set to do the calculations once a second, tracing each time I do the average, after setting creeper and I haven't noticed any mismatches as the integer turns over.

It looks like the 'SetCreeper' doesn't actually change the value until after the script is done running.

Either that, or creeper values are just not granular enough until the updated build is out to show a mismatch.

Anyway, I'm using this.  Much code is mirrored in the comments with me using parenthesis to help me parse what is doing what. #Set creeper in position 1 to the average of Position 1 + 2
#(55 35 (((CurrentCoords GetCreeper) (55 31 GetCreeper) add) 2 div) SetCreeper)
55 35 CurrentCoords GetCreeper 55 31 GetCreeper add 2 div SetCreeper
# Set Creep in position 2 to the average of position 1 and 2
#(CurrentCoords (((CurrentCoords GetCreeper) (55 31 GetCreeper) add) 2 div) SetCreeper)
CurrentCoords CurrentCoords GetCreeper 55 31 GetCreeper add 2 div SetCreeper
30 Delay
EDIT: Using a shorter delay causes tons of creeper to just be 'lost' when it's too thin, as it divides already-thin creeper by two, and evaporates it.