Next: Playing a game against the AI, Previous: Online learning with a multi-layer perceptron, Up: Machine learning with tarot [Contents][Index]
All AI predictors are in fact used the same way. Given a bunch of possibilities to choose from at a given state, predict the score for each possibility. From this function, we could derive interesting elements that do not depend on the particular implementation. For instance, playing the next move. It is very tiresome to enumerate all possible cases for each different game event type, and even intractable for the discard.
To solve this problem, the ‘TarotAi’ opaque struct is in fact generic, and can be used to develop plugins. For instance, here is how you would implement a stupid AI that would always predict a score of 42 in a solo game.
#ifdef HAVE_CONFIG_H #include <config.h> #endif /* HAVE_CONFIG_H */ #include <tarot.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #ifndef LOCALEDIR #define LOCALEDIR "/usr/share/locale" #endif /* NOT LOCALEDIR */ #ifndef DATADIR #define DATADIR "/usr/share/tarot" #endif /* NOT DATADIR */ /* We can parameterize the response, not to always answer 42! */ struct state { double response; }; static void my_eval (void *user_data, const TarotGame *base, size_t n_candidates, TarotGameEvent **candidates, size_t start, size_t max, double *scores) { /* At the current state in the /base/ game, we want to make a * prediction for each candidate event. */ struct state *state = (struct state *) user_data; size_t i; for (i = 0; i < n_candidates; i++) { /* Modification of any elements in the candidates array is not * documented. So let us put a 'const' here. */ const TarotGameEvent *event = candidates[i]; double prediction; /* What prediction to make if we play /event/ in /base/? */ prediction = state->response; /* Store the prediction in the scores array, but discard the * first /start/ items and only store /max/ items */ if (i < start || i > start + max) { /* This event is not relevant */ } else { scores[i - start] = prediction; } } } static void my_learn (void *user_data, const TarotGame *base, const TarotGameEvent *event, double final_score) { /* We are made aware that predicting /final_score/ for playing * /event/ in /base/ was the right thing to do. */ struct state *state = (struct state *) user_data; if (final_score == state->response) { printf ("Ha! I was right!\n"); } else { printf ("I missed that by %f points.\n", (final_score - state->response) / 2); } } static void * my_dup (void *user_data) { /* Return an allocated copy of user_data, we don't use it here */ abort (); return NULL; } static void my_destruct (void *user_data) { /* Do not forget to free the associated resources! */ struct state *state = (struct state *) user_data; state->response = 0; } int main () { /* Our new AI will play against the library AI in a solo game! */ TarotGame *game_data; TarotSolo *game; TarotAi *ai; const TarotGameEvent *event; struct state ai_state; tarot_set_datadir (DATADIR); if (tarot_init (LOCALEDIR) != 0) { fprintf (stderr, "Error: could not initialize libtarot.\n"); return EXIT_FAILURE; } game = tarot_solo_alloc (); if (tarot_solo_setup_random (game, 5, 1, strlen ("Random!"), "Random!") != TAROT_GAME_OK) { fprintf (stderr, "Error: this seed is invalid.\n"); return EXIT_FAILURE; } /* game_data is here so that we can query the game by the eyes of * our AI */ game_data = tarot_solo_get_alloc (game); ai_state.response = 42; ai = tarot_ai_alloc (&ai_state, my_eval, my_learn, my_dup, my_destruct); while (tarot_game_step (game_data) != TAROT_END) { double predicted_score; const TarotGameEvent *best; best = tarot_ai_best (ai, game_data, &predicted_score); tarot_game_free (game_data); if (best == NULL) { /* Waiting for the setup, deal, or dog -- but impossible in * a solo game */ assert (0); } else { printf ("By doing that, I plan to make %f points!\n", predicted_score / 2); if (tarot_solo_add (game, best) != TAROT_GAME_OK) { assert (0); } } game_data = tarot_solo_get_alloc (game); } tarot_ai_free (ai); assert (ai_state.response == 0); tarot_game_free (game_data); tarot_solo_free (game); tarot_quit (); return EXIT_SUCCESS; }
Create a new AI with the given behavior. Please refer to the example above for how to define the function pointers. The eval, learn, dup and destruct hooks are transparently called by ‘tarot_ai_eval’, ‘tarot_ai_learn’, ‘tarot_ai_dup’ and ‘tarot_ai_free’, so they share the same semantics.
Create an AI as the perceptron trained by the maintainer, but does not save its training iterations.
Create an AI that will give random scores and cannot learn.
Create an AI that will play perfectly with probability 1 - fuzziness, and uniformly random with probability fuzziness.
Return an allocated copy of ai.
Destroy ai (also calls the destruct hook).
Try to play each of the n candidate events in base, and compute the corresponding scores. Despite the absence of ‘const’, candidates should not be modified. This function may be called with one specific event from the candidates; thus the start first candidates should not be considered and only the max first values should be written to scores.
Learn that playing event in base results in the given final score.
Check who is the strongest player in the deal defined by owners (a 78-player array giving the owner of each card). n_cards is always set to 78. The game considered has n players, and may be with call. In such a case, call is set to the best possible call.
In any case, confidence is set to the predicted score for the strongest player to push.
Get the best possible event that the next player in state can play. The return value is only valid for the lifetime of ai, or until a new call to this function is made.
Get the RMSE error between the situation predicted in all situations in the validation game and the actual score, averaged over all players.
Next: Playing a game against the AI, Previous: Online learning with a multi-layer perceptron, Up: Machine learning with tarot [Contents][Index]