BatBrain -- a central nervous system for consult scripts

Just posted an updated batfactors using integers for effects that include spaces. This needs r15372 or later to work. Hopefully now we can stop most of the error reports being made in the ZLib thread (wish there was a way to capture that error and send people to the appropriate thread).


Did you use the since command or get_revision() function to ensure that people get a useful failure message?
 
Last edited:
Thanks for the brainstorming help guys. As there are various items and skills that can become available mid-combat, looks like building a shortlist on the first page load and foreaching that instead of factors[] is probably not going to work. But it was only part of a larger idea which I believe can still work.

I'm pretty excited about my refactoring of tracking events. I've almost completely reformatted it to make it more robust as well as more detailed. Also it writes to disk less.

The new format for happenings is going to be boolean[monster, int, string, int]. Those indices are [m, total_turns_played(), action id, round] and the boolean indicates whether the action is a player action or not. We also don't write happenings to disk every time set_happened() is called anymore, but rather once at the end of event detection in act(). This means 1) we are writing to disk much less, and ii) enqueued actions are not saved to disk, since for all current purposes of BatBrain that's unnecessary.

Most things should be the same for calling scripts, however. If a script is calling set_happened() anywhere, that may be affected, but otherwise things should work as normal.

I'm trying to work out some strange issues which I believe are probably caused by transforming a monster, as with CLEESH or the wand of pigification. Since we're now tracking events per monster, transforming a monster mid-fight causes some issues. And since you all are so good at finding odd cases, can anyone think of any other case where a monster may be transformed into a different monster mid-fight? All I can think of at the moment is mariachis in the hacienda.

@Bale: Um, it was a batfactors update. Don't think "since" works in data files. I'll add it to BatBrain for the next update though, just in case.
 
Well! The r57 Update begins a series of speed improvements, with a large number of fixes and optimizations. I feel that speed has become BatBrain's biggest weakness, and BatBrain really ought not to have any, so there you go. The first thing to be overhauled is, as mentioned above:

Happenings Tracking

BatBrain now reads your session log! This means that event tracking can catch up to the present if you perform some actions in your CCS before running a BatBrain-powered script. It also means that performing non-macro actions in BatMan RE will still be tracked! I think this is a fantastic change!

Happenings are now stored in a [monster, int, string, int] => boolean map. Those indices equate to m, turncount (which is now set to total_turns_played() rather than my_turncount()), the action id (e.g. "skill 3004"), and the round the action happened. You can now check events that happened with far greater detail, though the old happened() function still works as before.

Furthermore, happenings are read from disk once per BatBrain instance (as before) and saved to disk only once per page load (rather than once per happening registered as before), which ought to save quite a bit of time writing to disk. Queued actions are not even written to disk at all. The downside is we added a bit of time reading from disk (by accessing the session log).

If BatBrain cannot find "Encounter: "+last_monster() in your session log (which happens fairly often due to monsters with varying names, such as hobos), BatBrain will revert to tracking happenings the old way. I'm considering ignoring the monster and simply checking from the most recent Encounter regardless of monster name, but I'm going to try this "safer" version first.

Other Tweaks

So, I added a main() to BatBrain to make it easy to profile, and I noticed that merge() was taking a pretty huge amount of time. A fair number of instances of merge() were simply hackish ways of copying an advevent by merging it with a blank one. So, I made new, faster copy() functions for spreads, substats, and advevents, and BatBrain now uses those in place of all the merge(a, new advevent)s that it used to.

The next step towards streamlining merge() is that it now actually alters the first advevent specified. So rather than doing myevent = merge(myevent, otherevent), which was by the way the most common usage of merge(), we can simply use the merge command by itself. This necessitated various code changes in cases where merge() was not used reflexively, but it helped streamline that function.

