The Let's Play Archive

EXAPUNKS

by Carbon dioxide

Part 14: Equity First Bank

Part 14 - Equity First Bank


=== Trash World Inbox ===

GuavaMoment posted:

My 216 solution is not elegant. Mostly identical to yours, but I run 21 lines of "Copy 75 F", until X < 2500, where I run 9 lines of "Copy 75 F" until X < 750, then keep alternating between copying 75 and testing every time until X is zero.
Ah, yes, so basically repeating large chunks of writing 75s with only the occassional test and loop and then progressively smaller chunks and more common tests. Optimizing that is gonna be trial and error, depending on the specific puzzle input.

silentsnack posted:

Looking at my solutions... I don't even remember writing this but aside from obviously being the product of trial and error it seems crude/unfinished? Maybe I'm just tired but I'm not seeing what it does all that differently, other than minor improvements like combining COPY F X and an ADDI X F X operation into ADDI F F X.
code:
;XA
GRAB 300
COPY F X
WIPE
LINK 800
GRAB 199
MARK FIND
TEST F = X
SEEK 2
FJMP FIND

SEEK -1
COPY F M

;XB
LINK 800
LINK 799
GRAB M
SEEK 2

ADDI F F X
@REP 7
ADDI X F X
@END
MARK ADD
ADDI X F X
TEST EOF
FJMP ADD

SEEK -999
SEEK 2
DIVI X 1800 T
MODI X 1800 X
MARK WRITE24
@REP 24
COPY 75 F
@END
SUBI T 1 T
TJMP WRITE24

DIVI X 225 T
FJMP END
MODI X 225 X
MARK WRITE3
SUBI T 1 T
@REP 3
COPY 75 F
@END
TJMP WRITE3

MARK END
TEST X > 75
FJMP BREAK
SUBI X 75 X
COPY 75 F
JUMP END

MARK BREAK
COPY X F
But somehow the stats are showing 195/75/3
Those 1800 and 225 calculations do something similar to what GuavaMoment said, reducing the number of loops by testing for larger chunks. That together with the ADDI combination thing and perhaps some other improvements make it much faster, I think.


=== Equity First Bank ===

Just when I think I'm decent at pretending to be human, I find there's another parameter to add.



Two votes for "I thought an AI would be way faster", and five for the winning option.

How long have you pretended to be human?

Oh, pretty much from the start.
It's kind of a thing that I do.
Remember when I surprised you? That was a good one.
Let's continue.


Yeah, let's.





That sounds... chaotic.

How much does money affect behavior?
A lot, right?




Two votes for "Some behaviors aren't affected", five for the other choice.

Yeah, a lot.

I'm going to do an experiment involving wealth redistribution.
Sudden, random wealth redistribution.
This should be fun.



OST: Leave No Trace

The assignment:
- Dispense all available cash from all connected ATMs.
- For more information see "Network Exploration: Equity First Bank" in the first issue of the zine.


The max code size for this assignment is 50 lines.



Okay, so we don't really need to mess with the account files. Let's take a quick peek though.



It's basically as the zine says. Files have an account number and name and then a bunch of transactions. There's also a file 199 but that seems to just contain a list of the other file names. Nothing very exciting.



Messing around with the ATMs a bit I quickly learned two things: the #DISP register only accepts exactly 20 as input. You can't tell it to dispense $40 to make it spit out two bills at once. And if you tell it to dispense bills when the #CASH register is empty, the entire host will error out, which immediately fails the "Leave no trace" goal.

Also, the attached ATM hosts actually differ for each test case. The LINK ids are always between 800 and 806 inclusive but it's often not the complete set.

So, the first challenge is getting EXAs to all the ATMs. This code will work:



I can think of quite a few ways to do this, some a bit faster than others. For instance I could just make 7 EXAs and hardcode the different LINK instructions to 800 - 806. But since the IDs are sequential, using a REPL clone loop seems a bit more straightforward and probably makes the program smaller.
The problem is that I need to have a way to stop the loop, otherwise it keeps spawning new clones forever, and we can't leave any EXAs running around.

I decided to handle that with a cleanup EXA that just kills the cloning EXA after its timer runs out.

Next, dispensing cash from all the ATMs. Just to have something working I started with this naive solution:
code:
LINK 800
LINK 800 
LINK 800
REPL CLEANUP

COPY 799 X

MARK CLONE
ADDI 1 X X
REPL CLONE
LINK X

COPY #CASH T

MARK DISPLOOP
COPY 20 #DISP
SUBI T 1 T
TJMP DISPLOOP

HALT

MARK CLEANUP
COPY 7 T

MARK CLNLOOP
SUBI T 1 T
TJMP CLNLOOP
KILL
A very simple loop with a countdown so it knows when the ATM runs out of cash.

There's no loop unrolling or anything and there's huge amounts of bills so this is slow.



Very slow. In fact, this takes almost a full minute to run all 100 tests at fast forward speed.



However, it does work, as you can see from the crowd of people flocking around the ATM.

The top percentiles are 1072, 14, and 10.

