BatBrain -- a central nervous system for consult scripts

Another little error with Harpoon!, the formula currently says "max(800.0,floor(fvars["buffedmus"]/4.0))", but that leads to very large numbers and should be changed to "min(800.0,floor(fvars["buffedmus"]/4.0))" since we want the smallest value of the two.
 
I want to speed up to_event() using a matcher rather than a split_string(", ") followed by a bunch of excise(""," ") on the resultant bits. However, my regex tongs, though they have enabled me to overcome my fear of dealing with regexes at all, are not powerful enough to deal with the necessary regex for this. I have already sustained some minor injuries from my attempts thus far, and am therefore calling upon the bold and courageous scripters of this community, who fear no regex, great or small.

Here's the sitch. The data file for events has a "special" field, which contains a comma(+space)-delimited list of "keyword value", like so:

att -3, regular, stun min(1,monsteratt/4)*0.5, stats 0/0/2, oncrit, !! this is totally not a real event!

I want a two-group matcher that gives me the keyword in group 1 and the value in group 2. It should skip keywords without values (like "regular" and "oncrit" above). The value may contain spaces after the initial space separating the keyword from the value.

The keywords are finite, so one possibility would be to match all possible keywords that require values -- something like this

[^ or ", "](hp|mp|att|def|stun|meat|monster|notmonster|item|stats|!!) (group 2, accepting all characters except)[$ or ", "]

However, I'd like it to accept any all-lowercase keyword, to allow easy future expansion.

The brave individual who posts the winning regex gets two free copies of the updated BatBrain, one for you and one for a friend! Please enclose the regex safely in
Code:
 tags when you post it, so that it doesn't damage any other posts.  Thanks!

ETA: posting it quickly will probably mean a BatBrain update tonight! (it's 10:45pm here now)
 
Last edited:
Here is what I came up with for now:
Code:
"\\s*((?!!)[^,\\s]+)*\\s*([^,]*(?:,(?!\\s)[^,]*)*),?\\s*"
And the result, with the example you posted:
Code:
> call test.ash

match found
att
-3
match found
regular

match found
stun
min(1,monsteratt/4)*0.5
match found
stats
0/0/2
match found
oncrit

match found
0
!! this is totally not a real event!
match found
0
I don't really know what happens with the last two matches, but it kinda works!

EDIT: it also works if you have several commas in the value, as long as they are not followed by a space:
Code:
> call test.ash

testing: att -3, regular, stun min(1,monsteratt/4)*0.5,more
match found
att
-3
match found
regular

match found
stun
min(1,monsteratt/4)*0.5,more
match found
0
 
Last edited:
Wow, I'm very glad you enclosed that in a code box, it looks very dangerous.

It shouldn't match "regular" or "oncrit" since those keywords don't have values. In other words, all matches will contain a space separating the keyword from the value. That probably makes it a good deal easier than what you were attempting to do!
 
I though you were going to do something like this:
PHP:
while ( matcher.find() )
{
   string key = matcher.group( 1 );
   string value = matcher.group( 2 );
   if ( key == "regular" ) { ... }
   else if ( key == "oncrit" ) { ... }
   else if ( key == "stun" ) { int s = evaluate( value ); ... }
   ...
 }
Skipping specific keywords is actually harder to do I think, without spelling out for the regex all the ones you want to skip.
 
It's not just specific keywords, it's keywords without values. Those flags aren't parsed in to_event() -- and I can just check them using a simple contains_text(). So for our purposes, if the chunk doesn't have the space separating keyword from value, it's not a match.

I've come up with this using some tutorials and a regex tester, and it's worked on all my provided examples thus far:

Code:
([a-z!]+?) (.+?)(?:$|, )

I'll put that in, and if someone finds something wrong with it later, I'm sure they'll report it.

I can't believe that I may have solved this problem by myself. It may be that I'm stronger than I've believed all these years. It may be that my fear was defining who I was, and actually if I just believe in myself and allow myself to be brave, I can accomplish anything. It may be a case of self-fulfilling prophesy and now, by setting out to accomplish something rather than presuming failure, I've proven my fear wrong.

Or it may be that this particular regex is just lying in wait, and when the moment is right, it will leap for my throat.
 
Oh, it was easier than I thought...
Try this one:
Code:
[URL="file://\\s*("]\\s*([/URL][^,\\s]+)\\s+((?:(?:,(?!\\s))?[^,]*)*),?\\s*
It skips single keywords not followed by a space.

You can play around with it here: http://regexr.com?2u4mm

EDIT: hahaha, why do simple when you can do complicated. Yours work wonderfully.
 
Last edited:
1.8 Update!

I'm very pleased with all the goodies in this update. Please enjoy reading about how awesome this script is becoming.

As I mentioned before, the damage cap update is not happening this time. I did, however, cap the hipster's attack in batfactors since it was causing very inaccurate predictions. BatBrain also tracks the Hipster's substats-granting action, although as of yet it doesn't do anything differently about it.

Robust Tracking

Speaking of tracking, there's now a new mechanism for tracking once-per-combat happenings which can't be otherwise detected. The previous method of storing these in global variables worked fine -- assuming that script ran continuously. But for fight.php overrides or interrupted script execution, the variables are reset and the script therefore gives erroneous information. So now, the script saves tracked information in an external int[string] map. The string is the event being tracked, and the integer is the turn on which the event last happened. The map is only written if an event actually happens.

Current happenings tracked:
  • famspent: For hobo monkeys, Slimelings, Spirit Hobos, and GGG's. Your familiar has used up its special actions.
  • smusted: Your Bandersnatch has created a cloud of Smust around your opponent.
  • waved: You have waved at a raver this combat.
  • mistletoed: You have cast Stealth Mistletoe.
  • stolen: You have successfully stolen an item by pickpocketing or Rave Steal.
  • mother_element: You have triggered Mother Slime's resistance to element.
  • hipster_stats: Your Mini-Hipster has performed its stats-granting action.

I'm very excited to finally see my fight.php relay override wise up about Mother Slime's resistances throughout combat, rather than only on the round in which they are triggered.

You can use this mechanism to track happenings as well, using these functions:

boolean happened(string occurrance)
void set_happened(string occurrance)


Use the former to check if something has happened. Use the latter to inform BatBrain that whatever it is has happened.

Critically Awesome

Thanks to all of the recent hitchance reworking, it was a simple matter to add the critical event to your regular attack. Many weapons or other items cause something extra to happen on critical hits, so those items were added to batfactors and the critical event was factored in.

Poisoned? Eh, It's Nothing

BatBrain now ignores two things: 1) mafia's autoAntidote setting, and b) harmless poisons. A poison is considered harmless if, having the effect, the monster still can't hit you and your attack can still kill the monster before maxround. Toad in the Hole, however, is always dangerous.