BatBrain still takes uncomfortably long to load for a character with ALL THE SKILLSES (I'm a fully skilled Boris at the moment though and it took a reasonable 0.9 seconds). This was only the first step of optimizations, however, and the big one (actions loading) is going to be my next project.

A Question

I do have a question related to speed enhancements, however: we could save about 0.2 seconds if we collapsed all the ranges specified in batfactors. BatBrain spends about 0.2 seconds every time it loads collapsing those ranges, and it seems to me we could save some time by pre-collapsing them. If a day ever comes when BatBrain accounts for damage ranges, I strongly doubt it will be soon (meaning within the next several years). The original impetus for adding them was that StDoodle was thinking about sharing the data file for his own combat script, which to my knowledge never materialized. I'm all for collapsing them and saving 0.2 seconds * every BatBrain instance * every player for several years. Anyone agree? Disagree? Feel ambivalent?

Additional Changes

A few other things got fixed or supported:

  • Oh by the way, for those of you who like to mance pasta and go around fighting monsters together with your thralls, I have just added support for them (the thralls)! Since they affect combat they are now a new category in batfactors (though there aren't many of them, I still like making their formulae publicly editable).
  • I fleshed out support for the "nopotato" monster flag which we learned about from those KoL monster attribute screencaps. All potato familiars in batfactors now have the "potato" keyword in their special category (at least I think I got them all), meaning that if a monster with the "nopotato" keyword is encountered (presently I don't know of any such monsters, but surely they exist, Shirley), your potato-type familiars will be properly ignored.
  • You know how some monsters have auras that deal damage? Who do they deal damage to? The monster? No, the player, duh! Yep.
  • When taking a look through WHAM to see how badly I'd broken it with this update, I noticed that BatBrain still lacked support for two Video Game boss qualities: "Ignores armor" and "Reduced damage from spells". We didn't know how to support that before, but thanks to the overhaul of a while ago making BatBrain monster knowledge more closely resemble KoL's, we can now support both of them. Added!
  • I was taming some turtles a while ago when I noticed that I was never properly whipping the French. One must always properly whip the French. So I checked it out, and it seems that whereas once guard turtles wearing berets were called French guard turtles, now it seems they are still called regular guard turtles. I suppose racial assumptions are dangerous things to make. After all, when you assume, you make an as out of Su and me. Anyway, we are now assumption-agnostic regarding said turtles, and will whip all beret-wearing turtles regardless of ancestry.
Enjoy!
 
Collapse the damage ranges. They're never used so it is just a time consuming overhead. I could forsee an optional safe mode of combat that makes use of (2*low+high)/3 for damage prediction instead of the usual spread average, but since nobody has any plans for that, I can't justify it.

Question: custom actions aren't supposed to be added to opts, right? I recently found myself having to explicitly ban them when I sort opts. Do I misremember how that is supposed to work? Maybe that was a fix in the current release so this question isn't even relevant. Maybe that was an error on my part though since I wasn't using WHAM.


I suppose racial assumptions are dangerous things to make. After all, when you assume, you make an as out of Su and me. Anyway, we are now assumption-agnostic regarding said turtles, and will whip all beret-wearing turtles regardless of ancestry.

LoL! Great!
 
Last edited:
Answer: Your understanding was originally the case, but custom actions were allowed loose into opts[] quite a while ago (at the time specific custom keywords were added). This allows get_action() to work for custom actions, and also allows cool custom groupings of actions (such as "yellow", "banish", and so forth). If you are doing your own custom sorts you must explicitly skip them, as you'll note BatBrain does in its <type>_action() functions.

EDIT:

BY THE WAY

Can someone tell me if this transitions okay without manually deleting your old happenings file? I'm curious what happens. I may need to post a big boldface warning telling people they need to delete their old happenings file -- or add some transitional code that somehow detects the old format and wipes it first.
 
Last edited:
[*]Oh by the way, for those of you who like to mance pasta and go around fighting monsters together with your thralls, I have just added support for them (the thralls)! Since they affect combat they are now a new category in batfactors (though there aren't many of them, I still like making their formulae publicly editable).
This seems to have broken SmartStasis for me. The log gives me the following errors:

Expression syntax errors for 'modifier_eval()':
Can't understand thralllevel-4))*thralllevel
Expected ), found
Expected ), found (zlib.ash, line 186)

By editing zlib I was able to get the actual expression that it attempts to evaluate:

min(0.75,max(0,thralllevel-4))*thralllevel

My CCS looks like this:

[ sloppy seconds burger ]
skill transcendent olfaction
skill pocket crumbs
consult SmartStasis
attack with weapon

Here's a full battle:


[957277] Sloppy Seconds Diner
Encounter: Extra-Ketchup Jungle Burger
Strategy: /Users/brian/Library/Application Support/KoLmafia/ccs/default.ccs [default]
Round 0: Parco Molo wins initiative!
Round 1: Parco Molo casts POCKET CRUMBS!
Round 2: Extra-Ketchup Jungle Burger drops 1222 attack power.
Round 2: Extra-Ketchup Jungle Burger drops 1444 defense.
Round 2: Medium says, I sense an unexpected windfall in your near future! and hands you some meat.
You gain 6 Meat.
Round 2: Mimic cranks up the throttle on his little helicopter. Vroom!
Round 2: Mimic mimics a red-and-white striped barber pole, spinning in front of your opponent. It looks mesmerized.
Round 2: Extra-Ketchup Jungle Burger drops 48 attack power.
Round 2: Extra-Ketchup Jungle Burger drops 48 defense.
Round 2: Graaaaugh slashes at your opponent with bloody razor-sharp... noodles? Anyway it does 28 damage.
Round 2: Extra-Ketchup Jungle Burger takes 28 damage.
Evaluating 'min(0.75,max(0,thralllevel-4))*thralllevel'...
Expression syntax errors for 'modifier_eval()':
Can't understand thralllevel-4))*thralllevel
Expected ), found
Expected ), found (zlib.ash, line 187)
Round 2: Parco Molo attacks!
Round 3: Extra-Ketchup Jungle Burger takes 11529 damage.
Round 3: Medium says, I sense an unexpected windfall in your near future! and hands you some meat.
You gain 10 Meat.
Round 3: Graaaaugh claws your opponent with filthy lasagna fingernails, dealing 26 damage.
Round 3: Extra-Ketchup Jungle Burger takes 26 damage.
Round 3: Parco Molo wins the fight!
After Battle: A love dragonfly buzzes softly in your ear.
You gain 4 Smarm
You gain 23 hit points
You gain 8 Mana Points
After Battle: Medium surveys the scene from atop the throne and sighs.
After Battle: Graaaaugh winks rapidly at you... or maybe it's just a twitch?
You gain 1875 Meat
You acquire an item: Meat-inflating powder
You gain 729 Strengthliness
You gain 1494 Enchantedness
You gain 694 Roguishness
 
I do have a question related to speed enhancements, however: we could save about 0.2 seconds if we collapsed all the ranges specified in batfactors. BatBrain spends about 0.2 seconds every time it loads collapsing those ranges, and it seems to me we could save some time by pre-collapsing them. If a day ever comes when BatBrain accounts for damage ranges, I strongly doubt it will be soon (meaning within the next several years). The original impetus for adding them was that StDoodle was thinking about sharing the data file for his own combat script, which to my knowledge never materialized. I'm all for collapsing them and saving 0.2 seconds * every BatBrain instance * every player for several years. Anyone agree? Disagree? Feel ambivalent?
There is a feature I added to ASH a couple of years ago to allow "constant" data structures to be evaluated once, when the script is first loaded into a KoLmafia instance. This is static.ash:

Code:
static
{
    int a = 0;
    print( "a = " + a );
    print( "loading static data" );
    a = 1;
}

print( "a = " + a );
Here is running it twice:

> static.ash

a = 0
loading static data
a = 1

> static.ash

a = 1
I expect that you can see how this can be used to do time consuming data manipulation exactly once.

Now, if I understand correctly, you want to read batfactors each time you run it and initialize data from that. You can still use this scheme if you have a way of knowing that batfactors has not changed since the last time you parsed it. Timestamp, version number, whatever; that's just a matter of data design for you.

But ASH already has the feature you need (added, by the way, with you, specifically, in mind), to eliminate repeating time-consuming one-time processing of static data.
 
Code:
r10765 | veracity0 | 2012-03-11 18:23:42 -0400 (Sun, 11 Mar 2012) | 3 lines

Add a "final" scope type to ASH which allows data/commands to be executed only
once per session.
...
r10774 | veracity0 | 2012-03-12 10:03:47 -0400 (Mon, 12 Mar 2012) | 2 lines

ASH: "final" -> "static
I guess "a couple of years ago" was "just shy of three years ago".
 
@Veracity: Wow. That slipped right under my radar (which has admittedly not been constantly on). It looks fantastic. I'll have to play with it and see what I can come up with.

@Crowther: Are you actually experiencing said loop? Because the knife isn't combat_reusable and you only have one, right? So if you enqueue it once it ought to be gone from opts[]. All the same, editing batfactors to include "once" wouldn't hurt anything.
 
@Crowther: Are you actually experiencing said loop? Because the knife isn't combat_reusable and you only have one, right? So if you enqueue it once it ought to be gone from opts[]. All the same, editing batfactors to include "once" wouldn't hurt anything.

Yes, I experienced it on a couple of occasions. Adding once seemed to fix the problem. I doubt I had more than one. I can check my logs this weekend. I'll also add once myself, when I get a chance to slow down and carefully edit batfactors.
 
Well, my event tonight was canceled last minute, so I have more time than expected. I've updated batfactors.txt.

My logs show me only acquiring a single knife, which is not surprising. Ed doesn't even need that one. I've noticed that WHAM decides to use the knife instead of attack for the killing blow, which is a bit silly, but harmless. Normally that works. I see it many times in my logs. The inifinite loop happened when the killing blow failed. I wonder if "Adjusted combat item count: electric boning knife" has something to do with this?

Code:
[937] The Hidden Apartment Building
Encounter: pygmy witch accountant
Round 0: Crowther wins initiative!
> WHAM: Monster HP is 188.0.
> WHAM: Running SmartStasis
> WHAM: Throwing some pocket crumbs at yoru opponent
> Custom action: skill 7170 (no stun)
Round 1: Crowther executes a macro!
Round 1: Crowther casts POCKET CRUMBS!
Round 2: pygmy witch accountant drops 18 attack power.
Round 2: pygmy witch accountant drops 22 defense.
You acquire an item: old love note
> Look! You found 1 old love note (1μ)!
> WHAM: Starting evaluation and performing of attack
> Look! You found 1 old love note (1μ)!
> WHAM: Enqueuing a stun to help with the battle
> WHAM: We are going to 9-shot with Summon Love Gnats, attack with your weapon, attack with your weapon, attack with your weapon, attack with your weapon, attack with your weapon, attack with your weapon, attack with your weapon and electric boning knife.
Round 2: Crowther executes a macro!
Round 2: Crowther casts SUMMON LOVE GNATS!
Round 3: Crowther attacks!
Round 4: pygmy witch accountant takes 7 damage.
Round 4: Crowther attacks!
Round 5: pygmy witch accountant takes 31 damage.
Round 5: Crowther attacks!
Round 6: pygmy witch accountant takes 7 damage.
Round 6: Crowther attacks!
Round 7: pygmy witch accountant takes 8 damage.
Round 7: Crowther attacks!
Round 8: pygmy witch accountant takes 8 damage.
Round 8: Crowther attacks!
Round 9: pygmy witch accountant takes 6 damage.
Round 9: Crowther attacks!
Round 10: pygmy witch accountant takes 7 damage.
Round 10: pygmy witch accountant takes 12 damage.
You lose 20 hit points
Round 10: Crowther uses the electric boning knife!
Round 11: pygmy witch accountant takes 8 damage.
Round 11: pygmy witch accountant takes 10 damage.
You lose 19 hit points
Adjusted combat item count: electric boning knife
> WHAM: Current monster HP is calculated to 84.0
> WHAM: Starting evaluation and performing of attack
> WHAM: We are going to 4-shot with attack with your weapon, attack with your weapon, attack with your weapon and electric boning knife.
Round 11: Crowther executes a macro!
Round 11: Crowther attacks!
Round 12: pygmy witch accountant takes 7 damage.
Round 12: pygmy witch accountant takes 7 damage.
Round 12: pygmy witch accountant takes 10 damage.
You lose 21 hit points
Round 12: Crowther attacks!
Round 13: pygmy witch accountant takes 7 damage.
Round 13: pygmy witch accountant takes 9 damage.
Round 13: pygmy witch accountant takes 13 damage.
You lose 18 hit points
Round 13: Crowther attacks!
Round 14: pygmy witch accountant takes 8 damage.
Round 14: pygmy witch accountant takes 9 damage.
Round 14: Crowther uses the electric boning knife!
Adjusted combat item count: electric boning knife
> WHAM: Current monster HP is calculated to 14.0
> WHAM: Starting evaluation and performing of attack
> WHAM: We are going to 1-shot with attack with your weapon.
Round 15: Crowther executes a macro!
Round 15: Crowther attacks!
Round 16: pygmy witch accountant takes 5 damage.
Round 16: pygmy witch accountant takes 8 damage.
> WHAM: Current monster HP is calculated to 1.0
> WHAM: Starting evaluation and performing of attack
> WHAM: We are going to 1-shot with electric boning knife.
Round 16: Crowther executes a macro!
Round 16: Crowther uses the electric boning knife!
Adjusted combat item count: electric boning knife
> WHAM: Current monster HP is calculated to 1.0
> WHAM: Starting evaluation and performing of attack
> WHAM: We are going to 1-shot with electric boning knife.
Round 17: Crowther executes a macro!
Round 17: Crowther uses the electric boning knife!
Adjusted combat item count: electric boning knife
> WHAM: Current monster HP is calculated to 1.0
> WHAM: Starting evaluation and performing of attack
> WHAM: We are going to 1-shot with electric boning knife.
Round 18: Crowther executes a macro!
Round 18: Crowther uses the electric boning knife!
Adjusted combat item count: electric boning knife
> WHAM: Current monster HP is calculated to 1.0
> WHAM: Starting evaluation and performing of attack
> WHAM: We are going to 1-shot with electric boning knife.
It actually went 85 rounds, because it's hard to stop things when I play on a remote sessions.

