#Balatrobot - Botting API Mod

236 messages · Page 1 of 1 (latest)

bold summit
#

I am proud to introduce Balatrobot, a botting API for Balatro:
https://github.com/besteon/balatrobot

This mod requires Steamodded (tested on v0.9.3).

The bots are capable of running through an entire game of Balatro unassisted! Play hands, buy cards, and open boosters all using the API. The API will automatically query your bot for a choice of action when the time comes, no heavy lifting required.

To create a bot, just modify Bot.lua. There is an example on the github page to get you started.

I would love feedback - I am sure there are still bugs in the later stages of the run (I didn't have time to make a bot get that far yet egg ).

Currently requires some source code knowledge to get the relevant data for your botting decisions. A standardized gamestate data format coming soon. And if there is enough interest, perhaps a websocket/web API so that bots can be written in languages other than Lua.

Your bot can be as complex as you want it to be, but the API is designed to be easy to use! Here is an example of how a bot can choose between skipping or selecting a blind:

GitHub

Contribute to besteon/balatrobot development by creating an account on GitHub.

pliant patrol
#

I wonder how consistent the timing of this is with Ankh

gray ferry
#

without testing and thus without guarantee, I'd say this should work fine on latest steamodded

pliant patrol
#

would be huge for TASing

gray ferry
#

either way you should update... most mods will stop being compatible with 0.7.2 as they update

pliant patrol
#

or also how accurate the inputs are compared to frame-perfect inputs in RTA

bold summit
# pliant patrol would be huge for TASing

The API only uses button functions once they are actually active. It may not be 100% TAS ready yet from a "could a human do this?" perspective, but it is quite close already.

placid vault
#

So is this TAS or the game plays by itself

bold summit
bold summit
gray ferry
#

I don't think this is really using any of the Steamodded APIs? there's not much a Steamodded update can break in that case

bold summit
#

Although I think Steamodded has changed how other .lua files are loaded in so that part might be broken in the latest release.

gray ferry
#

yeah should be fine then unless you're destructively overriding functions the loader uses

gray ferry
bold summit
covert chasm
#

nice work!

bold summit
gray ferry
#

neat

soft ermine
#

now you dont even need to play the game, the game plays for you

gray ferry
#

The game plays ~~for ~~you

#

important distinction

bold summit
#

I also wrote a hooking library (hook.lua) that might be useful to other modders. Implements breakpoints and callbacks for functions and onread/write for tables. Breakpoints being functions that run right before the hooked function.

soft ermine
gray ferry
#

yes

pseudo light
pliant patrol
#

my sum of best segments was ~1:25.5 on this seed, wouldn't be surprised to see a low 1:24 TAS

bold summit
keen haven
#

cant you combine this with immolate to make a bot that can beat any seed automatically?

#

i might try to make a balatro bot that can complete any seed automatically

pliant patrol
#

You probably don’t even need Immolate

keen haven
#

I mean youd have to see into the future

bold summit
#

you could simulate the rng calls but I don't think that is as fun as making a smart bot that adapts on the fly. But go for it!

keen haven
pliant patrol
pliant patrol
#
for k, v in pairs(G.P_CENTER_POOLS.Back) do
    if v.name == Bot.SETTINGS.deck then
        G.GAME.selected_back:change_to(v)
        G.GAME.viewed_back:change_to(v)
    end
end

This is how I fixed it

bold summit
#

I will add that

pliant patrol
#

trying just the first ante blind skips with action_delay=0 it's already way faster than a human can do it

#

there's some delay between skipping multiple blinds that must not be detected

bold summit
#

I currently don't check controller locks. But if you find the condition it should be easy to change it with a "firewhenready"

forest dagger
#

i can tell that it would just be called with like deck == "Red Deck" or whatever, i just have no idea where i would actually put this to apply that

pliant patrol
bold summit
# pliant patrol I put this before whenever it calls `start_run` in middleware.lua

I have pushed this change. I'm working on recreating your WR, going to implement a better way for the bot to choose when to rearrange/sell cards. I haven't implemented selling cards yet. I also want to adjust how cards are selected and various other timing bits. Right now, the cards are selected sequentially so that it looks nice to watch, but I will add some bot settings to adjust. Perhaps make a TAS bot mode

#

I also noticed that when opening a booster, the Skip button becomes available before all the cards are drawn. The bot will wait for G.STATE_COMPLETE before performing the action, I will work on that as well

pliant patrol
#

working on a wrapper that makes this more TAS-like

pliant patrol
#

well this is what happens when I try to buy a card

#

actually this appears to be my bad

#

Looking at it more, it’s only crashing because I’m having it buy the 2nd item

#

nvm I'm dumb

pliant patrol
#

Finished the TAS btw

#

The final time is 1:23.056

#

Biggest things I’m noticing from a TAS standpoint is skipping the spectral pack too late and skipping blinds too soon

#

For small optimizations you can also pre-select cards while they are being drawn

#

I used a backup strategy since Joker selling wasn’t implemented

#

(the inputs are in a separate file so that the wrapper can be used for more runs)

#

this is the splits against my PB

#

Why the discrepancies? In Ante 2, the bot’s advantage is lowered due to the money gaining animations, and a slightly faster strategy to buy the joker is possible (doing it during the tag animation)

#

In Ante 7, the bot waits for a foil tag animation that you can skip, and has to deal with the same time loss over its faster "strategy"

#

I’m going to mess around a bit with running the game at fixed FPS

#

At 100 FPS it yields 1:21.380

#

At 1,000 FPS it goes up to 1:21.917. Weird

#

At 10 FPS it’s 3:39.799 :/

#

Makes me wonder what the ideal FPS for the game is

#

At 60 FPS it’s 1:22.066

keen haven
bold summit
keen haven
pliant patrol
#

Yeah, TMInterface was my guide, only other TAS tool I’ve used

#

And with that it’s literally like:
0 press up
10 steer right
20 steer left
etc…

#

The next step to making the TAS wrapper would be to do that with what I have so that it’s not as verbose

keen haven
#

@pliant patrol if you ever make a tas tool, PLEASE have it be more like fceux not tminterface. I dislike tminterface sooo much

pliant patrol
#

TASing Balatro is fundamentally different from both because its event based

bold summit
#

@pliant patrol I have posted a new update, it will function at 0 action speed now, which is 1 action per frame if the event condition is met.

Also added selling jokers. Bot.lua has the new TAS already in it. The blind and booster skip timing are still WIP. i am working on a TAS input system (which will double as saving a bot run). How does this format look to you for ease of TASing?:

ACTION,Args

For example

SELECT_BLIND
PLAY_HAND,1,2,3,4,5
PLAY_HAND,1
BUY_CARD,2
END_SHOP
SKIP_BLIND
SELECT_BLIND

feedback appreciated

pliant patrol
#

That format looks great

#

One thing I would recommend though is a default state just to make writing TASes a bit easier

#

For instance running SELECT_BLIND if nothing is satisfied

#

For the future, some good things to add related to TAS would be a UI for savestates/loadstates (autosaving the actions along the way) and undoing actions

#

And also a way to see future features of a seed, but that is beyond the scope of the tool

#

Another good thing to add to it would be a way to write comments

#

The TAS GUI and input tracker could probably be a separate mod as long as the input format remains standardized

bold summit
#

I was thinking of defaults already, I added more actions like rearrange_hand where you can return nothing and it's skipped. I can do that for all actions.

Yeah I hate designing GUIs so that probably won't happen unless someone makes a pull request

pliant patrol
#

There are some that wouldn’t make sense though (mainly some default for playing/discarding)

bold summit
#

The saving and loading of the TAS file format is the extent of my TAS related plans for this mod. My goal was really the botting API so I think I might make a websocket server and a standard gamestate format so I can write my bot in python

pliant patrol
#

I understand that 👍

bold summit
#

But the TAS file format would let me share the results of my bot without sharing my bot code

pliant patrol
#

I can make some better TAS tools when I get a chance, although I’ll probably put most of my modding efforts on Immolate and Ankh right now

keen haven
pliant patrol
#

if anything a set FPS could be beneficial

#

because by default the FPS is dynamic and that affects the time of TASes

keen haven
#

oh yea totally

#

force 30 / 60 / 120 fps

#

and tas runs in 30

bold summit
#
SKIP_BLIND
DISCARD_HAND,2,3,6,7
PLAY_HAND,1,3,4,5,8
BUY_CARD,2
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND,1
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND,1
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND,1
BUY_CARD,2
END_SHOP
SKIP_BLIND
SKIP_BLIND
SELL_JOKER,2
PLAY_HAND,1
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND,1
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND,1
END_SHOP
SKIP_BLIND
SKIP_BLIND
SKIP_BOOSTER_PACK
PLAY_HAND,1```
bold summit
# pliant patrol That format looks great

Logging and replaying are now implemented. Bot.SETTINGS.replay sets whether the Bot will read from a file or log actions as the bot plays (false will log the file)
I haven't gotten to file management really, the file format is just SEED_DECK_STAKE_CHALLENGE.run, and it is written to the Balatro.exe folder (not the mod's folder, need to fix that)

#

The filename is generated from Bot.SETTINGS

pliant patrol
#

logging is very powerful - I might use some of that code in Ankh (with credit, if you're okay with it)

#

^that tool is for speedrunning, so having a log of all the actions in a run or session is powerful for verification purposes

bold summit
pliant patrol
#

ah, it hooks the bot functions

#

I'll probably use that as a reference but when I add it to Ankh it'll use player input

bold summit
#

I will probably be changing the format slightly and turning it into a websocket server so I can write my actual bot logic in Python

bold summit
pliant patrol
#

ty

#

in fact, I would probably make that its own library, as it can be used for Ankh as well as an in-game TAS tool

bold summit
#

Sure, I can do that. I can comment it better as well

pliant patrol
#

I was mainly referring to my own tool that would record player input

#

but with the context I didn't really clarify that well lol

bold summit
#

oh gotcha

keen haven
#

as said before, timing is probably important, for logging stuff timing can also help

bold summit
#

Speedrunning isn't really my thing

pliant patrol
#

rate limiting isn't very important, the main idea I have is logging runs
Which would be useful both to check the legitimacy of a play session for Ankh and to record/playback inputs for a TAS tool

bold summit
pliant patrol
#

yep, I'll try and make something to log runs at some point in the near future

#

I want to make a website for seed analyzing first

narrow ivy
#

could Twitch Plays Balatro be done with this, or is this purely for tas

pliant patrol
#

The backend could be used for that

bold summit
idle spruce
#

Is there a record/replay mod that works with this?

#

Also interested in trying out TASing with it

keen haven
bold summit
# idle spruce Is there a record/replay mod that works with this?

Yes this works on a basic level at the moment. To replay, change bot.lua Bot.SETTINGS.replay to true.

The replay file will then be read from the folder where Balatro.exe is located. The file name is SEED_DECK_STAKE_CHALLENGE.run, replacing those values from Bot.SETTINGS. For example: "1OGB5WO_Plasma Deck_1_.run", and Bot.SETTINGS must match.

If replay is false, then the actions performed by bot.lua will be logged appropriately (it does not record actions performed by the player). Here is an example replay file:

SKIP_BLIND
SKIP_BLIND
DISCARD_HAND|2,3,6,7
PLAY_HAND|1,3,4,5,8
BUY_CARD|2
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND|1
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND|1
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND|1
BUY_CARD|2
END_SHOP
SKIP_BLIND
SKIP_BLIND
SELL_JOKER|2
PLAY_HAND|1
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND|1
END_SHOP
SKIP_BLIND
SKIP_BLIND
PLAY_HAND|1
END_SHOP
SKIP_BLIND
SKIP_BLIND
SKIP_BOOSTER_PACK
PLAY_HAND|1```
idle spruce
pliant patrol
#

It currently only records bot actions

idle spruce
#

Hmm, wonder if something like divvy's history could be easily converted

idle spruce
#

Do mods have file system write access?

pliant patrol
#

Yes

idle spruce
#

It's limited to the balatro folder right?

pliant patrol
#

Yes, unless it uses nativefs

pseudo light
#

Steamodded use Nativefs when used with lovely

#

And if Nativefs is available

bold summit
# idle spruce Do mods have file system write access?

Yes they do, and it is not limited. Mods have code execution on your system (which means they can do anything a normal user can do). If you do not trust a mod, then do not use it, because it could have malware.

idle spruce
bold summit
idle spruce
#

meh, mods are pretty small for the most part so it's not too hard to just read the code

bold summit
#

And mods also have access to the love2d environment so anything that can be done in love, mods can do.

bold summit
#
    mybot = Bot(deck ="Plasma Deck",
                stake = 1,
                seed = "1OGB5WO")

    mybot.skip_or_select_blind = skip_or_select_blind
    mybot.select_cards_from_hand = select_cards_from_hand
    mybot.select_shop_action = select_shop_action
    mybot.select_booster_action = select_booster_action
    mybot.sell_jokers = sell_jokers
    mybot.rearrange_jokers = rearrange_jokers
    mybot.use_or_sell_consumables = use_or_sell_consumables
    mybot.rearrange_consumables = rearrange_consumables
    mybot.rearrange_hand = rearrange_hand

    mybot.run()```

Here is an example of all that is needed to write your own bot using Python (bot logic not included). Just overwrite each of the functions from the base Bot class with your own decision making logic.

I am still working on sending the entire gamestate to the Python interface. That will be coming next!
pseudo light
pliant patrol
#

@fallow pumice this might help you in creating a Twitch Plays for this game

bold summit
#

The API already uses text so you wouldn't really need to do anything other than verify that a command is valid (my API kind of does that, but since I provide the client as well, it could use more checks)

fallow pumice
#

this looks awesome, might be a bit too advanced for me but I will check it out

bold summit
fallow pumice
#

okay, ill spend some time today to figure it out. might be back later with some stupid questions.

fallow pumice
#

Ive installed Steamodded, to install Balatrobot do I just need to place the files from the git into the Mods folder?

#

looking at bot.py could do something like this?

#

def chooseaction(self):
if self.G['state'] == State.GAME_OVER:
self.running = False

    # Mapping keyboard inputs to class actions
    keyboard_inputs = {
        'b': Actions.SELECT_BLIND,
        's': Actions.SKIP_BLIND,
        'p': Actions.PLAY_HAND,
#

print("Enter your action: ")
key = input().lower()

    if key in keyboard_inputs:
        action = keyboard_inputs[key]
        return self.perform_action(action)
    else:
        print("Invalid input. Try again.")
        return None

def perform_action(self, action):
    match action:
        case Actions.SELECT_BLIND:
            return self.select_blind(action)
        case Actions.SKIP_BLIND:
            return self.skip_blind(action)
        # Add more cases 
        case _:
            print("Action not implemented.")
            return None
bold summit
#

Bot_example.py has the bot functions you need to implement

fallow pumice
#

this look correct so far?

#

my bot works with vjoy to send inputs from twitch chat to the host machine

#

i can use another program like joytokey to convert the vjoy inputs to keyboard inputs

#

it a bit hacky but it tends to work well

#

so once i can have the entire game working via keyboard inputs the rest should be straightforward

fallow pumice
bold summit
#

looks loaded to me

#

Ok so I think for what you are trying to do, you should modify run() of bot.py

#
        self.verifyimplemented()
        self.state = { }

        self.running = True

        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.settimeout(1.0)

        while self.running:
            self.sendcmd('HELLO')

            jsondata = { }
            try:
                data = self.sock.recv(4096)
                jsondata = json.loads(data)

                if 'response' in jsondata:
                    print(jsondata['response'])
                else:
                    self.G = jsondata
                    if self.G['waitingForAction']:
                        # Choose next action
                        # YOU PROBABLY WANT TO CHANGE THIS SECTION
                        ##########################################################
                        action = self.chooseaction()
                        if action == None:
                            raise ValueError("All actions must return a value!")
                        
                        cmdstr = self.actionToCmd(action)
                        ##########################################################
                        # TO
                        cmdstr = GetNextTwitchChatMessage() # Or something
                        ##########################################################
                        print(f'CMD: {cmdstr}')
                        self.sendcmd(cmdstr)
            except socket.error:
                self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                self.sock.settimeout(1.0)```
#

@fallow pumice ^

#

You will only want to send commands to the API when its actually waiting for an action, otherwise you will clog the queue when chat is spamming

#

The API should ignore all invalid commands

#

bot_example.py is really designed for implementing logic at each action, but that is the job of twitch chat for your use case, so modifying bot.py is probably better

#

and you can just get rid of the call to verifyimplemented()

bold summit
solemn trellis
#

How do I run the python bot? "python bot_example.py" doesn't seem to do anything

bold summit
#

Please paste the error you see if you are getting one

solemn trellis
#

Yeah it's installed with steamodded. It just hangs like this when I run bot_example.py

bold summit
#

Open the game to the main menu first, then run the script

solemn trellis
west spruce
#

I've been messing with the python api version quite a bit, and made some modifications to fit my own use case (e.g. getting port from args, and adding the cards in hand to the gamestate)

#

But I'm currently having an issue where only one copy of the game seems to work at a time. If I run 3 copies configured to different sockets, only the first one will successfully link up with the api. Any idea why that might happen?

west spruce
#

Actually I'm a moron, run() runs synchronously, so the first bot is blocking the others

west spruce
#

Currently hitting about 3 hands per second across 5 instances of the game, so 15 hands played per second total. I noticed something above about 1 action per frame? How is that achieveable?

bold summit
#

Each update, the action queue is checked and if there is an action waiting then it's popped and processed

west spruce
#

Gotcha, but that doesn't necessarily mean it's actually possible to take an action every frame

bold summit
#

But the actions will have more or less the same frame the action becomes available if the queue is populated

west spruce
#

Okay great. Yeah I've been working on cutting out/speeding up as many of the animations as possible. Kinda tricky, but definitely running a lot faster

#

Oh btw, for the server rules, if I make a mod forked off of yours for training an RL agent to play Balatro, is that cool to share here? (And/or do you want me to PR any of the changes I make)

pliant patrol
west spruce
#

Opened a PR, gonna focus on setting up an RL gym proof of concept now

bold summit
bold summit
west spruce
#

Put together a proof of concept RL training implementation, gonna polish that up and then push it tomorrow probably

pliant patrol
#

What’s the preferred comment format for TAS files? Going to start working on logging for Ankh sometime soon and want the files to be compatible with Balatrobot

queen willow
#

I know that there's the speedup/uncapped fps feature, but do you have a sense of how possible it would be to mod Balatro to run in a purely "headless" mode where it's not doing any rendering and purely doing card logic and communicating with the socket? Is that theoretically possible, or not even worth trying

west spruce
#

It's possible. I reduced rendering to 1/100 frames, and you could disable rendering entirely in a similar way

#

Unfortunately that doesn't do as much as you'd think to speed up the game without also ripping out a lot of other functionality to skip animations etc

#

Been working bit by bit to remove those, but it's tricky to do without causing instabilities

bold summit
queen willow
west spruce
#

Kinda yeah. The initial stuff has been merged into Besteon's repo though, and I haven't pushed up the new stuff yet

#

Learning ray rllib and having some issues transitioning

fringe bolt
#

Is this working with the last version of Balatro and steamodded on Mac? I just tried and out of the box it crashed.

west spruce
#

Might have bricked something with my update, let me look at my changes

#

Rolling back a commit will probably fix it if that's the case

#

Seems like it's not getting the port for the socket setup

#

If you provide it as the first command line argument e.g. 12346 it should work

#

But it should also be grabbing it from the config file?

fringe bolt
#

Mm I think is just my setup that has something weird, working on windows

bold summit
#

I never tested it on Mac originally

west spruce
#

Adding a bunch more checks and sync logic for the python API and got significant stability and speed improvements

#

Those seem worth adding on your repo, but I've also been adding lots of experimental stuff that's useful to me, but maybe not for your other use cases

#

Not sure what the best way of syncing the repos is

bold summit
#

Feel free to make pull requests!

solemn trellis
west spruce
#

So for the sped up game logic you can adjust or disable that in config.lua

#

The crash it looks like a method in the flushbot class is being calling with the wrong number of arguments for some reason

#

Oh, yeah it looks like it's explicitly passing "self" as the first argument, but then python would add self again automatically

#

Try removing that first arf

#

Arg

pliant patrol
#

finally got balatrobot to work

#

I did one test run and it made it to ante 2 big blind somehow

#

now to get it to do the thing

worthy belfry
#

How do you check what is remaining in your deck? I couldn't find it in the data but I also could just be blind. Thanks!

gray ferry
worthy belfry
gray ferry
#

The best resource for such things would be Balatro's source code itself, which you can view simply by extracting the game executable with something like 7-zip

worthy belfry
#

Is it possible to have the bot give up on the current run and start a new run before getting a gameover?

west spruce
#

I don't think the currently published branch has that ability, but I have some modifications locally

#

My changes have gotten a bit squirrelly though

#

I can probably isolate the mod side changes and push those, but it might break some of the python stuff unless I make the corresponding changes there.

slender shard
#

could this be used for a "twitch plays" version of balatro where its soley chat inputs? Left, Right, Up, Down, A, B, X, Y?

narrow ivy
#

i think someone's working on that

slender shard
#

Sick