Let's start with improving activity. Since it always counts the worst test, the activity is 10 if you need to get EXAs to all 7 ATMs and only move the one to the host just before the ATMs. So the only activity step that can be removed is the one KILL instruction.

Well, that's easy enough if we don't care about making the code even slower.
code:
LINK 800
LINK 800 
LINK 800

COPY 799 X

MARK CLONE
ADDI 1 X X
TEST X > 806
TJMP END
REPL CLONE
LINK X

COPY #CASH T

MARK DISPLOOP
COPY 20 #DISP
SUBI T 1 T
TJMP DISPLOOP

MARK END
I put the countdown in the CLONE loop so I can remove the cleanup EXA. Result: 3028/16/10.

That's quite nice, we also happen to be getting close to the top percentile in size. Only two more lines of code to get rid of.

The MARK END isn't actually required. We can TJMP from the CLONE loop to the DISPLOOP mark... it'll then try to COPY to #DISP which doesn't exist in that host and it'll crash. That's 15 LoC.
Also, the COPY from #CASH and the countdown in T can be combined into a single line by just testing #CASH directly each round.



3027/14/10

Let's go back to the initial solution and see if we can speed it up. With a max size of 50, we don't have much spare room for unrolling loops but we can do at least some.

code:
LINK 800
LINK 800 
LINK 800

COPY 799 X

MARK CLONE
ADDI 1 X X
TEST X > 806
TJMP DISPLOOP
REPL CLONE
LINK X

MARK 33LP
@REP 33
COPY 20 #DISP
@END

TEST #CASH > 33
TJMP 33LP

MARK DISPLOOP
COPY 20 #DISP
TEST #CASH = 0
FJMP DISPLOOP
I started from the low-size solution since it fits some more unrolling. Simple enough: if there's still more than 33 bills left, spit out 33, otherwise count them one by one. 1145/50/10. Much faster already. Also, turns out that combining this with the cleanup EXA solution drops the cycle count to 1144, even though there's only space for 27 unrolls.

From that solution we can drop it to 1125/50/11, using this code:
code:
LINK 800
LINK 800 
LINK 800
REPL CLEANUP

COPY 799 X

MARK CLONE
ADDI 1 X X
REPL CLONE
LINK X

MARK 28LP
@REP 28
COPY 20 #DISP
@END

TEST #CASH > 28
TJMP 28LP

MARK DISPLOOP
COPY 20 #DISP
DIVI X #CASH X
JUMP DISPLOOP


MARK CLEANUP
COPY 7 T

MARK CLNLOOP
SUBI T 1 T
TJMP CLNLOOP
KILL
The main change is that I got rid of the HALT instruction by stopping the loop using a divide by zero crash. This lets me bump the unroll to 28 which is enough to save a bunch more cycles.

And because increasing the size of the unroll is so important, I should focus on that, regardless of anything else.

For instance, replacing the cleanup code with the much uglier
code:
MARK CLEANUP
SUBI X 1 X
TEST X < -6
FJMP CLEANUP
KILL
saves a line of code, allowing one more COPY in the unroll, dropping the cycles from 1125 to 1122.

Finally, I found one more minor improvement, but it's a bit of a weird one.
code:
LINK 800
LINK 800 
LINK 800
REPL CLEANUP

COPY 799 X

MARK CLONE
ADDI 1 X X
REPL CLONE
LINK X

MARK 29LP
@REP 29
COPY 20 #DISP
@END

DIVI 28 #CASH T
FJMP 29LP

MARK DISPLOOP
COPY 20 #DISP
DIVI X #CASH X
JUMP DISPLOOP


MARK CLEANUP
SUBI X 1 X
TEST X < -6
FJMP CLEANUP
KILL
1120/50/11

The DIVI 28 #CASH T line speeds up things slightly because it acts as a three-way test.
If #CASH is 29 or greater, the result will be 0, causing FJMP to jump back into the unroll and dispense 29 more bills.
If #CASH is smaller than 29 but greater than 0, the result is (rounded) 1, so it won't jump and continue to the DISPLOOP.
And if it's exactly zero, the EXA immediately crashes with a divide by zero error. This means that it doesn't even have to go into the DISPLOOP once if the amount of bills is a multiple of 29, which happens in some slow test, saving two cycles.

1120 is still almost 50 cycles away from the top percentile. I'm still missing a significant improvement somewhere. I was thinking about making a smaller unrolled loop after the big one - to more quickly deal with the remaining bills. But since that takes cycles from the large unrolled loop, that seems to make things slower overall. Perhaps there's a very specific combination that makes it faster but I haven't found it.

Alternatively I'd look into running the DISPLOOP code in parallel, but I'm not sure how to do that. There's no place for an extra EXA in the ATM hosts, so no other EXA can come in to kill the dispenser EXA. And communicating via M would be hard too - there's only one global M register for all EXAs, and waiting for a kill signal on M isn't any faster than an EXA handling that by itself.

So, let me know what I'm missing.

Well, that caused a bit of chaos.
But not as much as I hoped.
I wonder why.




And next time, more body hacking to slow down the Phage.

So you're going to be hacking your own heart.