-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
208 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
## Capabilities | ||
See (TODO link to tierlist video) for a general overview of capabilities and their current state. | ||
|
||
### Ironclad | ||
- Missing cards: several, but also specifically: Infernal Blade, True Grit | ||
|
||
### Silent | ||
- Missing cards: Distraction, All Out Attack, Well Laid Plans, Masterful Stab, Doppelganger, Setup, Nightmare, Alchemize, | ||
- Strategies around poison use would require additional comparator adjustments: e.g. who to best target with Corpse Explosion? When do we want to apply Catalyst? | ||
|
||
### Defect | ||
- Missing cards: Force Field, Hologram, Rebound, Seek, Static Discharge, White Noise | ||
|
||
### Watcher | ||
- Missing cards: Conjure Blade, Foreign Influence, Meditate, Omniscience, Vault | ||
|
||
### General | ||
- **Calculation limit:** The bot will stop thinking after it has evaluated 11,000 battle paths for one play. This will often result in it just playing the card that's all the way on the right. This is an intentional trade-off vs waiting a long time. Most likely to happen when you've got a LOT of cards in hand, the potential to play a lot of them, and 3+ enemies. | ||
- **Heart**: The bot doesn't know how to find the keys | ||
- **Bug**: Prayer Wheel card reward is only checked if first card reward is picked up | ||
- **Missing colorless cards**: Chrysalis, Discovery, Forethought, Jack of All Trades, Madness, Metamorphosis, Panic Button, Purity, Secret Technique, Secret Weapon, The Bomb, Thinking Ahead, Transmutation, Violence | ||
|
||
### Misc improvement ideas | ||
- Know that Writhing Mass will change intent after each hit. Could do: stop dealing damage when we can block the hit. Also avoid the curse. | ||
- Know when Time Eater is going to heal, so don't waste resources on him. | ||
- Add purchasing of potions (e.g. Ritual Potion) to the shop purchase handler. | ||
|
||
We maintain a big backlog of issues, let us know if you want inspiration. :D |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
## How to: Make your own bot | ||
In Bottled AI, a bot Strategy is a collection of configurations that make up its behavior. Some configurations are straightforward, like "what character do we play?" or "what cards do we pick up?". | ||
Others are a little more complicated, like "how do we weigh damaging opponents vs powering ourselves up?". | ||
|
||
Changing a Strategy's configurations lets you customize your own bot. | ||
|
||
### Create a new bot Strategy | ||
1) Go to `\rs\ai` | ||
2) Copy the entire `_example` folder | ||
3) Rename example.py and EXAMPLE_STRATEGY to match your new Strategy's name | ||
4) Search for `_example` imports and rename them | ||
5) (Change the Strategy that is run in `main.py`) | ||
|
||
### Basics | ||
- Come up with an idea of how you want the bot to behave | ||
- Adjust the lists in your Strategy's `config.py` file and your strategy's handlers. | ||
|
||
### State of Bottled AI | ||
Important! For the current state of Bottled AI's capabilities, see [capabilities.py](capabilities.py). | ||
|
||
|
||
## Advanced | ||
### Handlers | ||
Handlers contain sets of behavior to be applied when their `can_handle` conditions are applied. | ||
There are a set of Common handler shared between Strategies, but you can create new handlers on a Strategy level. | ||
Remember to adjust which handlers are used in your `[your_strategy's_name.py]`. | ||
|
||
### Battle | ||
Battles are the most complicated part of the bot. In battle, the bot will simulate/calculate the outcome from playing all of its cards in all possible configurations. We call this part the 'calculator'. Secondly, the bot will then play cards based on which plays led to the most desirable outcome. | ||
|
||
There are therefore 2 ways to adjust in-battle behavior: | ||
1) **Extending the simulation**: i.e. teaching the bot what happens when a card is played / relic triggered / etc. Place to start: `\rs\calculator\battle_state`. | ||
2) **Changing the desirability of outcomes** to impact its decision-making. Place to start: `\rs\common\comparators\common_general_comparator.py` | ||
|
||
### Other | ||
- If you're making deeper behavior adjustments, we recommend adding some tests covering your new functionality See `/tests` | ||
- TODO probably more |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
from typing import List | ||
|
||
from rs.game.screen_type import ScreenType | ||
from rs.machine.command import Command | ||
from rs.machine.handlers.handler import Handler | ||
from rs.machine.handlers.handler_action import HandlerAction | ||
from rs.machine.state import GameState | ||
|
||
# see also common_combat_reward_handler.py for discarding potions | ||
# these potions might still sneak into our slots with entropric brew | ||
dont_play_potions = [ | ||
'Smoke Bomb', | ||
'Elixir Potion', | ||
'Liquid Memories', | ||
'Snecko Oil', | ||
'Stance Potion', | ||
'Ambrosia', | ||
'Distilled Chaos', | ||
] | ||
|
||
|
||
class PotionsBaseHandler(Handler): | ||
|
||
def can_handle(self, state: GameState) -> bool: | ||
# must be implemented by children | ||
pass | ||
|
||
def handle(self, state: GameState) -> HandlerAction: | ||
pot = self.get_potions_to_play(state)[0] | ||
wait_command = "wait 30" | ||
if pot['requires_target']: | ||
target = 0 | ||
for m_index, monster in enumerate(state.get_monsters()): # Find the back-est monster that isn't dead | ||
if monster['name'] == 'Reptomancer': # Special case since he might not be in the back | ||
target = m_index | ||
break | ||
if not monster['is_gone']: | ||
target = m_index | ||
return HandlerAction(commands=[wait_command, "potion use " + str(pot['idx']) + " " + str(target), wait_command]) | ||
return HandlerAction(commands=[wait_command, "potion use " + str(pot['idx']), wait_command]) | ||
|
||
def get_potions_to_play(self, state: GameState) -> List[dict]: | ||
to_play = [] | ||
for idx, pot in enumerate(state.get_potions()): | ||
if pot['can_use'] and pot['name'] not in dont_play_potions: | ||
pot['idx'] = idx | ||
to_play.append(pot) | ||
return to_play | ||
|
||
|
||
class PotionsEliteHandler(PotionsBaseHandler): | ||
def __int__(self): | ||
super().__init__() | ||
|
||
def can_handle(self, state: GameState) -> bool: | ||
hp_per = state.get_player_health_percentage() * 100 | ||
return state.has_command(Command.POTION) \ | ||
and state.combat_state() \ | ||
and state.screen_type() == ScreenType.NONE.value \ | ||
and state.game_state()['room_type'] == "MonsterRoomElite" \ | ||
and (hp_per <= 50 and state.combat_state()['turn'] == 1) \ | ||
and self.get_potions_to_play(state) | ||
|
||
|
||
class PotionsEventFightHandler(PotionsBaseHandler): # Treat most Event Fights like Elites | ||
def __int__(self): | ||
super().__init__() | ||
|
||
def can_handle(self, state: GameState) -> bool: | ||
hp_per = state.get_player_health_percentage() * 100 | ||
return state.has_command(Command.POTION) \ | ||
and state.combat_state() \ | ||
and state.screen_type() == ScreenType.NONE.value \ | ||
and state.game_state()['room_type'] == "EventRoom" \ | ||
and not state.has_monster("Fungi Beast") \ | ||
and (hp_per <= 50 and state.combat_state()['turn'] == 1) \ | ||
and self.get_potions_to_play(state) | ||
|
||
|
||
class PotionsBossHandler(PotionsBaseHandler): | ||
def __int__(self): | ||
super().__init__() | ||
|
||
def can_handle(self, state: GameState) -> bool: | ||
return state.has_command(Command.POTION) \ | ||
and state.combat_state() \ | ||
and state.screen_type() == ScreenType.NONE.value \ | ||
and state.game_state()['room_type'] == "MonsterRoomBoss" \ | ||
and state.combat_state()['turn'] == 1 \ | ||
and self.get_potions_to_play(state) |