Oh, and "your" is spelled "yoru" in WHAM.ash, but this is the wrong thread for that and maybe more.

I wonder why WHAM miscalculated the kill three times? Bad RNG?
 
Answer: Your understanding was originally the case, but custom actions were allowed loose into opts[] quite a while ago (at the time specific custom keywords were added). This allows get_action() to work for custom actions, and also allows cool custom groupings of actions (such as "yellow", "banish", and so forth). If you are doing your own custom sorts you must explicitly skip them, as you'll note BatBrain does in its <type>_action() functions.

Thank you so much! I knew I remembered that, but it wasn't working that way now. Confusion solved.
 
@Crowther: Yes, actually mafia thinks you lose both the beehive and the boning knife when you use them in combat, but I believe that's a separate bug (which one of us should probably report). The fact that you don't lose the item (and mafia corrects itself quickly when it sees the item in your dropdown) is probably what's confusing BatBrain, since it checks item_amount() to determine if you have items available. The knife and the beehive should both be flagged as "once".

@Bale: You are welcome, good sir!

@Veracity: I've been playing with this a bit. My first several test scripts wouldn't work until I figured out that variables need to be actually declared within the static scope for it to work, which makes it a little awkward to have it use a function:

PHP:
boolean weneedtoreload;
void loadfactors();   // need to pre-declare

static {
   string[item] factors;
   int mynum;
   loadfactors();
   weneedtoreload = false;
}
void loadfactors() {
   print("loading...");
   file_to_map("collectors.txt",factors);
   foreach i,c in factors factors[i] = c + " blahblah";
   mynum = random(100);
   print("loaded");
}
if (weneedtoreload) {
   loadfactors();
}

print("facts: "+count(factors));
print("num: "+mynum);

Resulted in:

> call test

loading...
loaded
facts: 6
num: 57

> call test

facts: 6
num: 57

Nice. It also means that if I want to use it with global variables, it cannot be inside a function; it must be at top level before any functions accessing those variables, which means by the time a script gets to main() the static code will already have happened. Now that I get those limitations I believe I'll have a go at integrating this into BatBrain. It should mean that we don't have to worry about collapsing those ranges in batfactors, since once BatBrain collapses them on the first run of the script (which already takes quite a bit longer due to version checking), it will be able to access collapsed data from that point forward. Exciting!
 
Back
Top