-
Notifications
You must be signed in to change notification settings - Fork 712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AI framework #7
Comments
The easiest way to enumerate would be to add an optional "enumerateMoves" function to the Game constructor but this puts a lot of work on the user's plate. I think some sort of "these are the ranges of legal values for the arguments" system would be easier, along with letting moves return errors for conditionally illegal moves. (Checking a move then returning an error is slower and thus a generic AI will be weaker with this approach, but I think that's acceptable. People shouldn't expect the world of a generic AI.) I don't have any suggestions for the end of turn problem. |
For the actual AI portion I was thinking:
It'll be embarrassingly bad if no heuristic function is specified, but still playable enough to debug a game. And there are relatively simple heuristics that can beat most humans at chess with a small search tree. |
An optional extra function to implement sounds quite reasonable: Something like:
perhaps? In fact, given that END_TURN can just be treated like just another move, it would fit right in. Example:
The user could optionally provide weights for these as a heuristic (and doesn't need to even list all possible moves), and the AI code could do some reasonable things like making sure at least one move is made before END_TURN. What do you think? |
This also takes care of the illegal move problem (the function that the user implements only returns legal moves). |
(G) => List of move types and arg ranges seems reasonable. Since moves take an argument list, arg ranges need to be a list of lists. Chess example:
Discarding illegal moves tends to be tricky and error prone, I think it's still worth adding that to the move function (it can be handy for clients too). That could be as simple as the move function returning falsey instead of a game state, or something else that allows an actual error message to come back. Regardless, the game would neglect to apply the move, and an AI wouldn't try that branch. If illegal moves can be discarded, I can write the enumeration as:
Which would perform slowly, but be very quick to prototype with. |
Some games allow passing (Go), some require exactly one move (Chess), some allow a variable number of moves and passing (Magic), Arimaa* requires 1 to 4 moves. *Not that the default AI for Arimaa would perform particularly well anyway, but as an example. |
re: handling illegal moves The main reason that I chose to go the current route is because that's how Redux does it, and it's a paradigm that developers are familiar with (i.e. always returning state). I'm not opposed to changing the paradigm if there is a good reason to do so, though. Let's give it a try. I'll take this as an action item for myself while you work on the enumeration function bit.
If we treat END_TURN as just another move, then this should be taken care of automatically. For example, END_TURN would be the only valid move after 4 moves of Arimaa. For games requiring exactly one move, END_TURN is not a valid move until another move has been made, and so on. |
I've started a branch ai that implements the invalid move logic we talked about. Whenever you're ready, send me a PR to add stuff to this branch with the AI code. |
Hi! I just heard about your project on HackerNews and it is very similar of what I was trying to do with this project: https://github.com/Felizardo/turnato http://turnato.com Anyhow, after I launched the first chess and checkers on the website I saw that most people wanted single player option... So I was starting from scratch to have an API compatible with MDPs (Markov decision process) as I want to implement more complex board games that have non-deterministic outcomes (i.e. any game with dice). So, besides adding legal moves, it would be great to add how much probability each action can lead to a new state. I just finished CS221, and I think this API for MDP would work very well for any board game. Also, using this standard API will be very helpful for AI researchers trying to implement reinforcement learning algorithms, as we could provide rule implementation of several games in a standard fashion. Let me know what you think, I am more than happy to join efforts 👍 |
I haven't had nearly the amount of free time I expected myself to have; feel free to steal any AI work from me. I can think of two ways to handle random options:
|
How about adding support for move-validating function, shared between server and client? Proposed move as an argument, and the |
@justrag Then you are just delegating the responsibility to know what are the possible moves (the input of this function) to the UI. Which is not great as it is part of the rules of the game. And any AI algorithm will need a list of possible moves, and if there is a non-deterministic factor (a dice), it will also need the probability for each new state that can happen (rolled 1, rolled 2, rolled 3, etc...) An algorithm like Minimax will need to create a tree of possible actions and states and calculate the expected reward for each action. See this article for an introduction on Minimax: |
@tolmar I agree that adding weights would be less ergonomic to other users, which is not what we want. However, I think we can use flags like you said and assume if the person does not provide an weight, it is 100% of probability (which is deterministic). I am going to familiarize myself a little bit more with the current API and propose changes later. |
++ to adding some standard way of enumerating legal moves given a state of the game. It's not going to be practical to have a tree search try all possible moves including illegal ones; the state space will blow up after only two or three levels of depth. |
@felizardo I actually meant a function that was being used both in client and server: for the UI to visualize possible moves and for the server before applying reducer. |
Ok, now that we have a way to know if the game is over and who won, we need to tackle the move enumeration. After this we should be able to implement a tree search or minimax. @nicolodavis I saw that you are returning undefined for all illegal moves on the ai branch, which seems like a good way to leverage the way reducers work. So I am going to implement over the weekend something like was proposed by @tolmar: It would be OK for the user of the library to provide invalid moves on this enumeration, because in this case the subsequent move reducer would return undefined, and we can stop exploring this branch. It would be more efficient to return only the minimal amount of moves to look for, though. But this way we can let the trade-off of ease to develop x efficiency to be on the library user's hands. |
Ok folks, I wrote a proposal for how the AI framework API should be: I included everything in a google doc so it is easier for people to comment on specifics, and it is easier to describe the framework as a whole. Please let me know what you think 👍 |
I'll spend some time in the next few weeks working on AI stuff (in addition to UI stuff). My general plan at the moment is to bundle in a generic MCTS bot that is able to play the game with increasingly higher skill depending on how much information the developer provides: Level 1 (done):
Level 2:
Will probably need to provide some knobs to fine tune node selection and simulation strategies for even more advanced users, but I'll try to make it very minimal config for users that just want something to play the game better than random. |
^love this. |
Level 1 is done! The tutorial now demonstrates how to add AI. |
(This might not be the best place to ask this question) What if I want to implement my own custom AI algorithm? Does the framework support such functionality? Should I just put my AI logic in the |
I think that is possible but not the most desirable or efficient. We should probably bundle a bot interface that is completely specified by the user instead. |
Is there any update on this? I already reduced iterations and playoutDepth but it is still very slow |
Yes, you can use an evaluation function inside the objectives. Let me know how it goes! |
After reading
So I am confused and did not find any documentation or usage of objectives in the linked projects, maybe you can give me an example? |
Can you please allow to pass depth and iterations options to the bots? The values in the code are too heavy for my game and I have to edit node_modules manually |
This might be a touching on #563 and/or #292 , but it seems that in AI's Is there a "bot" way of querying which moves are available to me given my current turn state? It seems that I have to re-invent the |
Hi there, const objectives = () => ({
'play-on-square-0': {
checker: G => G.cells[0] !== null,
weight: 10.0,
},
}); But there doesn't seem to be any way of defining this via How does one set up a bot as the second player in a local single-player without needing to manually interact with the "AI" debug panel? In the absence of a docs update, a simple example in |
@nemoDreamer The current state of things isn’t really very flexible. Local single player vs bot(s) is the only scenario that is supported in a more or less straightforward way: import { Local } from 'boardgame.io/multiplayer';
import { MCTSBot, RandomBot } from 'boardgame.io/ai';
const BgioClient = Client({
game: YourGameObject,
numPlayers: 3,
multiplayer: Local({
bots: {
'1': MCTSBot,
'2': RandomBot,
},
}),
// ...
}); This sets up a local master that runs the provided bot for the playerIDs specified in the As you can see, you only pass the bot class, which is instantiated internally, so there’s no way currently to configure the options you mentioned — only There is an additional lower-level way to use the AI modules that gives access to more controls, but only if you have access to the plain Javascript client: import { Client } from 'boardgame.io/client';
import { MCTSBot, Step } from 'boardgame.io/ai';
const client = Client({ game: YourGame, /* ... */ });
const bot = new MCTSBot({
game: YourGame,
enumerate: YourGame.ai.enumerate,
// objectives, iterations, playOutDepth, ...
});
const botPlayerIDs = new Set(['1']);
client.subscribe(state => {
if (!state) return;
if (botPlayerIDs.has(state.ctx.currentPlayer)) {
Step(client, bot);
}
});
client.start(); I’d say while this works, it’s more of an interim solution until the proper API gets designed for bots. You can even make this work with React by using the |
Thank you SO MUCH, @delucis ...! And that example is super helpful! Hey, if we can pass a class to the class CustomMCTSBot extends MCTSBot {
constructor(config, ...args) {
super({
...config,
objectives: { /* ... */ },
iterations: 500,
playoutDepth: 25,
},
...args
);
}
} ... haven't tried yet, might be totally misguided. Update: yup! Totally works 🤣 |
Side-note: is it normal with... objectives: () => {
"custom-objective": {
checker: (G, ctx) => {
return true|false
}
weight: 10.0,
}
} ...for Doh: never mind... It's the JS console that's just trying to catch up to ~25000 console logs... Takes a while 😬 |
Nice! Didn’t think of using a derivative class like that. If you wanted to read those options off your game object, you could do that too. (I’d guess something like this should actually be the default behaviour in any case.) class CustomMCTSBot extends MCTSBot {
constructor(opts) {
super({ ...opts, ...opts.game.ai });
}
} So your game can then look something like: const game = {
ai: {
enumerate: () => {},
objectives () => {},
iterations: 500,
playoutDepth: 25,
},
// ...
}; Should definitely update the “advanced” AI example to use something like this as it’s way simpler than the acrobatics required in that example. |
I have only played with the library for a few hours but I noticed that the MCTS bot seemed to be ignoring my defined objectives. Looking at
|
@hermannloose I’m not sure what was intended originally, but you can extend the MCTS bot class as described above to pass the objectives through. |
I don't want to extend the MCTS bot, as it already handles objectives: boardgame.io/src/ai/mcts-bot.ts Line 61 in c8c648e
What I meant was that when going to the AI section in the debug panel of my game and using the MCTS bot with the "play" or "simulate" buttons, the objectives, if defined, are simply not passed to the MCTS bot instantiated by the debug panel, at the point that I linked to above:
|
Ah, sorry, I missed that this was in the debug panel (rather than with a Local master as above). Yes, it would probably be good to pass all the bot = new MCTSBot({
...client.game.ai,
game: client.game,
iterationCallback,
}); |
It's almost possible to write a generic AI using the existing framework, but there are two problems:
The text was updated successfully, but these errors were encountered: