Étape 0a.3 : moteur embarqué (cœur de gnubg), engine.h et engine-cli

Fusion du travail du sous-agent (branche moteur-embarque-travail) ; plan
supprimé à la clôture, son résultat est repris dans la merge request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre MARTIN
2026-11-02 14:02:56 +01:00
co-authored by Claude Opus 5.5
68 changed files with 131963 additions and 15 deletions
+13 -6
View File
@@ -8,15 +8,18 @@ Les contenus pédagogiques (leçons, textes) sont sous licence CC BY-SA 4.0.
- **GNU Backgammon** — https://www.gnu.org/software/gnubg/
Copyright © les auteurs de GNU Backgammon. Licence GPLv3 ou ultérieure.
Ustagammon s'appuie entièrement sur son moteur pour les règles, les coups,
le videau et les évaluations.
le videau et les évaluations. Copie non modifiée de son cœur dans
`engine/gnubg/` (commit 14b6798 ; liste dans `engine/gnubg/PROVENANCE.md`),
avec ses poids `gnubg.weights` dans `engine/data/`.
- **Table d'équité de match Kazaross-XG2** (`engine/data/Kazaross-XG2.xml`),
Copyright © 2011 Neil Kazaross, transcrite pour gnubg par Michael Petch ;
distribuée avec gnubg, copie libre sous réserve de garder sa notice.
Les projets suivants sont étudiés comme points de départ ; ils seront crédités
précisément ici dès que du code en sera repris :
Étudié comme référence de lecture, sans reprise de code :
- **gnubg-nn**, bibliothèque d'évaluation de GNU Backgammon —
https://git.savannah.gnu.org/cgit/gnubg/gnubg-nn.git
- **CBG**, portage Android de GNU Backgammon (GPLv3) —
https://github.com/clavierhaus/gnubg-android
https://github.com/clavierhaus/gnubg-android ; son fichier
`jni-bridge/src/stubs.c` a servi de repère pour isoler le cœur de gnubg.
## Outils de développement
@@ -33,6 +36,10 @@ précisément ici dès que du code en sera repris :
`gnubg_ts0.bd` sont produits par ce programme.
- **jq** (licence MIT), lecture des JSON dans `tests/test_reference.sh` ;
utilisé sur le PC, pas dans Docker.
- **Image `ustagammon/build-c`** (`docker/build-c/Dockerfile`), construite sur
Debian trixie : gcc (GPLv3 avec exception d'exécution), make (GPLv3),
GLib (LGPL 2.1 ou ultérieure, uniquement pour les outils `makeweights` et
`makebearoff` lancés au build ; le moteur embarqué n'en contient rien).
## Remerciements
+17 -4
View File
@@ -15,10 +15,17 @@ SHELLCHECK_IMAGE := koalaman/shellcheck-alpine:v0.10.0
GHERKIN_IMAGE := ustagammon/gherkin:2
# GNU Backgammon de référence, compilé au commit figé par l'ADR 0005.
REFERENCE_IMAGE := ustagammon/gnubg-reference:14b6798
# Chaîne de compilation C (docker/build-c/Dockerfile) : changer l'étiquette
# à chaque modification du Dockerfile, pour que make setup la reconstruise.
BUILD_C_IMAGE := ustagammon/build-c:1
export SHELLCHECK_IMAGE GHERKIN_IMAGE
export SHELLCHECK_IMAGE GHERKIN_IMAGE BUILD_C_IMAGE
.PHONY: help setup build test lint deploy clean reference-image reference
# Lance une commande dans la chaîne C, sous l'utilisateur courant pour que
# build/ lui appartienne.
DOCKER_C := docker run --rm --user "$$(id -u):$$(id -g)" -v "$(CURDIR):/src" -w /src $(BUILD_C_IMAGE)
.PHONY: help setup build engine test lint deploy clean reference-image reference
help: ## Liste les commandes disponibles
@echo "Ustagammon : commandes disponibles"
@@ -32,10 +39,16 @@ setup: ## Installe la chaîne d'outils (images Docker) ; sans effet si déjà fa
@docker image inspect $(GHERKIN_IMAGE) >/dev/null 2>&1 \
|| docker build -q -t $(GHERKIN_IMAGE) docker/gherkin
@$(MAKE) reference-image
@docker image inspect $(BUILD_C_IMAGE) >/dev/null 2>&1 \
|| docker build -t $(BUILD_C_IMAGE) docker/build-c
@echo "Chaîne d'outils prête."
build: ## Compile le moteur (si nécessaire) puis l'application
@echo "Rien à compiler pour l'instant : le moteur arrive à l'étape 0a."
build: engine ## Compile le moteur (si nécessaire) puis l'application
engine: ## Compile le moteur, ses données et build/engine-cli (dans Docker)
@docker image inspect $(BUILD_C_IMAGE) >/dev/null 2>&1 \
|| { echo "Chaîne C absente : lancer d'abord make setup"; exit 1; }
@$(DOCKER_C) make -f engine/engine.mk -j2 --no-print-directory
test: ## Lance tous les tests
@tests/run.sh
+14
View File
@@ -0,0 +1,14 @@
# Chaîne de compilation C d'Ustagammon (moteur embarqué et engine-cli).
# Construite une fois par `make setup` ; le build tourne dedans, jamais avec
# les outils de l'hôte (ADR 0004).
#
# - gcc et make : compilation du moteur et d'engine-cli ;
# - libglib2.0-dev : uniquement pour les outils officiels de gnubg lancés au
# build (makeweights, makebearoff), qui produisent les données du moteur.
# Le moteur embarqué lui-même n'utilise pas GLib (shim engine/compat/).
FROM debian:trixie
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc libc6-dev make pkg-config libglib2.0-dev \
&& rm -rf /var/lib/apt/lists/*
-1
View File
@@ -14,7 +14,6 @@ Acquis : socle du dépôt, organisation (ADR 0001, 0003, 0006), source du moteur
| Sous-étape | Contenu | État | Plan |
| --- | --- | --- | --- |
| 0a.3 | Interface C `engine/include/engine.h` et `engine-cli` (JSON) | 🔄 | `plans/2026-11-02-moteur-embarque.md` |
| 0a.4 | `make test` compare `engine-cli` aux références | ⏳ | |
| 0b | Moteur en WebAssembly, page de test dans un Web Worker, `make deploy-web`, `make bench` | ⏳ | |
| Mesures | Pierre relève les mesures sur le Fairphone 4 | ⏳ | |
+306
View File
@@ -0,0 +1,306 @@
/*
* engine-cli -- interroge le moteur d'Ustagammon en ligne de commande.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Usage :
* engine-cli [--ply N] [--data DOSSIER] COMMANDE ARGUMENTS
*
* Commandes :
* bestmove POSITION_ID MATCH_ID DÉS meilleur coup (DÉS : ex. 31)
* moves POSITION_ID MATCH_ID DÉS coups légaux classés, évalués
* legal POSITION_ID MATCH_ID DÉS coups légaux, sans évaluation
* cube POSITION_ID MATCH_ID décision de videau
* eval POSITION_ID MATCH_ID évaluation de la position
*
* --ply : niveau de gnubg, de 0 (défaut) à 4.
* --data : dossier des données ; par défaut « data » à côté du programme.
*
* Sortie : un objet JSON sur la sortie standard. En cas d'erreur, un objet
* {"error": {"code": ..., "message": ...}} et un code de sortie non nul :
* 1 pour une erreur du moteur, 2 pour une erreur d'utilisation.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "engine.h"
#define EXIT_ENGINE_ERROR 1
#define EXIT_USAGE_ERROR 2
/* Arguments de la ligne de commande, une fois lus. */
typedef struct {
int ply;
const char *data_dir;
const char *command;
const char *args[3];
int arg_count;
} options;
/* Code d'erreur JSON pour chaque engine_status. */
static const char *
status_code(engine_status status)
{
switch (status) {
case ENGINE_OK:
return "ok";
case ENGINE_ERROR_NOT_INITIALISED:
return "not_initialised";
case ENGINE_ERROR_DATA:
return "invalid_data";
case ENGINE_ERROR_POSITION_ID:
return "invalid_position_id";
case ENGINE_ERROR_MATCH_ID:
return "invalid_match_id";
case ENGINE_ERROR_DICE:
return "invalid_dice";
case ENGINE_ERROR_PLY:
return "invalid_ply";
case ENGINE_ERROR_TOO_MANY_MOVES:
return "too_many_moves";
case ENGINE_ERROR_INTERNAL:
return "internal_error";
}
return "unknown_error";
}
/* Les messages sont des constantes sans guillemets : pas d'échappement. */
static int
print_error(const char *code, const char *message, int exit_code)
{
printf("{\"error\": {\"code\": \"%s\", \"message\": \"%s\"}}\n", code, message);
return exit_code;
}
static int
print_engine_error(engine_status status)
{
return print_error(status_code(status), engine_status_message(status), EXIT_ENGINE_ERROR);
}
static int
usage_error(const char *message)
{
return print_error("usage", message, EXIT_USAGE_ERROR);
}
static void
print_probabilities(const float p[ENGINE_NUM_PROBABILITIES])
{
printf("{\"win\": %.6f, \"win_gammon\": %.6f, \"win_backgammon\": %.6f, "
"\"lose_gammon\": %.6f, \"lose_backgammon\": %.6f}",
p[ENGINE_WIN], p[ENGINE_WIN_GAMMON], p[ENGINE_WIN_BACKGAMMON],
p[ENGINE_LOSE_GAMMON], p[ENGINE_LOSE_BACKGAMMON]);
}
static void
print_move(const engine_move *m, int with_evaluation)
{
printf("{\"move\": \"%s\", \"position_id\": \"%s\"", m->notation, m->position_id);
if (with_evaluation) {
printf(", \"ply\": %d, \"equity\": %.6f, \"probabilities\": ", m->ply, m->equity);
print_probabilities(m->probabilities);
}
printf("}");
}
static void
print_match(const engine_match *m)
{
printf("{\"match_length\": %d, \"score\": [%d, %d], \"cube_value\": %d, "
"\"cube_owner\": %d, \"player_on_roll\": %d, \"crawford\": %s, \"jacoby\": %s}",
m->match_length, m->score[0], m->score[1], m->cube_value, m->cube_owner,
m->player_on_roll, m->crawford ? "true" : "false", m->jacoby ? "true" : "false");
}
/* Lit des dés au format « 31 » ; renvoie 0 si le format est faux. */
static int
parse_dice(const char *text, int *die1, int *die2)
{
if (strlen(text) != 2 || text[0] < '0' || text[0] > '9' || text[1] < '0' || text[1] > '9')
return 0;
*die1 = text[0] - '0';
*die2 = text[1] - '0';
return 1;
}
/* bestmove, moves et legal. */
static int
run_moves_command(const options *opt)
{
const char *position_id = opt->args[0], *match_id = opt->args[1];
int best_only = strcmp(opt->command, "bestmove") == 0;
int evaluate = strcmp(opt->command, "legal") != 0;
int die1, die2, count, i;
engine_move *moves;
engine_status status;
if (opt->arg_count != 3)
return usage_error("arguments attendus : POSITION_ID MATCH_ID DÉS");
if (!parse_dice(opt->args[2], &die1, &die2))
return usage_error("dés attendus sous la forme 31");
moves = malloc(sizeof *moves * ENGINE_MAX_MOVES);
if (!moves)
return print_error("out_of_memory", "mémoire insuffisante", EXIT_ENGINE_ERROR);
if (evaluate)
status = engine_best_moves(position_id, match_id, die1, die2, opt->ply,
moves, ENGINE_MAX_MOVES, &count);
else
status = engine_legal_moves(position_id, match_id, die1, die2,
moves, ENGINE_MAX_MOVES, &count);
if (status != ENGINE_OK) {
free(moves);
return print_engine_error(status);
}
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"dice\": [%d, %d]",
position_id, match_id, die1, die2);
if (evaluate)
printf(", \"ply\": %d", opt->ply);
if (best_only) {
printf(", \"best_move\": ");
if (count > 0)
print_move(&moves[0], 1);
else
printf("null");
} else {
printf(", \"count\": %d, \"moves\": [", count);
for (i = 0; i < count; i++) {
printf(i ? ", " : "");
print_move(&moves[i], evaluate);
}
printf("]");
}
printf("}\n");
free(moves);
return EXIT_SUCCESS;
}
/* eval et cube. */
static int
run_position_command(const options *opt)
{
const char *position_id = opt->args[0], *match_id = opt->args[1];
engine_match match;
engine_status status;
if (opt->arg_count != 2)
return usage_error("arguments attendus : POSITION_ID MATCH_ID");
status = engine_decode_match(match_id, &match);
if (status != ENGINE_OK)
return print_engine_error(status);
if (strcmp(opt->command, "eval") == 0) {
engine_evaluation e;
status = engine_evaluate(position_id, match_id, opt->ply, &e);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"ply\": %d, \"match\": ",
position_id, match_id, opt->ply);
print_match(&match);
printf(", \"probabilities\": ");
print_probabilities(e.probabilities);
printf(", \"cubeless_equity\": %.6f, \"cubeful_equity\": %.6f}\n",
e.cubeless_equity, e.cubeful_equity);
} else {
engine_cube c;
status = engine_cube_decision(position_id, match_id, opt->ply, &c);
if (status != ENGINE_OK)
return print_engine_error(status);
printf("{\"position_id\": \"%s\", \"match_id\": \"%s\", \"ply\": %d, \"match\": ",
position_id, match_id, opt->ply);
print_match(&match);
printf(", \"probabilities\": ");
print_probabilities(c.probabilities);
printf(", \"no_double\": %.6f, \"double_take\": %.6f, \"double_pass\": %.6f, "
"\"decision\": \"%s\"}\n", c.no_double, c.double_take, c.double_pass, c.decision);
}
return EXIT_SUCCESS;
}
/* Dossier « data » à côté du programme. */
static char *
default_data_dir(const char *program)
{
const char *slash = strrchr(program, '/');
size_t dir_length = slash ? (size_t) (slash - program) : 1;
char *dir = malloc(dir_length + sizeof "/data");
if (!dir)
return NULL;
if (slash)
memcpy(dir, program, dir_length);
else
dir[0] = '.';
strcpy(dir + dir_length, "/data");
return dir;
}
/* Lit les options ; renvoie un message d'erreur, ou NULL si tout va bien. */
static const char *
parse_options(int argc, char **argv, options *opt)
{
int i;
memset(opt, 0, sizeof *opt);
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "--ply") == 0 && i + 1 < argc) {
char *end;
opt->ply = (int) strtol(argv[++i], &end, 10);
if (*end || end == argv[i])
return "--ply attend un nombre";
} else if (strcmp(argv[i], "--data") == 0 && i + 1 < argc) {
opt->data_dir = argv[++i];
} else if (argv[i][0] == '-' && argv[i][1] == '-') {
return "option inconnue ou sans valeur";
} else if (!opt->command) {
opt->command = argv[i];
} else if (opt->arg_count < 3) {
opt->args[opt->arg_count++] = argv[i];
} else {
return "trop d'arguments";
}
}
if (!opt->command)
return "commande attendue : bestmove, moves, legal, cube ou eval";
return NULL;
}
int
main(int argc, char **argv)
{
options opt;
const char *problem = parse_options(argc, argv, &opt);
char *data_dir = NULL;
engine_status status;
int result;
if (problem)
return usage_error(problem);
if (!opt.data_dir) {
data_dir = default_data_dir(argv[0]);
if (!data_dir)
return print_error("out_of_memory", "mémoire insuffisante", EXIT_ENGINE_ERROR);
opt.data_dir = data_dir;
}
status = engine_init(opt.data_dir);
free(data_dir);
if (status != ENGINE_OK)
return print_engine_error(status);
if (strcmp(opt.command, "bestmove") == 0 || strcmp(opt.command, "moves") == 0
|| strcmp(opt.command, "legal") == 0)
result = run_moves_command(&opt);
else if (strcmp(opt.command, "eval") == 0 || strcmp(opt.command, "cube") == 0)
result = run_position_command(&opt);
else
result = usage_error("commande inconnue : bestmove, moves, legal, cube ou eval");
engine_shutdown();
return result;
}
+23
View File
@@ -0,0 +1,23 @@
/*
* backgammon.h -- en-tête réduit remplaçant celui de GNU Backgammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Le backgammon.h d'origine déclare l'état complet de l'application de
* bureau (match en cours, commandes, réglages d'interface). Le cœur repris
* dans engine/gnubg/ n'en utilise qu'une petite partie, déclarée ici avec
* les mêmes noms et les mêmes types que dans l'original (commit 14b6798).
* Les définitions correspondantes sont dans engine/compat/gnubg-support.c.
*/
#ifndef BACKGAMMON_H
#define BACKGAMMON_H
#include "common.h"
#include "output.h"
#include "eval.h"
#include <glib/gi18n.h>
#endif
+58
View File
@@ -0,0 +1,58 @@
/*
* config.h -- configuration de compilation du moteur embarqué d'Ustagammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Remplace le config.h que produit le configure de GNU Backgammon. Seules les
* macros lues par les fichiers repris dans engine/gnubg/ sont définies ; les
* valeurs suivent ce que configure.ac choisit sur une Debian récente avec gcc.
*/
#ifndef USTAGAMMON_CONFIG_H
#define USTAGAMMON_CONFIG_H
#define VERSION "1.08.003"
#define PACKAGE "gnubg"
#define GETTEXT_PACKAGE "gnubg"
/* Compilateur : gcc ou clang, qui comprennent tous deux ces extensions. */
#define HAVE___ATTRIBUTE__ 1
#define HAVE_FUNC_ATTRIBUTE_CONST 1
#define HAVE_FUNC_ATTRIBUTE_FALLTHROUGH 1
#define HAVE_FUNC_ATTRIBUTE_FORMAT 1
#define HAVE_FUNC_ATTRIBUTE_PURE 1
#define HAVE_FUNC_ATTRIBUTE_UNUSED 1
#define HAVE___BUILTIN_EXPECT 1
#define HAVE___BUILTIN_CLZ 1
/* Bibliothèque C POSIX (glibc, bionic d'Android, musl d'Emscripten). */
#define HAVE_UNISTD_H 1
#define HAVE_STDINT_H 1
#define HAVE_POSIX_MEMALIGN 1
/*
* Pas de threads : une seule instance du moteur, appels sérialisés
* (ADR 0005). USE_MULTITHREAD reste donc non défini.
*/
/*
* Instructions SIMD. Par défaut, C générique partout (x86 compris), ce qui
* est aussi le seul chemin possible en WebAssembly.
*
* Chemin NEON prévu pour arm64 (Android), activé en compilant avec
* -DUSTAGAMMON_NEON ; il reprend les définitions que configure produit avec
* --enable-simd=neon. Le test d'instructions à l'exécution est inutile :
* NEON est obligatoire sur arm64.
*/
#if defined(USTAGAMMON_NEON)
#if !defined(__aarch64__)
#error "USTAGAMMON_NEON n'est prévu que pour arm64"
#endif
#define USE_SIMD_INSTRUCTIONS 1
#define USE_NEON 1
#define HAVE_NEON 1
#define DISABLE_SIMD_TEST 1
#endif
#endif /* USTAGAMMON_CONFIG_H */
+79
View File
@@ -0,0 +1,79 @@
/*
* external.h -- en-tête réduit remplaçant celui de GNU Backgammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Les deux structures ci-dessous sont reprises telles quelles de external.h
* de GNU Backgammon (commit 14b6798, Copyright (C) 2001-2025 the AUTHORS).
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* drawboard.c (repris pour FormatMove, la notation des coups) inclut
* external.h pour ces deux types, utilisés par ProcessFIBSBoardInfo. Le
* reste de l'interface externe de gnubg (sockets) n'a pas lieu d'être ici.
*/
#ifndef EXTERNAL_H
#define EXTERNAL_H
#include <glib.h>
#include "backgammon.h"
#define MAX_NAME_LEN 32
typedef struct {
/*
* These must be in the same order than the FIBS board string definition,
* starting at Match Length.
* See description at http://www.fibs.com/fibs_interface.html#board_state
*/
int nMatchTo;
int nScore;
int nScoreOpp;
int anFIBSBoard[26];
int nTurn;
int anDice[2];
int anOppDice[2];
int nCube;
int fCanDouble;
int fOppCanDouble;
int fDoubled;
int nColor;
int nDirection;
int nHome; /* unused, set to 0 */
int nBar; /* unused, set to 25 */
int nOnHome; /* unused */
int nOnHomeOpp; /* unused */
int nOnBar; /* unused */
int nOnBarOpp; /* unused */
int nCanMove; /* unused */
int fForcedMOve; /* unused */
int fPostCrawford;
/*
* Redoubles from FIBS board is not usable by GNUbg.
* A flag for match play is put at the same place
*/
int fNonCrawford;
int nVersion;
int padding[51];
/* These must be last */
GString *gsName;
GString *gsOpp;
} FIBSBoardInfo;
typedef struct {
char szPlayer[MAX_NAME_LEN];
char szOpp[MAX_NAME_LEN];
int nMatchTo;
int nScore;
int nScoreOpp;
int anDice[2];
int nCube;
int fCubeOwner;
int fDoubled;
int fCrawford;
int fJacoby;
int nResignation;
TanBoard anBoard;
} ProcessedFIBSBoard;
#endif
+14
View File
@@ -0,0 +1,14 @@
/*
* format.h -- en-tête réduit remplaçant celui de GNU Backgammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* eval.c inclut format.h sans utiliser ses fonctions d'affichage dans les
* parties reprises : l'en-tête est volontairement vide.
*/
#ifndef FORMAT_H
#define FORMAT_H
#endif
+658
View File
@@ -0,0 +1,658 @@
/*
* glib-shim.c -- implémentation du shim minimal de GLib (voir glib-shim/glib.h).
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Écrit pour Ustagammon d'après la documentation publique de GLib ; aucun code
* de GLib n'est repris.
*/
#define _DEFAULT_SOURCE
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "glib.h"
/* ---------------------------------------------------------------- Erreurs */
void
ustagammon_g_assert_failed(const char *expr, const char *file, int line)
{
fprintf(stderr, "moteur : assertion « %s » fausse (%s:%d)\n", expr, file, line);
abort();
}
/* Crée une GError dont le message est « message : detail ». */
static GError *
new_error(gint code, const char *message, const char *detail)
{
GError *e = g_new0(GError, 1);
size_t size = strlen(message) + strlen(detail) + 4;
e->code = code;
e->message = g_malloc(size);
snprintf(e->message, size, "%s : %s", message, detail);
return e;
}
static void
set_error(GError **error, gint code, const char *message, const char *detail)
{
if (error)
*error = new_error(code, message, detail);
}
void
g_error_free(GError *error)
{
if (!error)
return;
g_free(error->message);
g_free(error);
}
/* ---------------------------------------------------------------- Mémoire */
static gpointer
check_allocation(gpointer p, gsize n)
{
if (!p && n) {
fprintf(stderr, "moteur : échec d'allocation de %zu octets\n", n);
abort();
}
return p;
}
gpointer
g_malloc(gsize n)
{
return n ? check_allocation(malloc(n), n) : NULL;
}
gpointer
g_malloc0(gsize n)
{
return n ? check_allocation(calloc(1, n), n) : NULL;
}
void
g_free(gpointer p)
{
free(p);
}
gpointer
g_memdup2(gconstpointer mem, gsize n)
{
gpointer p;
if (!mem || !n)
return NULL;
p = g_malloc(n);
memcpy(p, mem, n);
return p;
}
gchar *
g_strdup(const gchar *s)
{
return s ? (gchar *) g_memdup2(s, strlen(s) + 1) : NULL;
}
/* --------------------------------------------------------------- Messages */
/* Tout part sur la sortie d'erreur : la sortie standard appartient à
* l'application (JSON d'engine-cli). */
void
g_print(const gchar *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
void
g_printerr(const gchar *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
void
g_warning(const gchar *format, ...)
{
va_list args;
fputs("moteur (avertissement) : ", stderr);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputc('\n', stderr);
}
void
g_error(const gchar *format, ...)
{
va_list args;
fputs("moteur (erreur fatale) : ", stderr);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputc('\n', stderr);
abort();
}
/* ---------------------------------------------------------------- Chaînes */
/*
* strtod indépendant de la locale : le point est toujours le séparateur
* décimal. Si la locale courante utilise un autre caractère (la virgule en
* français), on le substitue au point dans une copie avant d'appeler strtod.
*/
gdouble
g_ascii_strtod(const gchar *nptr, gchar **endptr)
{
const char *point = localeconv()->decimal_point;
char *copy, *end;
double value;
size_t i;
if (!point || strlen(point) != 1 || point[0] == '.')
return strtod(nptr, endptr);
copy = g_strdup(nptr);
for (i = 0; copy[i]; i++)
if (copy[i] == '.')
copy[i] = point[0];
value = strtod(copy, &end);
if (endptr)
*endptr = (gchar *) nptr + (end - copy);
g_free(copy);
return value;
}
guint64
g_ascii_strtoull(const gchar *nptr, gchar **endptr, guint base)
{
return (guint64) strtoull(nptr, endptr, (int) base);
}
static int
ascii_tolower(int c)
{
return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c;
}
gint
g_ascii_strncasecmp(const gchar *s1, const gchar *s2, gsize n)
{
for (; n > 0; n--, s1++, s2++) {
int c1 = ascii_tolower((unsigned char) *s1);
int c2 = ascii_tolower((unsigned char) *s2);
if (c1 != c2 || !c1)
return c1 - c2;
}
return 0;
}
gint
g_ascii_strcasecmp(const gchar *s1, const gchar *s2)
{
return g_ascii_strncasecmp(s1, s2, (gsize) -1);
}
const gchar *
g_strerror(gint errnum)
{
return strerror(errnum);
}
/* Copie tronquée et toujours terminée ; renvoie la longueur de src. */
gsize
g_strlcpy(gchar *dest, const gchar *src, gsize dest_size)
{
gsize len = strlen(src);
if (dest_size > 0) {
gsize n = len < dest_size - 1 ? len : dest_size - 1;
memcpy(dest, src, n);
dest[n] = '\0';
}
return len;
}
/* Assemble des morceaux de chemin avec « / », sans doubler le séparateur. */
gchar *
g_build_filename(const gchar *first, ...)
{
va_list args;
const gchar *part;
size_t total = 1;
gchar *result;
va_start(args, first);
for (part = first; part; part = va_arg(args, const gchar *))
total += strlen(part) + 1;
va_end(args);
result = g_malloc(total);
result[0] = '\0';
va_start(args, first);
for (part = first; part; part = va_arg(args, const gchar *)) {
size_t len = strlen(result);
if (!*part)
continue;
if (len > 0 && result[len - 1] != '/')
strcat(result, "/");
if (len > 0 && *part == '/')
part++;
strcat(result, part);
}
va_end(args);
return result;
}
/* --------------------------------------------------------------- Fichiers */
gboolean
g_file_test(const gchar *filename, GFileTest test)
{
struct stat st;
if (stat(filename, &st) != 0)
return FALSE;
if (test & G_FILE_TEST_EXISTS)
return TRUE;
if ((test & G_FILE_TEST_IS_REGULAR) && S_ISREG(st.st_mode))
return TRUE;
if ((test & G_FILE_TEST_IS_DIR) && S_ISDIR(st.st_mode))
return TRUE;
if ((test & G_FILE_TEST_IS_EXECUTABLE) && access(filename, X_OK) == 0)
return TRUE;
return FALSE;
}
/* Lit un fichier entier en mémoire, terminé par un octet nul. */
gboolean
g_file_get_contents(const gchar *filename, gchar **contents, gsize *length, GError **error)
{
FILE *f;
long size;
gchar *buffer;
*contents = NULL;
f = fopen(filename, "rb");
if (!f) {
set_error(error, errno, "lecture impossible", filename);
return FALSE;
}
if (fseek(f, 0, SEEK_END) != 0 || (size = ftell(f)) < 0 || fseek(f, 0, SEEK_SET) != 0) {
fclose(f);
set_error(error, EIO, "taille illisible", filename);
return FALSE;
}
buffer = g_malloc((size_t) size + 1);
if (fread(buffer, 1, (size_t) size, f) != (size_t) size) {
fclose(f);
g_free(buffer);
set_error(error, EIO, "lecture incomplète", filename);
return FALSE;
}
fclose(f);
buffer[size] = '\0';
*contents = buffer;
if (length)
*length = (gsize) size;
return TRUE;
}
/* Fichier projeté en mémoire en lecture seule (mmap). */
struct UstagammonMappedFile {
gchar *contents;
gsize length;
};
GMappedFile *
g_mapped_file_new(const gchar *filename, gboolean writable, GError **error)
{
GMappedFile *m;
struct stat st;
void *p = NULL;
int fd;
if (writable) {
set_error(error, EINVAL, "projection en écriture non prise en charge", filename);
return NULL;
}
fd = open(filename, O_RDONLY);
if (fd < 0) {
set_error(error, errno, "ouverture impossible", filename);
return NULL;
}
if (fstat(fd, &st) != 0) {
set_error(error, errno, "taille illisible", filename);
close(fd);
return NULL;
}
if (st.st_size > 0) {
p = mmap(NULL, (size_t) st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (p == MAP_FAILED) {
set_error(error, errno, "projection impossible", filename);
close(fd);
return NULL;
}
}
close(fd);
m = g_new0(GMappedFile, 1);
m->contents = p;
m->length = (gsize) st.st_size;
return m;
}
gchar *
g_mapped_file_get_contents(GMappedFile *file)
{
return file->contents;
}
gsize
g_mapped_file_get_length(GMappedFile *file)
{
return file->length;
}
void
g_mapped_file_unref(GMappedFile *file)
{
if (!file)
return;
if (file->contents)
munmap(file->contents, file->length);
g_free(file);
}
/* ------------------------------------------------------------- XML simple */
/*
* Analyseur XML réduit à ce qu'utilisent les tables d'équité de gnubg
* (fichiers XML de engine/data/) : éléments, attributs entre guillemets, texte,
* commentaires, prologue <?xml?> et <!DOCTYPE> sans sous-ensemble interne,
* entités &lt; &gt; &amp; &quot; &apos;. Tout le reste est une erreur.
*/
struct UstagammonMarkupParseContext {
const GMarkupParser *parser;
gpointer user_data;
GDestroyNotify user_data_dnotify;
};
GMarkupParseContext *
g_markup_parse_context_new(const GMarkupParser *parser, GMarkupParseFlags flags,
gpointer user_data, GDestroyNotify user_data_dnotify)
{
GMarkupParseContext *c = g_new0(GMarkupParseContext, 1);
(void) flags;
c->parser = parser;
c->user_data = user_data;
c->user_data_dnotify = user_data_dnotify;
return c;
}
void
g_markup_parse_context_free(GMarkupParseContext *context)
{
if (!context)
return;
if (context->user_data_dnotify)
context->user_data_dnotify(context->user_data);
g_free(context);
}
#define MAX_DEPTH 32
#define MAX_ATTRIBUTES 8
/* Copie [start, end) en décodant les entités ; NULL si une entité est inconnue. */
static gchar *
decode_text(const char *start, const char *end)
{
static const struct {
const char *name;
char c;
} entities[] = {
{ "&lt;", '<' }, { "&gt;", '>' }, { "&amp;", '&' }, { "&quot;", '"' }, { "&apos;", '\'' }
};
gchar *out = g_malloc((size_t) (end - start) + 1);
gchar *o = out;
const char *p = start;
while (p < end) {
size_t i, n = sizeof entities / sizeof entities[0];
if (*p != '&') {
*o++ = *p++;
continue;
}
for (i = 0; i < n; i++) {
size_t len = strlen(entities[i].name);
if ((size_t) (end - p) >= len && strncmp(p, entities[i].name, len) == 0) {
*o++ = entities[i].c;
p += len;
break;
}
}
if (i == n) {
g_free(out);
return NULL;
}
}
*o = '\0';
return out;
}
static int
is_name_char(char c)
{
return isalnum((unsigned char) c) || c == '-' || c == '_' || c == ':' || c == '.';
}
static const char *
skip_spaces(const char *p, const char *end)
{
while (p < end && isspace((unsigned char) *p))
p++;
return p;
}
/* Copie le nom qui commence en p ; avance *after juste derrière. */
static gchar *
read_name(const char *p, const char *end, const char **after)
{
const char *q = p;
gchar *name;
while (q < end && is_name_char(*q))
q++;
*after = q;
if (q == p)
return NULL;
name = g_malloc((size_t) (q - p) + 1);
memcpy(name, p, (size_t) (q - p));
name[q - p] = '\0';
return name;
}
/*
* Lit les attributs d'une balise ouvrante jusqu'à « > » ou « /> ».
* Renvoie la position après la balise, ou NULL en cas d'erreur.
*/
static const char *
read_attributes(const char *p, const char *end, gchar **names, gchar **values, int *empty)
{
int n = 0;
names[0] = values[0] = NULL;
for (;;) {
const char *value_start;
char quote;
p = skip_spaces(p, end);
if (p >= end)
return NULL;
if (*p == '>')
return p + 1;
if (*p == '/' && p + 1 < end && p[1] == '>') {
*empty = 1;
return p + 2;
}
if (n == MAX_ATTRIBUTES)
return NULL;
names[n] = read_name(p, end, &p);
names[n + 1] = values[n] = values[n + 1] = NULL;
if (!names[n])
return NULL;
p = skip_spaces(p, end);
if (p >= end || *p != '=')
return NULL;
p = skip_spaces(p + 1, end);
if (p >= end || (*p != '"' && *p != '\''))
return NULL;
quote = *p++;
value_start = p;
while (p < end && *p != quote)
p++;
if (p >= end)
return NULL;
values[n] = decode_text(value_start, p);
if (!values[n])
return NULL;
n++;
p++;
}
}
static void
free_strings(gchar **strings)
{
for (; *strings; strings++)
g_free(*strings);
}
gboolean
g_markup_parse_context_parse(GMarkupParseContext *context, const gchar *text,
gssize text_len, GError **error)
{
const GMarkupParser *mp = context->parser;
const char *p = text;
const char *end = text + (text_len < 0 ? (gssize) strlen(text) : text_len);
gchar *stack[MAX_DEPTH];
int depth = 0;
const char *problem = NULL;
GError *err = NULL;
while (p < end && !problem && !err) {
if (*p != '<') {
/* Texte jusqu'à la prochaine balise. */
const char *start = p;
gchar *t;
while (p < end && *p != '<')
p++;
if (depth == 0) {
if (skip_spaces(start, p) != p)
problem = "texte hors de l'élément racine";
continue;
}
t = decode_text(start, p);
if (!t)
problem = "entité inconnue";
else if (mp->text)
mp->text(context, t, strlen(t), context->user_data, &err);
g_free(t);
} else if (end - p >= 4 && strncmp(p, "<!--", 4) == 0) {
const char *q = p + 4;
while (q + 3 <= end && strncmp(q, "-->", 3) != 0)
q++;
if (q + 3 > end)
problem = "commentaire non terminé";
p = q + 3;
} else if (end - p >= 2 && (p[1] == '?' || p[1] == '!')) {
const char *q = memchr(p, '>', (size_t) (end - p));
if (!q || memchr(p, '[', (size_t) (q - p)))
problem = "déclaration non prise en charge";
else
p = q + 1;
} else if (end - p >= 2 && p[1] == '/') {
/* Balise fermante. */
gchar *name = read_name(p + 2, end, &p);
p = skip_spaces(p, end);
if (!name || p >= end || *p != '>')
problem = "balise fermante mal formée";
else if (depth == 0 || strcmp(stack[depth - 1], name) != 0)
problem = "balise fermante inattendue";
else {
depth--;
if (mp->end_element)
mp->end_element(context, name, context->user_data, &err);
g_free(stack[depth]);
p++;
}
g_free(name);
} else {
/* Balise ouvrante, éventuellement vide (<a/>). */
gchar *names[MAX_ATTRIBUTES + 1], *values[MAX_ATTRIBUTES + 1];
int empty = 0;
gchar *name = read_name(p + 1, end, &p);
names[0] = values[0] = NULL;
if (name)
p = read_attributes(p, end, names, values, &empty);
if (!name || !p)
problem = "balise ouvrante mal formée";
else if (depth == MAX_DEPTH)
problem = "imbrication trop profonde";
else {
if (mp->start_element)
mp->start_element(context, name, (const gchar **) names,
(const gchar **) values, context->user_data, &err);
if (!err && empty && mp->end_element)
mp->end_element(context, name, context->user_data, &err);
}
free_strings(names);
free_strings(values);
if (!problem && !err && !empty)
stack[depth++] = name;
else
g_free(name);
}
}
if (!problem && !err && depth > 0)
problem = "document incomplet";
while (depth > 0)
g_free(stack[--depth]);
if (problem)
err = new_error(0, "XML invalide", problem);
if (err) {
if (mp->error)
mp->error(context, err, context->user_data);
if (error)
*error = err;
else
g_error_free(err);
return FALSE;
}
return TRUE;
}
+212
View File
@@ -0,0 +1,212 @@
/*
* glib.h -- shim minimal de GLib pour le moteur embarqué d'Ustagammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Le cœur de GNU Backgammon repris dans engine/gnubg/ utilise une petite
* partie de GLib. Plutôt que d'embarquer GLib entière (taille, portage Android
* et WebAssembly), ce fichier en fournit le sous-ensemble utilisé, avec le
* même comportement observable pour le moteur. Implémentation :
* engine/compat/glib-shim.c.
*
* Écarts assumés avec GLib :
* - g_print écrit sur la sortie d'erreur (et non la sortie standard), pour ne
* jamais mêler de messages de diagnostic à la sortie JSON d'engine-cli ;
* - GMarkup ne reconnaît que le XML simple des tables d'équité de match
* (éléments, attributs, texte, commentaires, prologue, DOCTYPE, entités
* prédéfinies et numériques) ; pas de CDATA ni d'instructions dans le texte ;
* - g_error et l'échec d'allocation interrompent le programme (abort), comme
* GLib : ces chemins signalent des erreurs internes, pas des erreurs
* d'utilisation ;
* - ni threads, ni atomiques, ni listes GLib : le moteur est compilé sans
* USE_MULTITHREAD.
*
* Ce fichier ne sert qu'au moteur embarqué. Les outils de génération de
* données (makeweights, makebearoff) sont compilés avec la vraie GLib.
*/
#ifndef USTAGAMMON_GLIB_SHIM_H
#define USTAGAMMON_GLIB_SHIM_H
#include <float.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <alloca.h>
/* Types de base. */
typedef char gchar;
typedef unsigned char guchar;
typedef int gint;
typedef unsigned int guint;
typedef int gboolean;
typedef short gshort;
typedef unsigned short gushort;
typedef long glong;
typedef unsigned long gulong;
typedef float gfloat;
typedef double gdouble;
typedef void *gpointer;
typedef const void *gconstpointer;
typedef size_t gsize;
typedef ptrdiff_t gssize;
typedef int8_t gint8;
typedef uint8_t guint8;
typedef int16_t gint16;
typedef uint16_t guint16;
typedef int32_t gint32;
typedef uint32_t guint32;
typedef int64_t gint64;
typedef uint64_t guint64;
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define G_PI 3.1415926535897932384626433832795028841971693993751
#define G_PI_2 1.5707963267948966192313216916397514420985846996876
#define G_GSIZE_FORMAT "zu"
#define G_STRINGIFY_ARG(x) #x
#define G_STRINGIFY(x) G_STRINGIFY_ARG(x)
#define G_STRLOC __FILE__ ":" G_STRINGIFY(__LINE__)
/*
* Version de GLib imitée : 2.84 (celle de Debian trixie), pour que le code
* de gnubg choisisse les mêmes branches qu'avec la vraie bibliothèque
* (g_memdup2 plutôt que g_memdup, par exemple).
*/
#define GLIB_MAJOR_VERSION 2
#define GLIB_MINOR_VERSION 84
#define GLIB_MICRO_VERSION 0
#define GLIB_CHECK_VERSION(major, minor, micro) \
(GLIB_MAJOR_VERSION > (major) || \
(GLIB_MAJOR_VERSION == (major) && GLIB_MINOR_VERSION > (minor)) || \
(GLIB_MAJOR_VERSION == (major) && GLIB_MINOR_VERSION == (minor) && \
GLIB_MICRO_VERSION >= (micro)))
/* Assertions : désactivées par G_DISABLE_ASSERT, comme dans le build officiel. */
void ustagammon_g_assert_failed(const char *expr, const char *file, int line);
#if defined(G_DISABLE_ASSERT)
#define g_assert(expr) ((void) 0)
#define g_assert_not_reached() ((void) 0)
#else
#define g_assert(expr) \
((expr) ? (void) 0 : ustagammon_g_assert_failed(#expr, __FILE__, __LINE__))
#define g_assert_not_reached() \
ustagammon_g_assert_failed("code inatteignable", __FILE__, __LINE__)
#endif
#define g_return_val_if_fail(expr, val) \
do { if (!(expr)) { \
g_warning("%s : condition « %s » fausse", G_STRLOC, #expr); \
return (val); } } while (0)
#define g_return_if_fail(expr) \
do { if (!(expr)) { \
g_warning("%s : condition « %s » fausse", G_STRLOC, #expr); \
return; } } while (0)
/* Mémoire. */
gpointer g_malloc(gsize n);
gpointer g_malloc0(gsize n);
void g_free(gpointer p);
gpointer g_memdup2(gconstpointer mem, gsize n);
gchar *g_strdup(const gchar *s);
#define g_new(type, n) ((type *) g_malloc(sizeof(type) * (gsize) (n)))
#define g_new0(type, n) ((type *) g_malloc0(sizeof(type) * (gsize) (n)))
#define g_alloca(size) alloca(size)
/* Messages. */
void g_print(const gchar *format, ...) __attribute__((format(printf, 1, 2)));
void g_printerr(const gchar *format, ...) __attribute__((format(printf, 1, 2)));
void g_warning(const gchar *format, ...) __attribute__((format(printf, 1, 2)));
void g_error(const gchar *format, ...) __attribute__((format(printf, 1, 2), noreturn));
/* Chaînes. */
gdouble g_ascii_strtod(const gchar *nptr, gchar **endptr);
guint64 g_ascii_strtoull(const gchar *nptr, gchar **endptr, guint base);
gint g_ascii_strcasecmp(const gchar *s1, const gchar *s2);
gint g_ascii_strncasecmp(const gchar *s1, const gchar *s2, gsize n);
const gchar *g_strerror(gint errnum);
gchar *g_build_filename(const gchar *first, ...);
gsize g_strlcpy(gchar *dest, const gchar *src, gsize dest_size);
/* GString : seul le type est utilisé (structures FIBS de drawboard.c). */
typedef struct {
gchar *str;
gsize len;
gsize allocated_len;
} GString;
/* Erreurs. */
typedef guint32 GQuark;
typedef struct {
GQuark domain;
gint code;
gchar *message;
} GError;
void g_error_free(GError *error);
/* Fichiers. */
typedef enum {
G_FILE_TEST_IS_REGULAR = 1 << 0,
G_FILE_TEST_IS_SYMLINK = 1 << 1,
G_FILE_TEST_IS_DIR = 1 << 2,
G_FILE_TEST_IS_EXECUTABLE = 1 << 3,
G_FILE_TEST_EXISTS = 1 << 4
} GFileTest;
gboolean g_file_test(const gchar *filename, GFileTest test);
gboolean g_file_get_contents(const gchar *filename, gchar **contents, gsize *length, GError **error);
/* Fichier projeté en mémoire (bases de fin de partie .bd). */
typedef struct UstagammonMappedFile GMappedFile;
GMappedFile *g_mapped_file_new(const gchar *filename, gboolean writable, GError **error);
gchar *g_mapped_file_get_contents(GMappedFile *file);
gsize g_mapped_file_get_length(GMappedFile *file);
void g_mapped_file_unref(GMappedFile *file);
/* Analyseur XML simple (tables d'équité de match). */
typedef struct UstagammonMarkupParseContext GMarkupParseContext;
typedef void (*GDestroyNotify)(gpointer data);
typedef enum {
G_MARKUP_DO_NOT_USE_THIS_UNSUPPORTED_FLAG = 1 << 0,
G_MARKUP_TREAT_CDATA_AS_TEXT = 1 << 1,
G_MARKUP_PREFIX_ERROR_POSITION = 1 << 2,
G_MARKUP_IGNORE_QUALIFIED = 1 << 3
} GMarkupParseFlags;
typedef struct {
void (*start_element)(GMarkupParseContext *context, const gchar *element_name,
const gchar **attribute_names, const gchar **attribute_values,
gpointer user_data, GError **error);
void (*end_element)(GMarkupParseContext *context, const gchar *element_name,
gpointer user_data, GError **error);
void (*text)(GMarkupParseContext *context, const gchar *text, gsize text_len,
gpointer user_data, GError **error);
void (*passthrough)(GMarkupParseContext *context, const gchar *passthrough_text,
gsize text_len, gpointer user_data, GError **error);
void (*error)(GMarkupParseContext *context, GError *error, gpointer user_data);
} GMarkupParser;
GMarkupParseContext *g_markup_parse_context_new(const GMarkupParser *parser, GMarkupParseFlags flags,
gpointer user_data, GDestroyNotify user_data_dnotify);
gboolean g_markup_parse_context_parse(GMarkupParseContext *context, const gchar *text,
gssize text_len, GError **error);
void g_markup_parse_context_free(GMarkupParseContext *context);
#endif /* USTAGAMMON_GLIB_SHIM_H */
+20
View File
@@ -0,0 +1,20 @@
/*
* glib/gi18n.h -- shim minimal de GLib (voir ../glib.h).
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Pas de traduction dans le moteur : les messages de gnubg restent en
* anglais et ne sont destinés qu'aux journaux de diagnostic.
*/
#ifndef USTAGAMMON_GLIB_GI18N_H
#define USTAGAMMON_GLIB_GI18N_H
#include "../glib.h"
#define _(s) (s)
#define N_(s) (s)
#define gettext(s) (s)
#endif
+14
View File
@@ -0,0 +1,14 @@
/*
* glib/gprintf.h -- shim minimal de GLib (voir ../glib.h).
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*/
#ifndef USTAGAMMON_GLIB_GPRINTF_H
#define USTAGAMMON_GLIB_GPRINTF_H
#include <stdio.h>
#include "../glib.h"
#endif
+18
View File
@@ -0,0 +1,18 @@
/*
* glib/gstdio.h -- shim minimal de GLib (voir ../glib.h).
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*/
#ifndef USTAGAMMON_GLIB_GSTDIO_H
#define USTAGAMMON_GLIB_GSTDIO_H
#include <stdio.h>
#include <unistd.h>
#include "../glib.h"
#define g_fopen fopen
#define g_unlink unlink
#endif
+64
View File
@@ -0,0 +1,64 @@
/*
* gnubg-support.c -- définitions attendues par le cœur de gnubg.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Le cœur repris dans engine/gnubg/ appelle quelques fonctions et lit
* quelques variables définies ailleurs dans GNU Backgammon (gnubg.c, set.c,
* output.c, util.c, multithread.c). Ce fichier en fournit des versions
* minimales pour le moteur embarqué, compilé sans threads ni interface.
* Liste établie à l'édition des liens ; le fichier jni-bridge/src/stubs.c de
* CBG (GPLv3) a servi de point de repère, sans reprise de code.
*/
#include <stdarg.h>
#include <stdio.h>
#include "config.h"
#include "backgammon.h"
#include "multithread.h"
#include "util.h"
#include "gnubg-support.h"
/* Code de retour des tâches (multithread.c, branche sans threads). */
int asyncRet;
/* Dossier des données (gnubg.wd, bases .bd) : gnubg le tient en global. */
static char *pkg_data_dir;
void
gnubg_support_set_data_dir(const char *dir)
{
g_free(pkg_data_dir);
pkg_data_dir = g_strdup(dir);
}
char *
getPkgDataDir(void)
{
return pkg_data_dir ? pkg_data_dir : ".";
}
/* Sorties de diagnostic : toujours sur la sortie d'erreur. */
void
outputerrf(const char *sz, ...)
{
va_list args;
va_start(args, sz);
vfprintf(stderr, sz, args);
va_end(args);
}
void
print_utf8_to_locale(const gchar *sz)
{
fputs(sz, stderr);
}
void
PrintError(const char *message)
{
perror(message);
}
+14
View File
@@ -0,0 +1,14 @@
/*
* gnubg-support.h -- réglages du cœur de gnubg propres à Ustagammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*/
#ifndef USTAGAMMON_GNUBG_SUPPORT_H
#define USTAGAMMON_GNUBG_SUPPORT_H
/* Fixe le dossier où gnubg cherche gnubg.wd et les bases .bd. */
void gnubg_support_set_data_dir(const char *dir);
#endif
+40
View File
@@ -0,0 +1,40 @@
/*
* host-tools-support.c -- complément pour l'outil makebearoff de gnubg.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* makebearoff (compilé avec la vraie GLib, pour générer les bases .bd au
* build) appelle GetTemporaryFile, définie dans util.c de gnubg. Version
* minimale, même contrat : ouvre un fichier temporaire en lecture-écriture
* et rend son nom, à supprimer puis libérer par l'appelant.
*/
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <glib.h>
#include "util.h"
FILE *
GetTemporaryFile(const char *nameTemplate, char **retName)
{
FILE *pf;
int fd;
if (!retName)
return NULL;
fd = g_file_open_tmp(nameTemplate, retName, NULL);
if (fd < 0)
return NULL;
pf = fdopen(fd, "wb+");
if (!pf) {
close(fd);
unlink(*retName);
g_free(*retName);
*retName = NULL;
}
return pf;
}
+64
View File
@@ -0,0 +1,64 @@
/*
* multithread.h -- en-tête réduit remplaçant celui de GNU Backgammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Le moteur embarqué est compilé sans USE_MULTITHREAD (ADR 0005). Ne restent
* que les définitions de la branche monothread de l'original : les macros
* MT_* et les données locales au fil unique (td.tld).
*/
#ifndef MULTITHREAD_H
#define MULTITHREAD_H
#include "config.h"
#if defined(USE_MULTITHREAD)
#error "Le moteur embarqué d'Ustagammon est compilé sans threads"
#endif
#include "backgammon.h"
#define multi_debug(x)
typedef struct {
int id;
move *aMoves;
NNState *pnnState;
} ThreadLocalData;
typedef struct {
ThreadLocalData *tld;
} ThreadData;
extern ThreadData td;
/* Définies dans mtsupport.c (branche sans threads). */
extern void MT_InitThreads(void);
extern void MT_Close(void);
extern ThreadLocalData *MT_CreateThreadLocalData(int id);
#if !defined(MAX_NUMTHREADS)
#define MAX_NUMTHREADS 1
#endif
extern int asyncRet;
#define MT_Exclusive() {}
#define MT_Release() {}
#define MT_GetNumThreads() 1
#define MT_SetResultFailed() asyncRet = -1
#define MT_SafeInc(x) (++(*x))
#define MT_SafeIncValue(x) (++(*x))
#define MT_SafeIncCheck(x) ((*x)++)
#define MT_SafeAdd(x, y) ((*x) += y)
#define MT_SafeDec(x) (--(*x))
#define MT_SafeDecCheck(x) ((--(*x)) == 0)
#define MT_SafeGet(x) (*x)
#define MT_SafeSet(x, y) ((*x) = y)
#define MT_SafeCompare(x, y) ((*x) == y)
#define MT_GetThreadID() 0
#define MT_Get_nnState() td.tld->pnnState
#define MT_Get_aMoves() td.tld->aMoves
#define MT_GetTLD() td.tld
#endif
+21
View File
@@ -0,0 +1,21 @@
/*
* output.h -- en-tête réduit remplaçant celui de GNU Backgammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Seules les fonctions de sortie utilisées par le cœur sont déclarées, avec
* les signatures de l'original. Dans le moteur embarqué, elles écrivent sur
* la sortie d'erreur (voir engine/compat/gnubg-support.c).
*/
#ifndef OUTPUT_H
#define OUTPUT_H
#include <stdarg.h>
#include <glib.h>
extern void outputerrf(const char *sz, ...) __attribute__((format(printf, 1, 2)));
extern void print_utf8_to_locale(const gchar *sz);
#endif
+25
View File
@@ -0,0 +1,25 @@
/*
* util.h -- en-tête réduit remplaçant celui de GNU Backgammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* getPkgDataDir() renvoie le dossier des données du moteur, fixé à
* l'initialisation (engine_init). Définitions : gnubg-support.c.
*/
#ifndef UTIL_H
#define UTIL_H
#include <glib.h>
#include <stdio.h>
extern char *getPkgDataDir(void);
#define BuildFilename(file) g_build_filename(getPkgDataDir(), file, NULL)
#define BuildFilename2(file1, file2) g_build_filename(getPkgDataDir(), file1, file2, NULL)
extern void PrintError(const char *message);
extern FILE *GetTemporaryFile(const char *nameTemplate, char **retName);
#endif
+765
View File
@@ -0,0 +1,765 @@
<?xml version = "1.0"?>
<!DOCTYPE met PUBLIC "-//GNU Backgammon//DTD Match Equity Tables//EN"
"met.dtd">
<!--
Copyright (C) 2011 Neil Kazaross
Table rolled up to 9 point match by eXtreme Gammon. Then uses
R/K MET which was rolled up to 15 and extrapolated to 25 points
Transcribed for use by GNUbg by Michael Petch <mpetch@capp-sysware.com>
This file is distributed as a part of the GNU Backgammon program.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without any warranty.
-->
<met>
<info>
<name>Kazaross XG2 25 point MET</name>
<description>Generated using XG rollouts to 9pts, GNUbg 0.90 2-ply full rollouts to 15 points. Extended to 25pts by projecting take points.</description>
<length>25</length>
</info>
<pre-crawford-table type="explicit">
<row>
<me>0.50000</me>
<me>0.67736</me>
<me>0.75076</me>
<me>0.81436</me>
<me>0.84179</me>
<me>0.88731</me>
<me>0.90724</me>
<me>0.93250</me>
<me>0.94402</me>
<me>0.959275</me>
<me>0.966442</me>
<me>0.975534</me>
<me>0.979845</me>
<me>0.985273</me>
<me>0.987893</me>
<me>0.99114</me>
<me>0.99273</me>
<me>0.99467</me>
<me>0.99563</me>
<me>0.99679</me>
<me>0.99737</me>
<me>0.99807</me>
<me>0.99842</me>
<me>0.99884</me>
<me>0.99905</me>
</row>
<row>
<me>0.32264</me>
<me>0.50000</me>
<me>0.59947</me>
<me>0.66870</me>
<me>0.74359</me>
<me>0.79940</me>
<me>0.84225</me>
<me>0.87539</me>
<me>0.90197</me>
<me>0.923034</me>
<me>0.939311</me>
<me>0.952470</me>
<me>0.962495</me>
<me>0.970701</me>
<me>0.976887</me>
<me>0.98196</me>
<me>0.98580</me>
<me>0.98893</me>
<me>0.99129</me>
<me>0.99322</me>
<me>0.99466</me>
<me>0.99585</me>
<me>0.99675</me>
<me>0.99746</me>
<me>0.99802</me>
</row>
<row>
<me>0.24924</me>
<me>0.40053</me>
<me>0.50000</me>
<me>0.57150</me>
<me>0.64795</me>
<me>0.71123</me>
<me>0.76209</me>
<me>0.80468</me>
<me>0.84017</me>
<me>0.870638</me>
<me>0.894417</me>
<me>0.914831</me>
<me>0.930702</me>
<me>0.944426</me>
<me>0.954931</me>
<me>0.96399</me>
<me>0.97093</me>
<me>0.97687</me>
<me>0.98139</me>
<me>0.98522</me>
<me>0.98814</me>
<me>0.99062</me>
<me>0.99248</me>
<me>0.99407</me>
<me>0.99527</me>
</row>
<row>
<me>0.18564</me>
<me>0.33130</me>
<me>0.42850</me>
<me>0.50000</me>
<me>0.57732</me>
<me>0.64285</me>
<me>0.69924</me>
<me>0.74577</me>
<me>0.78799</me>
<me>0.824059</me>
<me>0.853955</me>
<me>0.879141</me>
<me>0.900233</me>
<me>0.918040</me>
<me>0.932657</me>
<me>0.94495</me>
<me>0.95499</me>
<me>0.96341</me>
<me>0.97021</me>
<me>0.97589</me>
<me>0.98044</me>
<me>0.98422</me>
<me>0.98726</me>
<me>0.98975</me>
<me>0.99174</me>
</row>
<row>
<me>0.15821</me>
<me>0.25641</me>
<me>0.35205</me>
<me>0.42268</me>
<me>0.50000</me>
<me>0.56635</me>
<me>0.62638</me>
<me>0.67786</me>
<me>0.72540</me>
<me>0.767055</me>
<me>0.802732</me>
<me>0.833654</me>
<me>0.859934</me>
<me>0.882866</me>
<me>0.902013</me>
<me>0.91847</me>
<me>0.93223</me>
<me>0.94397</me>
<me>0.95367</me>
<me>0.96189</me>
<me>0.96864</me>
<me>0.97432</me>
<me>0.97896</me>
<me>0.98283</me>
<me>0.98600</me>
</row>
<row>
<me>0.11269</me>
<me>0.20060</me>
<me>0.28877</me>
<me>0.35715</me>
<me>0.43365</me>
<me>0.50000</me>
<me>0.56261</me>
<me>0.61636</me>
<me>0.66787</me>
<me>0.713057</me>
<me>0.753427</me>
<me>0.788634</me>
<me>0.819569</me>
<me>0.846648</me>
<me>0.869999</me>
<me>0.89021</me>
<me>0.90756</me>
<me>0.92246</me>
<me>0.93508</me>
<me>0.94583</me>
<me>0.95488</me>
<me>0.96254</me>
<me>0.96894</me>
<me>0.97432</me>
<me>0.97879</me>
</row>
<row>
<me>0.09276</me>
<me>0.15775</me>
<me>0.23791</me>
<me>0.30076</me>
<me>0.37362</me>
<me>0.43739</me>
<me>0.50000</me>
<me>0.55480</me>
<me>0.60854</me>
<me>0.656283</me>
<me>0.700209</me>
<me>0.739054</me>
<me>0.774121</me>
<me>0.805203</me>
<me>0.832566</me>
<me>0.85659</me>
<me>0.87761</me>
<me>0.89591</me>
<me>0.91171</me>
<me>0.92535</me>
<me>0.93702</me>
<me>0.94703</me>
<me>0.95553</me>
<me>0.96276</me>
<me>0.96887</me>
</row>
<row>
<me>0.06750</me>
<me>0.12461</me>
<me>0.19532</me>
<me>0.25423</me>
<me>0.32214</me>
<me>0.38364</me>
<me>0.44520</me>
<me>0.50000</me>
<me>0.55442</me>
<me>0.603718</me>
<me>0.649899</me>
<me>0.691356</me>
<me>0.729447</me>
<me>0.763593</me>
<me>0.794397</me>
<me>0.82158</me>
<me>0.84578</me>
<me>0.86714</me>
<me>0.88589</me>
<me>0.90230</me>
<me>0.91658</me>
<me>0.92898</me>
<me>0.93968</me>
<me>0.94891</me>
<me>0.95682</me>
</row>
<row>
<me>0.05598</me>
<me>0.09803</me>
<me>0.15983</me>
<me>0.21201</me>
<me>0.27460</me>
<me>0.33213</me>
<me>0.39146</me>
<me>0.44558</me>
<me>0.50000</me>
<me>0.550196</me>
<me>0.597926</me>
<me>0.641481</me>
<me>0.682119</me>
<me>0.718927</me>
<me>0.752814</me>
<me>0.78301</me>
<me>0.81037</me>
<me>0.83483</me>
<me>0.85662</me>
<me>0.87591</me>
<me>0.89294</me>
<me>0.90791</me>
<me>0.92098</me>
<me>0.93240</me>
<me>0.94230</me>
</row>
<row>
<me>0.040725</me>
<me>0.076966</me>
<me>0.129362</me>
<me>0.175941</me>
<me>0.232945</me>
<me>0.286943</me>
<me>0.343717</me>
<me>0.396282</me>
<me>0.449804</me>
<me>0.500000</me>
<me>0.548547</me>
<me>0.593459</me>
<me>0.635880</me>
<me>0.674830</me>
<me>0.711113</me>
<me>0.74371</me>
<me>0.77375</me>
<me>0.80093</me>
<me>0.82543</me>
<me>0.84741</me>
<me>0.86703</me>
<me>0.88448</me>
<me>0.89991</me>
<me>0.91353</me>
<me>0.92550</me>
</row>
<row>
<me>0.033558</me>
<me>0.060689</me>
<me>0.105583</me>
<me>0.146045</me>
<me>0.197268</me>
<me>0.246573</me>
<me>0.299791</me>
<me>0.350101</me>
<me>0.402074</me>
<me>0.451453</me>
<me>0.500000</me>
<me>0.545552</me>
<me>0.589242</me>
<me>0.629736</me>
<me>0.667927</me>
<me>0.70303</me>
<me>0.73530</me>
<me>0.76494</me>
<me>0.79198</me>
<me>0.81648</me>
<me>0.83862</me>
<me>0.85849</me>
<me>0.87629</me>
<me>0.89214</me>
<me>0.90622</me>
</row>
<row>
<me>0.024466</me>
<me>0.047530</me>
<me>0.085169</me>
<me>0.120859</me>
<me>0.166346</me>
<me>0.211366</me>
<me>0.260946</me>
<me>0.308644</me>
<me>0.358519</me>
<me>0.406541</me>
<me>0.454448</me>
<me>0.500000</me>
<me>0.544068</me>
<me>0.585701</me>
<me>0.625259</me>
<me>0.66178</me>
<me>0.69610</me>
<me>0.72778</me>
<me>0.75703</me>
<me>0.78381</me>
<me>0.80826</me>
<me>0.83044</me>
<me>0.85051</me>
<me>0.86856</me>
<me>0.88476</me>
</row>
<row>
<me>0.020155</me>
<me>0.037505</me>
<me>0.069298</me>
<me>0.099767</me>
<me>0.140066</me>
<me>0.180431</me>
<me>0.225879</me>
<me>0.270553</me>
<me>0.317881</me>
<me>0.364120</me>
<me>0.410758</me>
<me>0.455932</me>
<me>0.500000</me>
<me>0.541943</me>
<me>0.582545</me>
<me>0.62036</me>
<me>0.65619</me>
<me>0.68966</me>
<me>0.72081</me>
<me>0.74963</me>
<me>0.77619</me>
<me>0.80054</me>
<me>0.82276</me>
<me>0.84295</me>
<me>0.86123</me>
</row>
<row>
<me>0.014727</me>
<me>0.029299</me>
<me>0.055574</me>
<me>0.081960</me>
<me>0.117134</me>
<me>0.153352</me>
<me>0.194797</me>
<me>0.236407</me>
<me>0.281073</me>
<me>0.325170</me>
<me>0.370264</me>
<me>0.414299</me>
<me>0.458057</me>
<me>0.500000</me>
<me>0.540750</me>
<me>0.57942</me>
<me>0.61634</me>
<me>0.65117</me>
<me>0.68391</me>
<me>0.71448</me>
<me>0.74290</me>
<me>0.76917</me>
<me>0.79339</me>
<me>0.81559</me>
<me>0.83586</me>
</row>
<row>
<me>0.012107</me>
<me>0.023113</me>
<me>0.045069</me>
<me>0.067343</me>
<me>0.097987</me>
<me>0.130001</me>
<me>0.167434</me>
<me>0.205603</me>
<me>0.247186</me>
<me>0.288887</me>
<me>0.332073</me>
<me>0.374741</me>
<me>0.417455</me>
<me>0.459250</me>
<me>0.500000</me>
<me>0.53916</me>
<me>0.57679</me>
<me>0.61261</me>
<me>0.64659</me>
<me>0.67859</me>
<me>0.70862</me>
<me>0.73664</me>
<me>0.76265</me>
<me>0.78669</me>
<me>0.80883</me>
</row>
<row>
<me>0.00886</me>
<me>0.01804</me>
<me>0.03601</me>
<me>0.05505</me>
<me>0.08153</me>
<me>0.10979</me>
<me>0.14341</me>
<me>0.17842</me>
<me>0.21699</me>
<me>0.25629</me>
<me>0.29697</me>
<me>0.33822</me>
<me>0.37964</me>
<me>0.42058</me>
<me>0.46084</me>
<me>0.50000</me>
<me>0.53796</me>
<me>0.57441</me>
<me>0.60929</me>
<me>0.64241</me>
<me>0.67376</me>
<me>0.70323</me>
<me>0.73084</me>
<me>0.75657</me>
<me>0.78046</me>
</row>
<row>
<me>0.00727</me>
<me>0.01420</me>
<me>0.02907</me>
<me>0.04501</me>
<me>0.06777</me>
<me>0.09244</me>
<me>0.12239</me>
<me>0.15422</me>
<me>0.18963</me>
<me>0.22625</me>
<me>0.26470</me>
<me>0.30390</me>
<me>0.34381</me>
<me>0.38366</me>
<me>0.42321</me>
<me>0.46204</me>
<me>0.50000</me>
<me>0.53676</me>
<me>0.57222</me>
<me>0.60618</me>
<me>0.63856</me>
<me>0.66925</me>
<me>0.69822</me>
<me>0.72542</me>
<me>0.75087</me>
</row>
<row>
<me>0.00533</me>
<me>0.01107</me>
<me>0.02313</me>
<me>0.03659</me>
<me>0.05603</me>
<me>0.07754</me>
<me>0.10409</me>
<me>0.13286</me>
<me>0.16517</me>
<me>0.19907</me>
<me>0.23506</me>
<me>0.27222</me>
<me>0.31034</me>
<me>0.34883</me>
<me>0.38739</me>
<me>0.42559</me>
<me>0.46324</me>
<me>0.50000</me>
<me>0.53574</me>
<me>0.57023</me>
<me>0.60336</me>
<me>0.63501</me>
<me>0.66510</me>
<me>0.69356</me>
<me>0.72038</me>
</row>
<row>
<me>0.00437</me>
<me>0.00871</me>
<me>0.01861</me>
<me>0.02979</me>
<me>0.04633</me>
<me>0.06492</me>
<me>0.08829</me>
<me>0.11411</me>
<me>0.14338</me>
<me>0.17457</me>
<me>0.20802</me>
<me>0.24297</me>
<me>0.27919</me>
<me>0.31609</me>
<me>0.35341</me>
<me>0.39071</me>
<me>0.42778</me>
<me>0.46426</me>
<me>0.50000</me>
<me>0.53475</me>
<me>0.56838</me>
<me>0.60073</me>
<me>0.63171</me>
<me>0.66122</me>
<me>0.68921</me>
</row>
<row>
<me>0.00321</me>
<me>0.00678</me>
<me>0.01478</me>
<me>0.02411</me>
<me>0.03811</me>
<me>0.05417</me>
<me>0.07465</me>
<me>0.09770</me>
<me>0.12409</me>
<me>0.15259</me>
<me>0.18352</me>
<me>0.21619</me>
<me>0.25037</me>
<me>0.28552</me>
<me>0.32141</me>
<me>0.35759</me>
<me>0.39382</me>
<me>0.42977</me>
<me>0.46525</me>
<me>0.50000</me>
<me>0.53387</me>
<me>0.56667</me>
<me>0.59830</me>
<me>0.62864</me>
<me>0.65760</me>
</row>
<row>
<me>0.00263</me>
<me>0.00534</me>
<me>0.01186</me>
<me>0.01956</me>
<me>0.03136</me>
<me>0.04512</me>
<me>0.06298</me>
<me>0.08342</me>
<me>0.10706</me>
<me>0.13297</me>
<me>0.16138</me>
<me>0.19174</me>
<me>0.22381</me>
<me>0.25710</me>
<me>0.29138</me>
<me>0.32624</me>
<me>0.36144</me>
<me>0.39664</me>
<me>0.43162</me>
<me>0.46613</me>
<me>0.50000</me>
<me>0.53303</me>
<me>0.56508</me>
<me>0.59603</me>
<me>0.62576</me>
</row>
<row>
<me>0.00193</me>
<me>0.00415</me>
<me>0.00938</me>
<me>0.01578</me>
<me>0.02568</me>
<me>0.03746</me>
<me>0.05297</me>
<me>0.07102</me>
<me>0.09209</me>
<me>0.11552</me>
<me>0.14151</me>
<me>0.16956</me>
<me>0.19946</me>
<me>0.23083</me>
<me>0.26336</me>
<me>0.29677</me>
<me>0.33075</me>
<me>0.36499</me>
<me>0.39927</me>
<me>0.43333</me>
<me>0.46697</me>
<me>0.50000</me>
<me>0.53226</me>
<me>0.56360</me>
<me>0.59391</me>
</row>
<row>
<me>0.00158</me>
<me>0.00325</me>
<me>0.00752</me>
<me>0.01274</me>
<me>0.02104</me>
<me>0.03106</me>
<me>0.04447</me>
<me>0.06032</me>
<me>0.07902</me>
<me>0.10009</me>
<me>0.12371</me>
<me>0.14949</me>
<me>0.17724</me>
<me>0.20661</me>
<me>0.23735</me>
<me>0.26916</me>
<me>0.30178</me>
<me>0.33490</me>
<me>0.36829</me>
<me>0.40170</me>
<me>0.43492</me>
<me>0.46774</me>
<me>0.50000</me>
<me>0.53153</me>
<me>0.56221</me>
</row>
<row>
<me>0.00116</me>
<me>0.00254</me>
<me>0.00593</me>
<me>0.01025</me>
<me>0.01717</me>
<me>0.02568</me>
<me>0.03724</me>
<me>0.05109</me>
<me>0.06760</me>
<me>0.08647</me>
<me>0.10786</me>
<me>0.13144</me>
<me>0.15705</me>
<me>0.18441</me>
<me>0.21331</me>
<me>0.24343</me>
<me>0.27458</me>
<me>0.30644</me>
<me>0.33878</me>
<me>0.37136</me>
<me>0.40397</me>
<me>0.43640</me>
<me>0.46847</me>
<me>0.50000</me>
<me>0.53086</me>
</row>
<row>
<me>0.00095</me>
<me>0.00198</me>
<me>0.00473</me>
<me>0.00826</me>
<me>0.01400</me>
<me>0.02121</me>
<me>0.03113</me>
<me>0.04318</me>
<me>0.05770</me>
<me>0.07450</me>
<me>0.09378</me>
<me>0.11524</me>
<me>0.13877</me>
<me>0.16414</me>
<me>0.19117</me>
<me>0.21954</me>
<me>0.24913</me>
<me>0.27962</me>
<me>0.31079</me>
<me>0.34240</me>
<me>0.37424</me>
<me>0.40609</me>
<me>0.43779</me>
<me>0.46914</me>
<me>0.50000</me>
</row>
</pre-crawford-table>
<post-crawford-table type="explicit" player="both">
<row>
<me>0.500000</me>
<me>0.48803</me>
<me>0.32264</me>
<me>0.31002</me>
<me>0.19012</me>
<me>0.18072</me>
<me>0.11559</me>
<me>0.10906</me>
<me>0.06953</me>
<me>0.065161</me>
<me>0.042069</me>
<me>0.039060</me>
<me>0.025371</me>
<me>0.023428</me>
<me>0.015304</me>
<me>0.014050</me>
<me>0.009240</me>
<me>0.008420</me>
<me>0.005560</me>
<me>0.005050</me>
<me>0.003360</me>
<me>0.003030</me>
<me>0.002030</me>
<me>0.001820</me>
<me>0.001230</me>
</row>
</post-crawford-table>
</met>
File diff suppressed because it is too large Load Diff
+371
View File
@@ -0,0 +1,371 @@
/*
* engine.c -- façade du moteur d'Ustagammon sur le cœur de GNU Backgammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Chaque fonction décode les identifiants de position, prépare le contexte
* de gnubg (cubeinfo, réglages d'évaluation) comme le fait gnubg de bureau
* pour ses commandes « hint » et « eval », puis appelle gnubg.
*/
#include <stdio.h>
#include <string.h>
#include "config.h"
#include "backgammon.h"
#include "drawboard.h"
#include "matchequity.h"
#include "matchid.h"
#include "multithread.h"
#include "positionid.h"
#include "gnubg-support.h"
#include "engine.h"
/* Nombre de castors autorisés en partie libre : valeur par défaut de gnubg. */
#define DEFAULT_BEAVERS 3
/* Fichier de table d'équité de match : celui de gnubg de bureau par défaut. */
#define MET_FILE "Kazaross-XG2.xml"
/* gnubg ne sait pas se réinitialiser après EvalShutdown : on retient l'état. */
static enum { NOT_STARTED, READY, STOPPED } engine_state = NOT_STARTED;
/* Une position décodée, prête pour gnubg. */
typedef struct {
TanBoard board;
cubeinfo cube;
engine_match match;
} position;
/* -------------------------------------------------------- Initialisation */
/* Vérifie l'en-tête de gnubg.wd, pour éviter l'exit() de EvalInitialise. */
static int
binary_weights_ok(const char *path)
{
float header[2];
FILE *f = fopen(path, "rb");
int ok;
if (!f)
return 0;
ok = fread(header, sizeof header[0], 2, f) == 2
&& header[0] == WEIGHTS_MAGIC_BINARY && header[1] == WEIGHTS_VERSION_BINARY;
fclose(f);
return ok;
}
static engine_status
load_match_equity_table(const char *data_dir)
{
char *path = g_build_filename(data_dir, MET_FILE, NULL);
int ok;
ok = g_file_test(path, G_FILE_TEST_IS_REGULAR);
if (ok) {
InitMatchEquity(path);
/* En cas d'échec, gnubg se rabat sans erreur sur la table de Zadeh. */
ok = miCurrent.szFileName && strcmp(miCurrent.szFileName, path) == 0;
}
g_free(path);
return ok ? ENGINE_OK : ENGINE_ERROR_DATA;
}
static engine_status
load_neural_nets_and_bearoff(const char *data_dir)
{
char *weights = g_build_filename(data_dir, "gnubg.wd", NULL);
int ok = binary_weights_ok(weights);
if (ok) {
EvalInitialise(NULL, weights, FALSE, NULL);
/* Bases de fin de partie : gnubg continue sans elles, pas nous. */
ok = pbc1 && !pbc1->fHeuristic && pbc2;
}
g_free(weights);
return ok ? ENGINE_OK : ENGINE_ERROR_DATA;
}
engine_status
engine_init(const char *data_dir)
{
engine_status status;
if (engine_state == READY)
return ENGINE_OK;
if (engine_state == STOPPED || !data_dir)
return ENGINE_ERROR_NOT_INITIALISED;
gnubg_support_set_data_dir(data_dir);
status = load_match_equity_table(data_dir);
if (status == ENGINE_OK)
status = load_neural_nets_and_bearoff(data_dir);
if (status != ENGINE_OK)
return status;
MT_InitThreads();
engine_state = READY;
return ENGINE_OK;
}
void
engine_shutdown(void)
{
if (engine_state != READY)
return;
MT_Close();
td.tld = NULL;
EvalShutdown();
engine_state = STOPPED;
}
const char *
engine_status_message(engine_status status)
{
switch (status) {
case ENGINE_OK:
return "succès";
case ENGINE_ERROR_NOT_INITIALISED:
return "moteur non initialisé";
case ENGINE_ERROR_DATA:
return "données du moteur absentes ou invalides";
case ENGINE_ERROR_POSITION_ID:
return "Position ID invalide";
case ENGINE_ERROR_MATCH_ID:
return "Match ID invalide";
case ENGINE_ERROR_DICE:
return "dés invalides (chacun entre 1 et 6)";
case ENGINE_ERROR_PLY:
return "niveau invalide";
case ENGINE_ERROR_TOO_MANY_MOVES:
return "tableau de coups trop petit";
case ENGINE_ERROR_INTERNAL:
return "erreur interne de gnubg";
}
return "erreur inconnue";
}
/* ------------------------------------------------------------- Décodage */
/* Vrai si id a exactement length caractères de l'alphabet base64 de gnubg. */
static int
is_base64_id(const char *id, size_t length)
{
size_t i;
if (!id || strlen(id) != length)
return 0;
for (i = 0; i < length; i++)
if (Base64((unsigned char) id[i]) == 255)
return 0;
return 1;
}
engine_status
engine_decode_match(const char *match_id, engine_match *match)
{
unsigned int dice[2];
int turn, resigned, doubled;
gamestate state;
if (!is_base64_id(match_id, L_MATCHID))
return ENGINE_ERROR_MATCH_ID;
if (MatchFromID(dice, &turn, &resigned, &doubled, &match->player_on_roll,
&match->cube_owner, &match->crawford, &match->match_length,
match->score, &match->cube_value, &match->jacoby, &state, match_id) != 0)
return ENGINE_ERROR_MATCH_ID;
return ENGINE_OK;
}
/* Décode Position ID et Match ID, et prépare le cubeinfo de gnubg. */
static engine_status
decode_position(const char *position_id, const char *match_id, position *pos)
{
engine_match *m = &pos->match;
engine_status status;
if (engine_state != READY)
return ENGINE_ERROR_NOT_INITIALISED;
if (!is_base64_id(position_id, L_POSITIONID) || !PositionFromID(pos->board, position_id))
return ENGINE_ERROR_POSITION_ID;
status = engine_decode_match(match_id, m);
if (status != ENGINE_OK)
return status;
if (SetCubeInfo(&pos->cube, m->cube_value, m->cube_owner, m->player_on_roll,
m->match_length, m->score, m->crawford, m->jacoby,
DEFAULT_BEAVERS, VARIATION_STANDARD) != 0)
return ENGINE_ERROR_MATCH_ID;
return ENGINE_OK;
}
static engine_status
check_dice(int die1, int die2)
{
if (die1 < 1 || die1 > 6 || die2 < 1 || die2 > 6)
return ENGINE_ERROR_DICE;
return ENGINE_OK;
}
/* Réglages prédéfinis de gnubg pour un niveau, sans bruit. */
static engine_status
eval_context_for_ply(int ply, evalcontext *context)
{
if (ply < 0 || ply > ENGINE_MAX_PLY)
return ENGINE_ERROR_PLY;
*context = aecSettings[SETTINGS_0PLY + ply];
return ENGINE_OK;
}
/* En match, gnubg calcule en chances de gain du match : on convertit. */
static float
normalized_equity(float score, const cubeinfo *cube)
{
return cube->nMatchTo ? mwc2eq(score, cube) : score;
}
/* ------------------------------------------------------------------ Coups */
static void
fill_move(engine_move *out, const move *m, const position *pos, int evaluated)
{
int i;
FormatMove(out->notation, (ConstTanBoard) pos->board, m->anMove);
snprintf(out->position_id, sizeof out->position_id, "%s", PositionIDFromKey(&m->key));
out->ply = evaluated ? (int) m->esMove.ec.nPlies : -1;
for (i = 0; i < ENGINE_NUM_PROBABILITIES; i++)
out->probabilities[i] = evaluated ? m->arEvalMove[i] : 0.0f;
out->equity = evaluated ? normalized_equity(m->rScore, &pos->cube) : 0.0f;
}
engine_status
engine_legal_moves(const char *position_id, const char *match_id, int die1, int die2,
engine_move *moves, int max_moves, int *count)
{
position pos;
movelist list;
engine_status status;
unsigned int i;
status = decode_position(position_id, match_id, &pos);
if (status == ENGINE_OK)
status = check_dice(die1, die2);
if (status != ENGINE_OK)
return status;
/* La liste pointe vers un tableau interne de gnubg : rien à libérer. */
GenerateMoves(&list, (ConstTanBoard) pos.board, die1, die2, FALSE);
if (list.cMoves > (unsigned int) max_moves)
return ENGINE_ERROR_TOO_MANY_MOVES;
for (i = 0; i < list.cMoves; i++)
fill_move(&moves[i], &list.amMoves[i], &pos, 0);
*count = (int) list.cMoves;
return ENGINE_OK;
}
engine_status
engine_best_moves(const char *position_id, const char *match_id, int die1, int die2, int ply,
engine_move *moves, int max_moves, int *count)
{
position pos;
movelist list;
evalcontext context;
engine_status status;
unsigned int i;
status = decode_position(position_id, match_id, &pos);
if (status == ENGINE_OK)
status = check_dice(die1, die2);
if (status == ENGINE_OK)
status = eval_context_for_ply(ply, &context);
if (status != ENGINE_OK)
return status;
/* Comme la commande « hint » de gnubg, avec les filtres « normaux ». */
if (FindnSaveBestMoves(&list, die1, die2, (ConstTanBoard) pos.board, NULL, FALSE, 0.0f,
&pos.cube, &context, aaamfMoveFilterSettings[0]) < 0)
return ENGINE_ERROR_INTERNAL;
status = ENGINE_OK;
if (list.cMoves > (unsigned int) max_moves)
status = ENGINE_ERROR_TOO_MANY_MOVES;
else {
for (i = 0; i < list.cMoves; i++)
fill_move(&moves[i], &list.amMoves[i], &pos, 1);
*count = (int) list.cMoves;
}
g_free(list.amMoves);
return status;
}
/* ------------------------------------------------- Évaluation et videau */
engine_status
engine_evaluate(const char *position_id, const char *match_id, int ply,
engine_evaluation *evaluation)
{
position pos;
evalcontext context;
float output[NUM_ROLLOUT_OUTPUTS];
engine_status status;
int i;
status = decode_position(position_id, match_id, &pos);
if (status == ENGINE_OK)
status = eval_context_for_ply(ply, &context);
if (status != ENGINE_OK)
return status;
if (GeneralEvaluationE(output, (ConstTanBoard) pos.board, &pos.cube, &context) < 0)
return ENGINE_ERROR_INTERNAL;
for (i = 0; i < ENGINE_NUM_PROBABILITIES; i++)
evaluation->probabilities[i] = output[i];
evaluation->cubeless_equity = output[OUTPUT_EQUITY];
evaluation->cubeful_equity = normalized_equity(output[OUTPUT_CUBEFUL_EQUITY], &pos.cube);
return ENGINE_OK;
}
/* Noms anglais des décisions, dans l'ordre de l'énumération cubedecision. */
static const char *const cube_decision_names[] = {
"double_take", "double_pass", "no_double_take", "too_good_take", "too_good_pass",
"double_beaver", "no_double_beaver", "redouble_take", "redouble_pass",
"no_redouble_take", "too_good_redouble_take", "too_good_redouble_pass",
"no_redouble_beaver", "no_double_dead_cube", "no_redouble_dead_cube",
"not_available", "optional_double_take", "optional_redouble_take",
"optional_double_beaver", "optional_double_pass", "optional_redouble_pass"
};
engine_status
engine_cube_decision(const char *position_id, const char *match_id, int ply, engine_cube *cube)
{
position pos;
evalcontext context;
float output[2][NUM_ROLLOUT_OUTPUTS];
float equities[4];
cubedecision decision;
engine_status status;
int i;
status = decode_position(position_id, match_id, &pos);
if (status == ENGINE_OK)
status = eval_context_for_ply(ply, &context);
if (status != ENGINE_OK)
return status;
if (GeneralCubeDecisionE(output, (ConstTanBoard) pos.board, &pos.cube, &context, NULL) < 0)
return ENGINE_ERROR_INTERNAL;
/* FindCubeDecision renvoie des équités déjà normalisées. */
decision = FindCubeDecision(equities, output, &pos.cube);
if ((size_t) decision >= sizeof cube_decision_names / sizeof cube_decision_names[0])
return ENGINE_ERROR_INTERNAL;
for (i = 0; i < ENGINE_NUM_PROBABILITIES; i++)
cube->probabilities[i] = output[0][i];
cube->no_double = equities[OUTPUT_NODOUBLE];
cube->double_take = equities[OUTPUT_TAKE];
cube->double_pass = equities[OUTPUT_DROP];
cube->decision = cube_decision_names[decision];
return ENGINE_OK;
}
+124
View File
@@ -0,0 +1,124 @@
# engine.mk -- compilation du moteur embarqué, de ses données et d'engine-cli.
#
# Lancé dans l'image Docker ustagammon/build-c par `make engine` (Makefile
# racine), depuis la racine du dépôt. Tout est produit dans build/ :
#
# build/libengine.a le moteur : cœur de gnubg + shim GLib + engine.c
# build/engine-cli le programme en ligne de commande
# build/data/ les données lues par le moteur à l'exécution
# build/host/ les outils officiels de gnubg qui génèrent les
# données (makeweights, makebearoff), avec la vraie GLib
#
# Les dépendances vers les en-têtes sont suivies (-MMD) : un second appel ne
# recompile rien si aucune source n'a changé.
BUILD := build
GNUBG := engine/gnubg
COMPAT := engine/compat
CC := gcc
AR := ar
# Options du build officiel de gnubg (configure.ac) : -O3 -ffast-math et
# assertions GLib désactivées. Elles s'appliquent à tout le code, pour que
# le moteur calcule comme gnubg de bureau.
OPTIMISATION := -O3 -ffast-math -DG_DISABLE_ASSERT
# Notre propre C : C11 strict, aucun avertissement toléré.
OUR_WARNINGS := -std=c11 -Wall -Wextra -Werror
# Code de gnubg : compilé tel quel, avec ses propres réglages d'avertissement.
GNUBG_WARNINGS :=
ENGINE_INCLUDES := -I$(COMPAT) -I$(COMPAT)/glib-shim -I$(GNUBG) -I$(GNUBG)/lib -Iengine/include
HOST_INCLUDES := -I$(COMPAT) -I$(GNUBG) -I$(GNUBG)/lib $(shell pkg-config --cflags glib-2.0)
HOST_DEFINES := -DLOCALEDIR=\"/usr/share/locale\"
HOST_LIBS := $(shell pkg-config --libs glib-2.0) -lm
# Fichiers de gnubg compilés dans le moteur (liste : engine/gnubg/PROVENANCE.md).
GNUBG_CORE := eval.c positionid.c matchid.c matchequity.c mec.c bearoff.c \
bearoffgammon.c mtsupport.c lib/neuralnet.c lib/neuralnetsse.c lib/inputs.c \
lib/cache.c lib/list.c lib/isaac.c lib/md5.c
GNUBG_ENGINE := $(GNUBG_CORE) drawboard.c
ENGINE_OBJECTS := $(GNUBG_ENGINE:%.c=$(BUILD)/obj/gnubg/%.o) \
$(BUILD)/obj/compat/glib-shim.o $(BUILD)/obj/compat/gnubg-support.o \
$(BUILD)/obj/engine.o
CLI_OBJECTS := $(BUILD)/obj/engine-cli/main.o
HOST_CORE_OBJECTS := $(GNUBG_CORE:%.c=$(BUILD)/host/gnubg/%.o) $(BUILD)/host/compat/gnubg-support.o
DATA := $(BUILD)/data/gnubg.wd $(BUILD)/data/gnubg_os0.bd $(BUILD)/data/gnubg_ts0.bd \
$(BUILD)/data/Kazaross-XG2.xml
.PHONY: all
all: $(BUILD)/engine-cli $(DATA)
# ------------------------------------------------------------------ Moteur
$(BUILD)/obj/gnubg/%.o: $(GNUBG)/%.c
@mkdir -p $(@D)
$(CC) $(OPTIMISATION) $(GNUBG_WARNINGS) $(ENGINE_INCLUDES) -MMD -MP -c $< -o $@
$(BUILD)/obj/compat/%.o: $(COMPAT)/%.c
@mkdir -p $(@D)
$(CC) $(OPTIMISATION) $(OUR_WARNINGS) $(ENGINE_INCLUDES) -MMD -MP -c $< -o $@
$(BUILD)/obj/engine.o: engine/engine.c
@mkdir -p $(@D)
$(CC) $(OPTIMISATION) $(OUR_WARNINGS) $(ENGINE_INCLUDES) -MMD -MP -c $< -o $@
$(BUILD)/obj/engine-cli/%.o: engine-cli/%.c
@mkdir -p $(@D)
$(CC) $(OPTIMISATION) $(OUR_WARNINGS) -Iengine/include -MMD -MP -c $< -o $@
$(BUILD)/libengine.a: $(ENGINE_OBJECTS)
rm -f $@
$(AR) rcs $@ $^
$(BUILD)/engine-cli: $(CLI_OBJECTS) $(BUILD)/libengine.a
$(CC) $^ -lm -o $@
# ----------------------------------------- Outils de génération des données
$(BUILD)/host/gnubg/%.o: $(GNUBG)/%.c
@mkdir -p $(@D)
$(CC) $(OPTIMISATION) $(GNUBG_WARNINGS) $(HOST_INCLUDES) $(HOST_DEFINES) -MMD -MP -c $< -o $@
$(BUILD)/host/compat/%.o: $(COMPAT)/%.c
@mkdir -p $(@D)
$(CC) $(OPTIMISATION) $(OUR_WARNINGS) $(HOST_INCLUDES) -MMD -MP -c $< -o $@
$(BUILD)/host/makeweights: $(BUILD)/host/gnubg/makeweights.o $(BUILD)/host/gnubg/lib/neuralnet.o \
$(BUILD)/host/gnubg/lib/neuralnetsse.o $(BUILD)/host/compat/gnubg-support.o
$(CC) $^ $(HOST_LIBS) -o $@
$(BUILD)/host/makebearoff: $(BUILD)/host/gnubg/makebearoff.o $(HOST_CORE_OBJECTS) \
$(BUILD)/host/compat/host-tools-support.o
$(CC) $^ $(HOST_LIBS) -o $@
# ------------------------------------------------------------------ Données
# Mêmes commandes que le Makefile.am de gnubg. Écriture dans un fichier
# temporaire, pour ne jamais laisser une base à moitié écrite.
$(BUILD)/data/gnubg.wd: engine/data/gnubg.weights $(BUILD)/host/makeweights
@mkdir -p $(@D)
$(BUILD)/host/makeweights -f $@.tmp $<
mv $@.tmp $@
# Comme dans gnubg, les bases ne sont générées que si elles manquent : elles
# ne dépendent pas du code (longues à produire).
$(BUILD)/data/gnubg_os0.bd: | $(BUILD)/host/makebearoff
@mkdir -p $(@D)
$(BUILD)/host/makebearoff -o 6 -s 7999999 -f $@.tmp
mv $@.tmp $@
$(BUILD)/data/gnubg_ts0.bd: | $(BUILD)/host/makebearoff
@mkdir -p $(@D)
$(BUILD)/host/makebearoff -t 6x6 -f $@.tmp
mv $@.tmp $@
$(BUILD)/data/Kazaross-XG2.xml: engine/data/Kazaross-XG2.xml
@mkdir -p $(@D)
cp $< $@
-include $(shell find $(BUILD) -name '*.d' 2>/dev/null)
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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, either version 3 of the License, or
(at your option) any later version.
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 <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+73
View File
@@ -0,0 +1,73 @@
# Provenance de la copie de GNU Backgammon
Les fichiers de ce dossier sont copiés de GNU Backgammon
(https://git.savannah.gnu.org/cgit/gnubg.git), commit
`14b67984737ded0f6de43d63dcb789f729d9b06b` (version 1.08.003, 2026-10-23),
choisi par l'ADR 0005. Licence : GPLv3 ou ultérieure (`COPYING`). Les
en-têtes de copyright sont conservés.
**Aucun fichier n'est modifié** : chacun est identique octet pour octet à
l'original. Vérification, depuis un clone de gnubg à ce commit :
```sh
for f in $(cd engine/gnubg && find . -type f ! -name PROVENANCE.md); do
cmp "engine/gnubg/$f" "chemin/du/clone/gnubg/$f" || echo "différent : $f"
done
```
Les espaces en fin de ligne de l'original sont gardés ; `tests/lint.sh`
exclut donc ce dossier (et `engine/data/`) de cette vérification.
## Fichiers repris
Compilés dans le moteur embarqué (`build/libengine.a`) :
| Fichier | Rôle |
| --- | --- |
| `eval.c`, `eval.h`, `movefilters.inc` | Évaluation, génération et classement des coups, videau |
| `positionid.c`, `positionid.h` | Position ID |
| `matchid.c`, `matchid.h` | Match ID |
| `matchequity.c`, `matchequity.h`, `mec.c`, `mec.h` | Tables d'équité de match |
| `bearoff.c`, `bearoff.h`, `bearoffgammon.c`, `bearoffgammon.h` | Bases de fin de partie |
| `drawboard.c`, `drawboard.h` | Notation des coups (`FormatMove`) |
| `mtsupport.c` | Données du fil d'exécution unique (branche sans threads) |
| `lib/neuralnet.c`, `lib/neuralnet.h`, `lib/neuralnetsse.c`, `lib/inputs.c`, `lib/simd.h`, `lib/sigmoid.h` | Réseaux de neurones |
| `lib/cache.c`, `lib/cache.h` | Cache d'évaluations |
| `lib/isaac.c`, `lib/isaac.h`, `lib/isaacs.h`, `lib/md5.c`, `lib/md5.h` | Hasards (bruit, désactivé ici) et hachage |
| `lib/list.c`, `lib/list.h` | Listes (tables d'équité) |
| `lib/gnubg-types.h`, `common.h`, `dice.h` | Types et définitions communes |
Compilés seulement pour générer les données au build (`build/host/`), avec
la vraie GLib, comme dans le build officiel :
| Fichier | Rôle |
| --- | --- |
| `makeweights.c` | `gnubg.weights` → `gnubg.wd` (réseaux en binaire) |
| `makebearoff.c` | Bases `gnubg_os0.bd` et `gnubg_ts0.bd` |
Données, dans `engine/data/` (non modifiées non plus) : `gnubg.weights`
(racine du dépôt gnubg) et `Kazaross-XG2.xml` (`met/`), la table d'équité
de match par défaut de gnubg de bureau.
## Ce qui remplace le reste de gnubg
Les fichiers de gnubg non repris sont remplacés par des fichiers propres à
Ustagammon, dans `engine/compat/`, trouvés en premier par le compilateur :
| Fichier | Remplace |
| --- | --- |
| `config.h` | Le `config.h` produit par `configure` (sans threads, C générique, NEON prévu) |
| `glib-shim/glib.h`, `glib-shim/glib/*.h`, `glib-shim.c` | GLib, pour le moteur embarqué seulement |
| `backgammon.h`, `output.h`, `util.h`, `format.h`, `multithread.h`, `external.h` | En-têtes réduits aux déclarations utilisées par le cœur |
| `gnubg-support.c` | Fonctions et variables de `gnubg.c`, `output.c`, `util.c`, `multithread.c` utilisées par le cœur |
| `host-tools-support.c` | `GetTemporaryFile` de `util.c`, pour `makebearoff` |
La liste des symboles à fournir a été établie à l'édition des liens. Le
fichier `jni-bridge/src/stubs.c` de CBG (https://github.com/clavierhaus/gnubg-android,
GPLv3) a servi de point de repère, sans reprise de code.
## Mettre à jour la copie
Copier les mêmes fichiers depuis le nouveau commit, mettre à jour le commit
ci-dessus, puis `make engine` et `make test`. Une erreur de compilation ou
d'édition des liens indique un symbole à ajouter dans `engine/compat/`.
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
/*
* Copyright (C) 2002-2004 Joern Thyssen <jthyssen@dk.ibm.com>
* Copyright (C) 2003-2018 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef BEAROFF_H
#define BEAROFF_H
#include "gnubg-types.h"
#include <glib.h>
typedef enum {
BEAROFF_INVALID,
BEAROFF_ONESIDED,
BEAROFF_TWOSIDED,
BEAROFF_HYPERGAMMON
} bearofftype;
typedef struct {
bearofftype bt; /* type of bearoff database */
unsigned int nPoints; /* number of points covered by database */
unsigned int nChequers; /* number of chequers for one-sided database */
/* one sided dbs */
int fCompressed; /* is database compressed? */
int fGammon; /* gammon probs included */
int fND; /* normal distribution instead of exact dist? */
int fHeuristic; /* heuristic database? */
/* two sided dbs */
int fCubeful; /* cubeful equities included */
FILE *pf; /* file pointer */
char *szFilename; /* filename */
GMappedFile *map;
unsigned char *p; /* pointer to data in memory */
} bearoffcontext;
enum bearoffoptions {
BO_NONE = 0,
BO_IN_MEMORY = 1,
BO_MUST_BE_ONE_SIDED = 2,
BO_MUST_BE_TWO_SIDED = 4,
BO_HEURISTIC = 8
};
extern bearoffcontext *BearoffInit(const char *szFilename, unsigned int bo, void (*p) (unsigned int));
extern int
BearoffEval(const bearoffcontext * pbc, const TanBoard anBoard, float arOutput[]);
extern void
BearoffStatus(const bearoffcontext * pbc, char *sz);
extern int
BearoffDump(const bearoffcontext * pbc, const TanBoard anBoard, char *sz);
extern int
BearoffDist(const bearoffcontext * pbc, unsigned int nPosID,
float arProb[32], float arGammonProb[32],
float ar[4], unsigned short int ausProb[32], unsigned short int ausGammonProb[32]);
extern int
BearoffCubeful(const bearoffcontext * pbc, unsigned int iPos, float ar[4], unsigned short int aus[4]);
extern void BearoffClose(bearoffcontext * pbc);
extern int
isBearoff(const bearoffcontext * pbc, const TanBoard anBoard);
extern float fnd(float x, float mu, float sigma);
extern int
BearoffHyper(const bearoffcontext * pbc, unsigned int iPos, float arOutput[], float arEquity[]);
#endif /* BEAROFF_H */
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2000-2002 Joseph Heled <joseph@gnubg.org>
* Copyright (C) 2006-2008 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: bearoffgammon.h,v 1.12 2018/06/16 14:54:30 plm Exp $
*/
#if !defined( BEAROFFGAMMON_H )
#define BEAROFFGAMMON_H
/* pack for space */
struct GammonProbs {
unsigned int p1:16; /* 0 - 36^2 */
unsigned int p2:16; /* 0 - 36^3 */
unsigned int p3:24; /* 0 - 36^4 */
unsigned int p0:8; /* 0 - 36 */
};
extern struct GammonProbs *getBearoffGammonProbs(const unsigned int board[6]);
extern long *getRaceBGprobs(const unsigned int board[6]);
#define RBG_NPROBS 5
#endif
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2006-2009 Jon Kinsey <jonkinsey@gmail.com>
* Copyright (C) 2006-2017 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/*! \file common.h
* \brief Odd definitions
*/
#ifndef COMMON_H
#define COMMON_H
#include <math.h>
#include "config.h"
#ifndef HAVE___ATTRIBUTE__
#define __attribute__(Spec) /* Empty */
#endif /* HAVE___ATTRIBUTE__ */
#define F_PI (float)G_PI
#define F_PI_2 (float)G_PI_2
/*! \brief Safe error value
*/
#define ERR_VAL (-FLT_MAX)
/*! \brief Macro to extract sign
*/
#ifndef SGN
#define SGN(x) ((x) > 0 ? 1 : -1)
#endif
/*! \brief typedef of signal handler function
*/
#if HAVE_SIGACTION
typedef struct sigaction psighandler;
#elif HAVE_SIGVEC
typedef struct sigvec psighandler;
#else
typedef void (*psighandler) (int);
#endif
/* abs returns unsigned int by definition */
#define Abs(a) ((unsigned int)abs(a))
/* Do we need to use g_utf8_casefold() for utf8 anywhere? */
#define StrCaseCmp(s1, s2) g_ascii_strcasecmp(s1, s2)
#define StrNCaseCmp(s1, s2, n) g_ascii_strncasecmp(s1, s2, (gsize)n)
/* if the following are defined as macros undefine them */
#ifdef strcasecmp
#undef strcasecmp
#endif
#ifdef strncasecmp
#undef strncasecmp
#endif
/* Avoid new code using strcase functions */
#define strcasecmp strcasecmp_error_use_StrCaseCmp
#define strncasecmp strncasecmp_error_use_StrNCaseCmp
/* Macro to mark parameters that aren't used in the function */
#ifdef UNUSED
#elif defined(HAVE_FUNC_ATTRIBUTE_UNUSED)
#define UNUSED(x) UNUSED_ ## x __attribute__((unused))
#elif defined(_lint)
#define UNUSED(x) /*lint -e{715, 818}*/ _unused_##x
#else
#define UNUSED(x) _unused_##x
#endif
/* Helper macros for __builtin_expect */
#ifdef HAVE___BUILTIN_EXPECT
#define likely(expression) __builtin_expect(!!(expression), 1)
#define unlikely(expression) __builtin_expect(!!(expression), 0)
#else
#define likely(expression) (expression)
#define unlikely(expression) (expression)
#endif
#endif
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright (C) 1999-2002 Gary Wong <gtw@gnu.org>
* Copyright (C) 1999-2016 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef DICE_H
#define DICE_H
#include <stdio.h>
typedef enum {
RNG_BBS, RNG_ISAAC, RNG_MD5, RNG_MERSENNE,
RNG_MANUAL, RNG_RANDOM_DOT_ORG, RNG_FILE,
NUM_RNGS
} rng;
typedef struct rngcontext rngcontext;
extern const char *aszRNG[NUM_RNGS];
extern const char *aszRNGTip[NUM_RNGS];
extern char szDiceFilename[];
extern rng rngCurrent;
extern rngcontext *rngctxCurrent;
rngcontext *CopyRNGContext(rngcontext * rngctx);
extern void free_rngctx(rngcontext * rngctx);
extern void *InitRNG(unsigned long *pnSeed, int *pfInitFrom, int fSet, rng rngx);
extern void CloseRNG(rng rngx, rngcontext * rngctx);
extern void DestroyRNG(rng rngx, rngcontext ** rngctx);
extern void PrintRNGSeed(rng rngx, rngcontext * rngctx);
extern void PrintRNGCounter(rng rngx, rngcontext * rngctx);
extern void InitRNGSeed(unsigned int n, rng rngx, rngcontext * rngctx);
extern int RNGSystemSeed(rng rngx, void *p, unsigned long *pnSeed);
extern int RollDice(unsigned int anDice[2], rng * prng, rngcontext * rngctx);
#if defined(HAVE_LIBGMP)
extern int InitRNGSeedLong(char *sz, rng rng, rngcontext * rngctx);
extern int InitRNGBBSModulus(const char *sz, rngcontext * rngctx);
extern int InitRNGBBSFactors(char *sz0, char *sz1, rngcontext * rngctx);
#endif
extern FILE *OpenDiceFile(rngcontext * rngctx, const char *sz);
extern char *GetDiceFileName(rngcontext * rngctx);
#endif
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
/*
* Copyright (C) 1999-2001 Gary Wong <gtw@gnu.org>
* Copyright (C) 2000-2015 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef DRAWBOARD_H
#define DRAWBOARD_H
#include "gnubg-types.h"
#include "external.h"
extern int fClockwise; /* Player 1 moves clockwise */
extern char *DrawBoard(char *sz, const TanBoard anBoard, int fRoll, char *asz[], char *szMatchID, int nChequers);
/* Fill the buffer pch with a representation of the move anMove, assuming
* the board looks like anBoard. pch must have room for 28 characters plus
* a trailing 0 (consider the move `bar/24* 23/22* 21/20* 19/18*'). */
#define FORMATEDMOVESIZE 29
extern char *FormatMove(char *sz, const TanBoard anBoard, const int anMove[8]);
extern char *FormatMovePlain(char *sz, const TanBoard anBoard, const int anMove[8]);
extern int ParseMove(char *pch, int an[8]);
extern void CanonicalMoveOrder(int an[]);
/* Fill the buffer pch with a FIBS "boardstyle 3" description of the game. */
extern char *FIBSBoard(char *pch, TanBoard anBoard, int fRoll,
const char *szPlayer, const char *szOpp, int nMatchTo,
int nScore, int nOpponent, int nDice0, int nDice1,
int nCube, int fCubeOwner, int fDoubled, int fTurn,
int fCrawford, int nChequers, int fPostCrawford);
/* Process a board info structure from external interface */
extern int ProcessFIBSBoardInfo(FIBSBoardInfo * brdInfo, ProcessedFIBSBoard * procBrd);
#endif
+6396
View File
File diff suppressed because it is too large Load Diff
+549
View File
@@ -0,0 +1,549 @@
/*
* Copyright (C) 1998-2002 Gary Wong <gtw@gnu.org>
* Copyright (C) 2000-2025 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef EVAL_H
#define EVAL_H
#include "dice.h"
#include "bearoff.h"
#include "neuralnet.h"
#include "cache.h"
#define EXP_LOCK_FUN(ret, name, ...) \
typedef ret (*f_##name)( __VA_ARGS__); \
extern f_##name name; \
extern ret name##NoLocking( __VA_ARGS__); \
extern ret name##WithLocking( __VA_ARGS__)
#define WEIGHTS_VERSION "1.01"
#define WEIGHTS_VERSION_BINARY 1.01f
#define WEIGHTS_MAGIC_BINARY 472.3782f
#define NUM_OUTPUTS 5
#define NUM_CUBEFUL_OUTPUTS 4
#define NUM_ROLLOUT_OUTPUTS 7
#define BETA_HIDDEN 0.1f
#define BETA_OUTPUT 1.0f
#define OUTPUT_WIN 0
#define OUTPUT_WINGAMMON 1
#define OUTPUT_WINBACKGAMMON 2
#define OUTPUT_LOSEGAMMON 3
#define OUTPUT_LOSEBACKGAMMON 4
#define OUTPUT_EQUITY 5 /* NB: neural nets do not output equity, only
* rollouts do. */
#define OUTPUT_CUBEFUL_EQUITY 6
/* Cubeful evaluations */
typedef enum {
OUTPUT_OPTIMAL = 0,
OUTPUT_NODOUBLE,
OUTPUT_TAKE,
OUTPUT_DROP
} CubefulOutputs;
/* A trivial upper bound on the number of (complete or incomplete)
* legal moves of a single roll: if all 15 chequers are spread out,
* then there are 18 C 4 + 17 C 3 + 16 C 2 + 15 C 1 = 3875
* combinations in which a roll of 11 could be played (up to 4 choices from
* 15 chequers, and a chequer may be chosen more than once). The true
* bound will be lower than this (because there are only 26 points,
* some plays of 15 chequers must "overlap" and map to the same
* resulting position), but that would be more difficult to
* compute. */
#define MAX_INCOMPLETE_MOVES 3875
#define MAX_MOVES 3060
typedef struct {
int Accept; /* always allow this many moves.
-1 means don't use this level */
int Extra; /* and add up to this many more */
float Threshold; /* if they are within this equity difference */
} movefilter;
/* we'll have filters for 1..4 ply evaluation */
#define MAX_FILTER_PLIES 4
extern movefilter defaultFilters[MAX_FILTER_PLIES][MAX_FILTER_PLIES];
typedef struct {
/* FIXME expand this... e.g. different settings for different position
* classes */
unsigned int fCubeful:1; /* cubeful evaluation */
unsigned int nPlies:4;
unsigned int fUsePrune:1;
unsigned int fDeterministic:1;
unsigned int :25; /* padding */
float rNoise; /* standard deviation */
} evalcontext;
/* identifies the format of evaluation info in .sgf files
* early (pre extending rollouts) had no version numbers
* extendable rollouts have a version number of 1
* with pruning nets, we have two possibilities - reduction could still
* be enabled (version = 2) or, given the speedup and performance
* improvements, I assume we will drop reduction entirely. (ver = 3 or more)
* When presented with an .sgf file, gnubg will attempt to work out what
* data is present in the file based on the version number
*/
#define SGF_FORMAT_VER 3
typedef struct {
evalcontext aecCube[2], aecChequer[2]; /* evaluation parameters */
evalcontext aecCubeLate[2], aecChequerLate[2]; /* ... for later moves */
evalcontext aecCubeTrunc, aecChequerTrunc; /* ... at truncation point */
movefilter aaamfChequer[2][MAX_FILTER_PLIES][MAX_FILTER_PLIES];
movefilter aaamfLate[2][MAX_FILTER_PLIES][MAX_FILTER_PLIES];
unsigned int fCubeful:1; /* Cubeful rollout */
unsigned int fVarRedn:1; /* variance reduction */
unsigned int fInitial:1; /* roll out as opening position */
unsigned int fRotate:1; /* quasi-random dice */
unsigned int fTruncBearoff2:1; /* cubeless rollout: trunc at BEAROFF2 */
unsigned int fTruncBearoffOS:1; /* cubeless rollout: trunc at BEAROFF_OS */
unsigned int fLateEvals:1; /* enable different evals for later moves */
unsigned int fDoTruncate:1; /* enable truncated rollouts */
unsigned int fStopOnSTD:1; /* stop when std's are small enough */
unsigned int fStopOnJsd:1;
unsigned int fStopMoveOnJsd:1; /* stop multi-line rollout when jsd
* is small enough */
unsigned short nTruncate; /* truncation */
unsigned int nTrials; /* number of rollouts */
unsigned short nLate; /* switch evaluations on move nLate of game */
rng rngRollout;
unsigned long nSeed;
unsigned int nMinimumGames; /* always do at least this many */
float rStdLimit; /* stop when std < this */
unsigned int nMinimumJsdGames;
float rJsdLimit;
unsigned int nGamesDone;
float rStoppedOnJSD;
int nSkip;
} rolloutcontext;
typedef struct {
float rEquity;
float rJSD;
int nOrder;
int nRank;
} jsdinfo;
typedef enum {
EVAL_NONE, EVAL_EVAL, EVAL_ROLLOUT
} evaltype;
/* enumeration of variations of backgammon
* (starting position and/or special rules) */
extern bgvariation bgvDefault;
extern int anChequers[NUM_VARIATIONS];
extern const char *aszVariations[NUM_VARIATIONS];
extern const char *aszVariationCommands[NUM_VARIATIONS];
/*
* Cubeinfo contains the information necessary for evaluation
* of a position.
* These structs are placed here so that the move struct can be defined
*/
typedef struct {
/*
* nCube: the current value of the cube,
* fCubeOwner: the owner of the cube,
* fMove: the player for which we are
* calculating equity for,
* fCrawford, fJacoby, fBeavers: optional rules in effect,
* arGammonPrice: the gammon prices;
* [ 0 ] = gammon price for player 0,
* [ 1 ] = gammon price for player 1,
* [ 2 ] = backgammon price for player 0,
* [ 3 ] = backgammon price for player 1.
*
*/
int nCube, fCubeOwner, fMove, nMatchTo, anScore[2], fCrawford, fJacoby, fBeavers;
float arGammonPrice[4];
bgvariation bgv;
} cubeinfo;
typedef struct {
evaltype et;
evalcontext ec;
rolloutcontext rc;
} evalsetup;
typedef enum {
DOUBLE_TAKE,
DOUBLE_PASS,
NODOUBLE_TAKE,
TOOGOOD_TAKE,
TOOGOOD_PASS,
DOUBLE_BEAVER,
NODOUBLE_BEAVER,
REDOUBLE_TAKE,
REDOUBLE_PASS,
NO_REDOUBLE_TAKE,
TOOGOODRE_TAKE,
TOOGOODRE_PASS,
NO_REDOUBLE_BEAVER,
NODOUBLE_DEADCUBE, /* cube is dead (match play only) */
NO_REDOUBLE_DEADCUBE, /* cube is dead (match play only) */
NOT_AVAILABLE, /* Cube not available */
OPTIONAL_DOUBLE_TAKE,
OPTIONAL_REDOUBLE_TAKE,
OPTIONAL_DOUBLE_BEAVER,
OPTIONAL_DOUBLE_PASS,
OPTIONAL_REDOUBLE_PASS
} cubedecision;
typedef enum {
DT_NORMAL,
DT_BEAVER,
DT_RACCOON,
NUM_DOUBLE_TYPES
} doubletype;
/*
* TT_NA can happen if a single position was loaded from a sgf file.
* To check for beavers, use "> TT_NORMAL", not "!= TT_NORMAL".
*/
typedef enum {
TT_NA,
TT_NORMAL,
TT_BEAVER
} taketype;
extern const char *aszDoubleTypes[NUM_DOUBLE_TYPES];
/*
* prefined settings
*/
#define NUM_SETTINGS 9
#define SETTINGS_4PLY 8
#define SETTINGS_3PLY 7
#define SETTINGS_2PLY 6
#define SETTINGS_1PLY 5
#define SETTINGS_0PLY 4
#define SETTINGS_ADVANCED 3
#define SETTINGS_INTERMEDIATE 2
#define SETTINGS_NOVICE 1
#define SETTINGS_BEGINNER 0
extern evalcontext aecSettings[NUM_SETTINGS];
extern evalcontext ecBasic;
extern int aiSettingsMoveFilter[NUM_SETTINGS];
extern const char *aszSettings[NUM_SETTINGS];
#define NUM_MOVEFILTER_SETTINGS 2
extern const char *aszMoveFilterSettings[NUM_MOVEFILTER_SETTINGS];
extern movefilter aaamfMoveFilterSettings[NUM_MOVEFILTER_SETTINGS][MAX_FILTER_PLIES][MAX_FILTER_PLIES];
typedef enum {
CMARK_NONE,
CMARK_ROLLOUT
} CMark;
typedef struct {
int anMove[8];
positionkey key;
unsigned int cMoves, cPips;
/* scores for this move */
float rScore, rScore2;
/* evaluation for this move */
float arEvalMove[NUM_ROLLOUT_OUTPUTS];
float arEvalStdDev[NUM_ROLLOUT_OUTPUTS];
evalsetup esMove;
CMark cmark;
} move;
extern int fInterrupt;
extern cubeinfo ciCubeless;
extern const char *aszEvalType[(int) EVAL_ROLLOUT + 1];
extern bearoffcontext *pbc1;
extern bearoffcontext *pbc2;
extern bearoffcontext *pbcOS;
extern bearoffcontext *pbcTS;
extern bearoffcontext *apbcHyper[3];
typedef struct {
unsigned int cMoves; /* and current move when building list */
unsigned int cMaxMoves, cMaxPips;
int iMoveBest;
float rBestScore;
move *amMoves;
} movelist;
/* cube efficiencies */
extern float rOSCubeX;
extern float rRaceFactorX[2];
extern float rRaceCoefficientX[2];
extern float rRaceMax[2];
extern float rRaceMin[2];
extern float rCrashedX[2];
extern float rContactX[2];
/* position classes */
typedef enum {
CLASS_OVER = 0, /* Game already finished */
CLASS_HYPERGAMMON1, /* hypergammon with 1 chequers */
CLASS_HYPERGAMMON2, /* hypergammon with 2 chequers */
CLASS_HYPERGAMMON3, /* hypergammon with 3 chequers */
CLASS_BEAROFF2, /* Two-sided bearoff database (in memory) */
CLASS_BEAROFF_TS, /* Two-sided bearoff database (on disk) */
CLASS_BEAROFF1, /* One-sided bearoff database (in memory) */
CLASS_BEAROFF_OS, /* One-sided bearoff database /on disk) */
CLASS_RACE, /* Race neural network */
CLASS_CRASHED, /* Contact, one side has less than 7 active checkers */
CLASS_CONTACT /* Contact neural network */
} positionclass;
#define N_CLASSES (CLASS_CONTACT + 1)
#define CLASS_PERFECT CLASS_BEAROFF_TS
#define CLASS_GOOD CLASS_BEAROFF_OS /* Good enough to not need SanityCheck */
typedef int (*classevalfunc) (const TanBoard anBoard, float arOutput[], const bgvariation bgv, NNState * nnStates);
extern classevalfunc acef[N_CLASSES];
/* Evaluation cache size is 2^SIZE entries */
#define CACHE_SIZE_DEFAULT 19
#define CACHE_SIZE_GUIMAX 23
#define CFMONEY(arEquity,pci) \
( ( (pci)->fCubeOwner == -1 ) ? arEquity[ 2 ] : \
( ( (pci)->fCubeOwner == (pci)->fMove ) ? arEquity[ 1 ] : arEquity[ 3 ] ) )
#define CFHYPER(arEquity,pci) \
( ( (pci)->fCubeOwner == -1 ) ? \
( ( (pci)->fJacoby ) ? arEquity[ 2 ] : arEquity[ 1 ] ) : \
( ( (pci)->fCubeOwner == (pci)->fMove ) ? arEquity[ 0 ] : arEquity[ 3 ] ) )
extern void EvalInitialise(char *szWeights, char *szWeightsBinary, int fNoBearoff, void (*pfProgress) (unsigned int));
extern int EvalShutdown(void);
extern void EvalStatus(char *szOutput);
extern int EvalNewWeights(int nSize);
extern int EvalSave(const char *szWeights);
EXP_LOCK_FUN(int, EvaluatePosition, NNState * nnStates, const TanBoard anBoard, float arOutput[],
cubeinfo * const pci, const evalcontext * pec);
extern void
InvertEvaluationR(float ar[NUM_ROLLOUT_OUTPUTS], const cubeinfo * pci);
extern void
InvertEvaluation(float ar[NUM_OUTPUTS]);
EXP_LOCK_FUN(int, FindBestMove, int anMove[8], int nDice0, int nDice1,
TanBoard anBoard, const cubeinfo * pci, evalcontext * pec, movefilter aamf[MAX_FILTER_PLIES][MAX_FILTER_PLIES]);
EXP_LOCK_FUN(int, FindnSaveBestMoves, movelist * pml,
int nDice0, int nDice1, const TanBoard anBoard,
positionkey * keyMove, int fAnalyse, const float rThr,
const cubeinfo * pci, const evalcontext * pec, movefilter aamf[MAX_FILTER_PLIES][MAX_FILTER_PLIES]);
extern void
PipCount(const TanBoard anBoard, unsigned int anPips[2]);
extern int
ThorpCount(const TanBoard anBoard, int *pnLeader, float *adjusted, int *pnTrailer);
extern int
KeithCount(const TanBoard anBoard, int pn[2]);
extern int
IsightCount(const TanBoard anBoard, int pn[2]);
extern int
DumpPosition(const TanBoard anBoard, char *szOutput,
const evalcontext * pec, cubeinfo * pci, int fOutputMWC,
int fOutputWinPC, int fOutputInvert, const char *szMatchID);
extern void
SwapSides(TanBoard anBoard);
extern int GameStatus(const TanBoard anBoard, bgvariation bgv);
extern void EvalCacheFlush(void);
extern int EvalCacheResize(unsigned int cNew);
extern int EvalCacheStats(unsigned int *pcUsed, unsigned int *pcLookup, unsigned int *pcHit);
extern double GetEvalCacheSize(void);
void SetEvalCacheSize(unsigned int size);
extern unsigned int GetEvalCacheEntries(void);
extern int GetCacheMB(int size);
extern evalCache cEval;
extern evalCache cpEval;
extern unsigned int cCache;
extern int
GenerateMoves(movelist * pml, const TanBoard anBoard, int n0, int n1, int fPartial);
extern int ApplySubMove(TanBoard anBoard, int iSrc, int nRoll, int fCheckLegal);
extern int ApplyMove(TanBoard anBoard, const int anMove[8], int fCheckLegal);
extern positionclass ClassifyPosition(const TanBoard anBoard, bgvariation bgv);
/* internal use only */
extern void EvalRaceBG(const TanBoard anBoard, float arOutput[], bgvariation bgv);
extern float
Utility(float ar[NUM_OUTPUTS], const cubeinfo * pci);
extern float
UtilityME(float ar[NUM_OUTPUTS], const cubeinfo * pci);
extern int
SetCubeInfoMoney(cubeinfo * pci, int nCube, int fCubeOwner,
int fMove, int fJacoby, int fBeavers, bgvariation bgv);
extern int
SetCubeInfo(cubeinfo * pci, int nCube, int fCubeOwner,
int fMove, int nMatchTo, const int anScore[2],
int fCrawford, int fJacoby, int fBeavers, bgvariation bgv);
extern void
swap_us(unsigned int *p0, unsigned int *p1);
extern void
swap(int *p0, int *p1);
extern void
SanityCheck(const TanBoard anBoard, float arOutput[]);
extern int
EvalOver(const TanBoard anBoard, float arOutput[], bgvariation bgv, NNState * nnStates);
extern float
KleinmanCount(int nPipOnRoll, int nPipNotOnRoll);
extern int
GetDPEq(int *pfCube, float *prDPEq, const cubeinfo * pci);
extern float
mwc2eq(float rMwc, const cubeinfo * pci);
extern float
eq2mwc(float rEq, const cubeinfo * pci);
extern float
se_mwc2eq(float rMwc, const cubeinfo * pci);
extern float
se_eq2mwc(float rEq, const cubeinfo * pci);
extern char
*FormatEval(char *sz, evalsetup * pes);
extern cubedecision FindCubeDecision(float arDouble[], float aarOutput[][NUM_ROLLOUT_OUTPUTS], const cubeinfo * pci);
EXP_LOCK_FUN(int, GeneralCubeDecisionE, float aarOutput[2][NUM_ROLLOUT_OUTPUTS],
const TanBoard anBoard, cubeinfo * const pci, const evalcontext * pec, const evalsetup * pes);
EXP_LOCK_FUN(int, GeneralEvaluationE, float arOutput[NUM_ROLLOUT_OUTPUTS],
const TanBoard anBoard, cubeinfo * const pci, const evalcontext * pec);
extern int
cmp_evalsetup(const evalsetup * pes1, const evalsetup * pes2);
extern int
cmp_evalcontext(const evalcontext * pec1, const evalcontext * pec2);
extern char
*GetCubeRecommendation(cubedecision cd);
extern cubedecision
FindBestCubeDecision(float arDouble[], float aarOutput[2][NUM_ROLLOUT_OUTPUTS], const cubeinfo * pci);
extern int
getCurrentGammonRates(float aarRates[2][2],
float arOutput[], const TanBoard anBoard, cubeinfo * pci, const evalcontext * pec);
extern void
getMoneyPoints(float aaarPoints[2][7][2], int fJacoby, int fBeavers, float aarRates[2][2]);
extern void
getMatchPoints(float aaarPoints[2][4][2],
int afAutoRedouble[2], int afDead[2], const cubeinfo * pci, float aarRates[2][2]);
extern void
getCubeDecisionOrdering(int aiOrder[3],
float arDouble[4], float aarOutput[2][NUM_ROLLOUT_OUTPUTS], const cubeinfo * pci);
extern float
getPercent(cubedecision cd, const float arDouble[]);
extern void
RefreshMoveList(movelist * pml, int *ai);
EXP_LOCK_FUN(int, ScoreMove, NNState * nnStates, move * pm, const cubeinfo * pci, const evalcontext * pec, int nPlies);
extern void
CopyMoveList(movelist * pmlDest, const movelist * pmlSrc);
extern int
isCloseCubedecision(const float arDouble[]);
extern int
isMissedDouble(float arDouble[], float aarOutput[2][NUM_ROLLOUT_OUTPUTS], int fDouble, const cubeinfo * pci);
extern unsigned int
locateMove(const TanBoard anBoard, const int anMove[8], const movelist * pml);
extern int
equal_movefilter(int i, const movefilter amf1[MAX_FILTER_PLIES], const movefilter amf2[MAX_FILTER_PLIES]);
extern int
equal_movefilters(movefilter aamf1[MAX_FILTER_PLIES][MAX_FILTER_PLIES],
movefilter aamf2[MAX_FILTER_PLIES][MAX_FILTER_PLIES]);
extern doubletype DoubleType(int fDoubled, int fMove, int fTurn);
extern int
PerfectCubeful(bearoffcontext * pbc, const TanBoard anBoard, float arEquity[]);
extern void
baseInputs(const TanBoard anBoard, float arInput[]);
extern int CompareMoves(const move * pm0, const move * pm1);
extern float EvalEfficiency(const TanBoard anBoard, positionclass pc, int ply);
extern float Cl2CfMoney(float arOutput[NUM_OUTPUTS], cubeinfo * pci, float rCubeX);
extern float Cl2CfMatch(float arOutput[NUM_OUTPUTS], cubeinfo * pci, float rCubeX);
extern float Noise(const evalcontext * pec, const TanBoard anBoard, int iOutput);
extern int EvalKey(const evalcontext * pec, int nPlies, const cubeinfo * pci, int fCubefulEquity);
extern void MakeCubePos(const cubeinfo aciCubePos[], int cci, int fTop, cubeinfo aci[], int fInvert);
extern void GetECF3(float arCubeful[], int cci, float arCf[], cubeinfo aci[]);
extern int EvaluatePerfectCubeful(const TanBoard anBoard, float arEquity[], bgvariation bgv);
extern neuralnet nnContact, nnRace, nnCrashed;
extern neuralnet nnpContact, nnpRace, nnpCrashed;
#endif
+349
View File
@@ -0,0 +1,349 @@
/*
* Copyright (C) 1997-2000 Gary Wong <gtw@gnu.org>
* Copyright (C) 2002-2022 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include "cache.h"
#include "positionid.h"
#if defined(USE_MULTITHREAD)
#include "multithread.h"
#if defined(__GNUC__) && ( \
(( __GNUC__ * 100 + __GNUC_MINOR__ >= 401 ) \
&& (defined (__i486) || defined (__x86_64))) \
|| (( __GNUC__ * 100 + __GNUC_MINOR__ >= 404 ) \
&& defined (__i386)) \
)
static inline void
cache_lock(evalCache * pc, uint32_t k)
{
while (__sync_lock_test_and_set(&(pc->entries[k].lock), 1))
while (__sync_fetch_and_add(&pc->entries[k].lock, 0))
__asm__ __volatile__ ("pause":::"memory");
}
static inline void
cache_unlock(evalCache * pc, uint32_t k)
{
__sync_lock_release(&(pc->entries[k].lock));
}
#else /* not x86 or older gcc: no suitable intrinsics */
static inline void
WaitForLock(volatile int *lock)
{
do {
MT_SafeDec(lock);
} while (MT_SafeIncCheck(lock));
}
static inline void
cache_lock(evalCache * pc, uint32_t k)
{
if (MT_SafeIncCheck(&(pc->entries[k].lock)))
WaitForLock(&(pc->entries[k].lock));
}
static inline void
cache_unlock(evalCache * pc, uint32_t k)
{
MT_SafeDec(&(pc->entries[k].lock));
}
#endif
#endif /* USE_MULTITHREAD */
static int
CacheGetSize(unsigned int s)
{
unsigned int size;
if (s == 0)
return 0;
if (s > 1u << 31)
return -1;
/* A cache size of 1 would allocate zero cache nodes */
if (s == 1)
s = 2;
size = s;
/* adjust size to smallest power of 2 GE to s */
while ((s & (s - 1)) != 0)
s &= (s - 1);
size = (s < size) ? 2 * s : s;
/* CacheResize() returns the cache size as an int. */
if (size > (unsigned int) INT_MAX)
return -1;
return (int) size;
}
int
CacheCreate(evalCache * pc, unsigned int s)
{
int size = CacheGetSize(s);
if (size < 0)
return -1;
#if CACHE_STATS
pc->cLookup = 0;
pc->cHit = 0;
pc->nAdds = 0;
#endif
pc->size = (unsigned int) size;
if (pc->size == 0) {
pc->hashMask = 0;
pc->entries = NULL;
return 0;
}
pc->hashMask = (pc->size >> 1) - 1;
/* Avoid "comparison is always false due to limited range of data type"
* warning on 64bits architectures.
* Adapt (or remove the condition) if the involved types are changed.
*/
#if (SIZE_MAX / 64 < UINT_MAX)
/* Ensure the allocation size fits in size_t */
if ((size_t) (pc->size / 2) > SIZE_MAX / sizeof(*pc->entries))
return -1;
#endif
pc->entries = (cacheNode *) malloc((pc->size / 2) * sizeof(*pc->entries));
if (pc->entries == NULL)
return -1;
CacheFlush(pc);
return 0;
}
/* MurmurHash3 https://code.google.com/p/smhasher/wiki/MurmurHash */
extern uint32_t
GetHashKey(uint32_t hashMask, const cacheNodeDetail * restrict e)
{
uint32_t hash = (uint32_t) e->nEvalContext;
int i;
hash *= 0xcc9e2d51;
hash = (hash << 15) | (hash >> (32 - 15));
hash *= 0x1b873593;
hash = (hash << 13) | (hash >> (32 - 13));
hash = hash * 5 + 0xe6546b64;
for (i = 0; i < 7; i++) {
uint32_t k = e->key.data[i];
k *= 0xcc9e2d51;
k = (k << 15) | (k >> (32 - 15));
k *= 0x1b873593;
hash ^= k;
hash = (hash << 13) | (hash >> (32 - 13));
hash = hash * 5 + 0xe6546b64;
}
/* Real MurmurHash3 has a "hash ^= len" here,
* but for us len is constant. Skip it */
hash ^= hash >> 16;
hash *= 0x85ebca6b;
hash ^= hash >> 13;
hash *= 0xc2b2ae35;
hash ^= hash >> 16;
return (hash & hashMask);
}
uint32_t
CacheLookupWithLocking(evalCache * restrict pc, const cacheNodeDetail * restrict e, float * restrict arOut, float * restrict arCubeful)
{
uint32_t const l = GetHashKey(pc->hashMask, e);
#if CACHE_STATS
#if defined(USE_MULTITHREAD)
MT_SafeInc(&pc->cLookup);
#else
++pc->cLookup;
#endif
#endif
#if defined(USE_MULTITHREAD)
cache_lock(pc, l);
#endif
if (!EqualKeys(pc->entries[l].nd_primary.key, e->key) || pc->entries[l].nd_primary.nEvalContext != e->nEvalContext) { /* Not in primary slot */
if (!EqualKeys(pc->entries[l].nd_secondary.key, e->key) || pc->entries[l].nd_secondary.nEvalContext != e->nEvalContext) { /* Cache miss */
#if defined(USE_MULTITHREAD)
cache_unlock(pc, l);
#endif
return l;
} else { /* Found in second slot, promote "hot" entry */
cacheNodeDetail tmp = pc->entries[l].nd_primary;
pc->entries[l].nd_primary = pc->entries[l].nd_secondary;
pc->entries[l].nd_secondary = tmp;
}
}
/* Cache hit */
memcpy(arOut, pc->entries[l].nd_primary.ar, sizeof(float) * 5 /*NUM_OUTPUTS */ );
if (arCubeful)
*arCubeful = pc->entries[l].nd_primary.ar[5]; /* Cubeful equity stored in slot 5 */
#if defined(USE_MULTITHREAD)
cache_unlock(pc, l);
#endif
#if CACHE_STATS
#if defined(USE_MULTITHREAD)
MT_SafeInc(&pc->cHit);
#else
++pc->cHit;
#endif
#endif
return CACHEHIT;
}
uint32_t
CacheLookupNoLocking(evalCache * restrict pc, const cacheNodeDetail * restrict e, float *restrict arOut, float * restrict arCubeful)
{
uint32_t const l = GetHashKey(pc->hashMask, e);
#if CACHE_STATS
++pc->cLookup;
#endif
if (!EqualKeys(pc->entries[l].nd_primary.key, e->key) || pc->entries[l].nd_primary.nEvalContext != e->nEvalContext) { /* Not in primary slot */
if (!EqualKeys(pc->entries[l].nd_secondary.key, e->key) || pc->entries[l].nd_secondary.nEvalContext != e->nEvalContext) { /* Cache miss */
return l;
} else { /* Found in second slot, promote "hot" entry */
cacheNodeDetail tmp = pc->entries[l].nd_primary;
pc->entries[l].nd_primary = pc->entries[l].nd_secondary;
pc->entries[l].nd_secondary = tmp;
}
}
/* Cache hit */
memcpy(arOut, pc->entries[l].nd_primary.ar, sizeof(float) * 5 /*NUM_OUTPUTS */ );
if (arCubeful)
*arCubeful = pc->entries[l].nd_primary.ar[5]; /* Cubeful equity stored in slot 5 */
#if CACHE_STATS
++pc->cHit;
#endif
return CACHEHIT;
}
void
CacheAddWithLocking(evalCache * restrict pc, const cacheNodeDetail * restrict e, uint32_t l)
{
#if defined(USE_MULTITHREAD)
cache_lock(pc, l);
#endif
pc->entries[l].nd_secondary = pc->entries[l].nd_primary;
pc->entries[l].nd_primary = *e;
#if defined(USE_MULTITHREAD)
cache_unlock(pc, l);
#endif
#if CACHE_STATS
#if defined(USE_MULTITHREAD)
MT_SafeInc(&pc->nAdds);
#else
++pc->nAdds;
#endif
#endif
}
/* CacheAddNoLocking() is inlined and in cache.h */
void
CacheDestroy(const evalCache * pc)
{
free(pc->entries);
}
void
CacheFlush(const evalCache * pc)
{
unsigned int k;
for (k = 0; k < pc->size / 2; ++k) {
pc->entries[k].nd_primary.key.data[0] = (unsigned int) -1;
pc->entries[k].nd_secondary.key.data[0] = (unsigned int) -1;
#if defined(USE_MULTITHREAD)
pc->entries[k].lock = 0;
#endif
}
}
int
CacheResize(evalCache * pc, unsigned int cNew)
{
evalCache newCache = { 0 };
int size = CacheGetSize(cNew);
if (size < 0)
return -1;
if ((unsigned int) size == pc->size)
return size;
if (CacheCreate(&newCache, cNew) != 0)
return -1;
CacheDestroy(pc);
*pc = newCache;
return (int) pc->size;
}
#if CACHE_STATS
void
CacheStats(const evalCache * pc, unsigned int *pcLookup, unsigned int *pcHit, unsigned int *pcUsed)
{
if (pcLookup)
*pcLookup = pc->cLookup;
if (pcHit)
*pcHit = pc->cHit;
if (pcUsed)
*pcUsed = pc->nAdds;
}
#endif
+100
View File
@@ -0,0 +1,100 @@
/*
* Copyright (C) 1997-2000 Gary Wong <gtw@gnu.org>
* Copyright (C) 2002-2018 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef CACHE_H
#define CACHE_H
#include "config.h"
#ifdef HAVE_STDINT_H
#include <stdint.h>
#else
typedef unsigned int uint32_t;
#endif
#include "gnubg-types.h"
/* Set to calculate simple cache stats */
#define CACHE_STATS 0
typedef struct {
positionkey key; /* size = 28 */
int nEvalContext;
float ar[6];
} cacheNodeDetail;
typedef struct {
cacheNodeDetail nd_primary;
cacheNodeDetail nd_secondary;
#if defined(USE_MULTITHREAD)
int lock;
#endif
} cacheNode;
/* name used in eval.c */
typedef cacheNodeDetail evalcache;
typedef struct {
cacheNode *entries;
unsigned int size;
uint32_t hashMask;
#if CACHE_STATS
unsigned int nAdds;
unsigned int cLookup;
unsigned int cHit;
#endif
} evalCache;
/* Cache size will be adjusted to a power of 2 */
int CacheCreate(evalCache * pc, unsigned int size);
int CacheResize(evalCache * pc, unsigned int cNew);
#define CACHEHIT ((uint32_t)-1)
/* returns a value which is passed to CacheAdd (if a miss) */
uint32_t CacheLookupWithLocking(evalCache * pc, const cacheNodeDetail * e, float *arOut, float *arCubeful);
uint32_t CacheLookupNoLocking(evalCache * pc, const cacheNodeDetail * e, float *arOut, float *arCubeful);
void CacheAddWithLocking(evalCache * pc, const cacheNodeDetail * e, uint32_t l);
static inline void
CacheAddNoLocking(evalCache * pc, const cacheNodeDetail * e, const uint32_t l)
{
pc->entries[l].nd_secondary = pc->entries[l].nd_primary;
pc->entries[l].nd_primary = *e;
#if CACHE_STATS
++pc->nAdds;
#endif
}
void CacheFlush(const evalCache * pc);
void CacheDestroy(const evalCache * pc);
#if CACHE_STATS
void CacheStats(const evalCache * pc, unsigned int *pcLookup, unsigned int *pcHit, unsigned int *pcUsed);
#endif
#if defined(HAVE_FUNC_ATTRIBUTE_PURE)
uint32_t GetHashKey(uint32_t hashMask, const cacheNodeDetail * e) __attribute((pure));
#else
uint32_t GetHashKey(uint32_t hashMask, const cacheNodeDetail * e);
#endif
#endif
+71
View File
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2007-2009 Christian Anthon <anthon@kiku.dk>
* Copyright (C) 2007-2011 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: gnubg-types.h,v 1.9 2013/06/16 02:16:23 mdpetch Exp $
*/
#ifndef GNUBG_TYPES_H
#define GNUBG_TYPES_H
typedef unsigned int TanBoard[2][25];
typedef const unsigned int (*ConstTanBoard)[25];
typedef enum {
VARIATION_STANDARD, /* standard backgammon */
VARIATION_NACKGAMMON, /* standard backgammon with nackgammon starting
* position */
VARIATION_HYPERGAMMON_1, /* 1-chequer hypergammon */
VARIATION_HYPERGAMMON_2, /* 2-chequer hypergammon */
VARIATION_HYPERGAMMON_3, /* 3-chequer hypergammon */
NUM_VARIATIONS
} bgvariation;
typedef enum {
GAME_NONE, GAME_PLAYING, GAME_OVER, GAME_RESIGNED, GAME_DROP
} gamestate;
typedef struct {
TanBoard anBoard;
unsigned int anDice[2]; /* (0,0) for unrolled dice */
int fTurn; /* who makes the next decision */
int fResigned;
int fResignationDeclined;
int fDoubled;
int cGames;
int fMove; /* player on roll */
int fCubeOwner;
int fCrawford;
int fPostCrawford;
int nMatchTo;
int anScore[2];
int nCube;
unsigned int cBeavers;
bgvariation bgv;
int fCubeUse;
int fJacoby;
gamestate gs;
} matchstate;
typedef union {
unsigned int data[7];
} positionkey;
typedef union {
unsigned char auch[10];
} oldpositionkey;
#endif
+238
View File
@@ -0,0 +1,238 @@
/*
* Copyright (C) 2006 Oystein Johansen <oystein@gnubg.org>
* Copyright (C) 2011-2018 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: inputs.c,v 1.12 2018/05/12 20:59:35 plm Exp $
*/
#include "config.h"
#include "gnubg-types.h"
#include "simd.h"
#include "eval.h"
#if defined(USE_SIMD_INSTRUCTIONS)
#if defined(USE_NEON)
#include <arm_neon.h>
#elif defined(USE_AVX)
#include <immintrin.h>
#elif defined(USE_SSE2)
#include <emmintrin.h>
#else
#include <xmmintrin.h>
#endif
#else
typedef float float_vector[4];
#endif /* USE_SIMD_INSTRUCTIONS */
typedef SSE_ALIGN(float float_vec_aligned[sizeof(float_vector)/sizeof(float)]);
SSE_ALIGN (static float_vec_aligned inpvec[16]) = {
/* 0 */ { 0.0, 0.0, 0.0, 0.0},
/* 1 */ { 1.0, 0.0, 0.0, 0.0},
/* 2 */ { 0.0, 1.0, 0.0, 0.0},
/* 3 */ { 0.0, 0.0, 1.0, 0.0},
/* 4 */ { 0.0, 0.0, 1.0, 0.5},
/* 5 */ { 0.0, 0.0, 1.0, 1.0},
/* 6 */ { 0.0, 0.0, 1.0, 1.5},
/* 7 */ { 0.0, 0.0, 1.0, 2.0},
/* 8 */ { 0.0, 0.0, 1.0, 2.5},
/* 9 */ { 0.0, 0.0, 1.0, 3.0},
/* 10 */ { 0.0, 0.0, 1.0, 3.5},
/* 11 */ { 0.0, 0.0, 1.0, 4.0},
/* 12 */ { 0.0, 0.0, 1.0, 4.5},
/* 13 */ { 0.0, 0.0, 1.0, 5.0},
/* 14 */ { 0.0, 0.0, 1.0, 5.5},
/* 15 */ { 0.0, 0.0, 1.0, 6.0}};
SSE_ALIGN(static float_vec_aligned inpvecb[16]) = {
/* 0 */ { 0.0, 0.0, 0.0, 0.0},
/* 1 */ { 1.0, 0.0, 0.0, 0.0},
/* 2 */ { 1.0, 1.0, 0.0, 0.0},
/* 3 */ { 1.0, 1.0, 1.0, 0.0},
/* 4 */ { 1.0, 1.0, 1.0, 0.5},
/* 5 */ { 1.0, 1.0, 1.0, 1.0},
/* 6 */ { 1.0, 1.0, 1.0, 1.5},
/* 7 */ { 1.0, 1.0, 1.0, 2.0},
/* 8 */ { 1.0, 1.0, 1.0, 2.5},
/* 9 */ { 1.0, 1.0, 1.0, 3.0},
/* 10 */ { 1.0, 1.0, 1.0, 3.5},
/* 11 */ { 1.0, 1.0, 1.0, 4.0},
/* 12 */ { 1.0, 1.0, 1.0, 4.5},
/* 13 */ { 1.0, 1.0, 1.0, 5.0},
/* 14 */ { 1.0, 1.0, 1.0, 5.5},
/* 15 */ { 1.0, 1.0, 1.0, 6.0}};
#if defined(USE_SIMD_INSTRUCTIONS)
extern SIMD_AVX_STACKALIGN void
baseInputs(const TanBoard anBoard, float arInput[])
{
int i = 3;
const unsigned int *pB = &anBoard[0][0];
float *pInput = &arInput[0];
#if defined(HAVE_SSE)
__m128 vec0;
__m128 vec1;
__m128 vec2;
__m128 vec3;
__m128 vec4;
__m128 vec5;
__m128 vec6;
__m128 vec7;
#elif defined(HAVE_NEON)
float32x4_t vec0;
float32x4_t vec1;
float32x4_t vec2;
float32x4_t vec3;
float32x4_t vec4;
float32x4_t vec5;
float32x4_t vec6;
float32x4_t vec7;
#endif
while (i--) {
#if defined(HAVE_SSE)
vec0 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput, vec0);
vec1 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec1);
vec2 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec2);
vec3 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec3);
vec4 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec4);
vec5 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec5);
vec6 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec6);
vec7 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec7);
#else
vec0 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput, vec0);
vec1 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec1);
vec2 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec2);
vec3 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec3);
vec4 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec4);
vec5 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec5);
vec6 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec6);
vec7 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec7);
#endif
pInput += 4;
}
/* bar */
#if defined(HAVE_SSE)
vec0 = _mm_load_ps(inpvecb[*pB++]);
_mm_store_ps(pInput, vec0);
#else
vec0 = vld1q_f32(inpvecb[*pB++]);
vst1q_f32(pInput, vec0);
#endif
pInput += 4;
i = 3;
while (i--) {
#if defined(HAVE_SSE)
vec0 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput, vec0);
vec1 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec1);
vec2 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec2);
vec3 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec3);
vec4 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec4);
vec5 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec5);
vec6 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec6);
vec7 = _mm_load_ps(inpvec[*pB++]);
_mm_store_ps(pInput += 4, vec7);
#else
vec0 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput, vec0);
vec1 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec1);
vec2 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec2);
vec3 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec3);
vec4 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec4);
vec5 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec5);
vec6 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec6);
vec7 = vld1q_f32(inpvec[*pB++]);
vst1q_f32(pInput += 4, vec7);
#endif
pInput += 4;
}
/* bar */
#if defined(HAVE_SSE)
vec0 = _mm_load_ps(inpvecb[*pB]);
_mm_store_ps(pInput, vec0);
#else
vec0 = vld1q_f32(inpvecb[*pB++]);
vst1q_f32(pInput, vec0);
#endif
return;
}
#else
extern void
baseInputs(const TanBoard anBoard, float arInput[])
{
int j, i;
for (j = 0; j < 2; ++j) {
float *afInput = arInput + j * 25 * 4;
const unsigned int *board = anBoard[j];
/* Points */
for (i = 0; i < 24; i++) {
const unsigned int nc = board[i];
afInput[i * 4 + 0] = inpvec[nc][0];
afInput[i * 4 + 1] = inpvec[nc][1];
afInput[i * 4 + 2] = inpvec[nc][2];
afInput[i * 4 + 3] = inpvec[nc][3];
}
/* Bar */
{
const unsigned int nc = board[24];
afInput[24 * 4 + 0] = inpvecb[nc][0];
afInput[24 * 4 + 1] = inpvecb[nc][1];
afInput[24 * 4 + 2] = inpvecb[nc][2];
afInput[24 * 4 + 3] = inpvecb[nc][3];
}
}
}
#endif
+145
View File
@@ -0,0 +1,145 @@
/*
* -----------------------------------------------------------------------------
* rand.c: By Bob Jenkins. My random number generator, ISAAC. Public Domain.
* MODIFIED:
* 960327: Creation (addition of randinit, really)
* 970719: use context, not global variables, for internal state
* 980324: added main (ifdef'ed out), also rearranged randinit()
* 010626: Note that this is public domain
* -----------------------------------------------------------------------------
*/
/*
* Minor modifications for use with GNU Backgammon.
* Copyright (C) 1999-2007 the AUTHORS
*
* $Id: isaac.c,v 1.8 2013/06/16 02:16:24 mdpetch Exp $
*/
/*lint --e{661,662} Ignore warnings of out of bounds array access (as ok) */
/* No configuration used in this file
* #include "config.h" */
#include "isaac.h"
#define ind(mm,x) ((mm)[((x)>>2)&(RANDSIZ-1)])
#define rngstep(mix,a,b,mm,m,m2,r,x) \
{ \
x = *m; \
a = ((a^(mix)) + *(m2++)) & 0xffffffff; \
*(m++) = y = (ind(mm,x) + a + b) & 0xffffffff; \
*(r++) = b = (ind(mm,y>>RANDSIZL) + x) & 0xffffffff; \
}
void
isaac(randctx * ctx)
{
register ub4 a, b, x, y, *m, *mm, *m2, *r, *mend;
mm = ctx->randmem;
r = ctx->randrsl;
a = ctx->randa;
b = (ctx->randb + (++ctx->randc)) & 0xffffffff;
for (m = mm, mend = m2 = m + (RANDSIZ / 2); m < mend;) {
rngstep(a << 13, a, b, mm, m, m2, r, x);
rngstep(a >> 6, a, b, mm, m, m2, r, x);
rngstep(a << 2, a, b, mm, m, m2, r, x);
rngstep(a >> 16, a, b, mm, m, m2, r, x);
}
for (m2 = mm; m2 < mend;) {
rngstep(a << 13, a, b, mm, m, m2, r, x);
rngstep(a >> 6, a, b, mm, m, m2, r, x);
rngstep(a << 2, a, b, mm, m, m2, r, x);
rngstep(a >> 16, a, b, mm, m, m2, r, x);
}
ctx->randb = b;
ctx->randa = a;
}
#define mix(a,b,c,d,e,f,g,h) \
{ \
a^=b<<11; d+=a; b+=c; \
b^=c>>2; e+=b; c+=d; \
c^=d<<8; f+=c; d+=e; \
d^=e>>16; g+=d; e+=f; \
e^=f<<10; h+=e; f+=g; \
f^=g>>4; a+=f; g+=h; \
g^=h<<8; b+=g; h+=a; \
h^=a>>9; c+=h; a+=b; \
}
/* if (flag==TRUE), then use the contents of randrsl[] to initialize mm[]. */
void
irandinit(randctx * ctx, word flag)
{
word i;
ub4 a, b, c, d, e, f, g, h;
ub4 *m, *r;
ctx->randa = ctx->randb = ctx->randc = 0;
m = ctx->randmem;
r = ctx->randrsl;
a = b = c = d = e = f = g = h = 0x9e3779b9; /* the golden ratio */
for (i = 0; i < 4; ++i) { /* scramble it */
mix(a, b, c, d, e, f, g, h);
}
if (flag) {
/* initialize using the contents of r[] as the seed */
for (i = 0; i < RANDSIZ; i += 8) {
a += r[i];
b += r[i + 1];
c += r[i + 2];
d += r[i + 3];
e += r[i + 4];
f += r[i + 5];
g += r[i + 6];
h += r[i + 7];
mix(a, b, c, d, e, f, g, h);
m[i] = a;
m[i + 1] = b;
m[i + 2] = c;
m[i + 3] = d;
m[i + 4] = e;
m[i + 5] = f;
m[i + 6] = g;
m[i + 7] = h;
}
/* do a second pass to make all of the seed affect all of m */
for (i = 0; i < RANDSIZ; i += 8) {
a += m[i];
b += m[i + 1];
c += m[i + 2];
d += m[i + 3];
e += m[i + 4];
f += m[i + 5];
g += m[i + 6];
h += m[i + 7];
mix(a, b, c, d, e, f, g, h);
m[i] = a;
m[i + 1] = b;
m[i + 2] = c;
m[i + 3] = d;
m[i + 4] = e;
m[i + 5] = f;
m[i + 6] = g;
m[i + 7] = h;
}
} else {
for (i = 0; i < RANDSIZ; i += 8) {
/* fill in mm[] with messy stuff */
mix(a, b, c, d, e, f, g, h);
m[i] = a;
m[i + 1] = b;
m[i + 2] = c;
m[i + 3] = d;
m[i + 4] = e;
m[i + 5] = f;
m[i + 6] = g;
m[i + 7] = h;
}
}
isaac(ctx); /* fill in the first set of results */
ctx->randcnt = RANDSIZ; /* prepare to use the first set of results */
}
+60
View File
@@ -0,0 +1,60 @@
/*
* -----------------------------------------------------------------------------
* rand.h: definitions for a random number generator
* By Bob Jenkins, 1996, Public Domain
* MODIFIED:
* 960327: Creation (addition of randinit, really)
* 970719: use context, not global variables, for internal state
* 980324: renamed seed to flag
* 980605: recommend RANDSIZL=4 for noncryptography.
* 010626: note this is public domain
* -----------------------------------------------------------------------------
*/
/*
* Minor modifications for use with GNU Backgammon.
* Copyright (C) 1999-2006 the AUTHORS
*
* $Id: isaac.h,v 1.5 2013/06/16 02:16:24 mdpetch Exp $
*/
#include "isaacs.h"
#ifndef ISAAC_H
#define ISAAC_H
#define RANDSIZL (4) /* I recommend 8 for crypto, 4 for simulations */
#define RANDSIZ (1<<RANDSIZL)
/* context of random number generator */
struct randctx {
ub4 randcnt;
ub4 randrsl[RANDSIZ];
ub4 randmem[RANDSIZ];
ub4 randa;
ub4 randb;
ub4 randc;
};
typedef struct randctx randctx;
/*
* ------------------------------------------------------------------------------
* If (flag==TRUE), then use the contents of randrsl[0..RANDSIZ-1] as the seed.
* ------------------------------------------------------------------------------
*/
void irandinit(randctx * r, word flag);
void isaac(randctx * r);
/*
* ------------------------------------------------------------------------------
* Call irand(/o_ randctx *r _o/) to retrieve a single 32-bit random value
* ------------------------------------------------------------------------------
*/
#define irand(r) \
(!(r)->randcnt-- ? \
(isaac(r), (r)->randcnt=RANDSIZ-1, (r)->randrsl[(r)->randcnt]) : \
(r)->randrsl[(r)->randcnt])
#endif
+47
View File
@@ -0,0 +1,47 @@
/*
* -----------------------------------------------------------------------------
* standard.hi: Standard definitions and types, Bob Jenkins
* -----------------------------------------------------------------------------
*/
/*
* Minor modifications for use with GNU Backgammon.
* Copyright (C) 1999-2013 the AUTHORS
*
* $Id: isaacs.h,v 1.5 2013/09/09 21:06:21 plm Exp $
*/
#ifndef ISAACS_H
#define ISAACS_H
#if 0
/* Pre-64bits relics. Most of them are not used anyway */
typedef unsigned long int ub4; /* unsigned 4-byte quantities */
#define UB4MAXVAL 0xffffffff
typedef signed long int sb4;
#define SB4MAXVAL 0x7fffffff
typedef unsigned short int ub2;
#define UB2MAXVAL 0xffff
typedef signed short int sb2;
#define SB2MAXVAL 0x7fff
typedef unsigned char ub1;
#define UB1MAXVAL 0xff
typedef signed char sb1; /* signed 1-byte quantities */
#define SB1MAXVAL 0x7f
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#else
typedef unsigned int uint32_t;
#endif
typedef uint32_t ub4; /* unsigned 4-byte quantities */
#define UB4MAXVAL 0xffffffff
typedef int word; /* fastest type available */
#endif
+75
View File
@@ -0,0 +1,75 @@
/*
* Copyright (C) 1996-2001 Gary Wong <gtw@gnu.org>
* Copyright (C) 2004-2026 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* No configuration used in this file
* #include "config.h" */
#include "list.h"
#include <stddef.h>
#include <stdlib.h>
#include <glib.h>
int
ListCreate(listOLD * pl)
{
pl->plPrev = pl->plNext = pl;
pl->p = NULL;
return 0;
}
listOLD *
ListInsert(listOLD * pl, void *p)
{
listOLD *plNew = g_malloc(sizeof(listOLD));
plNew->p = p;
plNew->plNext = pl;
plNew->plPrev = pl->plPrev;
pl->plPrev = plNew;
plNew->plPrev->plNext = plNew;
return plNew;
}
void
ListDelete(listOLD * pl)
{
pl->plPrev->plNext = pl->plNext;
pl->plNext->plPrev = pl->plPrev;
g_free(pl);
}
void
ListDeleteAll(const listOLD * pl)
{
listOLD *pc = pl->plNext;
while (pc != pl) {
listOLD *pn = pc->plNext;
g_free(pc->p);
ListDelete(pc);
pc = pn;
}
g_assert(ListEmpty(pl));
}
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright (C) 1996-1999 Gary Wong <gtw@gnu.org>
* Copyright (C) 2004-2026 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef LIST_H
#define LIST_H
typedef struct list {
struct list *plPrev;
struct list *plNext;
void *p;
} listOLD;
/* Renamed to listOLD - use GList instead (hopefullly replace existing usage eventually */
extern int ListCreate(listOLD * pl);
extern listOLD *ListInsert(listOLD * pl, void *p);
extern void ListDelete(listOLD * pl);
extern void ListDeleteAll(const listOLD * pl);
static inline int ListEmpty(const listOLD *pl)
{
return ((pl->plNext == pl) && (pl->plPrev == pl));
}
#endif
+407
View File
@@ -0,0 +1,407 @@
/*
* Functions to compute MD5 message digest of files or memory blocks.
* according to the definition of MD5 in RFC 1321 from April 1992,
* by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.
* Copyright (C) 1995, 1996, 1997, 1999, 2000 Free Software Foundation, Inc.
*
* Minor modifications for use with GNU Backgammon.
* Copyright (C) 2000-2019 the AUTHORS
*
* License changed from the GNU LGPL to the GNU GPL (as permitted under Term 3
* of the GNU LGPL) by Gary Wong for distribution with GNU Backgammon.
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: md5.c,v 1.15 2019/12/19 09:23:04 Superfly_Jon Exp $
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include "md5.h"
/* Be conservative and always perform explicit byte-ordering. This is
* redundant on little-endian architectures, but is portable. */
#define WRITE( p, n ) \
( ( (unsigned char *) (p) )[ 0 ] = (unsigned char)( (n) & 0xFF ), \
( (unsigned char *) (p) )[ 1 ] = (unsigned char)(( (n) & 0xFF00 ) >> 8), \
( (unsigned char *) (p) )[ 2 ] = (unsigned char)(( (n) & 0xFF0000 ) >> 16), \
( (unsigned char *) (p) )[ 3 ] = (unsigned char)(( (n) & 0xFF000000 ) >> 24) )
#define READ( p ) \
( ( (const unsigned char *) (p) )[ 0 ] | \
( (unsigned int)((const unsigned char *) (p) )[ 1 ]) << 8 | \
( (unsigned int)((const unsigned char *) (p) )[ 2 ]) << 16 | \
( (unsigned int)((const unsigned char *) (p) )[ 3 ]) << 24 )
/* This array contains the bytes used to pad the buffer to the next
* 64-byte boundary. (RFC 1321, 3.1: Step 1) */
/* But this isn't used ???? */
/*lint -e{785} Ignore not enough initilizer warning */
static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
/* Initialize structure containing state of computation.
* (RFC 1321, 3.3: Step 3) */
void
md5_init_ctx(struct md5_ctx *ctx)
{
ctx->A = 0x67452301;
ctx->B = 0xefcdab89;
ctx->C = 0x98badcfe;
ctx->D = 0x10325476;
ctx->total[0] = ctx->total[1] = 0;
ctx->buflen = 0;
}
/* Put result from CTX in first 16 bytes following RESBUF. The result
* must be in little endian byte order.
*
* IMPORTANT: On some systems it is required that RESBUF is correctly
* aligned for a 32 bits value. */
void *
md5_read_ctx(const struct md5_ctx *ctx, void *resbuf)
{
char *r = (char *) resbuf;
WRITE(r, ctx->A);
WRITE(r + 4, ctx->B);
WRITE(r + 8, ctx->C);
WRITE(r + 12, ctx->D);
return resbuf;
}
/* Process the remaining bytes in the internal buffer and the usual
* prolog according to the standard and write the result to RESBUF.
*
* IMPORTANT: On some systems it is required that RESBUF is correctly
* aligned for a 32 bits value. */
void *
md5_finish_ctx(struct md5_ctx *ctx, void *resbuf)
{
/* Take yet unprocessed bytes into account. */
md5_uint32 bytes = ctx->buflen;
size_t pad;
/* Now count remaining bytes. */
ctx->total[0] += bytes;
if (ctx->total[0] < bytes)
++ctx->total[1];
pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
memcpy(&ctx->buffer[bytes], fillbuf, pad);
/* Put the 64-bit file length in *bits* at the end of the buffer. */
WRITE(ctx->buffer + bytes + pad, ctx->total[0] << 3);
WRITE(ctx->buffer + bytes + pad + 4, ((ctx->total[1] << 3) | (ctx->total[0] >> 29)));
/* Process last bytes. */
md5_process_block(ctx->buffer, bytes + pad + 8, ctx);
return md5_read_ctx(ctx, resbuf);
}
#if 0
/* Compute MD5 message digest for bytes read from STREAM. The
* resulting message digest number will be written into the 16 bytes
* beginning at RESBLOCK. */
int
md5_stream(FILE * stream, void *resblock)
{
/* Important: BLOCKSIZE must be a multiple of 64. */
#define BLOCKSIZE 4096
struct md5_ctx ctx;
char buffer[BLOCKSIZE + 72];
size_t sum;
/* Initialize the computation context. */
md5_init_ctx(&ctx);
/* Iterate over full file contents. */
for (;;) {
/* We read the file in blocks of BLOCKSIZE bytes. One call of the
* computation function processes the whole buffer so that with the
* next round of the loop another block can be read. */
size_t n;
sum = 0;
/* Read block. Take care for partial reads. */
do {
n = fread(buffer + sum, 1, BLOCKSIZE - sum, stream);
sum += n;
}
while (sum < BLOCKSIZE && n != 0);
if (n == 0 && ferror(stream))
return 1;
/* If end of file is reached, end the loop. */
if (n == 0)
break;
/* Process buffer with BLOCKSIZE bytes. Note that
* BLOCKSIZE % 64 == 0
*/
md5_process_block(buffer, BLOCKSIZE, &ctx);
}
/* Add the last bytes if necessary. */
if (sum > 0)
md5_process_bytes(buffer, sum, &ctx);
/* Construct result in desired memory. */
md5_finish_ctx(&ctx, resblock);
return 0;
}
#endif
/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The
* result is always in little endian byte order, so that a byte-wise
* output yields to the wanted ASCII representation of the message
* digest. */
void *
md5_buffer(const char *buffer, size_t len, void *resblock)
{
struct md5_ctx ctx;
/* Initialize the computation context. */
md5_init_ctx(&ctx);
/* Process whole buffer but last len % 64 bytes. */
md5_process_bytes(buffer, len, &ctx);
/* Put result in desired memory area. */
return md5_finish_ctx(&ctx, resblock);
}
void
md5_process_bytes(const void *buffer, size_t len, struct md5_ctx *ctx)
{
/* const void aligned_buffer = buffer; */
/* When we already have some bits in our internal buffer concatenate
* both inputs first. */
if (ctx->buflen != 0) {
size_t left_over = ctx->buflen;
size_t add = 128 - left_over > len ? len : 128 - left_over;
/* Only put full words in the buffer. */
add -= add % sizeof(md5_uint32);
memcpy(&ctx->buffer[left_over], buffer, add);
ctx->buflen += (md5_uint32) add;
if (ctx->buflen > 64) {
md5_process_block(ctx->buffer, ctx->buflen & ~63U, ctx);
ctx->buflen &= 63;
/* The regions in the following copy operation cannot overlap. */
memcpy(ctx->buffer, &ctx->buffer[(left_over + add) & ~63U], ctx->buflen);
}
buffer = (const char *) buffer + add;
len -= add;
}
/* Process available complete blocks. */
if (len > 64) {
md5_process_block(buffer, len & ~63U, ctx);
buffer = (const char *) buffer + (len & ~63U);
len &= 63;
}
/* Move remaining bytes in internal buffer. */
if (len > 0) {
size_t left_over = ctx->buflen;
memcpy(&ctx->buffer[left_over], buffer, len);
left_over += len;
if (left_over >= 64) {
md5_process_block(ctx->buffer, 64, ctx);
left_over -= 64;
memcpy(ctx->buffer, &ctx->buffer[64], left_over);
}
ctx->buflen = (md5_uint32) left_over;
}
}
/* These are the four functions used in the four steps of the MD5 algorithm
* and defined in the RFC 1321. The first function is a little bit optimized
* (as found in Colin Plumbs public domain implementation). */
/* #define FF(b, c, d) ((b & c) | (~b & d)) */
#define FF(b, c, d) (d ^ (b & (c ^ d)))
#define FG(b, c, d) FF (d, b, c)
#define FH(b, c, d) (b ^ c ^ d)
#define FI(b, c, d) (c ^ (b | ~d))
/* Process LEN bytes of BUFFER, accumulating context into CTX.
* It is assumed that LEN % 64 == 0. */
void
md5_process_block(const void *buffer, size_t len, struct md5_ctx *ctx)
{
md5_uint32 correct_words[16];
const md5_uint32 *words = (const md5_uint32 *) buffer;
size_t nwords = len / sizeof(md5_uint32);
const md5_uint32 *endp = words + nwords;
md5_uint32 A = ctx->A;
md5_uint32 B = ctx->B;
md5_uint32 C = ctx->C;
md5_uint32 D = ctx->D;
/* First increment the byte count. RFC 1321 specifies the possible
* length of the file up to 2^64 bits. Here we only compute the
* number of bytes. Do a double word increment. */
ctx->total[0] += (md5_uint32) len;
if (ctx->total[0] < len)
++ctx->total[1];
/* Process all bytes in the buffer with 64 bytes in each round of
* the loop. */
while (words < endp) {
md5_uint32 *cwp = correct_words;
md5_uint32 A_save = A;
md5_uint32 B_save = B;
md5_uint32 C_save = C;
md5_uint32 D_save = D;
/* First round: using the given function, the context and a constant
* the next context is computed. Because the algorithms processing
* unit is a 32-bit word and it is determined to work on words in
* little endian byte order we perhaps have to change the byte order
* before the computation. To reduce the work for the next steps
* we store the swapped words in the array CORRECT_WORDS. */
#define OP(a, b, c, d, s, T) \
do \
{ \
a += FF (b, c, d) + (*cwp++ = READ (words)) + T; \
++words; \
CYCLIC (a, s); \
a += b; \
} \
while (0)
/* It is unfortunate that C does not provide an operator for
* cyclic rotation. Hope the C compiler is smart enough. */
#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
/* Before we start, one word to the strange constants.
* They are defined in RFC 1321 as
*
* T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
*/
/* Round 1. */
/*lint --e{123, 717} Ignore macro warning */
OP(A, B, C, D, 7, 0xd76aa478);
OP(D, A, B, C, 12, 0xe8c7b756);
OP(C, D, A, B, 17, 0x242070db);
OP(B, C, D, A, 22, 0xc1bdceee);
OP(A, B, C, D, 7, 0xf57c0faf);
OP(D, A, B, C, 12, 0x4787c62a);
OP(C, D, A, B, 17, 0xa8304613);
OP(B, C, D, A, 22, 0xfd469501);
OP(A, B, C, D, 7, 0x698098d8);
OP(D, A, B, C, 12, 0x8b44f7af);
OP(C, D, A, B, 17, 0xffff5bb1);
OP(B, C, D, A, 22, 0x895cd7be);
OP(A, B, C, D, 7, 0x6b901122);
OP(D, A, B, C, 12, 0xfd987193);
OP(C, D, A, B, 17, 0xa679438e);
OP(B, C, D, A, 22, 0x49b40821);
/* For the second to fourth round we have the possibly swapped words
* in CORRECT_WORDS. Redefine the macro to take an additional first
* argument specifying the function to use. */
#undef OP
#define OP(f, a, b, c, d, k, s, T) \
do \
{ \
a += f (b, c, d) + correct_words[k] + T; \
CYCLIC (a, s); \
a += b; \
} \
while (0)
/* Round 2. */
OP(FG, A, B, C, D, 1, 5, 0xf61e2562);
OP(FG, D, A, B, C, 6, 9, 0xc040b340);
OP(FG, C, D, A, B, 11, 14, 0x265e5a51);
OP(FG, B, C, D, A, 0, 20, 0xe9b6c7aa);
OP(FG, A, B, C, D, 5, 5, 0xd62f105d);
OP(FG, D, A, B, C, 10, 9, 0x02441453);
OP(FG, C, D, A, B, 15, 14, 0xd8a1e681);
OP(FG, B, C, D, A, 4, 20, 0xe7d3fbc8);
OP(FG, A, B, C, D, 9, 5, 0x21e1cde6);
OP(FG, D, A, B, C, 14, 9, 0xc33707d6);
OP(FG, C, D, A, B, 3, 14, 0xf4d50d87);
OP(FG, B, C, D, A, 8, 20, 0x455a14ed);
OP(FG, A, B, C, D, 13, 5, 0xa9e3e905);
OP(FG, D, A, B, C, 2, 9, 0xfcefa3f8);
OP(FG, C, D, A, B, 7, 14, 0x676f02d9);
OP(FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
/* Round 3. */
OP(FH, A, B, C, D, 5, 4, 0xfffa3942);
OP(FH, D, A, B, C, 8, 11, 0x8771f681);
OP(FH, C, D, A, B, 11, 16, 0x6d9d6122);
OP(FH, B, C, D, A, 14, 23, 0xfde5380c);
OP(FH, A, B, C, D, 1, 4, 0xa4beea44);
OP(FH, D, A, B, C, 4, 11, 0x4bdecfa9);
OP(FH, C, D, A, B, 7, 16, 0xf6bb4b60);
OP(FH, B, C, D, A, 10, 23, 0xbebfbc70);
OP(FH, A, B, C, D, 13, 4, 0x289b7ec6);
OP(FH, D, A, B, C, 0, 11, 0xeaa127fa);
OP(FH, C, D, A, B, 3, 16, 0xd4ef3085);
OP(FH, B, C, D, A, 6, 23, 0x04881d05);
OP(FH, A, B, C, D, 9, 4, 0xd9d4d039);
OP(FH, D, A, B, C, 12, 11, 0xe6db99e5);
OP(FH, C, D, A, B, 15, 16, 0x1fa27cf8);
OP(FH, B, C, D, A, 2, 23, 0xc4ac5665);
/* Round 4. */
OP(FI, A, B, C, D, 0, 6, 0xf4292244);
OP(FI, D, A, B, C, 7, 10, 0x432aff97);
OP(FI, C, D, A, B, 14, 15, 0xab9423a7);
OP(FI, B, C, D, A, 5, 21, 0xfc93a039);
OP(FI, A, B, C, D, 12, 6, 0x655b59c3);
OP(FI, D, A, B, C, 3, 10, 0x8f0ccc92);
OP(FI, C, D, A, B, 10, 15, 0xffeff47d);
OP(FI, B, C, D, A, 1, 21, 0x85845dd1);
OP(FI, A, B, C, D, 8, 6, 0x6fa87e4f);
OP(FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
OP(FI, C, D, A, B, 6, 15, 0xa3014314);
OP(FI, B, C, D, A, 13, 21, 0x4e0811a1);
OP(FI, A, B, C, D, 4, 6, 0xf7537e82);
OP(FI, D, A, B, C, 11, 10, 0xbd3af235);
OP(FI, C, D, A, B, 2, 15, 0x2ad7d2bb);
OP(FI, B, C, D, A, 9, 21, 0xeb86d391);
/* Add the starting values of the context. */
A += A_save;
B += B_save;
C += C_save;
D += D_save;
}
/* Put checksum in context given as argument. */
ctx->A = A;
ctx->B = B;
ctx->C = C;
ctx->D = D;
}
+101
View File
@@ -0,0 +1,101 @@
/*
* Declaration of functions and data types used for MD5 sum computing
* library functions.
* Copyright (C) 1995, 1996, 1997, 1999, 2000 Free Software Foundation, Inc.
*
* Minor modifications for use with GNU Backgammon.
* Copyright (C) 2009-2021 the AUTHORS
*
* License changed from the GNU LGPL to the GNU GPL (as permitted under Term 3
* of the GNU LGPL) by Gary Wong for distribution with GNU Backgammon.
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: md5.h,v 1.8 2014/05/11 15:20:32 plm Exp $
*/
#ifndef MD5_H
#define MD5_H 1
#include <stdint.h>
#include <stdio.h>
#include "common.h"
typedef uint32_t md5_uint32;
/* Structure to save state of computation between the single steps. */
struct md5_ctx {
md5_uint32 A;
md5_uint32 B;
md5_uint32 C;
md5_uint32 D;
md5_uint32 total[2];
md5_uint32 buflen;
char buffer[128] __attribute__ ((__aligned__(__alignof__(md5_uint32))));
};
/*
* The following three functions are build up the low level used in
* the functions `md5_stream' and `md5_buffer'.
*/
/* Initialize structure containing state of computation.
* (RFC 1321, 3.3: Step 3) */
extern void md5_init_ctx(struct md5_ctx *ctx);
/* Starting with the result of former calls of this function (or the
* initialization function) update the context for the next LEN bytes
* starting at BUFFER.
* It is necessary that LEN is a multiple of 64!!! */
extern void md5_process_block(const void *buffer, size_t len, struct md5_ctx *ctx);
/* Starting with the result of former calls of this function (or the
* initialization function) update the context for the next LEN bytes
* starting at BUFFER.
* It is NOT required that LEN is a multiple of 64. */
extern void md5_process_bytes(const void *buffer, size_t len, struct md5_ctx *ctx);
/* Process the remaining bytes in the buffer and put result from CTX
* in first 16 bytes following RESBUF. The result is always in little
* endian byte order, so that a byte-wise output yields to the wanted
* ASCII representation of the message digest.
*
* IMPORTANT: On some systems it is required that RESBUF is correctly
* aligned for a 32 bits value. */
extern void *md5_finish_ctx(struct md5_ctx *ctx, void *resbuf);
/* Put result from CTX in first 16 bytes following RESBUF. The result is
* always in little endian byte order, so that a byte-wise output yields
* to the wanted ASCII representation of the message digest.
*
* IMPORTANT: On some systems it is required that RESBUF is correctly
* aligned for a 32 bits value. */
extern void *md5_read_ctx(const struct md5_ctx *ctx, void *resbuf);
#if 0
/* Compute MD5 message digest for bytes read from STREAM. The
* resulting message digest number will be written into the 16 bytes
* beginning at RESBLOCK. */
extern int md5_stream(FILE * stream, void *resblock);
#endif
/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The
* result is always in little endian byte order, so that a byte-wise
* output yields to the wanted ASCII representation of the message
* digest. */
extern void *md5_buffer(const char *buffer, size_t len, void *resblock);
#endif /* md5.h */
+569
View File
@@ -0,0 +1,569 @@
/*
* Copyright (C) 1998-2002 Gary Wong <gtw@gnu.org>
* Copyright (C) 2000-2019 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "backgammon.h"
#include "common.h"
#include <glib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include "neuralnet.h"
#include "simd.h"
#include "sigmoid.h"
static int
NeuralNetCreate(neuralnet * pnn, unsigned int cInput, unsigned int cHidden,
unsigned int cOutput, float rBetaHidden, float rBetaOutput)
{
pnn->cInput = cInput;
pnn->cHidden = cHidden;
pnn->cOutput = cOutput;
pnn->rBetaHidden = rBetaHidden;
pnn->rBetaOutput = rBetaOutput;
pnn->nTrained = 0;
if ((pnn->arHiddenWeight = sse_malloc(cHidden * cInput * sizeof(float))) == NULL)
return -1;
if ((pnn->arOutputWeight = sse_malloc(cOutput * cHidden * sizeof(float))) == NULL) {
sse_free(pnn->arHiddenWeight);
return -1;
}
if ((pnn->arHiddenThreshold = sse_malloc(cHidden * sizeof(float))) == NULL) {
sse_free(pnn->arOutputWeight);
sse_free(pnn->arHiddenWeight);
return -1;
}
if ((pnn->arOutputThreshold = sse_malloc(cOutput * sizeof(float))) == NULL) {
sse_free(pnn->arHiddenThreshold);
sse_free(pnn->arOutputWeight);
sse_free(pnn->arHiddenWeight);
return -1;
}
return 0;
}
extern void
NeuralNetDestroy(neuralnet * pnn)
{
sse_free(pnn->arHiddenWeight);
pnn->arHiddenWeight = 0;
sse_free(pnn->arOutputWeight);
pnn->arOutputWeight = 0;
sse_free(pnn->arHiddenThreshold);
pnn->arHiddenThreshold = 0;
sse_free(pnn->arOutputThreshold);
pnn->arOutputThreshold = 0;
}
#if !defined(USE_SIMD_INSTRUCTIONS)
/* separate context for race, crashed, contact
* -1: regular eval
* 0: save base
* 1: from base
*/
static inline NNEvalType
NNevalAction(NNState * pnState)
{
if (!pnState)
return NNEVAL_NONE;
switch (pnState->state) {
case NNSTATE_NONE:
{
/* incremental evaluation not useful */
return NNEVAL_NONE;
}
case NNSTATE_INCREMENTAL:
{
/* next call should return FROMBASE */
pnState->state = NNSTATE_DONE;
/* starting a new context; save base in the hope it will be useful */
return NNEVAL_SAVE;
}
case NNSTATE_DONE:
{
/* context hit! use the previously computed base */
return NNEVAL_FROMBASE;
}
}
/* never reached */
return NNEVAL_NONE; /* for the picky compiler */
}
static void
Evaluate(const neuralnet * pnn, const float arInput[], float ar[], float arOutput[], float *saveAr)
{
const unsigned int cHidden = pnn->cHidden;
unsigned int i, j;
float *prWeight;
/* Calculate activity at hidden nodes */
for (i = 0; i < cHidden; i++)
ar[i] = pnn->arHiddenThreshold[i];
prWeight = pnn->arHiddenWeight;
for (i = 0; i < pnn->cInput; i++) {
float const ari = arInput[i];
if (ari == 0.0f)
prWeight += cHidden;
else {
float *pr = ar;
if (ari == 1.0f)
for (j = cHidden; j; j--)
*pr++ += *prWeight++;
else
for (j = cHidden; j; j--)
*pr++ += *prWeight++ * ari;
}
}
if (saveAr)
memcpy(saveAr, ar, cHidden * sizeof(*saveAr));
for (i = 0; i < cHidden; i++)
ar[i] = sigmoid(-pnn->rBetaHidden * ar[i]);
/* Calculate activity at output nodes */
prWeight = pnn->arOutputWeight;
for (i = 0; i < pnn->cOutput; i++) {
float r = pnn->arOutputThreshold[i];
for (j = 0; j < cHidden; j++)
r += ar[j] * *prWeight++;
arOutput[i] = sigmoid(-pnn->rBetaOutput * r);
}
}
static void
EvaluateFromBase(const neuralnet * pnn, const float arInputDif[], float ar[], float arOutput[])
{
unsigned int i, j;
float *prWeight;
/* Calculate activity at hidden nodes */
/* for( i = 0; i < pnn->cHidden; i++ )
* ar[ i ] = pnn->arHiddenThreshold[ i ]; */
prWeight = pnn->arHiddenWeight;
for (i = 0; i < pnn->cInput; ++i) {
float const ari = arInputDif[i];
if (ari == 0.0f)
prWeight += pnn->cHidden;
else {
float *pr = ar;
if (ari == 1.0f)
for (j = pnn->cHidden; j; j--)
*pr++ += *prWeight++;
else if (ari == -1.0f)
for (j = pnn->cHidden; j; j--)
*pr++ -= *prWeight++;
else
for (j = pnn->cHidden; j; j--)
*pr++ += *prWeight++ * ari;
}
}
for (i = 0; i < pnn->cHidden; i++)
ar[i] = sigmoid(-pnn->rBetaHidden * ar[i]);
/* Calculate activity at output nodes */
prWeight = pnn->arOutputWeight;
for (i = 0; i < pnn->cOutput; i++) {
float r = pnn->arOutputThreshold[i];
for (j = 0; j < pnn->cHidden; j++)
r += ar[j] * *prWeight++;
arOutput[i] = sigmoid(-pnn->rBetaOutput * r);
}
}
extern int
NeuralNetEvaluate(const neuralnet * pnn, float arInput[], float arOutput[], NNState * pnState)
{
float *ar = (float *) g_alloca(pnn->cHidden * sizeof(float));
switch (NNevalAction(pnState)) {
case NNEVAL_NONE:
{
Evaluate(pnn, arInput, ar, arOutput, 0);
break;
}
case NNEVAL_SAVE:
{
pnState->cSavedIBase = pnn->cInput;
memcpy(pnState->savedIBase, arInput, pnn->cInput * sizeof(*ar));
Evaluate(pnn, arInput, ar, arOutput, pnState->savedBase);
break;
}
case NNEVAL_FROMBASE:
{
if (pnState->cSavedIBase != pnn->cInput) {
Evaluate(pnn, arInput, ar, arOutput, 0);
break;
}
memcpy(ar, pnState->savedBase, pnn->cHidden * sizeof(*ar));
{
float *r = arInput;
float *s = pnState->savedIBase;
unsigned int i;
for (i = 0; i < pnn->cInput; ++i, ++r, ++s) {
if (*r != *s /*lint --e(777) */ ) {
*r -= *s;
} else {
*r = 0.0;
}
}
}
EvaluateFromBase(pnn, arInput, ar, arOutput);
break;
}
}
return 0;
}
#endif
extern int
NeuralNetLoad(neuralnet * pnn, FILE * pf)
{
unsigned int i;
float *pr;
char dummy[16];
if (fscanf(pf, "%u %u %u %15s %f %f\n", &pnn->cInput, &pnn->cHidden,
&pnn->cOutput, dummy, &pnn->rBetaHidden,
&pnn->rBetaOutput) < 5 || pnn->cInput < 1 ||
pnn->cHidden < 1 || pnn->cOutput < 1 || pnn->rBetaHidden <= 0.0f || pnn->rBetaOutput <= 0.0f) {
errno = EINVAL;
return -1;
}
if (NeuralNetCreate(pnn, pnn->cInput, pnn->cHidden, pnn->cOutput, pnn->rBetaHidden, pnn->rBetaOutput))
return -1;
pnn->nTrained = 1;
for (i = pnn->cInput * pnn->cHidden, pr = pnn->arHiddenWeight; i; i--)
if (fscanf(pf, "%f\n", pr++) < 1)
return -1;
for (i = pnn->cHidden * pnn->cOutput, pr = pnn->arOutputWeight; i; i--)
if (fscanf(pf, "%f\n", pr++) < 1)
return -1;
for (i = pnn->cHidden, pr = pnn->arHiddenThreshold; i; i--)
if (fscanf(pf, "%f\n", pr++) < 1)
return -1;
for (i = pnn->cOutput, pr = pnn->arOutputThreshold; i; i--)
if (fscanf(pf, "%f\n", pr++) < 1)
return -1;
return 0;
}
extern int
NeuralNetLoadBinary(neuralnet * pnn, FILE * pf)
{
int dummy;
#define FREAD( p, c ) \
if ( fread( (p), sizeof( *(p) ), (c), pf ) < (unsigned int)(c) ) return -1
FREAD(&pnn->cInput, 1);
FREAD(&pnn->cHidden, 1);
FREAD(&pnn->cOutput, 1);
FREAD(&dummy, 1);
FREAD(&pnn->rBetaHidden, 1);
FREAD(&pnn->rBetaOutput, 1);
if (pnn->cInput < 1 || pnn->cHidden < 1 || pnn->cOutput < 1 || pnn->rBetaHidden <= 0.0f || pnn->rBetaOutput <= 0.0f) {
errno = EINVAL;
return -1;
}
if (NeuralNetCreate(pnn, pnn->cInput, pnn->cHidden, pnn->cOutput, pnn->rBetaHidden, pnn->rBetaOutput))
return -1;
pnn->nTrained = 1;
FREAD(pnn->arHiddenWeight, pnn->cInput * pnn->cHidden);
FREAD(pnn->arOutputWeight, pnn->cHidden * pnn->cOutput);
FREAD(pnn->arHiddenThreshold, pnn->cHidden);
FREAD(pnn->arOutputThreshold, pnn->cOutput);
#undef FREAD
return 0;
}
extern int
NeuralNetSaveBinary(const neuralnet * pnn, FILE * pf)
{
#define FWRITE( p, c ) \
if ( fwrite( (p), sizeof( *(p) ), (c), pf ) < (unsigned int)(c) ) return -1
FWRITE(&pnn->cInput, 1);
FWRITE(&pnn->cHidden, 1);
FWRITE(&pnn->cOutput, 1);
FWRITE(&pnn->nTrained, 1);
FWRITE(&pnn->rBetaHidden, 1);
FWRITE(&pnn->rBetaOutput, 1);
FWRITE(pnn->arHiddenWeight, pnn->cInput * pnn->cHidden);
FWRITE(pnn->arOutputWeight, pnn->cHidden * pnn->cOutput);
FWRITE(pnn->arHiddenThreshold, pnn->cHidden);
FWRITE(pnn->arOutputThreshold, pnn->cOutput);
#undef FWRITE
return 0;
}
#if defined(USE_SIMD_INSTRUCTIONS)
#if defined(DISABLE_SIMD_TEST)
int
SIMD_Supported(void)
{
return 1;
}
#else
#if defined(HAVE_SSE)
#if defined(__APPLE__) || defined(__FreeBSD__)
#include <sys/sysctl.h>
#endif
static int
check_for_cpuid(void)
{
int result;
__asm__ __volatile__ (
#if defined(ENVIRONMENT32) && defined(__PIC__)
/* We have to be careful to not destroy ebx if using PIC on 32bit builds */
"pushl %%ebx\n\t"
#endif
"mov $1, %%eax\n\t"
"shl $21, %%eax\n\t"
"mov %%eax, %%edx\n\t"
#if defined(ENVIRONMENT64)
"pushf\n\t"
"pop %%rax\n\t"
#else
"pushfl\n\t"
"popl %%eax\n\t"
#endif
"mov %%eax, %%ecx\n\t"
"xor %%edx, %%eax\n\t"
#if defined(ENVIRONMENT64)
"push %%rax\n\t"
"popf\n\t"
"pushf\n\t"
"pop %%rax\n\t"
#else
"pushl %%eax\n\t"
"popfl\n\t"
"pushfl\n\t"
"popl %%eax\n\t"
#endif
"xor %%ecx, %%eax\n\t"
"test %%edx, %%eax\n\t"
"jnz cpuid_success\n\t"
/* Failed (non-pentium compatible machine) */
"mov $-1, %%eax\n\t"
"jmp cpuid_finished\n\t"
"cpuid_success:"
"xor %%eax, %%eax\n\t"
"cpuid\n\t"
"cmp $1, %%eax\n\t"
/* If 0 returned processor doesn't hav feature test */
"jge feature_success\n\t"
/* Unlucky - somehow cpuid 1 isn't supported */
"mov $-2, %%eax\n\t"
"jmp cpuid_finished\n\t"
"feature_success:"
"xor %%eax, %%eax\n\t"
"cpuid_finished:"
#if defined(ENVIRONMENT32) && defined(__PIC__)
"popl %%ebx\n\t"
#endif
: "=a"(result)
:
: "%ecx",
#if !defined(ENVIRONMENT32) || !defined(__PIC__)
"%ebx",
#endif
"%edx");
return result;
}
static int
CheckSSE(void)
{
int result;
int cpuidchk;
if ((cpuidchk = check_for_cpuid()) < 0)
return cpuidchk;
#if defined(USE_AVX)
__asm__ __volatile__ (
#if defined(ENVIRONMENT32) && defined(__PIC__)
/* We have to be careful to not destroy ebx if using PIC on 32bit builds */
"pushl %%ebx\n\t"
#endif
"mov $1, %%eax\n\t"
"cpuid\n\t"
"and $0x018000000, %%ecx\n\t" /* Check bit 27 (OS uses XSAVE/XRSTOR)*/
"cmp $0x018000000, %%ecx\n\t" /* and bit 28 (AVX supported by CPU) */
"jne avx_unsupported\n\t"
"xor %%ecx, %%ecx\n\t" /* XFEATURE_ENABLED_MASK/XCR0 register number = 0 */
"xgetbv\n\t" /* XFEATURE_ENABLED_MASK register is in edx:eax" */
"and $0b110, %%eax\n\t"
"cmp $0b110, %%eax\n\t" /* check the AVX registers restore at context switch */
"jne avx_unsupported\n\t"
"mov $1, %%eax\n\t"
"jmp avx_end\n\t"
"avx_unsupported:"
"xor %%eax, %%eax\n\t"
"avx_end:"
#if defined(ENVIRONMENT32) && defined(__PIC__)
"popl %%ebx\n\t"
#endif
: "=a"(result) /* Result returned in result variable */
:
: "%ecx",
#if !defined(ENVIRONMENT32) || !defined(__PIC__)
"%ebx",
#endif
"%edx"); /* These are all destroyed by cpuid */
#else
#if defined(__APPLE__) || defined(__FreeBSD__)
size_t length = sizeof(result);
#if defined(__APPLE__)
int error = sysctlbyname("hw.optional.sse", &result, &length, NULL, 0);
#endif
#if defined(__FreeBSD__)
int error = sysctlbyname("hw.instruction_sse", &result, &length, NULL, 0);
#endif
if (error != 0)
result = 0;
#else
__asm__ __volatile__ (
#if defined(ENVIRONMENT32) && defined(__PIC__)
/* We have to be careful to not destroy ebx if using PIC on I386 */
"pushl %%ebx\n\t"
#endif
/* Check if sse is supported (bit 25/26 in edx from cpuid 1) */
"mov $1, %%eax\n\t"
"cpuid\n\t"
"mov $1, %%eax\n\t"
#if defined(USE_SSE2)
"shl $26, %%eax\n\t"
#else
"shl $25, %%eax\n\t"
#endif
"test %%eax, %%edx\n\t"
"jnz sse_success\n\t"
/* Not supported */
"mov $0, %%eax\n\t"
"jmp sse_end\n\t"
"sse_success:"
/* Supported */
"mov $1, %%eax\n\t"
"sse_end:"
#if defined(ENVIRONMENT32) && defined(__PIC__)
"pop %%ebx\n\t"
#endif
: "=a"(result)
:
: "%ecx",
#if !defined(ENVIRONMENT32) || !defined(__PIC__)
"%ebx",
#endif
"%edx");
#endif /* APPLE/BSD */
#endif /* USE_AVX */
return result;
}
#endif /* HAVE_SSE */
int
SIMD_Supported(void)
{
static int state = -3;
if (state == -3) {
#if defined(HAVE_NEON)
state = CheckNEON();
#elif defined(HAVE_SSE)
state = CheckSSE();
#else
state = -2;
#endif
}
return state;
}
#endif
#endif
+89
View File
@@ -0,0 +1,89 @@
/*
* Copyright (C) 1999-2002 Gary Wong <gtw@gnu.org>
* Copyright (C) 2002-2017 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: neuralnet.h,v 1.36 2020/06/05 20:08:31 plm Exp $
*/
#ifndef NEURALNET_H
#define NEURALNET_H
#include <stdio.h>
#include "common.h"
typedef struct {
unsigned int cInput;
unsigned int cHidden;
unsigned int cOutput;
int nTrained;
float rBetaHidden;
float rBetaOutput;
float *arHiddenWeight;
float *arOutputWeight;
float *arHiddenThreshold;
float *arOutputThreshold;
} neuralnet;
typedef enum {
NNEVAL_NONE,
NNEVAL_SAVE,
NNEVAL_FROMBASE
} NNEvalType;
typedef enum {
NNSTATE_NONE = -1,
NNSTATE_INCREMENTAL,
NNSTATE_DONE
} NNStateType;
typedef struct {
NNStateType state;
float *savedBase;
float *savedIBase;
#if !defined(USE_SIMD_INSTRUCTIONS)
unsigned int cSavedIBase;
#endif
} NNState;
extern void NeuralNetDestroy(neuralnet * pnn);
#if !defined(USE_SIMD_INSTRUCTIONS)
extern int NeuralNetEvaluate(const neuralnet * pnn, float arInput[], float arOutput[], NNState * pnState);
#else
extern int NeuralNetEvaluateSSE(const neuralnet * pnn, float arInput[], float arOutput[], NNState * pnState);
#endif
extern int NeuralNetLoad(neuralnet * pnn, FILE * pf);
extern int NeuralNetLoadBinary(neuralnet * pnn, FILE * pf);
extern int NeuralNetSaveBinary(const neuralnet * pnn, FILE * pf);
extern int SIMD_Supported(void);
/* Try to determine whether we are 64-bit or 32-bit */
#if defined(_WIN32) || defined(_WIN64)
#if defined(_WIN64)
#define ENVIRONMENT64
#else
#define ENVIRONMENT32
#endif
#endif
#if defined(__GNUC__)
#if defined(__x86_64__)
#define ENVIRONMENT64
#else
#define ENVIRONMENT32
#endif
#endif
#endif
+583
View File
@@ -0,0 +1,583 @@
/*
* Copyright (C) 2006-2009 Jon Kinsey <jonkinsey@gmail.com>
* Copyright (C) 2007-2021 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "common.h"
#if defined(USE_SIMD_INSTRUCTIONS)
#define DEBUG_SSE 0
#include "simd.h"
#include "neuralnet.h"
#include <string.h>
#if defined(USE_NEON)
#include <arm_neon.h>
#elif defined(USE_AVX)
#include <immintrin.h>
#elif defined(USE_SSE2)
#include <emmintrin.h>
#else
#include <xmmintrin.h>
#endif
#include <glib.h>
#include "sigmoid.h"
#if defined(HAVE_NEON)
#include <signal.h>
#include <setjmp.h>
#endif
float *
sse_malloc(size_t size)
{
#if defined(HAVE_POSIX_MEMALIGN)
void *ptr = NULL;
int ret;
ret = posix_memalign(&ptr, ALIGN_SIZE, size);
if (ret == 0)
return (float *)ptr;
/* mimic g_malloc() error message */
g_error("%s: failed to allocate %"G_GSIZE_FORMAT" bytes", G_STRLOC, size);
/* avoid warning: "control reaches end of non-void function" for
compilers that don't understand that g_error() never returns. */
return NULL;
#elif defined(HAVE__ALIGNED_MALLOC)
return (float *) _aligned_malloc(size, ALIGN_SIZE);
#else
return (float *) _mm_malloc(size, ALIGN_SIZE);
#endif
}
void
sse_free(float *ptr)
{
#if defined(HAVE_POSIX_MEMALIGN)
free(ptr);
#elif defined(HAVE__ALIGNED_MALLOC)
_aligned_free(ptr);
#else
_mm_free(ptr);
#endif
}
#if defined(HAVE_NEON)
static jmp_buf env;
static void
my_handler(int status)
{
longjmp(env, status);
}
int
CheckNEON(void)
{
void (*oldsig)(int);
oldsig = signal(SIGILL, my_handler);
if (setjmp(env)) {
signal(SIGILL, oldsig);
return 0;
} else {
#if defined(__ARM_FEATURE_SIMD32)
/* v7 */
__asm__ __volatile__ ("vmin.f32 q0, q0, q0");
#elif defined(__ARM_NEON)
/* aarch64 */
__asm__ __volatile__ ("fmin v0.4s, v0.4s, v0.4s");
#else
#error "Unexpected ARM architecture"
#endif
signal(SIGILL, oldsig);
return 1;
}
}
#endif
#if defined(USE_AVX) || defined(USE_SSE2) || defined(USE_NEON)
#include <stdint.h>
static const union {
float f[VEC_SIZE];
float_vector ps;
#if defined(USE_AVX)
} ones = { {
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}};
#else
} ones = { {
1.0f, 1.0f, 1.0f, 1.0f}};
#endif
static const union {
float f[VEC_SIZE];
float_vector ps;
#if defined(USE_AVX)
} tens = { {
10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 10.0f, 10.0f}};
#else
} tens = { {
10.0f, 10.0f, 10.0f, 10.0f}};
#endif
static const union {
int32_t i32[VEC_SIZE];
float_vector ps;
#if defined(USE_AVX)
} abs_mask = { {
0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF}};
#else
} abs_mask = { {
0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF}};
#endif
static inline float_vector
sigmoid_positive_ps(float_vector xin)
{
union {
int_vector i;
int32_t i32[VEC_SIZE];
} i;
float_vector ex;
float *ex_elem = (float *) &ex;
#if defined(USE_AVX)
float_vector x1 = _mm256_min_ps(xin, tens.ps);
#elif defined(HAVE_SSE)
float_vector x1 = _mm_min_ps(xin, tens.ps);
#else
float_vector x1 = vminq_f32(xin, tens.ps);
float_vector rec;
#endif
#if defined(USE_AVX)
x1 = _mm256_mul_ps(x1, tens.ps);
i.i = _mm256_cvttps_epi32(x1);
#elif defined(HAVE_SSE)
x1 = _mm_mul_ps(x1, tens.ps);
i.i = _mm_cvttps_epi32(x1);
#else
x1 = vmulq_f32(x1, tens.ps);
i.i = vcvtq_s32_f32(x1);
#endif
ex_elem[0] = e[i.i32[0]];
ex_elem[1] = e[i.i32[1]];
ex_elem[2] = e[i.i32[2]];
ex_elem[3] = e[i.i32[3]];
#if defined(USE_AVX)
ex_elem[4] = e[i.i32[4]];
ex_elem[5] = e[i.i32[5]];
ex_elem[6] = e[i.i32[6]];
ex_elem[7] = e[i.i32[7]];
#endif
#if defined(USE_AVX)
x1 = _mm256_sub_ps(x1, _mm256_cvtepi32_ps(i.i));
x1 = _mm256_add_ps(x1, tens.ps);
#if defined(USE_FMA3)
x1 = _mm256_fmadd_ps(x1, ex, ones.ps);
#else
x1 = _mm256_mul_ps(x1, ex);
x1 = _mm256_add_ps(x1, ones.ps);
#endif
#ifdef __FAST_MATH__
return _mm256_rcp_ps(x1);
#else
return _mm256_div_ps(ones.ps, x1);
#endif
#elif defined(HAVE_SSE)
x1 = _mm_sub_ps(x1, _mm_cvtepi32_ps(i.i));
x1 = _mm_add_ps(x1, tens.ps);
x1 = _mm_mul_ps(x1, ex);
x1 = _mm_add_ps(x1, ones.ps);
#ifdef __FAST_MATH__
return _mm_rcp_ps(x1);
#else
return _mm_div_ps(ones.ps, x1);
#endif
#else
x1 = vsubq_f32(x1, vcvtq_f32_s32(i.i));
x1 = vaddq_f32(x1, tens.ps);
x1 = vmulq_f32(x1, ex);
x1 = vaddq_f32(x1, ones.ps);
/* TODO: Check how many Newton-Raphson iterations are needed to match x86 rcp and div accuracy */
#ifdef __FAST_MATH__
rec = vrecpeq_f32(x1);
return vmulq_f32(vrecpsq_f32(x1, rec), rec);
#else
rec = vrecpeq_f32(x1);
rec = vmulq_f32(vrecpsq_f32(x1, rec), rec);
return vmulq_f32(vrecpsq_f32(x1, rec), rec);
#endif
#endif
}
static inline float_vector
sigmoid_ps(float_vector xin)
{
#if defined(USE_AVX)
float_vector mask = _mm256_cmp_ps(xin, _mm256_setzero_ps(), _CMP_LT_OS);
float_vector c;
xin = _mm256_and_ps(xin, abs_mask.ps); /* Abs. value by clearing signbit */
c = sigmoid_positive_ps(xin);
return _mm256_blendv_ps(c, _mm256_sub_ps(ones.ps, c), mask);
#elif defined(HAVE_SSE)
float_vector mask = _mm_cmplt_ps(xin, _mm_setzero_ps());
float_vector c;
xin = _mm_and_ps(xin, abs_mask.ps); /* Abs. value by clearing signbit */
c = sigmoid_positive_ps(xin);
/* _mm_blendv_ps() is only available with SSE4.1 or later */
return _mm_or_ps(_mm_andnot_ps(mask, c), _mm_and_ps(mask, _mm_sub_ps(ones.ps, c)));
#else
int_vector mask = (int_vector)vcltq_f32(xin, vdupq_n_f32(0.0f));
float_vector c;
xin = (float_vector)vandq_s32((int_vector)xin, (int_vector)abs_mask.ps); /* Abs. value by clearing signbit */
c = sigmoid_positive_ps(xin);
return vbslq_f32((uint32x4_t)mask, vsubq_f32(ones.ps, c), c);
#endif
}
#endif // USE_SSE2 or USE_AVX
#if defined(USE_SSE2)
#define INPUT_ADD() \
for (j = (cHidden >> LOG2VEC_SIZE); j; j--, pr += VEC_SIZE, prWeight += VEC_SIZE) { \
vec0 = _mm_load_ps(pr); \
vec1 = _mm_load_ps(prWeight); \
sum = _mm_add_ps(vec0, vec1); \
_mm_store_ps(pr, sum); \
}
#define INPUT_MULTADD() \
for (j = (cHidden >> LOG2VEC_SIZE); j; j--, pr += VEC_SIZE, prWeight += VEC_SIZE) { \
vec0 = _mm_load_ps(pr); \
vec1 = _mm_load_ps(prWeight); \
vec3 = _mm_mul_ps(vec1, scalevec); \
sum = _mm_add_ps(vec0, vec3); \
_mm_store_ps(pr, sum); \
}
#endif
#if defined(USE_AVX)
#define INPUT_ADD() \
for (j = (cHidden >> LOG2VEC_SIZE); j; j--, pr += VEC_SIZE, prWeight += VEC_SIZE) { \
vec0 = _mm256_load_ps(pr); \
vec1 = _mm256_load_ps(prWeight); \
sum = _mm256_add_ps(vec0, vec1); \
_mm256_store_ps(pr, sum); \
}
#if defined(USE_FMA3)
#define INPUT_MULTADD() \
for (j = (cHidden >> LOG2VEC_SIZE); j; j--, pr += VEC_SIZE, prWeight += VEC_SIZE) { \
vec0 = _mm256_load_ps(pr); \
vec1 = _mm256_load_ps(prWeight); \
sum = _mm256_fmadd_ps(vec1, scalevec, vec0); \
_mm256_store_ps(pr, sum); \
}
#else
#define INPUT_MULTADD() \
for (j = (cHidden >> LOG2VEC_SIZE); j; j--, pr += VEC_SIZE, prWeight += VEC_SIZE) { \
vec0 = _mm256_load_ps(pr); \
vec1 = _mm256_load_ps(prWeight); \
vec3 = _mm256_mul_ps(vec1, scalevec); \
sum = _mm256_add_ps(vec0, vec3); \
_mm256_store_ps(pr, sum); \
}
#endif
#endif
#if defined(USE_NEON)
#define INPUT_ADD() \
for (j = (cHidden >> LOG2VEC_SIZE); j; j--, pr += VEC_SIZE, prWeight += VEC_SIZE) { \
vec0 = vld1q_f32(pr); \
vec1 = vld1q_f32(prWeight); \
sum = vaddq_f32(vec0, vec1); \
vst1q_f32(pr, sum); \
}
#define INPUT_MULTADD() \
for (j = (cHidden >> LOG2VEC_SIZE); j; j--, pr += VEC_SIZE, prWeight += VEC_SIZE) { \
vec0 = vld1q_f32(pr); \
vec1 = vld1q_f32(prWeight); \
vec3 = vmulq_f32(vec1, scalevec); \
sum = vaddq_f32(vec0, vec3); \
vst1q_f32(pr, sum); \
}
#endif
static void
EvaluateSSE(const neuralnet * restrict pnn, const float arInput[], float ar[], float arOutput[])
{
const unsigned int cHidden = pnn->cHidden;
unsigned int i, j;
float *prWeight;
#if defined(USE_SSE2) || defined(USE_AVX) || defined(USE_NEON)
float *par;
#if defined(USE_FMA3)
float_vector vec0, vec1, scalevec, sum;
#else
float_vector vec0, vec1, vec3, scalevec, sum;
#endif
#endif
/* Calculate activity at hidden nodes */
memcpy(ar, pnn->arHiddenThreshold, cHidden * sizeof(float));
prWeight = pnn->arHiddenWeight;
if (pnn->cInput != 214) { /* everything but the racing net */
for (i = 0; i < 200;) { /* base inputs */
float ari = arInput[i++];
/* 3 binaries, 1 float */
if (likely(ari == 0.0f))
prWeight += cHidden;
else {
float *pr = ar;
INPUT_ADD();
}
ari = arInput[i++];
if (likely(ari == 0.0f))
prWeight += cHidden;
else {
float *pr = ar;
INPUT_ADD();
}
ari = arInput[i++];
if (likely(ari == 0.0f)) {
prWeight += cHidden;
/* If 3rd element is 0, so is 4th. Skip it */
prWeight += cHidden;
i++;
continue;
} else {
float *pr = ar;
INPUT_ADD();
}
ari = arInput[i++];
if (likely(ari == 0.0f))
prWeight += cHidden;
else {
float *pr = ar;
#if defined(USE_FMA3)
scalevec = _mm256_set1_ps(ari);
INPUT_MULTADD();
#elif defined(USE_NEON)
scalevec = vdupq_n_f32(ari);
INPUT_MULTADD();
#else
if (unlikely(ari == 1.0f)) {
INPUT_ADD();
} else {
#if defined(USE_AVX)
scalevec = _mm256_set1_ps(ari);
#elif defined(HAVE_SSE)
scalevec = _mm_set1_ps(ari);
#endif
INPUT_MULTADD();
}
#endif
} /* base inputs are done */
}
if (pnn->cInput == 250) /* Pruning nets are over, contact/crashed still have 2 * 25 floats */
for (i = 200; i < 250; i++) {
float const ari = arInput[i];
if (unlikely(ari == 0.0f))
prWeight += cHidden;
else {
float *pr = ar;
#if defined(USE_AVX)
scalevec = _mm256_set1_ps(ari);
#elif defined(HAVE_SSE)
scalevec = _mm_set1_ps(ari);
#else
scalevec = vdupq_n_f32(ari);
#endif
INPUT_MULTADD();
}
}
}
else /* racing net */
for (i = 0; i < pnn->cInput; i++) {
float const ari = arInput[i];
if (likely(ari == 0.0f))
prWeight += cHidden;
else {
float *pr = ar;
#if defined(USE_FMA3)
scalevec = _mm256_set1_ps(ari);
INPUT_MULTADD();
#elif defined(USE_NEON)
scalevec = vdupq_n_f32(ari);
INPUT_MULTADD();
#else
if (likely(ari == 1.0f)) {
INPUT_ADD();
} else {
#if defined(USE_AVX)
scalevec = _mm256_set1_ps(ari);
#elif defined(HAVE_SSE)
scalevec = _mm_set1_ps(ari);
#endif
INPUT_MULTADD();
}
#endif
}
}
#if defined(USE_SSE2) || defined(USE_AVX) || defined(USE_NEON)
#if defined(USE_AVX)
scalevec = _mm256_set1_ps(-pnn->rBetaHidden);
#elif defined(HAVE_SSE)
scalevec = _mm_set1_ps(-pnn->rBetaHidden);
#else
scalevec = vdupq_n_f32(-pnn->rBetaHidden);
#endif
for (par = ar, i = (cHidden >> LOG2VEC_SIZE); i; i--, par += VEC_SIZE) {
#if defined(USE_AVX)
float_vector vec = _mm256_load_ps(par);
vec = _mm256_mul_ps(vec, scalevec);
vec = sigmoid_ps(vec);
_mm256_store_ps(par, vec);
#elif defined(HAVE_SSE)
float_vector vec = _mm_load_ps(par);
vec = _mm_mul_ps(vec, scalevec);
vec = sigmoid_ps(vec);
_mm_store_ps(par, vec);
#else
float_vector vec = vld1q_f32(par);
vec = vmulq_f32(vec, scalevec);
vec = sigmoid_ps(vec);
vst1q_f32(par, vec);
#endif
}
#else
for (i = 0; i < cHidden; i++)
ar[i] = sigmoid(-pnn->rBetaHidden * ar[i]);
#endif
/* Calculate activity at output nodes */
prWeight = pnn->arOutputWeight;
for (i = 0; i < pnn->cOutput; i++) {
#if defined(USE_AVX)
SSE_ALIGN(float r[8]);
#else
float r;
#endif
float *pr = ar;
#if defined(USE_AVX)
sum = _mm256_setzero_ps();
#elif defined(HAVE_SSE)
sum = _mm_setzero_ps();
#else
sum = vdupq_n_f32(0.0f);
#endif
for (j = (cHidden >> LOG2VEC_SIZE); j; j--, prWeight += VEC_SIZE, pr += VEC_SIZE) {
#if defined(USE_AVX)
vec0 = _mm256_load_ps(pr); /* Eight floats into vec0 */
vec1 = _mm256_load_ps(prWeight); /* Eight weights into vec1 */
#if defined(USE_FMA3)
sum = _mm256_fmadd_ps(vec0, vec1, sum);
#else
vec3 = _mm256_mul_ps(vec0, vec1); /* Multiply */
sum = _mm256_add_ps(sum, vec3); /* Add */
#endif
#elif defined(HAVE_SSE)
vec0 = _mm_load_ps(pr); /* Four floats into vec0 */
vec1 = _mm_load_ps(prWeight); /* Four weights into vec1 */
vec3 = _mm_mul_ps(vec0, vec1); /* Multiply */
sum = _mm_add_ps(sum, vec3); /* Add */
#else
vec0 = vld1q_f32(pr); /* Four floats into vec0 */
vec1 = vld1q_f32(prWeight); /* Four weights into vec1 */
vec3 = vmulq_f32(vec0, vec1); /* Multiply */
sum = vaddq_f32(sum, vec3); /* Add */
#endif
}
#if defined(USE_AVX)
vec0 = _mm256_hadd_ps(sum, sum);
vec1 = _mm256_hadd_ps(vec0, vec0);
_mm256_store_ps(r, vec1);
arOutput[i] = sigmoid(-pnn->rBetaOutput * (r[0] + r[4] + pnn->arOutputThreshold[i]));
#elif defined(HAVE_SSE)
vec0 = _mm_shuffle_ps(sum, sum, _MM_SHUFFLE(2, 3, 0, 1));
vec1 = _mm_add_ps(sum, vec0);
vec0 = _mm_shuffle_ps(vec1, vec1, _MM_SHUFFLE(1, 1, 3, 3));
sum = _mm_add_ps(vec1, vec0);
_mm_store_ss(&r, sum);
arOutput[i] = sigmoid(-pnn->rBetaOutput * (r + pnn->arOutputThreshold[i]));
#else
{
float32x2_t vec0_h, vec0_l, vec1;
vec0_h = vget_high_f32(sum);
vec0_l = vget_low_f32(sum);
vec1 = vpadd_f32(vec0_h, vec0_l);
vec1 = vpadd_f32(vec1, vec1);
vst1_lane_f32(&r, vec1, 0);
arOutput[i] = sigmoid(-pnn->rBetaOutput * (r + pnn->arOutputThreshold[i]));
}
#endif
}
#if defined(USE_AVX)
_mm256_zeroupper();
#endif
}
extern int
NeuralNetEvaluateSSE(const neuralnet * restrict pnn, /*lint -e{818} */ float arInput[],
float arOutput[], NNState * UNUSED(pnState))
{
SSE_ALIGN(float ar[pnn->cHidden]);
#if DEBUG_SSE
g_assert(sse_aligned(arOutput));
g_assert(sse_aligned(ar));
g_assert(sse_aligned(arInput));
#endif
EvaluateSSE(pnn, arInput, ar, arOutput);
return 0;
}
#endif
+168
View File
@@ -0,0 +1,168 @@
/*
* Copyright (C) 1998-2002 Gary Wong <gtw@gnu.org>
* Copyright (C) 2008-2017 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef SIGMOID_H
#define SIGMOID_H
/* e[k] = exp(k/10) / 10 */
static float e[101] = {
0.10000000000000001f,
0.11051709180756478f,
0.12214027581601698f,
0.13498588075760032f,
0.14918246976412702f,
0.16487212707001281f,
0.18221188003905089f,
0.20137527074704767f,
0.22255409284924679f,
0.245960311115695f,
0.27182818284590454f,
0.30041660239464335f,
0.33201169227365473f,
0.36692966676192446f,
0.40551999668446748f,
0.44816890703380646f,
0.49530324243951152f,
0.54739473917271997f,
0.60496474644129461f,
0.66858944422792688f,
0.73890560989306509f,
0.81661699125676512f,
0.90250134994341225f,
0.99741824548147184f,
1.1023176380641602f,
1.2182493960703473f,
1.3463738035001691f,
1.4879731724872838f,
1.6444646771097049f,
1.817414536944306f,
2.0085536923187668f,
2.2197951281441637f,
2.4532530197109352f,
2.7112638920657881f,
2.9964100047397011f,
3.3115451958692312f,
3.6598234443677988f,
4.0447304360067395f,
4.4701184493300818f,
4.9402449105530168f,
5.4598150033144233f,
6.034028759736195f,
6.6686331040925158f,
7.3699793699595784f,
8.1450868664968148f,
9.0017131300521811f,
9.9484315641933776f,
10.994717245212353f,
12.151041751873485f,
13.428977968493552f,
14.841315910257659f,
16.402190729990171f,
18.127224187515122f,
20.033680997479166f,
22.140641620418716f,
24.469193226422039f,
27.042640742615255f,
29.886740096706028f,
33.029955990964865f,
36.503746786532886f,
40.34287934927351f,
44.585777008251675f,
49.274904109325632f,
54.457191012592901f,
60.184503787208222f,
66.514163304436181f,
73.509518924197266f,
81.24058251675433f,
89.784729165041753f,
99.227471560502622f,
109.66331584284585f,
121.19670744925763f,
133.9430764394418f,
148.02999275845451f,
163.59844299959269f,
180.80424144560632f,
199.81958951041173f,
220.83479918872089f,
244.06019776244983f,
269.72823282685101f,
298.09579870417281f,
329.44680752838406f,
364.09503073323521f,
402.38723938223131f,
444.7066747699858f,
491.47688402991344f,
543.16595913629783f,
600.29122172610175f,
663.42440062778894f,
733.19735391559948f,
810.3083927575384f,
895.52927034825075f,
989.71290587439091f,
1093.8019208165192f,
1208.8380730216988f,
1335.9726829661872f,
1476.4781565577266f,
1631.7607198015421f,
1803.3744927828525f,
1993.0370438230298f,
1993.0370438230298f /* one extra :-) */
};
/* Calculate an approximation to the sigmoid function 1 / ( 1 + e^x ).
* This is executed very frequently during neural net evaluation, so
* careful optimisation here pays off.
*
* Statistics on sigmoid(x) calls:
* * >99% of the time, x is positive.
* * 82% of the time, 3 < abs(x) < 8.
*
* 02/2017: The numbers above are 10+ years old
* (old neural nets, possibly pruning nets not yet in use).
* Current stats :
* * 85% of the time, x is positive, comprising 80% < 10 and 20% > 10
* * 15% of the time, x is negative with 99%+ > -10
*/
static inline float
sigmoid(float const xin)
{
if (likely(xin >= 0.0f)) {
/* xin is almost always positive; we place this branch of the `if'
* first, in the hope that the compiler/processor will predict the
* conditional branch will not be taken. */
if (likely(xin < 10.0f)) {
/* again, predict the branch not to be taken */
const float x1 = 10.0f * xin;
const int i = (int) x1;
return 1.0f / (1.0f + e[i] * ((float)(10 - i) + x1));
} else
return 1.0f / 19931.370438230298f;
} else {
if (likely(xin > -10.0f)) {
const float x1 = -10.0f * xin;
const int i = (int) x1;
return 1.0f - 1.0f / (1 + e[i] * ((float)(10 - i) + x1));
} else
return 19930.370438230298f / 19931.370438230298f;
}
}
#endif
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2005-2006 Jon Kinsey <jonkinsey@gmail.com>
* Copyright (C) 2005-2018 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: simd.h,v 1.9 2018/05/12 21:42:33 plm Exp $
*/
#ifndef SIMD_H
#define SIMD_H
#if defined(USE_SIMD_INSTRUCTIONS)
#include <stdlib.h>
#include "common.h"
#if defined(USE_AVX)
#define ALIGN_SIZE 32
#define VEC_SIZE 8
#define LOG2VEC_SIZE 3
#define float_vector __m256
#define int_vector __m256i
#else
#define ALIGN_SIZE 16
#define VEC_SIZE 4
#define LOG2VEC_SIZE 2
#if defined(HAVE_SSE)
#define float_vector __m128
#define int_vector __m128i
#elif defined(HAVE_NEON)
#define float_vector float32x4_t
#define int_vector int32x4_t
/*
* Workaround for clang on raspbian 9
* It defines __ARM_FEATURE_SIMD32 but its arm_neon.h needs __ARM_NEON
*/
#define __ARM_NEON 1
#else
#error "Inconsistent SIMD defines"
#endif
#endif
#if defined(_MSC_VER)
#define SSE_ALIGN(D) __declspec(align(ALIGN_SIZE)) D
#else
#define SSE_ALIGN(D) D __attribute__ ((aligned(ALIGN_SIZE)))
#endif
#if defined(__GNUC__) && defined(WIN32)
/* Align stack pointer on 16 byte boundary so SSE variables work correctly */
#define SIMD_STACKALIGN __attribute__((force_align_arg_pointer))
#if defined(USE_AVX)
#define SIMD_AVX_STACKALIGN __attribute__((force_align_arg_pointer))
#else
#define SIMD_AVX_STACKALIGN
#endif /* USE_AVX */
#else
#define SIMD_STACKALIGN
#define SIMD_AVX_STACKALIGN
#endif /* GNUC/WIN32 */
#define sse_aligned(ar) (!(((size_t)ar) % ALIGN_SIZE))
extern float *sse_malloc(size_t size);
extern void sse_free(float *ptr);
#if defined(HAVE_NEON)
extern int CheckNEON(void);
#endif
#else /* USE_SIMD_INSTRUCTIONS */
#define SSE_ALIGN(D) D
#define sse_malloc malloc
#define sse_free free
#define SIMD_STACKALIGN
#define SIMD_AVX_STACKALIGN
#endif
#endif /* SIMD_H */
File diff suppressed because it is too large Load Diff
+124
View File
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2000-2001 Gary Wong <gtw@gnu.org>
* Copyright (C) 2002-2019 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: makeweights.c,v 1.31 2022/02/02 22:04:12 plm Exp $
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <locale.h>
#include "eval.h" /* for WEIGHTS_VERSION */
#include "output.h"
static void
usage(char *prog)
{
g_printerr(_("Usage: %s [[-f] outputfile [inputfile]]\n"
" outputfile: Output to file instead of stdout\n"
" inputfile: Input from file instead of stdin\n"), prog);
exit(1);
}
extern int
main(int argc, /*lint -e{818} */ char *argv[])
{
neuralnet nn;
char szFileVersion[16];
static float ar[2] = { WEIGHTS_MAGIC_BINARY, WEIGHTS_VERSION_BINARY };
int c;
FILE *in = stdin, *out = stdout;
if (!setlocale(LC_ALL, "C") || !bindtextdomain(PACKAGE, LOCALEDIR) || !textdomain(PACKAGE)) {
perror("setting locale failed");
exit(1);
}
g_set_printerr_handler(print_utf8_to_locale);
if (argc > 1) {
int arg = 1;
if (!StrCaseCmp(argv[1], "-f"))
arg++; /* Skip */
if (argc > arg + 2)
usage(argv[0]);
if ((out = g_fopen(argv[arg], "wb")) == 0) {
g_printerr(_("Can't open output file"));
exit(1);
}
if (argc == arg + 2) {
if ((in = g_fopen(argv[arg + 1], "r")) == 0) {
g_printerr(_("Can't open input file"));
exit(1);
}
}
}
/* generate weights */
if (fscanf(in, "GNU Backgammon %15s\n", szFileVersion) != 1) {
g_printerr(_("%s: invalid weights file\n"), argv[0]);
fclose(in);
fclose(out);
return EXIT_FAILURE;
}
if (StrCaseCmp(szFileVersion, WEIGHTS_VERSION)) {
g_printerr(_("%s: incorrect weights version\n"
"(version %s is required, "
"but these weights are %s)\n"), argv[0], WEIGHTS_VERSION, szFileVersion);
fclose(in);
fclose(out);
return EXIT_FAILURE;
}
if (fwrite(ar, sizeof(ar[0]), 2, out) != 2) {
g_printerr(_("Failed to write neural net!"));
fclose(in);
fclose(out);
return EXIT_FAILURE;
}
for (c = 0; !feof(in); c++) {
if (NeuralNetLoad(&nn, in) == -1) {
g_printerr(_("Failed to load neural net!"));
fclose(in);
fclose(out);
return EXIT_FAILURE;
}
if (NeuralNetSaveBinary(&nn, out) == -1) {
g_printerr(_("Failed to save neural net!"));
fclose(in);
fclose(out);
return EXIT_FAILURE;
}
NeuralNetDestroy(&nn);
}
g_printerr(_("%d nets converted\n"), c);
fclose(in);
fclose(out);
return EXIT_SUCCESS;
}
File diff suppressed because it is too large Load Diff
+112
View File
@@ -0,0 +1,112 @@
/*
* Copyright (C) 1999-2003 Joern Thyssen <jth@gnubg.org>
* Copyright (C) 2000-2013 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef MATCHEQUITY_H
#define MATCHEQUITY_H
#include "eval.h"
#define MAXSCORE 64
#define MAXCUBELEVEL 7
/* Structure for information about match equity table */
typedef struct {
gchar *szName; /* Name of match equity table */
gchar *szFileName; /* File name of MET */
gchar *szDescription; /* Description of MET */
int nLength; /* Native length of MET or -1 if pure calculated table */
} metinfo;
/* macros for getting match equities */
#define GET_MET(i,j,aafMET) ( ( (i) < 0 ) ? 1.0f : ( ( (j) < 0 ) ? 0.0f : \
(aafMET [ i ][ j ]) ) )
#define GET_METPostCrawford(i,afBtilde) ( (i) < 0 ? 1.0f : afBtilde [ i ] )
/* current match equity table used by gnubg */
extern float aafMET[MAXSCORE][MAXSCORE];
extern float aafMETPostCrawford[2][MAXSCORE];
/* gammon prices (calculated once for efficiency) */
extern float aaaafGammonPrices[MAXCUBELEVEL]
[MAXSCORE][MAXSCORE][4];
extern float aaaafGammonPricesPostCrawford[MAXCUBELEVEL]
[MAXSCORE][2][4];
extern metinfo miCurrent;
extern float
getME(int nScore0, int nScore1, int nMatchTo,
int fPlayer,
int nPoints, int fWhoWins,
int fCrawford, float aafMET[MAXSCORE][MAXSCORE], float aafMETPostCrawford[2][MAXSCORE]);
extern float
getMEAtScore(int nScore0, int nScore1, int nMatchTo,
int fPlayer, int fCrawford,
float aafMET[MAXSCORE][MAXSCORE], float aafMETPostCrawford[2][MAXSCORE]);
/* Initialise match equity table */
void
InitMatchEquity(const char *szFileName);
/* Get double points */
extern int
GetPoints(float arOutput[5], const cubeinfo * pci, float arCP[2]);
extern float
GetDoublePointDeadCube(float arOutput[5], cubeinfo * pci);
extern void
invertMET(void);
/* enums for the entries in the arrays returned by getMEMultiple
* DoublePass, DoubleTakeWin, DoubleTakeWinGammon... for the first 8
* then the same values using CubePrimeValues
* DP = Double/Pass, DTWG = double/take/wing gammon, etc
* DPP = double/Pass with CubePrime values, etc.
*/
typedef enum {
/* player 0 wins, first cube value */
DP = 0, NDW = 0, DTW = 1, NDWG = 1, NDWB, DTWG, DTWB,
/* player 0 loses, first cube value */
NDL, DTL = 6, NDLG = 6, NDLB, DTLG, DTLB,
/* player 0 wins, 2nd cube value */
DPP0, DTWP0, NDWBP0, DTWGP0, DTWBP0,
/* player 0 loses, 2nd cube value */
NDLP0, DTLP0, NDLBP0, DTLGP0, DTLBP0,
/* player 0 wins, 3rd cube value */
DPP1, DTWP1, NDWBP1, DTWGP1, DTWBP1,
/* player 0 loses, 3rd cube value */
NDLP1, DTLP1, NDLBP1, DTLGP1, DTLBP1
} met_indices;
extern void
getMEMultiple(int nScore0, int nScore1, int nMatchTo,
int nCube,
int nCubePrime0, int nCubePrime1,
int fCrawford,
float aafMET[MAXSCORE][MAXSCORE], float aafMETPostCrawford[2][MAXSCORE], float *player0, float *player1);
#endif /* MATCHEQUITY_H */
+283
View File
@@ -0,0 +1,283 @@
/*
* Copyright (C) 2002-2003 Joern Thyssen <jthyssen@dk.ibm.com>
* Copyright (C) 2004-2013 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: matchid.c,v 1.30 2021/12/18 22:06:37 plm Exp $
*/
#include "config.h"
#include "backgammon.h"
#include "positionid.h"
#include "matchequity.h"
#include "matchid.h"
#include <string.h>
/*
* Calculate log2 of Cube value.
*
* Input:
* n: cube value
*
* Returns:
* log(n)/log(2)
*
*/
extern int
LogCube(int n)
{
int i = 0;
while (n >>= 1)
i++;
return i;
}
static void
SetBit(unsigned char *pc, unsigned int bitPos, int test, unsigned int iBit)
{
const unsigned int k = bitPos / 8;
const unsigned char rbit = (unsigned char) (0x1 << (bitPos % 8));
unsigned char c;
if (test & (0x1 << iBit))
c = rbit;
else
c = 0;
pc[k] = (pc[k] & (0xFF ^ rbit)) | c;
}
static void
SetBits(unsigned char *pc, unsigned int bitPos, unsigned int nBits, int iContent)
{
unsigned int i;
/* FIXME: rewrite SetBit, SetBits to be faster */
for (i = 0; i < nBits; i++) {
SetBit(pc, bitPos + i, iContent, i);
}
}
static void
GetBits(const unsigned char *pc, const unsigned int bitPos, const unsigned int nBits, int *piContent)
{
unsigned int i, j;
unsigned char c[2];
/* FIXME: rewrite GetBits to be faster */
c[0] = 0;
c[1] = 0;
for (i = 0, j = bitPos; i < nBits; i++, j++) {
unsigned int k, r;
k = j / 8;
r = j % 8;
SetBit(c, i, pc[k], r);
}
*piContent = c[0] | (c[1] << 8);
}
static char *
MatchIDFromKey(unsigned char auchKey[9])
{
unsigned char *puch = auchKey;
static char szID[L_MATCHID + 1];
char *pch = szID;
static const char aszBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int i;
for (i = 0; i < 3; i++) {
*pch++ = aszBase64[puch[0] >> 2];
*pch++ = aszBase64[((puch[0] & 0x03) << 4) | (puch[1] >> 4)];
*pch++ = aszBase64[((puch[1] & 0x0F) << 2) | (puch[2] >> 6)];
*pch++ = aszBase64[puch[2] & 0x3F];
puch += 3;
}
*pch = 0;
return szID;
}
extern char *
MatchID(const unsigned int anDice[2],
const int fTurn,
const int fResigned,
const int fDoubled,
const int fMove,
const int fCubeOwner, const int fCrawford, const int nMatchTo, const int anScore[2], const int nCube,
const int fJacobyInUse, const gamestate gs)
{
unsigned char auchKey[9];
memset(auchKey, 0, 9);
SetBits(auchKey, 0, 4, LogCube(nCube));
SetBits(auchKey, 4, 2, fCubeOwner & 0x3);
SetBits(auchKey, 6, 1, fMove);
SetBits(auchKey, 7, 1, fCrawford);
SetBits(auchKey, 8, 3, (int) gs);
SetBits(auchKey, 11, 1, fTurn);
SetBits(auchKey, 12, 1, fDoubled);
SetBits(auchKey, 13, 2, fResigned);
if (anDice[0] >= anDice[1]) {
SetBits(auchKey, 15, 3, anDice[0] & 0x7);
SetBits(auchKey, 18, 3, anDice[1] & 0x7);
} else {
SetBits(auchKey, 15, 3, anDice[1] & 0x7);
SetBits(auchKey, 18, 3, anDice[0] & 0x7);
}
SetBits(auchKey, 21, 15, nMatchTo & 0x7FFF);
SetBits(auchKey, 36, 15, anScore[0] & 0x7FFF);
SetBits(auchKey, 51, 15, anScore[1] & 0x7FFF);
SetBits(auchKey, 66, 1, (!fJacobyInUse));
return MatchIDFromKey(auchKey);
}
static int
MatchFromKey(int anDice[2],
int *pfTurn,
int *pfResigned,
int *pfDoubled,
int *pfMove, int *pfCubeOwner, int *pfCrawford, int *pnMatchTo, int anScore[2], int *pnCube,
int *pfJacoby, gamestate * pgs, const unsigned char *auchKey)
{
int temp;
GetBits(auchKey, 0, 4, pnCube);
*pnCube = 0x1 << *pnCube;
GetBits(auchKey, 4, 2, pfCubeOwner);
if (*pfCubeOwner && *pfCubeOwner != 1)
*pfCubeOwner = -1;
GetBits(auchKey, 6, 1, pfMove);
GetBits(auchKey, 7, 1, pfCrawford);
GetBits(auchKey, 8, 3, &temp);
*pgs = (gamestate) temp;
GetBits(auchKey, 11, 1, pfTurn);
GetBits(auchKey, 12, 1, pfDoubled);
GetBits(auchKey, 13, 2, pfResigned);
GetBits(auchKey, 15, 3, &anDice[0]);
GetBits(auchKey, 18, 3, &anDice[1]);
GetBits(auchKey, 21, 15, pnMatchTo);
GetBits(auchKey, 36, 15, &anScore[0]);
GetBits(auchKey, 51, 15, &anScore[1]);
GetBits(auchKey, 66, 1, pfJacoby);
*pfJacoby = !(*pfJacoby);
/* FIXME: implement a consistency check */
if (anDice[0] < 0 || anDice[0] > 6)
return -1;
if (anDice[1] < 0 || anDice[1] > 6)
return -1;
if (*pnMatchTo < 0 || *pnMatchTo > MAXSCORE)
return -1;
if (*pnMatchTo) {
if (anScore[0] < 0 || anScore[0] > *pnMatchTo)
return -1;
if (anScore[1] < 0 || anScore[1] > *pnMatchTo)
return -1;
} else {
/* money game */
if (*pfCrawford)
/* no Crawford game in money play */
return -1;
}
return 0;
}
extern int
MatchFromID(unsigned int anDice[2],
int *pfTurn,
int *pfResigned,
int *pfDoubled, int *pfMove, int *pfCubeOwner, int *pfCrawford, int *pnMatchTo, int anScore[2], int *pnCube,
int *pfJacoby, gamestate * pgs, const char *szMatchID)
{
unsigned char auchKey[9];
unsigned char *puch = auchKey;
unsigned char ach[L_MATCHID + 1];
unsigned char *pch = ach;
int i;
memset(ach, 0, sizeof(ach));
/* decode base64 into key */
for (i = 0; i < L_MATCHID && szMatchID[i]; i++)
pch[i] = Base64((unsigned char) szMatchID[i]);
for (i = 0; i < 3; i++) {
*puch++ = (unsigned char) (pch[0] << 2) | (pch[1] >> 4);
*puch++ = (unsigned char) (pch[1] << 4) | (pch[2] >> 2);
*puch++ = (unsigned char) (pch[2] << 6) | pch[3];
pch += 4;
}
/* get matchstate info from the key */
return MatchFromKey((int *) anDice, pfTurn, pfResigned, pfDoubled, pfMove, pfCubeOwner, pfCrawford, pnMatchTo,
(int *) anScore, pnCube, pfJacoby, pgs, auchKey);
}
/*
* Generate match ID from matchstate
*
* Input:
* pms: match state
*
*/
extern char *
MatchIDFromMatchState(const matchstate * pms)
{
return MatchID(pms->anDice,
pms->fTurn,
pms->fResigned,
pms->fDoubled, pms->fMove, pms->fCubeOwner, pms->fCrawford, pms->nMatchTo, pms->anScore, pms->nCube,
pms->fJacoby, pms->gs);
}
+53
View File
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2002 Joern Thyssen <jthyssen@dk.ibm.com>
* Copyright (C) 2008-2013 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
* $Id: matchid.h,v 1.18 2021/07/14 19:21:45 plm Exp $
*/
#ifndef MATCHID_H
#define MATCHID_H
#define L_MATCHID 12
typedef struct {
int anDice[2];
int fTurn;
int fResigned;
int fDoubled;
gamestate gs;
} posinfo;
extern int LogCube(int n);
extern char *MatchID(const unsigned int anDice[2],
int fTurn,
int fResigned,
int fDoubled,
int fMove,
int fCubeOwner,
int fCrawford, int nMatchTo, const int anScore[2],
int nCube, int fJacoby, gamestate gs);
extern int MatchFromID(unsigned int anDice[2],
int *pfTurn,
int *pfResigned,
int *pfDoubled, int *pfMove, int *pfCubeOwner, int *pfCrawford, int *pnMatchTo, int anScore[2], int *pnCube,
int *pfJacoby, gamestate * pgs, const char *szMatchID);
extern char *MatchIDFromMatchState(const matchstate * pms);
#endif
+506
View File
@@ -0,0 +1,506 @@
/*
* mec - match equity calculator for backgammon. calculate equity table
* given match length, gammon rate and winning probabilities.
*
* Copyright (C) 1996 Claes Tornberg <claest@it.kth.se>
* Copyright (C) 2004-2013 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
*
* Modified for usage with GNUbg by Joern Thyssen <jth@gnubg.org>:
*
* (1) make external entry for usage in GNUbg
* (2) change "double" to "metentry", and typedef metentry to float
* or double depending on MEC_STANDALONE
* (3) Compile with MEC_STANDALONE to get Tornberg's original program, e.g.,
*
* gcc -DMEC_STANDALONE mec.c -o mec
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#ifndef MEC_STANDALONE
#include "mec.h"
#endif
struct dp {
double e;
double w;
};
typedef struct dp dp;
static void post_crawford(double gr, double wpf, int ml, double **E, double fd2, double fd4);
static void crawford(double gr, double wpf, int ml, double **E);
static void pre_crawford(double gr, double wpf, int ml, double **E);
static dp dpt(int p, int o, int c, double gr, double wpp, double **E);
/*
* Arguments are (in this order):
* match length
* gammon rate
* winning percentage (favorite)
*/
#ifdef MEC_STANDALONE
int
main(int argc, char **argv)
{
/* match length */
int ml = argc > 1 ? atoi(argv[1]) : 9;
/* gammon rate, i.e. how many of games won/lost will be gammons */
double gr = argc > 2 ? atof(argv[2]) : 0;
/* Here one could argue for different approaches. Does the underdog
* in match (current score) have different gammon rate. Or does the
* underdog in match (equity at current score) have different gammon
* rate. This could be solved, but for now, we assume same rates. */
/* winning percentage for favorite in one game. NB: when one player
* is favorite, there is no symmetry in the equity table! I.e.
* E[i][1] = 1 - E[1][i] doesn't necessarily hold. This figure must
* be higher than or equal to .5, i.e. 50% */
double wpf = argc > 3 ? atof(argv[3]) : 0.5;
/* Equity chart, E[p][o] = equity for p when p is p away, and o is o away.
* If there is a game favorite, i.e. wpf > 0.5, p above is considered
* to be the favorite in each game. For now we also assume no free drop
* vigourish. This will only affect the computation of post-Crawford
* equities, and is quite easy to correct. */
double **E = (double **) malloc((ml + 1) * sizeof(double *));
double *ec = (double *) calloc((ml + 1) * (ml + 1), sizeof(double));
if (E == NULL || ec == NULL)
exit(EXIT_FAILURE);
{
int i;
/* Initialize E to point to correct positions */
for (i = 0; i <= ml; i++) {
E[i] = &ec[i * (ml + 1)];
}
/* Initialize E for 0-away scores */
for (i = 1; i <= ml; i++) {
E[0][i] = 1;
E[i][0] = 0;
}
}
/* Compute post-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length. Fill in the equity table. */
post_crawford(gr, wpf, ml, E, 0.0, 0.0);
{
int i;
printf("Post-Crawford:\n");
for (i = 1; i < ml; ++i)
printf("%6.3f", E[i][1]);
printf("\n");
for (i = 1; i < ml; ++i)
printf("%6.3f", E[1][i]);
printf("\n");
}
/* Compute Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length, and post-Crawford equities.
* Fill in the table. */
crawford(gr, wpf, ml, E);
/* Compute pre-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length, and Crawford equities.
* Fill in the table. */
pre_crawford(gr, wpf, ml, E);
/* Print the equity table. Nothing fancy. */
{
int i, j;
printf("Gammon rate %5.4f\n" "Winning %% %5.4f\n\n", gr, wpf);
printf("%3s", "");
for (i = 1; i <= ml; i++) {
printf("%6d", i);
}
printf("\n");
for (i = 1; i <= ml; i++) {
printf("%3d", i);
for (j = 1; j <= ml; j++)
printf("%6.3f", E[i][j]);
printf("\n");
}
}
free(ec);
free(E);
}
#else /* MEC_STANDALONE */
extern void
mec_pc(const float rGammonRate,
const float rFreeDrop2Away, const float rFreeDrop4Away, const float rWinRate, float arMetPC[MAXSCORE])
{
unsigned int i;
/* match length */
const unsigned int ml = 64;
/* gammon rate, i.e. how many of games won/lost will be gammons */
double gr = rGammonRate;
/* Here one could argue for different approaches. Does the underdog
* in match (current score) have different gammon rate. Or does the
* underdog in match (equity at current score) have different gammon
* rate. This could be solved, but for now, we assume same rates. */
/* winning percentage for favorite in one game. NB: when one player
* is favorite, there is no symmetry in the equity table! I.e.
* E[i][1] = 1 - E[1][i] doesn't necessarily hold. This figure must
* be higher than or equal to .5, i.e. 50% */
double wpf = rWinRate;
/* Equity chart, E[p][o] = equity for p when p is p away, and o is o away.
* If there is a game favorite, i.e. wpf > 0.5, p above is considered
* to be the favorite in each game. For now we also assume no free drop
* vigourish. This will only affect the computation of post-Crawford
* equities, and is quite easy to correct. */
double **E = (double **) malloc((ml + 1) * sizeof(double *));
double *ec = (double *) calloc((ml + 1) * (ml + 1), sizeof(double));
if (!E || !ec)
exit(-1); /* We're in trouble... */
{
/* Initialize E to point to correct positions */
for (i = 0; i <= ml; i++) {
E[i] = &ec[i * (ml + 1)];
}
/* Initialize E for 0-away scores */
for (i = 1; i <= ml; i++) {
E[0][i] = 1;
E[i][0] = 0;
}
}
/* Compute post-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length. Fill in the equity table. */
post_crawford(gr, wpf, ml, E, (double) rFreeDrop2Away, (double) rFreeDrop4Away);
/* save post Crawford equities */
for (i = 0; i < ml; ++i)
arMetPC[i] = (float) E[i + 1][1];
/* garbage collect */
free(ec);
free(E);
}
extern void
mec(const float rGammonRate, const float rWinRate,
/* const *//*lint -e{818} */ float aarMetPC[2][MAXSCORE],
float aarMet[MAXSCORE][MAXSCORE])
{
unsigned int i, j;
/* match length */
const unsigned int ml = 64;
/* gammon rate, i.e. how many of games won/lost will be gammons */
double gr = rGammonRate;
/* Here one could argue for different approaches. Does the underdog
* in match (current score) have different gammon rate. Or does the
* underdog in match (equity at current score) have different gammon
* rate. This could be solved, but for now, we assume same rates. */
/* winning percentage for favorite in one game. NB: when one player
* is favorite, there is no symmetry in the equity table! I.e.
* E[i][1] = 1 - E[1][i] doesn't necessarily hold. This figure must
* be higher than or equal to .5, i.e. 50% */
double wpf = rWinRate;
/* Equity chart, E[p][o] = equity for p when p is p away, and o is o away.
* If there is a game favorite, i.e. wpf > 0.5, p above is considered
* to be the favorite in each game. For now we also assume no free drop
* vigourish. This will only affect the computation of post-Crawford
* equities, and is quite easy to correct. */
double **E = (double **) malloc((ml + 1) * sizeof(double *));
double *ec = (double *) calloc((ml + 1) * (ml + 1), sizeof(double));
if (!E || !ec)
exit(-1); /* We're in trouble... */
{
/* Initialize E to point to correct positions */
for (i = 0; i <= ml; i++) {
E[i] = &ec[i * (ml + 1)];
}
/* Initialize E for 0-away scores */
for (i = 1; i <= ml; i++) {
E[0][i] = 1;
E[i][0] = 0;
}
}
/* Compute post-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length. Fill in the equity table. */
for (i = 0; i < ml; ++i)
E[i + 1][1] = aarMetPC[0][i];
for (i = 0; i < ml; ++i)
E[1][i + 1] = 1.0 - (double)aarMetPC[1][i];
/* Compute Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length, and post-Crawford equities.
* Fill in the table. */
crawford(gr, wpf, ml, E);
/* Compute pre-Crawford equities, given gammon rate, winning percentage
* for favorite (game), match length, and Crawford equities.
* Fill in the table. */
pre_crawford(gr, wpf, ml, E);
/* save the match equiy table */
for (i = 0; i < ml; ++i)
for (j = 0; j < ml; ++j)
aarMet[i][j] = (float) E[i + 1][j + 1];
/* garbage collect */
free(ec);
free(E);
}
#endif /* ! MEC_STANDALONE */
/* If last bit is zero, then x is even. */
#define even(x) (((x)&0x1)==0)
/* sq returns x if x is greater than zero, else it returns zero. */
#define sq(x) ((x)>0?(x):0)
static void
post_crawford(double gr, double wpf, int ml, /*lint -e{818} */ double **E,
double fd2, double fd4)
{
int i;
E[1][1] = wpf;
for (i = 2; i <= ml; i++) {
if (even(i)) {
/* Free drop condition exists */
E[1][i] = E[1][i - 1];
E[i][1] = E[i - 1][1];
/* jth: add empirical values for free drop */
if (i == 2) {
/* 2-away */
E[1][i] += fd2;
E[i][1] -= fd2;
} else if (i == 4) {
/* 4-away */
E[1][i] += fd4;
E[i][1] += fd4;
}
} else {
E[1][i] = /* Equity for favorite when 1-away, i-away */
E[0][i] * wpf /* Favorite wins */
+ E[1][sq(i - 2)] * (1 - wpf) * (1 - gr) /* Favorite loses single */
+E[1][sq(i - 4)] * (1 - wpf) * gr; /* Favorite loses gammon */
E[i][1] = /* Equity for favorite when i-away, 1-away */
E[i][0] * (1 - wpf) /* Favorite loses */
+E[sq(i - 2)][1] * wpf * (1 - gr) /* Favorite wins single */
+E[sq(i - 4)][1] * wpf * gr; /* Favorite wins gammon */
}
}
}
static void
crawford(double gr, double wpf, int ml, /*lint -e{818} */ double **E)
{
int i;
/* Compute Crawford equities. Do this backwards, since
* we overwrite post-Crawford equities with Crawford equities.
* In this way we only overwrite equities no longer needed. */
for (i = ml; i >= 2; i--) {
E[1][i] = /* Equity for favorite when 1-away,i-away */
E[0][i] * wpf /* Favorite wins */
+ E[1][i - 1] * (1 - wpf) * (1 - gr) /* Favorite loses single */
+E[1][i - 2] * (1 - wpf) * gr; /* Favorite loses gammon */
E[i][1] = /* Equity for favorite when i-away, 1-away */
E[i][0] * (1 - wpf) /* Favorite loses */
+E[i - 1][1] * wpf * (1 - gr) /* Favorite wins single */
+E[i - 2][1] * wpf * gr; /* Favorite wins gammon */
}
}
static void
pre_crawford(double gr, double wpf, int ml, double **E)
{
int i, j;
dp dpf, dpu;
double eq;
for (i = 2; i <= ml; i++)
for (j = i; j <= ml; j++) {
dpf = dpt(i, j, 2, gr, wpf, E);
dpu = dpt(j, i, 2, gr, 1 - wpf, E);
dpu.e = 1 - dpu.e;
dpu.w = 1 - dpu.w;
eq = dpu.e + (dpf.e - dpu.e) * (wpf - dpu.w) / (dpf.w - dpu.w);
E[i][j] = eq;
if (i != j) {
dpf = dpt(j, i, 2, gr, wpf, E);
dpu = dpt(i, j, 2, gr, 1 - wpf, E);
dpu.e = 1 - dpu.e;
dpu.w = 1 - dpu.w;
eq = dpu.e + (dpf.e - dpu.e) * (wpf - dpu.w) / (dpf.w - dpu.w);
E[j][i] = eq;
}
}
}
/* Compute point when p doubles o to c, assuming gammon rate gr,
* p wins wpp % of games, and equities E for favorite. At this
* point o does equally well passing the double as taking it. */
static dp
dpt(int p, int o, int c, double gr, double wpp, double **E)
{
dp dpo, dpp;
double e0, edp, wdp;
if (p <= c / 2) {
/* No reason for p to double o, since a single win
* is enough to win the match. */
dpp.e = 1;
dpp.w = 1;
return dpp;
}
/* p might double o to c since he needs more than a single
* game to win the match. */
/* Find out when o (re)doubles p to 2*c */
dpo = dpt(o, p, 2 * c, gr, 1 - wpp, E);
/* Find out equity for o if p does well and wins
* all games here (assuming no recube from o), i.e.
* o loses all games sitting on a c-cube. */
if (wpp > 0.5) {
/* o isn't game favorite, equity for o at o-away x-away is 1 - E[x][o]. */
e0 = (1 - E[sq(p - c)][o]) * (1 - gr)
+ (1 - E[sq(p - 2 * c)][o]) * gr;
} else {
/* o is game favorite, equity for o at o-away x-away is E[o][x]. */
e0 = E[o][sq(p - c)] * (1 - gr)
+ E[o][sq(p - 2 * c)] * gr;
}
/* Find out o:s equity if o passes the double to c, i.e. loses c / 2 points. */
if (wpp > 0.5) {
/* o isn't game favorite, equity for o at o-away x-away is 1 - E[x][o]. */
edp = 1 - E[sq(p - c / 2)][o];
} else {
/* o is game favorite, equity for o at o-away x-away is E[o][x]. */
edp = E[o][sq(p - c / 2)];
}
/* Find the winning percentage, which on the line from
* (w0?,e0) to (dpo.w,dpo.e) gives o an equity equal to O's
* equity passing the double to c (i.e. losing c / 2 pts) */
wdp = (edp - e0) * dpo.w / (dpo.e - e0);
/* Now we know when p should double o, expressed as
* winning percentage and equity for o. Return this
* expressed in figures for p */
dpp.e = 1 - edp;
dpp.w = 1 - wdp;
return dpp;
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright (C) 1999-2002 Joern Thyssen <jth@gnubg.org>
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef MEC_H
#define MEC_H
#include "matchequity.h"
extern void
mec(float rGammonRate, float rWinRate,
float aarMetPC[2][MAXSCORE],
float aarMet[MAXSCORE][MAXSCORE]);
extern void
mec_pc(float rGammonRate,
float rFreeDrop2Away, float rFreeDrop4Away, float rWinRate, float arMetPC[MAXSCORE]);
#endif /* MEC_H */
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright (C) 1998-2003 Gary Wong <gtw@gnu.org>
* Copyright (C) 1999-2025 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* Default movefilters */
#define MOVEFILTER_NORMAL \
{ { { .Accept = 0, .Extra = 12, .Threshold = 0.24f }, { .Accept = 0, .Extra = 0 }, { .Accept = 0, .Extra = 0 }, { .Accept = 0, .Extra = 0 } }, \
{ { .Accept = 0, .Extra = 12, .Threshold = 0.24f }, { .Accept = 0, .Extra = 6, .Threshold = 0.12f }, { .Accept = 0, .Extra = 0 }, { .Accept = 0, .Extra = 0 } }, \
{ { .Accept = 0, .Extra = 16, .Threshold = 0.32f }, { .Accept = 0, .Extra = 8, .Threshold = 0.16f }, { .Accept = 0, .Extra = 4, .Threshold = 0.06f }, { .Accept = 0, .Extra = 0 } }, \
{ { .Accept = 0, .Extra = 16, .Threshold = 0.32f }, { .Accept = 0, .Extra = 12, .Threshold = 0.16f }, { .Accept = 0, .Extra = 6, .Threshold = 0.08f }, { .Accept = 0, .Extra = 4, .Threshold = 0.06f } } }
#define MOVEFILTER_LARGE \
{ { { .Accept = 0, .Extra = 16, .Threshold = 0.32f }, { .Accept = 0, .Extra = 0 }, { .Accept = 0, .Extra = 0 }, { .Accept = 0, .Extra = 0 } }, \
{ { .Accept = 0, .Extra = 16, .Threshold = 0.32f }, { .Accept = 0, .Extra = 8, .Threshold = 0.16f }, { .Accept = 0, .Extra = 0 }, { .Accept = 0, .Extra = 0 } }, \
{ { .Accept = 0, .Extra = 24, .Threshold = 0.40f }, { .Accept = 0, .Extra = 12, .Threshold = 0.20f }, { .Accept = 0, .Extra = 6, .Threshold = 0.08f }, { .Accept = 0, .Extra = 0 } }, \
{ { .Accept = 0, .Extra = 32, .Threshold = 0.40f }, { .Accept = 0, .Extra = 16, .Threshold = 0.20f }, { .Accept = 0, .Extra = 8, .Threshold = 0.10f }, { .Accept = 0, .Extra = 6, .Threshold = 0.08f } } }
+313
View File
@@ -0,0 +1,313 @@
/*
* Copyright (C) 2007-2009 Jon Kinsey <jonkinsey@gmail.com>
* Copyright (C) 2007-2018 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
/*
* Multithreaded support functions, moved out of multithread.c
*/
#include "config.h"
#include "multithread.h"
#include <stdlib.h>
#if defined (DEBUG_MULTITHREADED)
#include <stdio.h>
#endif
#include <string.h>
#include "lib/simd.h"
SSE_ALIGN(ThreadData td);
extern ThreadLocalData *
MT_CreateThreadLocalData(int id)
{
ThreadLocalData *tld = (ThreadLocalData *) g_malloc(sizeof(ThreadLocalData));
tld->id = id;
tld->pnnState = (NNState *) g_malloc(sizeof(NNState) * 3);
tld->pnnState[CLASS_RACE - CLASS_RACE].savedBase = g_malloc0(nnRace.cHidden * sizeof(float));
tld->pnnState[CLASS_RACE - CLASS_RACE].savedIBase = g_malloc0(nnRace.cInput * sizeof(float));
tld->pnnState[CLASS_CRASHED - CLASS_RACE].savedBase = g_malloc0(nnCrashed.cHidden * sizeof(float));
tld->pnnState[CLASS_CRASHED - CLASS_RACE].savedIBase = g_malloc0(nnCrashed.cInput * sizeof(float));
tld->pnnState[CLASS_CONTACT - CLASS_RACE].savedBase = g_malloc0(nnContact.cHidden * sizeof(float));
tld->pnnState[CLASS_CONTACT - CLASS_RACE].savedIBase = g_malloc0(nnContact.cInput * sizeof(float));
tld->aMoves = (move *) g_malloc0(sizeof(move) * MAX_INCOMPLETE_MOVES);
return tld;
}
#if defined(USE_MULTITHREAD)
#if defined(DEBUG_MULTITHREADED) && defined(WIN32)
unsigned int mainThreadID;
#endif
static GMutex condMutex; /* Extra mutex needed for waiting */
static GPrivate private_item = G_PRIVATE_INIT(free);
extern void
TLSCreate(TLSItem * pItem)
{
*pItem = &private_item;
}
extern void
TLSSetValue(TLSItem pItem, size_t value)
{
size_t *pNew = (size_t *) g_malloc(sizeof(size_t));
*pNew = value;
g_private_set(pItem, (gpointer) pNew);
}
extern void
InitManualEvent(ManualEvent * pME)
{
ManualEvent pNewME = g_malloc(sizeof(*pNewME));
g_cond_init(&pNewME->cond);
pNewME->signalled = FALSE;
*pME = pNewME;
}
extern void
FreeManualEvent(ManualEvent ME)
{
g_cond_clear(&ME->cond);
g_free(ME);
}
extern void
WaitForManualEvent(ManualEvent ME)
{
gint64 end_time;
multi_debug("wait for manual event asks lock (condMutex)");
g_mutex_lock(&condMutex);
end_time = g_get_monotonic_time() + 10 * G_TIME_SPAN_SECOND;
multi_debug("wait for manual event gets lock (condMutex)");
while (!ME->signalled) {
multi_debug("waiting for manual event");
if (!g_cond_wait_until(&ME->cond, &condMutex, end_time))
break;
else {
multi_debug("still waiting for manual event");
}
}
g_mutex_unlock(&condMutex);
multi_debug("wait for manual event unlocks (condMutex)");
}
extern void
ResetManualEvent(ManualEvent ME)
{
multi_debug("reset manual event asks lock (condMutex)");
g_mutex_lock(&condMutex);
multi_debug("reset manual event gets lock (condMutex)");
ME->signalled = FALSE;
g_mutex_unlock(&condMutex);
multi_debug("reset manual event unlocks (condMutex)");
}
extern void
SetManualEvent(ManualEvent ME)
{
multi_debug("set manual event asks lock (condMutex)");
g_mutex_lock(&condMutex);
multi_debug("set manual event gets lock (condMutex)");
ME->signalled = TRUE;
g_cond_broadcast(&ME->cond);
g_mutex_unlock(&condMutex);
multi_debug("set manual event unlocks (condMutex)");
}
extern void
InitMutex(Mutex * pMutex)
{
g_mutex_init(pMutex);
}
extern void
FreeMutex(Mutex * mutex)
{
g_assert(MT_GetThreadID() == -1);
g_mutex_clear(mutex);
}
extern void
Mutex_Lock(Mutex * mutex)
{
g_mutex_lock(mutex);
}
extern void
Mutex_Release(Mutex * mutex)
{
g_mutex_unlock(mutex);
}
extern void
MT_InitThreads(void)
{
td.tasks = NULL;
MT_SafeSet(&td.doneTasks, 0);
td.addedTasks = 0;
td.totalTasks = -1;
InitManualEvent(&td.activity);
TLSCreate(&td.tlsItem);
TLSSetValue(td.tlsItem, (size_t) MT_CreateThreadLocalData(-1));
#if defined(DEBUG_MULTITHREADED) && defined(WIN32)
mainThreadID = GetCurrentThreadId();
#endif
InitMutex(&td.multiLock);
InitMutex(&td.queueLock);
InitManualEvent(&td.syncStart);
InitManualEvent(&td.syncEnd);
td.numThreads = 0;
}
extern void
CloseThread(void *UNUSED(unused))
{
ThreadLocalData *pTLD;
NNState *pnnState;
g_assert(MT_SafeCompare(&td.closingThreads, TRUE));
pTLD = (ThreadLocalData *) TLSGet(td.tlsItem);
pnnState = pTLD->pnnState;
g_free(pTLD->aMoves);
for (int i = 0; i < 3; i++) {
g_free(pnnState[i].savedBase);
g_free(pnnState[i].savedIBase);
}
g_free(pnnState);
g_free(pTLD);
MT_SafeInc(&td.result);
}
extern void
MT_Close(void)
{
MT_CloseThreads();
FreeManualEvent(td.activity);
FreeMutex(&td.multiLock);
FreeMutex(&td.queueLock);
FreeManualEvent(td.syncStart);
FreeManualEvent(td.syncEnd);
}
extern void
MT_Exclusive(void)
{
multi_debug("exclusive asks lock (multiLock)");
Mutex_Lock(&td.multiLock);
multi_debug("exclusive gets lock (multiLock)");
}
extern void
MT_Release(void)
{
Mutex_Release(&td.multiLock);
multi_debug("release unlocks (multiLock)");
}
#if defined(DEBUG_MULTITHREADED)
extern void
multi_debug(const char *str, ...)
{
int id;
va_list vl;
char tn[5];
char buf[1024];
/* Sync output so order makes some sense */
#if defined(WIN32)
WaitForSingleObject(td.multiLock, INFINITE);
#endif
/* With glib threads, locking here doesn't seem to work :
* GLib (gthread-posix.c): Unexpected error from C library during 'pthread_mutex_lock': Resource deadlock avoided. Aborting.
* Timestamp the output instead.
* Maybe the g_get_*_time() calls will sync output as well anyway... */
va_start(vl, str);
vsprintf(buf, str, vl);
id = MT_GetThreadID();
#if defined(WIN32)
if (id == -1 && GetCurrentThreadId() == mainThreadID)
#else
if (id == -1)
#endif
strcpy(tn, "MT");
else
sprintf(tn, "T%d", id);
printf("%" G_GINT64_FORMAT " %s: %s\n", g_get_monotonic_time(), tn, buf);
va_end(vl);
#if defined(WIN32)
ReleaseMutex(td.multiLock);
#endif
}
#endif
#else /* !defined(USE_MULTITHREAD) */
extern void
MT_InitThreads(void)
{
td.tld = MT_CreateThreadLocalData(-1);
}
extern void
MT_Close(void)
{
int i;
NNState *pnnState;
if (!td.tld)
return;
g_free(td.tld->aMoves);
pnnState = td.tld->pnnState;
for (i = 0; i < 3; i++) {
g_free(pnnState[i].savedBase);
g_free(pnnState[i].savedIBase);
}
g_free(pnnState);
g_free(td.tld);
}
#endif
+539
View File
@@ -0,0 +1,539 @@
/*
* Copyright (C) 1998-2002 Gary Wong <gtw@gnu.org>
* Copyright (C) 2000-2014 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*
*
* An implementation of the legacy position key/ID described at:
* https://www.gnu.org/software/gnubg/manual/gnubg.html#gnubg-tech_postionid
*
* This encoding creates IDs as compact as possible.
*
* For internal use in moves generation and evaluation we now use another
* key, much faster to encode or decode, that is essentially the raw board
* squeezed down to 4 bits/point.
*
* This library also calculates bearoff IDs, which are enumerations of the
*
* c+6
* C
* 6
*
* combinations of (up to c) chequers among 6 points.
*
*
* $Id: positionid.c,v 1.56 2021/01/26 22:09:24 plm Exp $
*/
#include "config.h"
#include <glib.h>
#include <errno.h>
#include <string.h>
#include "positionid.h"
extern void
PositionKey(const TanBoard anBoard, positionkey * pkey)
{
unsigned int i, j;
unsigned int *anpBoard = pkey->data;
for (i = 0, j = 0; i < 3; i++, j += 8) {
anpBoard[i] = anBoard[1][j] + (anBoard[1][j + 1] << 4)
+ (anBoard[1][j + 2] << 8) + (anBoard[1][j + 3] << 12)
+ (anBoard[1][j + 4] << 16) + (anBoard[1][j + 5] << 20)
+ (anBoard[1][j + 6] << 24) + (anBoard[1][j + 7] << 28);
anpBoard[i + 3] = anBoard[0][j] + (anBoard[0][j + 1] << 4)
+ (anBoard[0][j + 2] << 8) + (anBoard[0][j + 3] << 12)
+ (anBoard[0][j + 4] << 16) + (anBoard[0][j + 5] << 20)
+ (anBoard[0][j + 6] << 24) + (anBoard[0][j + 7] << 28);
}
anpBoard[6] = anBoard[0][24] + (anBoard[1][24] << 4);
}
extern void
PositionFromKey(TanBoard anBoard, const positionkey * pkey)
{
unsigned int i, j;
unsigned int const *anpBoard = pkey->data;
for (i = 0, j = 0; i < 3; i++, j += 8) {
anBoard[1][j] = anpBoard[i] & 0x0f;
anBoard[1][j + 1] = (anpBoard[i] >> 4) & 0x0f;
anBoard[1][j + 2] = (anpBoard[i] >> 8) & 0x0f;
anBoard[1][j + 3] = (anpBoard[i] >> 12) & 0x0f;
anBoard[1][j + 4] = (anpBoard[i] >> 16) & 0x0f;
anBoard[1][j + 5] = (anpBoard[i] >> 20) & 0x0f;
anBoard[1][j + 6] = (anpBoard[i] >> 24) & 0x0f;
anBoard[1][j + 7] = (anpBoard[i] >> 28) & 0x0f;
anBoard[0][j] = anpBoard[i + 3] & 0x0f;
anBoard[0][j + 1] = (anpBoard[i + 3] >> 4) & 0x0f;
anBoard[0][j + 2] = (anpBoard[i + 3] >> 8) & 0x0f;
anBoard[0][j + 3] = (anpBoard[i + 3] >> 12) & 0x0f;
anBoard[0][j + 4] = (anpBoard[i + 3] >> 16) & 0x0f;
anBoard[0][j + 5] = (anpBoard[i + 3] >> 20) & 0x0f;
anBoard[0][j + 6] = (anpBoard[i + 3] >> 24) & 0x0f;
anBoard[0][j + 7] = (anpBoard[i + 3] >> 28) & 0x0f;
}
anBoard[0][24] = anpBoard[6] & 0x0f;
anBoard[1][24] = (anpBoard[6] >> 4) & 0x0f;
}
/* In evaluations, the function above is often followed by swapping
* the board. This is expensive (SwapSides is about as costly as
* PositionFromKey itself).
* Provide one that fills the board already swapped. */
extern void
PositionFromKeySwapped(TanBoard anBoard, const positionkey * pkey)
{
unsigned int i, j;
unsigned int const *anpBoard = pkey->data;
for (i = 0, j = 0; i < 3; i++, j += 8) {
anBoard[0][j] = anpBoard[i] & 0x0f;
anBoard[0][j + 1] = (anpBoard[i] >> 4) & 0x0f;
anBoard[0][j + 2] = (anpBoard[i] >> 8) & 0x0f;
anBoard[0][j + 3] = (anpBoard[i] >> 12) & 0x0f;
anBoard[0][j + 4] = (anpBoard[i] >> 16) & 0x0f;
anBoard[0][j + 5] = (anpBoard[i] >> 20) & 0x0f;
anBoard[0][j + 6] = (anpBoard[i] >> 24) & 0x0f;
anBoard[0][j + 7] = (anpBoard[i] >> 28) & 0x0f;
anBoard[1][j] = anpBoard[i + 3] & 0x0f;
anBoard[1][j + 1] = (anpBoard[i + 3] >> 4) & 0x0f;
anBoard[1][j + 2] = (anpBoard[i + 3] >> 8) & 0x0f;
anBoard[1][j + 3] = (anpBoard[i + 3] >> 12) & 0x0f;
anBoard[1][j + 4] = (anpBoard[i + 3] >> 16) & 0x0f;
anBoard[1][j + 5] = (anpBoard[i + 3] >> 20) & 0x0f;
anBoard[1][j + 6] = (anpBoard[i + 3] >> 24) & 0x0f;
anBoard[1][j + 7] = (anpBoard[i + 3] >> 28) & 0x0f;
}
anBoard[1][24] = anpBoard[6] & 0x0f;
anBoard[0][24] = (anpBoard[6] >> 4) & 0x0f;
}
static inline void
addBits(unsigned char auchKey[10], unsigned int bitPos, unsigned int nBits)
{
unsigned int k = bitPos / 8;
unsigned int r = (bitPos & 0x7);
unsigned int b = (((unsigned int) 0x1 << nBits) - 1) << r;
auchKey[k] |= (unsigned char) b;
if (k < 8) {
auchKey[k + 1] |= (unsigned char) (b >> 8);
auchKey[k + 2] |= (unsigned char) (b >> 16);
} else if (k == 8) {
auchKey[k + 1] |= (unsigned char) (b >> 8);
}
}
extern void
oldPositionKey(const TanBoard anBoard, oldpositionkey * pkey)
{
unsigned int i, iBit = 0;
const unsigned int *j;
memset(pkey, 0, sizeof(oldpositionkey));
for (i = 0; i < 2; ++i) {
const unsigned int *const b = anBoard[i];
for (j = b; j < b + 25; ++j) {
const unsigned int nc = *j;
if (nc) {
addBits(pkey->auch, iBit, nc);
iBit += nc + 1;
} else {
++iBit;
}
}
}
}
extern void
oldPositionFromKey(TanBoard anBoard, const oldpositionkey * pkey)
{
int i = 0, j = 0, k;
const unsigned char *a;
memset(anBoard[0], 0, sizeof(anBoard[0]));
memset(anBoard[1], 0, sizeof(anBoard[1]));
for (a = pkey->auch; a < pkey->auch + 10; ++a) {
unsigned char cur = *a;
for (k = 0; k < 8; ++k) {
if ((cur & 0x1)) {
if (i >= 2 || j >= 25) { /* Error, so return - will probably show error message */
return;
}
++anBoard[i][j];
} else {
if (++j == 25) {
++i;
j = 0;
}
}
cur >>= 1;
}
}
}
static char *
oldPositionIDFromKey(const oldpositionkey * pkey)
{
unsigned char const *puch = pkey->auch;
static char szID[L_POSITIONID + 1];
char *pch = szID;
static const char aszBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int i;
for (i = 0; i < 3; i++) {
*pch++ = aszBase64[puch[0] >> 2];
*pch++ = aszBase64[((puch[0] & 0x03) << 4) | (puch[1] >> 4)];
*pch++ = aszBase64[((puch[1] & 0x0F) << 2) | (puch[2] >> 6)];
*pch++ = aszBase64[puch[2] & 0x3F];
puch += 3;
}
*pch++ = aszBase64[*puch >> 2];
*pch++ = aszBase64[(*puch & 0x03) << 4];
*pch = 0;
return szID;
}
extern char *
PositionIDFromKey(const positionkey * pkey)
{
TanBoard anBoard;
oldpositionkey okey;
PositionFromKey(anBoard, pkey);
oldPositionKey((ConstTanBoard) anBoard, &okey);
return oldPositionIDFromKey(&okey);
}
extern char *
PositionID(const TanBoard anBoard)
{
oldpositionkey key;
oldPositionKey(anBoard, &key);
return oldPositionIDFromKey(&key);
}
extern int
PositionFromXG(TanBoard anBoard, const char *pos)
{
int i;
for (i = 0; i < 26; i++) {
int p0, p1;
if (i == 0) {
p0 = 24;
p1 = -1;
} else if (i == 25) {
p0 = -1;
p1 = 24;
} else {
p0 = 24 - i;
p1 = i - 1;
}
if (pos[i] >= 'A' && pos[i] <= 'P') {
if (p0 > -1)
anBoard[0][p0] = 0;
anBoard[1][p1] = (unsigned int) (pos[i] - 'A' + 1);
} else if (pos[i] >= 'a' && pos[i] <= 'p') {
anBoard[0][p0] = (unsigned int) (pos[i] - 'a' + 1);
if (p1 > -1)
anBoard[1][p1] = 0;
} else if (pos[i] == '-') {
if (p0 > -1)
anBoard[0][p0] = 0;
if (p1 > -1)
anBoard[1][p1] = 0;
} else {
return 1;
}
}
return 0;
}
extern int
CheckPosition(const TanBoard anBoard)
{
unsigned int ac[2], i;
/* Check for a player with over 15 chequers */
for (i = ac[0] = ac[1] = 0; i < 25; i++)
if ((ac[0] += anBoard[0][i]) > 15 || (ac[1] += anBoard[1][i]) > 15) {
errno = EINVAL;
return 0;
}
/* Check for both players having chequers on the same point */
for (i = 0; i < 24; i++)
if (anBoard[0][i] && anBoard[1][23 - i]) {
errno = EINVAL;
return 0;
}
/* Check for both players on the bar against closed boards */
for (i = 0; i < 6; i++)
if (anBoard[0][i] < 2 || anBoard[1][i] < 2)
return 1;
if (!anBoard[0][24] || !anBoard[1][24])
return 1;
errno = EINVAL;
return 0;
}
extern void
ClosestLegalPosition(TanBoard anBoard)
{
unsigned int i, j, ac[2];
/* Limit each player to 15 chequers */
for (i = 0; i < 2; i++) {
ac[i] = 15;
for (j = 0; j < 25; j++) {
if (anBoard[i][j] <= ac[i])
ac[i] -= anBoard[i][j];
else {
anBoard[i][j] = ac[i];
ac[i] = 0;
}
}
}
/* Forbid both players having a chequer on the same point */
for (i = 0; i < 24; i++)
if (anBoard[0][i])
anBoard[1][23 - i] = 0;
/* If both players have closed boards, let at least one of them off
* the bar */
for (i = 0; i < 6; i++)
if (anBoard[0][i] < 2 || anBoard[1][i] < 2)
/* open board */
return;
if (anBoard[0][24])
anBoard[1][24] = 0;
}
extern unsigned char
Base64(const unsigned char ch)
{
if (ch >= 'A' && ch <= 'Z')
return (unsigned char)(ch - 'A');
if (ch >= 'a' && ch <= 'z')
return (unsigned char)(ch - 'a' + 26);
if (ch >= '0' && ch <= '9')
return (unsigned char)(ch - '0' + 52);
if (ch == '+')
return 62;
if (ch == '/')
return 63;
return 255;
}
extern int
PositionFromID(TanBoard anBoard, const char *pchEnc)
{
oldpositionkey key;
unsigned char ach[L_POSITIONID + 1], *pch = ach, *puch = key.auch;
int i;
memset(ach, 0, L_POSITIONID + 1);
for (i = 0; i < L_POSITIONID && pchEnc[i]; i++)
pch[i] = Base64((unsigned char) pchEnc[i]);
for (i = 0; i < 3; i++) {
*puch++ = (unsigned char) (pch[0] << 2) | (pch[1] >> 4);
*puch++ = (unsigned char) (pch[1] << 4) | (pch[2] >> 2);
*puch++ = (unsigned char) (pch[2] << 6) | pch[3];
pch += 4;
}
*puch = (unsigned char) (pch[0] << 2) | (pch[1] >> 4);
oldPositionFromKey(anBoard, &key);
return CheckPosition((ConstTanBoard) anBoard);
}
extern int
EqualBoards(const TanBoard anBoard0, const TanBoard anBoard1)
{
int i;
for (i = 0; i < 25; i++)
if (anBoard0[0][i] != anBoard1[0][i] || anBoard0[1][i] != anBoard1[1][i])
return 0;
return 1;
}
#define MAX_N 40
#define MAX_R 25
static unsigned int anCombination[MAX_N][MAX_R], fCalculated = 0;
static void
InitCombination(void)
{
unsigned int i, j;
for (i = 0; i < MAX_N; i++)
anCombination[i][0] = i + 1;
for (j = 1; j < MAX_R; j++)
anCombination[0][j] = 0;
for (i = 1; i < MAX_N; i++)
for (j = 1; j < MAX_R; j++)
anCombination[i][j] = anCombination[i - 1][j - 1] + anCombination[i - 1][j];
fCalculated = 1;
}
extern unsigned int
Combination(const unsigned int n, const unsigned int r)
{
g_assert(n <= MAX_N && r <= MAX_R);
if (!fCalculated)
InitCombination();
return anCombination[n - 1][r - 1];
}
static unsigned int
PositionF(unsigned int fBits, unsigned int n, unsigned int r)
{
if (n == r)
return 0;
return (fBits & (1u << (n - 1))) ? Combination(n - 1, r) +
PositionF(fBits, n - 1, r - 1) : PositionF(fBits, n - 1, r);
}
extern unsigned int
PositionBearoff(const unsigned int anBoard[], unsigned int nPoints, unsigned int nChequers)
{
unsigned int i, fBits, j;
if (nPoints == 0) {
g_assert_not_reached();
return 0;
}
for (j = nPoints - 1, i = 0; i < nPoints; i++)
j += anBoard[i];
fBits = 1u << j;
for (i = 0; i < nPoints - 1; i++) {
j -= anBoard[i] + 1;
fBits |= (1u << j);
}
return PositionF(fBits, nChequers + nPoints, nPoints);
}
static unsigned int
PositionInv(unsigned int nID, unsigned int n, unsigned int r)
{
unsigned int nC;
if (!r)
return 0;
else if (n == r)
return (1u << n) - 1;
nC = Combination(n - 1, r);
return (nID >= nC) ? (1u << (n - 1)) | PositionInv(nID - nC, n - 1, r - 1) : PositionInv(nID, n - 1, r);
}
extern void
PositionFromBearoff(unsigned int anBoard[], unsigned int usID, unsigned int nPoints, unsigned int nChequers)
{
unsigned int fBits = PositionInv(usID, nChequers + nPoints, nPoints);
unsigned int i, j;
for (i = 0; i < nPoints; i++)
anBoard[i] = 0;
j = nPoints - 1;
for (i = 0; i < (nChequers + nPoints); i++) {
if (fBits & (1u << i)) {
if (j == 0)
break;
j--;
} else
anBoard[j]++;
}
}
extern unsigned short
PositionIndex(unsigned int g, const unsigned int anBoard[6])
{
unsigned int i, fBits;
unsigned int j;
if (g == 0) {
g_assert_not_reached();
return 0;
}
j = g - 1;
for (i = 0; i < g; i++)
j += anBoard[i];
fBits = 1u << j;
for (i = 0; i < g - 1; i++) {
j -= anBoard[i] + 1;
fBits |= (1u << j);
}
/* FIXME: 15 should be replaced by nChequers, but the function is
* only called from bearoffgammon, so this should be fine. */
return (unsigned short) PositionF(fBits, 15, g);
}
+64
View File
@@ -0,0 +1,64 @@
/*
* Copyright (C) 1998-2002 Gary Wong <gtw@gnu.org>
* Copyright (C) 2000-2014 the AUTHORS
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#ifndef POSITIONID_H
#define POSITIONID_H
#include "gnubg-types.h"
#define L_POSITIONID 14
extern void PositionKey(const TanBoard anBoard, positionkey * pkey);
extern char *PositionID(const TanBoard anBoard);
extern char *PositionIDFromKey(const positionkey * pkey);
extern int PositionFromXG(TanBoard anBoard, const char * pos);
extern
unsigned int PositionBearoff(const unsigned int anBoard[], unsigned int nPoints, unsigned int nChequers);
extern void PositionFromKey(TanBoard anBoard, const positionkey * pkey);
extern void PositionFromKeySwapped(TanBoard anBoard, const positionkey * pkey);
/* Return 1 for success, 0 for invalid id */
extern int PositionFromID(TanBoard anBoard, const char *pchEnc);
extern void PositionFromBearoff(unsigned int anBoard[], unsigned int usID,
unsigned int nPoints, unsigned int nChequers);
extern unsigned short PositionIndex(unsigned int g, const unsigned int anBoard[6]);
#define EqualKeys(k1, k2) ((k1).data[0]==(k2).data[0]&&(k1).data[1]==(k2).data[1]&&(k1).data[2]==(k2).data[2]&&(k1).data[3]==(k2).data[3]&&(k1).data[4]==(k2).data[4]&&(k1).data[5]==(k2).data[5]&&(k1).data[6]==(k2).data[6])
#define CopyKey(ks, kd) (kd).data[0]=(ks).data[0],(kd).data[1]=(ks).data[1],(kd).data[2]=(ks).data[2],(kd).data[3]=(ks).data[3],(kd).data[4]=(ks).data[4],(kd).data[5]=(ks).data[5],(kd).data[6]=(ks).data[6]
extern int EqualBoards(const TanBoard anBoard0, const TanBoard anBoard1);
/* Return 1 for valid position, 0 for not */
extern int CheckPosition(const TanBoard anBoard);
extern void ClosestLegalPosition(TanBoard anBoard);
extern unsigned int Combination(unsigned int n, unsigned int r);
extern unsigned char Base64(unsigned char ch);
extern void oldPositionFromKey(TanBoard anBoard, const oldpositionkey * pkey);
extern void oldPositionKey(const TanBoard anBoard, oldpositionkey * pkey);
#endif
+155
View File
@@ -0,0 +1,155 @@
/*
* engine.h -- interface C du moteur de backgammon d'Ustagammon.
*
* Copyright (C) 2026 les auteurs d'Ustagammon.
* Licence : GPLv3 ou ultérieure (voir LICENSE).
*
* Le moteur est le cœur de GNU Backgammon (engine/gnubg/), seule autorité
* pour les règles, les coups, le videau et les évaluations. Cette interface
* n'expose aucun type de gnubg.
*
* Positions : une position est décrite par deux identifiants de gnubg,
* - le Position ID (14 caractères), qui code le plateau du point de vue du
* joueur au trait ;
* - le Match ID (12 caractères), qui code le reste : longueur du match (0 en
* partie libre), score, videau et son propriétaire, joueur au trait,
* Crawford, Jacoby. Les dés éventuellement codés dans le Match ID sont
* ignorés : les dés sont passés à part.
*
* Niveaux (« ply ») : réglages prédéfinis de gnubg, sans bruit, de 0
* (évaluation statique du réseau) à 4. gnubg compte les plis à partir de 0.
*
* Équités : toujours du point de vue du joueur au trait, normalisées pour
* un videau de 1. En match, gnubg convertit les chances de gain du match en
* équité normalisée, comme il l'affiche par défaut.
*
* Probabilités, dans l'ordre de gnubg : gain, gain gammon, gain backgammon,
* perte gammon, perte backgammon (les gammons incluent les backgammons).
*
* Contraintes : une seule instance du moteur (état global de gnubg) et des
* appels sérialisés, jamais concurrents. Toutes les fonctions renvoient un
* code engine_status ; aucune ne termine le programme pour une erreur
* d'utilisation.
*/
#ifndef USTAGAMMON_ENGINE_H
#define USTAGAMMON_ENGINE_H
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
ENGINE_OK = 0,
ENGINE_ERROR_NOT_INITIALISED, /* engine_init n'a pas réussi */
ENGINE_ERROR_DATA, /* données absentes ou invalides */
ENGINE_ERROR_POSITION_ID, /* Position ID invalide */
ENGINE_ERROR_MATCH_ID, /* Match ID invalide */
ENGINE_ERROR_DICE, /* dés hors de 1..6 */
ENGINE_ERROR_PLY, /* niveau hors de 0..ENGINE_MAX_PLY */
ENGINE_ERROR_TOO_MANY_MOVES, /* tableau de coups trop petit */
ENGINE_ERROR_INTERNAL /* échec signalé par gnubg */
} engine_status;
#define ENGINE_MAX_PLY 4
/* Nombre maximal de coups légaux pour un jet (MAX_MOVES de gnubg). */
#define ENGINE_MAX_MOVES 3060
/* Indices du tableau probabilities. */
enum {
ENGINE_WIN = 0,
ENGINE_WIN_GAMMON,
ENGINE_WIN_BACKGAMMON,
ENGINE_LOSE_GAMMON,
ENGINE_LOSE_BACKGAMMON,
ENGINE_NUM_PROBABILITIES
};
/* Un coup, avec son évaluation quand il a été classé. */
typedef struct {
char notation[32]; /* notation de gnubg, ex. « 8/5 6/5 » */
char position_id[16]; /* Position ID après le coup, plateau vu du
* joueur qui vient de jouer */
int ply; /* niveau de l'évaluation (-1 : non évalué) */
float probabilities[ENGINE_NUM_PROBABILITIES];
float equity; /* équité de classement (avec videau) */
} engine_move;
/* Évaluation d'une position, joueur au trait avant de lancer les dés. */
typedef struct {
float probabilities[ENGINE_NUM_PROBABILITIES];
float cubeless_equity; /* équité sans videau */
float cubeful_equity; /* équité avec videau */
} engine_evaluation;
/* Décision de videau du joueur au trait, avant de lancer les dés. */
typedef struct {
float probabilities[ENGINE_NUM_PROBABILITIES];
float no_double; /* équité si pas de double */
float double_take; /* équité si double et prend */
float double_pass; /* équité si double et passe */
const char *decision; /* décision de gnubg, en anglais : « no_double_take »,
* « double_take », « double_pass »,
* « too_good_pass », « not_available »… */
} engine_cube;
/* Description lisible d'un Match ID décodé. */
typedef struct {
int match_length; /* 0 : partie libre (money game) */
int score[2];
int cube_value;
int cube_owner; /* -1 : videau au centre ; sinon 0 ou 1 */
int player_on_roll; /* 0 ou 1 */
int crawford; /* partie Crawford en cours */
int jacoby; /* règle Jacoby (partie libre) */
} engine_match;
/*
* Charge les données du moteur depuis data_dir : gnubg.wd (réseaux),
* gnubg_os0.bd et gnubg_ts0.bd (fins de partie), Kazaross-XG2.xml (table
* d'équité de match). À appeler une fois avant toute autre fonction.
*/
engine_status engine_init(const char *data_dir);
/* Libère les ressources. Le moteur ne peut pas être réinitialisé ensuite. */
void engine_shutdown(void);
/* Message d'erreur en français pour un code de retour. */
const char *engine_status_message(engine_status status);
/* Décode un Match ID. */
engine_status engine_decode_match(const char *match_id, engine_match *match);
/*
* Coups légaux pour une position et un jet (die1, die2 dans 1..6), sans
* évaluation (ply vaut -1). moves doit pouvoir contenir max_moves coups ;
* ENGINE_MAX_MOVES suffit toujours. *count reçoit le nombre de coups (0 si
* le joueur ne peut pas jouer).
*/
engine_status engine_legal_moves(const char *position_id, const char *match_id,
int die1, int die2,
engine_move *moves, int max_moves, int *count);
/*
* Coups légaux classés du meilleur au moins bon, au niveau ply. Comme dans
* gnubg, les coups écartés par les filtres gardent l'évaluation du niveau
* inférieur (voir leur champ ply).
*/
engine_status engine_best_moves(const char *position_id, const char *match_id,
int die1, int die2, int ply,
engine_move *moves, int max_moves, int *count);
/* Évaluation d'une position au niveau ply. */
engine_status engine_evaluate(const char *position_id, const char *match_id,
int ply, engine_evaluation *evaluation);
/* Décision de videau au niveau ply. */
engine_status engine_cube_decision(const char *position_id, const char *match_id,
int ply, engine_cube *cube);
#ifdef __cplusplus
}
#endif
#endif /* USTAGAMMON_ENGINE_H */
+5
View File
@@ -29,6 +29,11 @@ for f in "${fichiers[@]}"; do
if grep -q $'\r' "$f"; then
signaler "$f : fins de ligne Windows (CRLF)"
fi
# Copies fidèles de GNU Backgammon (sources et données) : on ne
# reformate pas leurs espaces, pour rester identique à l'original.
case "$f" in
engine/gnubg/* | engine/data/*) continue ;;
esac
if grep -nE '[[:blank:]]+$' "$f" >/dev/null; then
signaler "$f : espaces en fin de ligne (lignes $(grep -nE '[[:blank:]]+$' "$f" | cut -d: -f1 | paste -sd, -))"
fi
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env bash
# Tests du moteur embarqué, via build/engine-cli (étape 0a.3) :
# meilleurs coups d'ouverture connus, nombre de coups légaux, bornes et
# symétrie des probabilités, décision de videau évidente, identifiants
# invalides. La comparaison systématique au gnubg de bureau viendra ensuite
# (tests/reference/, étape 0a.4).
#
# Lance `make engine` (sans effet si tout est à jour).
set -euo pipefail
cd "$(dirname "$0")/.."
# Lancé depuis `make test` : repartir d'un make indépendant du make parent.
unset MAKEFLAGS MFLAGS MAKELEVEL
make engine >/dev/null
cli=build/engine-cli
depart=4HPwATDgc/ABMA
# Match ID de partie libre, videau au centre, joueur 0 au trait.
libre_j0=MAEAAAAAAAAA
# Le même, joueur 1 au trait.
libre_j1=cAkAAAAAAAAA
# Match en 5 points, 4-2, partie Crawford, joueur 0 au trait.
crawford=sAGgAEAAEAAA
# Course : le joueur au trait n'a plus que deux dames, sur ses points 6 et 5 ;
# l'adversaire a ses 15 dames sur son point 1.
course=/38AAABQAAAAAA
echecs=0
echouer() {
echo " $1"
echecs=$((echecs + 1))
}
# Valeur d'une clé JSON numérique ou textuelle (première occurrence).
valeur() {
grep -oE "\"$1\": (\"[^\"]*\"|[-0-9.]+)" <<<"$2" | head -n1 | sed -E 's/^"[^"]*": "?//; s/"$//'
}
# Évalue une condition numérique avec awk ; ex. : vrai "0 <= 0.5 && 0.5 <= 1"
vrai() {
awk "BEGIN { exit !($1) }"
}
# 1. Meilleurs coups d'ouverture, sans ambiguïté, à 0 et 2 plis.
for ply in 0 2; do
while read -r des attendu; do
sortie="$($cli --ply "$ply" bestmove "$depart" "$libre_j0" "$des")"
coup="$(valeur move "$sortie")"
[ "$coup" = "$attendu" ] \
|| echouer "ouverture $des à $ply pli(s) : « $coup » au lieu de « $attendu »"
done <<-EOF
31 8/5 6/5
42 8/4 6/4
53 8/3 6/3
61 13/7 8/7
EOF
done
# Le Match ID du critère de réussite du plan (joueur 1 au trait) donne le même coup.
coup="$(valeur move "$($cli bestmove "$depart" "$libre_j1" 31)")"
[ "$coup" = "8/5 6/5" ] || echouer "ouverture 31 avec $libre_j1 : « $coup »"
# 2. Nombre de coups légaux. Course, jet 21 : 6/3, 5/2 ou 6/4 5/4.
n="$(valeur count "$($cli legal "$course" "$libre_j0" 21)")"
[ "$n" = 3 ] || echouer "course, jet 21 : $n coups légaux au lieu de 3"
# 3. Probabilités : bornes, cohérence et symétrie.
verifier_probabilites() {
local nom="$1" json="$2" g gg gbg pg pbg
g="$(valeur win "$json")"
gg="$(valeur win_gammon "$json")"
gbg="$(valeur win_backgammon "$json")"
pg="$(valeur lose_gammon "$json")"
pbg="$(valeur lose_backgammon "$json")"
vrai "0 <= $g && $g <= 1" || echouer "$nom : gain $g hors de [0, 1]"
vrai "0 <= $gbg && $gbg <= $gg && $gg <= $g" \
|| echouer "$nom : gain $g, gammon $gg, backgammon $gbg incohérents"
vrai "0 <= $pbg && $pbg <= $pg && $pg <= 1 - $g" \
|| echouer "$nom : gain $g, perte gammon $pg, perte backgammon $pbg incohérents"
}
for ply in 0 2; do
j0="$($cli --ply "$ply" eval "$depart" "$libre_j0")"
j1="$($cli --ply "$ply" eval "$depart" "$libre_j1")"
verifier_probabilites "départ à $ply pli(s)" "$j0"
# Le départ est symétrique : peu importe quel joueur est au trait.
[ "${j0#*\"probabilities\"}" = "${j1#*\"probabilities\"}" ] \
|| echouer "départ à $ply pli(s) : évaluations différentes selon le joueur au trait"
# Avantage au joueur qui lance les dés en premier, mais faible.
e="$(valeur cubeless_equity "$j0")"
vrai "0 < $e && $e < 0.2" || echouer "départ à $ply pli(s) : équité $e hors de ]0, 0.2["
done
course_eval="$($cli eval "$course" "$libre_j0")"
verifier_probabilites "course" "$course_eval"
g="$(valeur win "$course_eval")"
vrai "$g > 0.999" || echouer "course : gain $g, attendu quasi certain"
# Les coups classés ont aussi des probabilités cohérentes.
verifier_probabilites "coup 64 à 2 plis" "$($cli --ply 2 bestmove "$depart" "$libre_j0" 64)"
# 4. Videau : au départ, pas de double, en partie libre comme en match.
for match_id in "$libre_j0" "$crawford"; do
decision="$(valeur decision "$($cli cube "$depart" "$match_id")")"
case "$decision" in
no_double* | no_redouble* | not_available) ;;
*) echouer "videau au départ ($match_id) : « $decision » au lieu de pas de double" ;;
esac
done
# Partie Crawford : le videau n'est pas disponible.
decision="$(valeur decision "$($cli cube "$depart" "$crawford")")"
[ "$decision" = "no_double_dead_cube" ] || [ "$decision" = "not_available" ] \
|| echouer "videau en partie Crawford : « $decision »"
# À l'inverse, la course gagnée d'avance est un double/passe (règle Jacoby :
# jouer pour le gammon ne rapporte rien, videau au centre).
decision="$(valeur decision "$($cli cube "$course" "$libre_j0")")"
[ "$decision" = "double_pass" ] || echouer "videau en course gagnée : « $decision »"
# 5. Erreurs : JSON, code d'erreur précis et code de sortie non nul.
attendre_erreur() {
local code_attendu="$1"
shift
local sortie statut=0
sortie="$($cli "$@")" || statut=$?
[ "$statut" -ne 0 ] || echouer "« $* » aurait dû échouer"
grep -qF "\"code\": \"$code_attendu\"" <<<"$sortie" \
|| echouer "« $* » : code d'erreur attendu $code_attendu, reçu : $sortie"
}
attendre_erreur invalid_position_id bestmove 'ceci-nest-pas-un-id' "$libre_j0" 31
attendre_erreur invalid_position_id bestmove '//////////////' "$libre_j0" 31
attendre_erreur invalid_match_id bestmove "$depart" 'XYZ' 31
attendre_erreur invalid_dice bestmove "$depart" "$libre_j0" 71
attendre_erreur invalid_ply --ply 9 bestmove "$depart" "$libre_j0" 31
attendre_erreur usage bestmove "$depart" "$libre_j0"
attendre_erreur usage inconnue "$depart" "$libre_j0"
attendre_erreur invalid_data --data /dossier/absent bestmove "$depart" "$libre_j0" 31
if [ "$echecs" -gt 0 ]; then
echo " $echecs vérification(s) du moteur en échec."
exit 1
fi
+9 -4
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Vérifie le contrat du Makefile décrit dans CLAUDE.md :
# toutes les commandes obligatoires existent et apparaissent dans `make help`,
# et `make clean` peut être relancé sans erreur.
# et `make clean` supprime le dossier de build et peut être relancé sans erreur.
set -euo pipefail
cd "$(dirname "$0")/.."
@@ -17,6 +17,11 @@ for cible in setup build test lint deploy clean help; do
fi
done
make clean >/dev/null
make clean >/dev/null
[ ! -e build ] || { echo "make clean n'a pas supprimé build/"; exit 1; }
# make clean est essayé sur un dossier de build jetable : vider le vrai build/
# forcerait à recompiler le moteur et à régénérer ses données à chaque test.
faux_build="$(mktemp -d)"
touch "$faux_build/artefact"
make clean BUILD_DIR="$faux_build" >/dev/null
make clean BUILD_DIR="$faux_build" >/dev/null
[ ! -e "$faux_build" ] || { echo "make clean n'a pas supprimé le dossier de build"; exit 1; }
grep -qx 'BUILD_DIR := build' Makefile || { echo "BUILD_DIR ne vaut plus build"; exit 1; }