Next: , Previous: , Up: Machine learning with tarot   [Contents][Index]


5.4 A general interface for AI predictors

struct: TarotAi

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;
}
Function: TarotAi * tarot_ai_alloc (void *user_data, void (*eval) (void *, const TarotGame *, size_t, TarotGameEvent **, size_t, size_t, double *), void (*learn) (void *, const TarotGame *, const TarotGameEvent *, double), void (*destruct) (void *))

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.

Function: TarotAi * tarot_ai_alloc_perceptron ()

Create an AI as the perceptron trained by the maintainer, but does not save its training iterations.

Function: TarotAi * tarot_ai_alloc_random (size_t seed_size, const void *seed)

Create an AI that will give random scores and cannot learn.

Function: TarotAi * tarot_ai_alloc_fuzzy (double fuzziness, size_t seed_size, const void *seed, const TarotAi *base)

Create an AI that will play perfectly with probability 1 - fuzziness, and uniformly random with probability fuzziness.

Function: TarotAi * tarot_ai_dup (const TarotAi *ai)

Return an allocated copy of ai.

Function: void tarot_ai_free (TarotAi *ai)

Destroy ai (also calls the destruct hook).

Function: void tarot_ai_eval (TarotAi *ai, const TarotGame *base, size_t n, TarotGameEvent **candidates, size_t start, size_t max, double *scores)

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.

Function: void tarot_ai_learn (TarotAi *ai, const TarotGame *base, const TarotGameEvent *event, double final_score)

Learn that playing event in base results in the given final score.

Function: void tarot_ai_strongest_player (TarotAi *ai, size_t n_players, int with_call, size_t n_cards, const TarotPlayer *owners, double *confidence, TarotCard *call)

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.

Function: const TarotGameEvent * tarot_ai_best (TarotAi *ai, const TarotGame *state, double *score)

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.

Function: double tarot_ai_validate (TarotAi *ai, const TarotGame *validation_game)

Get the RMSE error between the situation predicted in all situations in the validation game and the actual score, averaged over all players.


Next: , Previous: , Up: Machine learning with tarot   [Contents][Index]