As a nifty side effect of adding this, my_stat() now works for "Muscle", "Mysticality", and "Moxie" and is sensitive to "whatif." This is a step along an exciting path towards making BatBrain totally speculative. Another side effect is that now I'm often seeing completely empty batrounds. :)

Misc

  • Fix the lower bounds issue in hitch-ance formula.
  • Fix the max => min issue in the Harpoon! formula.
  • During enqueueing, precede deleveling skills with Mistletoe if you have it available and haven't used it yet this combat. For skills where it has no effect, it's still completely harmless to cast so better detection of which skills it affects is unnecessary.
  • Parse events more efficiently using a matcher. Parse the 'stats' and 'note' fields, and include a note field in the advevent, since that extra info may be desirable for relay overrides.

Enjoy the crap out of this update!
 
Regarding the poison thing: If a poison is considered dangerous if you cannot kill the monster with your regular attack before the round limit, does that not mean that all poisons will be dangerous for myst-classes that spellsling their way through the game?
When I'm a myst-class I quite often run around in areas where my normal attack will not be able to hit the monster at all...
 
the integer is the turn on which the event last happened.
I know it's hard to differentiate fights, but this method has the disadvantage of not accounting for free fights or fights that end with free runaways. Adding a field for the monster name would allow further differentiation.

Maybe BatBrain should wipe BatMan_happenings.txt when a fight is lost or won. If the fight isn't finished with BatBrain running, it would be the user's or a script's responsibility to do so, I guess.
 
I think a feature request for some kind of unique ID for each action might be more in order. Maybe I'm going over the top, but it seems an idea. Alternately, one could use a time-stamp; there is, after all, nothing preventing you from having two 'identical' free fights in a row.
 
Hi. Since the latest updates, I've been getting the following in the airship, using a mosquito equipped with a tiny costume wardrobe (yes I know I'm crazy).
PHP:
[314] Fantasy Airship
Encounter: MagiMechTech MechaMech
Strategy: C:\Kolmafia\ccs\PJ_stasis.ccs [default]
Round 0: panama joe wins initiative!
Round 1: Mozz disappears into the wardrobe, and emerges dressed as a Snow Angel.
The string "1-1-1" does not look like an integer; returning 0 (BatBrain.ash, line 221)
then after steal and disco concentration.....
PHP:
Round 5: magimechtech mechamech drops 7 attack power.
Round 5: magimechtech mechamech drops 7 defense.
The string "1-1-1" does not look like an integer; returning 0 (BatBrain.ash, line 221)
Round 5: panama joe executes a macro!
Round 5: panama joe casts ENTANGLING NOODLES!
The string "1-1-1" does not look like an integer; returning 0 (BatBrain.ash, line 221)
You're on your own, partner.
Click here to continue in the relay browser.
 
