Commit 9b6fb5b6 authored by Vivien Kraus's avatar Vivien Kraus
Browse files

Introduce an abstraction over the AI

The =TarotAi= module now factors in all the boring stuff around
applying the AI, so we can now remove the code to compute the
strongest player in the features module, and integrate the stacking
predictor and the perceptron predictor directly in this framework, yet
again removing duplicated code to compute the best event (for
instance).
parent dfb3ff68
......@@ -40,6 +40,8 @@
/depcomp
/doc/basic-initialization
/doc/basic-initialization.c
/doc/dumb-ai
/doc/dumb-ai.c
/doc/duplicate-a-posteriori.c
/doc/events
/doc/events.c
......
......@@ -19,7 +19,8 @@ check_PROGRAMS += \
%reldir%/events \
%reldir%/xml \
%reldir%/petit-sec \
%reldir%/duplicate-a-posteriori
%reldir%/duplicate-a-posteriori \
%reldir%/dumb-ai
%canon_reldir%_basic_initialization_LDADD = src/libtarot/libtarot.la $(AM_LDADD)
%canon_reldir%_players_and_cards_LDADD = src/libtarot/libtarot.la $(AM_LDADD)
......@@ -27,3 +28,4 @@ check_PROGRAMS += \
%canon_reldir%_xml_LDADD = src/libtarot/libtarot.la $(AM_LDADD)
%canon_reldir%_petit_sec_LDADD = src/libtarot/libtarot.la $(AM_LDADD)
%canon_reldir%_duplicate_a_posteriori_LDADD = src/libtarot/libtarot.la $(AM_LDADD)
%canon_reldir%_dumb_ai_LDADD = src/libtarot/libtarot.la $(AM_LDADD)
......@@ -19,7 +19,8 @@ TANGLED = \
$(srcdir)/%reldir%/events.c \
$(srcdir)/%reldir%/petit-sec.c \
$(srcdir)/%reldir%/xml.c \
$(srcdir)/%reldir%/duplicate-a-posteriori.c
$(srcdir)/%reldir%/duplicate-a-posteriori.c \
$(srcdir)/%reldir%/dumb-ai.c
EXTRA_DIST += \
$(srcdir)/%reldir%/tarot.texi \
......
......@@ -2306,6 +2306,244 @@ Analyze the /validation game/ and return the mean error at each event
between what the /perceptron/ would predict and the actual score for
the player.
#+end_deftypefun
** A general interface for AI predictors
#+attr_texinfo: :options {struct} TarotAi
#+begin_deftp
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.
#+BEGIN_SRC C -i :tangle dumb-ai.c
#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;
}
#+END_SRC
#+end_deftp
#+attr_texinfo: :options {TarotAi *} tarot_ai_alloc (void *@var{user_data}, void (*@var{eval}) (void *, const TarotGame *, size_t, TarotGameEvent **, size_t, size_t, double *), void (*@var{learn}) (void *, const TarotGame *, const TarotGameEvent *, double), void (*@var{destruct}) (void *))
#+begin_deftypefun
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.
#+end_deftypefun
#+attr_texinfo: :options {TarotAi *} tarot_ai_alloc_perceptron ()
#+begin_deftypefun
Create an AI as the perceptron trained by the maintainer, but does not
save its training iterations.
#+end_deftypefun
#+attr_texinfo: :options {TarotAi *} tarot_ai_alloc_random (size_t @var{seed_size}, const void *@var{seed})
#+begin_deftypefun
Create an AI that will give random scores and cannot learn.
#+end_deftypefun
#+attr_texinfo: :options {TarotAi *} tarot_ai_dup (const TarotAi *@var{ai})
#+begin_deftypefun
Return an allocated copy of /ai/.
#+end_deftypefun
#+attr_texinfo: :options {void} tarot_ai_free (TarotAi *@var{ai})
#+begin_deftypefun
Destroy /ai/ (also calls the /destruct/ hook).
#+end_deftypefun
#+attr_texinfo: :options {void} tarot_ai_eval (TarotAi *@var{ai}, const TarotGame *@var{base}, size_t @var{n}, TarotGameEvent **@var{candidates}, size_t @var{start}, size_t @var{max}, double *@var{scores})
#+begin_deftypefun
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/.
#+end_deftypefun
#+attr_texinfo: :options {void} tarot_ai_learn (TarotAi *@var{ai}, const TarotGame *@var{base}, const TarotGameEvent *@var{event}, double @var{final_score})
#+begin_deftypefun
Learn that playing /event/ in /base/ results in the given /final
score/.
#+end_deftypefun
#+attr_texinfo: :options {void} tarot_ai_strongest_player (TarotAi *@var{ai}, size_t @var{n_players}, int @var{with_call}, size_t @var{n_cards}, const TarotPlayer *@var{owners}, double *@var{confidence}, TarotCard *@var{call})
#+begin_deftypefun
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.
#+end_deftypefun
#+attr_texinfo: :options {const TarotGameEvent *} tarot_ai_best (TarotAi *@var{ai}, const TarotGame *@var{state}, double *@var{score})
#+begin_deftypefun
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.
#+end_deftypefun
#+attr_texinfo: :options {double} tarot_ai_validate (TarotAi *@var{ai}, const TarotGame *@var{validation_game})
#+begin_deftypefun
Get the RMSE error between the situation predicted in all situations
in the /validation game/ and the actual score, averaged over all
players.
#+end_deftypefun
** High-level game insights
Given the ML building blocks, we have some opportunities for a few
interesting applications.
......
......@@ -48,3 +48,4 @@ include %reldir%/cnn/Makefile.am
include %reldir%/solo/Makefile.am
include %reldir%/guile/Makefile.am
include %reldir%/perceptron/Makefile.am
include %reldir%/ai/Makefile.am
# tarot implements the rules of the tarot game
# Copyright (C) 2019 Vivien Kraus
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
AM_CPPFLAGS += -I $(srcdir)/%reldir% -I %reldir%
tarotinclude_HEADERS += %reldir%/tarot/ai.h
src_libtarot_libtarot_la_SOURCES += \
%reldir%/ai.c \
%reldir%/tarot/ai_private.h \
%reldir%/tarot/ai_private_impl.h
INTROSPECTED_SOURCES += %reldir%/ai.c
/* tarot implements the rules of the tarot game
* Copyright (C) 2019 Vivien Kraus
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <tarot/ai_private.h>
#include <stdlib.h>
#include <assert.h>
#include "xalloc.h"
#include "gettext.h"
#define _(String) dgettext (PACKAGE, String)
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 *(*dup) (void *), void (*destruct) (void *))
{
TarotAi *ret = xmalloc (sizeof (TarotAi));
ai_construct (ret, user_data, eval, learn, dup, destruct);
return ret;
}
static inline void
eval_noop (void *state, const TarotGame * base, size_t n_candidates,
TarotGameEvent ** candidates, size_t start, size_t max,
double *scores)
{
(void) state;
(void) base;
(void) n_candidates;
(void) candidates;
(void) start;
(void) max;
(void) scores;
}
static inline void
learn_noop (void *state, const TarotGame * base, const TarotGameEvent * event,
double final_score)
{
(void) state;
(void) base;
(void) event;
(void) final_score;
}
static inline void *
dup_noop (void *state)
{
(void) state;
return NULL;
}
static inline void
destruct_noop (void *state)
{
(void) state;
}
TarotAi *
tarot_ai_dup (const TarotAi * ai)
{
TarotAi *ret =
tarot_ai_alloc (NULL, eval_noop, learn_noop, dup_noop, destruct_noop);
ai_copy (ret, ai);
return ret;
}
TarotAi *
tarot_ai_alloc_perceptron ()
{
return tarot_ai_alloc (tarot_perceptron_static_default (),
ai_perceptron_eval, ai_perceptron_learn,
ai_perceptron_dup, ai_perceptron_destruct);
}
TarotAi *
tarot_ai_alloc_random (size_t seed_size, const void *seed)
{
struct yarrow256_ctx *rng = xmalloc (sizeof (struct yarrow256_ctx));
yarrow256_init (rng, 0, NULL);
yarrow256_seed (rng, seed_size, seed);
return tarot_ai_alloc (rng, ai_random_eval, ai_random_learn,
ai_random_dup, ai_random_destruct);
}
void
tarot_ai_free (TarotAi * ai)
{
ai_destruct (ai);
free (ai);
}
void
tarot_ai_eval (TarotAi * ai, const TarotGame * base, size_t n_candidates,
TarotGameEvent ** candidates, size_t start, size_t max,
double *scores)
{
ai_eval (ai, base, n_candidates, candidates, start, max, scores);
}
void
tarot_ai_learn (TarotAi * ai, const TarotGame * base,
const TarotGameEvent * event, double final_score)
{
ai_learn (ai, base, event, final_score);
}
TarotPlayer
tarot_ai_strongest_player (TarotAi * ai, size_t n_players, int with_call,
size_t n_cards, const TarotPlayer * owners,
double *confidence, TarotCard * call)
{
return ai_strongest_player (ai, n_players, with_call, n_cards, owners,
confidence, call);
}
const TarotGameEvent *
tarot_ai_best (TarotAi * ai, const TarotGame * state, double *score)
{
return ai_best (ai, state, score);
}
double
tarot_ai_validate (TarotAi * ai, const TarotGame * validation_game)
{
return ai_validate (ai, validation_game);
}
#include <tarot/ai_private_impl.h>
/* tarot implements the rules of the tarot game
* Copyright (C) 2019 Vivien Kraus
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef H_TAROT_AI_INCLUDED
#define H_TAROT_AI_INCLUDED
#include <stddef.h>
#ifdef __cplusplus
extern "C"
{
#endif
struct TarotAi;
typedef struct TarotAi TarotAi;
/**
* tarot_ai_alloc: (skip)
*/
TarotAi *tarot_ai_alloc (void *user_data,
void (*eval) (void *user_data,
const TarotGame * base,
size_t n_candidates,
TarotGameEvent ** candidates,
size_t start, size_t max,
double *scores),
void (*learn) (void *user_data,
const TarotGame * base,
const TarotGameEvent * event,
double final_score),
void *(*dup) (void *user_data),
void (*destruct) (void *user_data));
/**
* tarot_ai_dup:
*/
TarotAi *tarot_ai_dup (const TarotAi *ai);
/**
* tarot_ai_alloc_perceptron: (constructor)
*/
TarotAi *tarot_ai_alloc_perceptron ();
/**
* tarot_ai_alloc_random: (constructor)
* @seed: (array length=seed_size) (element-type char):
*/
TarotAi *tarot_ai_alloc_random (size_t seed_size, const void *seed);
void tarot_ai_free (TarotAi *ai);
/**
* tarot_ai_eval:
* @candidates: (transfer none) (array length=n_candidates):
* @scores: (array length=max):
*/
void tarot_ai_eval (TarotAi *ai, const TarotGame *base, size_t n_candidates, TarotGameEvent **candidates, size_t start, size_t max, double *scores);
/**
* tarot_ai_learn:
*/
void tarot_ai_learn (TarotAi *ai, const TarotGame *base, const TarotGameEvent *event, double final_score);
/**
* tarot_ai_strongest_player:
* @owners: (array length=n_cards):
* @confidence: (out):
* @call: (out):
*/
TarotPlayer tarot_ai_strongest_player (TarotAi *ai, size_t n_players, int with_call, size_t n_cards, const TarotPlayer *owners, double *confidence, TarotCard *call);
/**
* tarot_ai_best:
* @score: (out):
*/
const TarotGameEvent *tarot_ai_best (TarotAi *ai, const TarotGame *state, double *score);
/**
* tarot_ai_validate:
*/
double tarot_ai_validate (TarotAi *ai, const TarotGame *validation_game);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* not H_TAROT_AI_INCLUDED */
/* tarot implements the rules of the tarot game
* Copyright (C) 2019 Vivien Kraus
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef H_TAROT_AI_PRIVATE_INCLUDED
#define H_TAROT_AI_PRIVATE_INCLUDED
#include <tarot_private.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
static void ai_construct (TarotAi * ai, 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 *(*dup) (void *), void (*destruct) (void *));
static void ai_copy (TarotAi * ai, const TarotAi * source);
static void ai_destruct (TarotAi * ai);
static void ai_random_eval (void *state, const TarotGame * base,
size_t n_candidates,
TarotGameEvent ** candidates, size_t start,
size_t max, double *scores);
static void ai_random_learn (void *state, const TarotGame * base,
const TarotGameEvent * event,
double final_score);
static void *ai_random_dup (void *state);
static void ai_random_destruct (void *state);
static void ai_perceptron_eval (void *state, const TarotGame * base,
size_t n_candidates,
TarotGameEvent ** candidates, size_t start,
size_t max, double *scores);
static void ai_perceptron_learn (void *state, const TarotGame * base,
const TarotGameEvent * event,
double final_score);
static void *ai_perceptron_dup (void *state);
static void ai_perceptron_destruct (void *state);
static void ai_eval (TarotAi * ai, const TarotGame * base,
size_t n_candidates, TarotGameEvent ** candidates,
size_t start, size_t max, double *scores);
static void ai_learn (TarotAi * ai, const TarotGame * base,