Change line 220
PHP:
case "stats": string[int] sts = split_string(bittles.group(2),"/");
to
PHP:
case "stats": string[int] sts = split_string(bittles.group(2),"-");
 
Another question, if I may. Batbrain seems to be using saucy salve rather recklessly for stasis when I'm using my rogue program rather than a seal tooth or the 0 MP skill, even when I have full HP. This is causing problems in runs where I am already short of meat because of running high ML and 80 rounds of seal tooth would still not kill the monster.

Would it be possible to add a user variable for handling such edge cases?
 
@Winterbay: so far, yes -- but this is just as previously when it blindly followed the setting even if it would kill you. A better suggestion for dangerous poison detection is welcome.

@lostcalpoly: Good thinking, but "famspent" means that the familiar is completely done and the familiar event returns empty. The Hipster still has goodies after giving stats.

@slyz: I thought of that, but concluded in the end that for now this is a better solution than nothing, since free combats are limited or expensive. Adding the monster name (or even round number) would help, but would create edge cases where they match for two consecutive fights. A timestamp wouldn't work, since any amount of time may have passed between two combat rounds.

Probably the best solution is for the map to be cleared in your mood. I suppose BatBrain could automatically add "int[string] empty; map_to_file(empty,"BatMan_happenings.txt");" on an unconditional trigger to your mood, but I don't want to be spamming people's moods either. Needs testing.

Also, the n-n-n => n/n/n issue is a batfactors issue, not something to change in the script. Using "-" as the separator was foolish of me, since there may be negative stats. For the most part I haven't added stats to batfactors yet, so if you notice something lacking stats information, feel free to add it. The format is "stats mus/mys/mox".

@heeheehee: My goodness, you're right. 'Occurrence' is one of the "spelling demons" -- one of the most frequently misspelled words -- and I therefore definitely shouldn't have blundered so. This from a former district spelling bee champion, no less. I feel the shame of the inadequate speller. (hahahaha Theraze)

@jwylot: I suspect this is due to the free MP it expects from your Rogue Program -- it makes Salve essentially "free". Salve would then be seen as the ideal stasis action, since it's free and does no damage (less than a seal tooth). This is one of the issues caused by using average values.

However, this gives me an idea. I think I'll rework the stasis_action() sort to consider all actions that would never kill the monster equal in terms of damage -- rather than sort by dmg_dealt() as our final criterion, sort by max(dmg_dealt(), monster_stat("hp") / max(1,maxround - round) + 5). That 5 is there as a little cushion against damage range swing. That means that if you're fighting a monster with 900 HP, only an action dealing at least 30 damage even has a chance to kill the monster, so all actions dealing < 30 damage will be considered equally for stasis, sorted by profit.

That ought to fix your issue, as well as improve the stasis action selection logic overall. Thanks for mentioning this, because I do believe this is an excellent enhancement to the sorting procedure. If I add attack deleveling to to_profit() it will also allow us to remove one of the sort criteria.
 
I know it's hard to differentiate fights, but this method has the disadvantage of not accounting for free fights or fights that end with free runaways. Adding a field for the monster name would allow further differentiation.

This is not the biggest problem. The big problem for me is hipster fights -- they do not take turns. Two hipster fights in a row will result in the second one beginning with BatBrain believing that it already has stats. This is quite a common event.

I think I'm going to have to clear my happenings as a standard part of my moodscript.


However, this gives me an idea. I think I'll rework the stasis_action() sort to consider all actions that would never kill the monster equal in terms of damage -- rather than sort by dmg_dealt() as our final criterion, sort by max(dmg_dealt(), monster_stat("hp") / max(1,maxround - round) + 5). That 5 is there as a little cushion against damage range swing. That means that if you're fighting a monster with 900 HP, only an action dealing at least 30 damage even has a chance to kill the monster, so all actions dealing < 30 damage will be considered equally for stasis, sorted by profit.

Changed line 923 to

sort opts by max(dmg_dealt(value.dmg), monster_stat("hp") / max(1,maxround - round) + 5);

The improvement was unequivocally superior. Thank you.
 
Last edited:
Changed line 923 to

sort opts by max(dmg_dealt(value.dmg), monster_stat("hp") / max(1,maxround - round) + 5);

The improvement was unequivocally superior. Thank you.

You mean line 1427 right? :)
(man my batbrain is much bigger than the original...)
 
Back
Top