eliot-dev
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Eliot-dev] eliot dic/compdic.c dic/dic_search.c dic/dic_se... [multibyt


From: eliot-dev
Subject: [Eliot-dev] eliot dic/compdic.c dic/dic_search.c dic/dic_se... [multibyte]
Date: Wed, 28 Dec 2005 16:47:36 +0000

CVSROOT:        /sources/eliot
Module name:    eliot
Branch:         multibyte
Changes by:     Olivier Teulière <address@hidden>      05/12/28 16:47:35

Modified files:
        dic            : compdic.c dic_search.c dic_search.h listdic.c 
        game           : Makefile.am bag.cpp board.cpp board.h coord.cpp 
                         coord.h duplicate.cpp duplicate.h freegame.cpp 
                         freegame.h game.cpp game.h history.cpp 
                         history.h player.cpp player.h pldrack.cpp 
                         pldrack.h round.cpp round.h tile.cpp tile.h 
                         training.cpp training.h turn.cpp turn.h 
        utils          : eliottxt.cpp game_io.cpp ncurses.cpp 
Added files:
        game           : encoding.cpp encoding.h 

Log message:
        Use wide-character strings internally instead of chars.
        - this is done on a branch first, because it has more impacts than 
expected
        - the Game library is 99% migrated, the regression scenario are 
working, but
        the loading of a game is probably more broken than on Head...
        - the Dic library is still using char* strings internally, so there is 
an
        adaptation layer converting wchar_t into char... this won't work for 
actual
        multi-byte characters obviously, but it's not worse than before for 
ASCII
        characters
        - the text and ncurses interfaces have been modified too, the wxWidgets 
one
        should be done soon

CVSWeb URLs:
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/dic/compdic.c.diff?only_with_tag=multibyte&tr1=1.6&tr2=1.6.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/dic/dic_search.c.diff?only_with_tag=multibyte&tr1=1.14&tr2=1.14.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/dic/dic_search.h.diff?only_with_tag=multibyte&tr1=1.10&tr2=1.10.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/dic/listdic.c.diff?only_with_tag=multibyte&tr1=1.6&tr2=1.6.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/Makefile.am.diff?only_with_tag=multibyte&tr1=1.11&tr2=1.11.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/bag.cpp.diff?only_with_tag=multibyte&tr1=1.5&tr2=1.5.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/board.cpp.diff?only_with_tag=multibyte&tr1=1.11&tr2=1.11.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/board.h.diff?only_with_tag=multibyte&tr1=1.10&tr2=1.10.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/coord.cpp.diff?only_with_tag=multibyte&tr1=1.7&tr2=1.7.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/coord.h.diff?only_with_tag=multibyte&tr1=1.5&tr2=1.5.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/duplicate.cpp.diff?only_with_tag=multibyte&tr1=1.14&tr2=1.14.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/duplicate.h.diff?only_with_tag=multibyte&tr1=1.10&tr2=1.10.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/encoding.cpp?only_with_tag=multibyte&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/encoding.h?only_with_tag=multibyte&rev=1.1.2.1
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/freegame.cpp.diff?only_with_tag=multibyte&tr1=1.16&tr2=1.16.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/freegame.h.diff?only_with_tag=multibyte&tr1=1.9&tr2=1.9.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/game.cpp.diff?only_with_tag=multibyte&tr1=1.27&tr2=1.27.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/game.h.diff?only_with_tag=multibyte&tr1=1.26&tr2=1.26.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/history.cpp.diff?only_with_tag=multibyte&tr1=1.8&tr2=1.8.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/history.h.diff?only_with_tag=multibyte&tr1=1.8&tr2=1.8.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/player.cpp.diff?only_with_tag=multibyte&tr1=1.12&tr2=1.12.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/player.h.diff?only_with_tag=multibyte&tr1=1.16&tr2=1.16.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/pldrack.cpp.diff?only_with_tag=multibyte&tr1=1.7&tr2=1.7.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/pldrack.h.diff?only_with_tag=multibyte&tr1=1.10&tr2=1.10.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/round.cpp.diff?only_with_tag=multibyte&tr1=1.8&tr2=1.8.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/round.h.diff?only_with_tag=multibyte&tr1=1.10&tr2=1.10.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/tile.cpp.diff?only_with_tag=multibyte&tr1=1.5&tr2=1.5.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/tile.h.diff?only_with_tag=multibyte&tr1=1.6&tr2=1.6.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/training.cpp.diff?only_with_tag=multibyte&tr1=1.14&tr2=1.14.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/training.h.diff?only_with_tag=multibyte&tr1=1.13&tr2=1.13.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/turn.cpp.diff?only_with_tag=multibyte&tr1=1.9&tr2=1.9.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/game/turn.h.diff?only_with_tag=multibyte&tr1=1.7&tr2=1.7.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/utils/eliottxt.cpp.diff?only_with_tag=multibyte&tr1=1.12&tr2=1.12.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/utils/game_io.cpp.diff?only_with_tag=multibyte&tr1=1.7&tr2=1.7.2.1&r1=text&r2=text
http://cvs.savannah.gnu.org/viewcvs/eliot/eliot/utils/ncurses.cpp.diff?only_with_tag=multibyte&tr1=1.19&tr2=1.19.2.1&r1=text&r2=text

Patches:
Index: eliot/dic/compdic.c
diff -u /dev/null eliot/dic/compdic.c:1.6.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/dic/compdic.c Wed Dec 28 16:47:35 2005
@@ -0,0 +1,333 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Elit 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include "hashtable.h"
+#include "dic_internals.h"
+
+//#define DEBUG_LIST
+//#define DEBUG_OUTPUT
+//#define DEBUG_OUTPUT_L2
+#define CHECK_RECURSION
+
+char*
+load_uncompressed(const char* file_name, unsigned int *dic_size)
+{
+  unsigned r;
+  char *uncompressed;
+  FILE* file_desc;
+
+  if ((file_desc = fopen (file_name, "r")) == NULL)
+    return NULL;
+
+  if ((uncompressed = (char*)malloc (sizeof(char)*(*dic_size))) == NULL)
+    return NULL;
+
+  r = fread (uncompressed, 1, *dic_size, file_desc);
+  if (r < *dic_size)
+    {
+      /* \n is 2 chars under MS OS */
+      printf("\n");
+      printf("** The number of bytes read is less than the size of the file 
**\n");
+      printf("** this may be OK if you run a Microsoft OS but not on Unix   
**\n");
+      printf("** please check the results.                                  
**\n");
+      printf("\n");
+      *dic_size = r;
+    }
+
+  fclose(file_desc);
+  return uncompressed;
+}
+
+
+int
+file_length(const char* file_name)
+{
+  struct stat stat_buf;
+  if (stat (file_name, &stat_buf) < 0)
+    return - 1;
+  return (int) stat_buf.st_size;
+}
+
+
+void
+skip_init_header(FILE* outfile, Dict_header *header)
+{
+  header->unused_1   = 0;
+  header->unused_2   = 0;
+  header->root       = 0;
+  header->nwords     = 0;
+  header->nodesused  = 1;
+  header->edgesused  = 1;
+  header->nodessaved = 0;
+  header->edgessaved = 0;
+
+  fwrite (header, sizeof(Dict_header), 1, outfile);
+}
+
+
+void
+fix_header(FILE* outfile, Dict_header* header)
+{
+  strcpy(header->ident,_COMPIL_KEYWORD_);
+  header->root = header->edgesused;
+  rewind (outfile);
+  fwrite (header, sizeof(Dict_header), 1, outfile);
+}
+
+
+void
+print_header_info(Dict_header *header)
+{
+  printf("============================\n");
+  printf("keyword length %lu bytes\n", strlen(_COMPIL_KEYWORD_));
+  printf("keyword size   %lu bytes\n", sizeof(_COMPIL_KEYWORD_));
+  printf("header size    %lu bytes\n", sizeof(Dict_header));
+  printf("\n");
+  printf("%d words\n",header->nwords);
+  printf("\n");
+  printf("root : %7d (edge)\n",header->root);
+  printf("root : %7lu (byte)\n",header->root * sizeof(Dawg_edge));
+  printf("\n");
+  printf("nodes : %d+%d\n",header->nodesused, header->nodessaved);
+  printf("edges : %d+%d\n",header->edgesused, header->edgessaved);
+  printf("============================\n");
+}
+
+
+void
+write_node(Dawg_edge *edges, int size, int num, FILE* outfile)
+{
+#ifdef DEBUG_OUTPUT
+  int i;
+  printf("writing %d edges\n",num);
+  for(i=0; i<num; i++)
+    {
+#ifdef DEBUG_OUTPUT_L2
+      printf("ptr=%2d t=%d l=%d f=%d chr=%2d (%c)\n",
+            edges[i].ptr, edges[i].term, edges[i].last,
+            edges[i].fill, edges[i].chr, edges[i].chr -1 +'a');
+#endif
+      fwrite (edges+i, sizeof(Dawg_edge), 1, outfile);
+    }
+#else
+  fwrite (edges, size, num, outfile);
+#endif
+}
+
+#define MAX_STRING_LENGTH 200
+
+
+#define MAX_EDGES 2000
+/* ods3: ??   */
+/* ods4: 1746 */
+
+/* global variables */
+FILE*       global_outfile;
+Dict_header global_header;
+Hash_table  global_hashtable;
+
+char        global_stringbuf[MAX_STRING_LENGTH]; /* Space for current string */
+char*       global_endstring;                    /* Marks END of current 
string */
+char*       global_input;
+char*       global_endofinput;
+
+/*
+ * Makenode takes a prefix (as position relative to stringbuf) and
+ * returns an index of the start node of a dawg that recognizes all
+ * words beginning with that prefix.  String is a pointer (relative
+ * to stringbuf) indicating how much of prefix is matched in the
+ * input.
+ */
+#ifdef CHECK_RECURSION
+int current_rec =0;
+int max_rec = 0;
+#endif
+
+unsigned int
+makenode(char *prefix)
+{
+  int    numedges;
+  Dawg_edge  edges[MAX_EDGES];
+  Dawg_edge *edgeptr = edges;
+  unsigned  *saved_position;
+
+#ifdef CHECK_RECURSION
+  current_rec++;
+  if (current_rec > max_rec)
+    max_rec = current_rec;
+#endif
+
+  while (prefix == global_endstring)
+    {
+      /* More edges out of node */
+      edgeptr->ptr  = 0;
+      edgeptr->term = 0;
+      edgeptr->last = 0;
+      edgeptr->fill = 0;
+      edgeptr->chr  = 0;
+
+      (*(edgeptr++)).chr = (*global_endstring++ = *global_input++) & 
DIC_CHAR_MASK;
+      if (*global_input == '\n')                 /* End of a word */
+        {
+          global_header.nwords++;
+          edgeptr[-1].term = 1;                  /* Mark edge as word */
+          *global_endstring++ = *global_input++; /* Skip \n */
+          if (global_input == global_endofinput) /* At end of input? */
+            break;
+
+         global_endstring = global_stringbuf;
+         while(*global_endstring == *global_input)
+           {
+              global_endstring++;
+              global_input++;
+           }
+        }
+      /* make dawg pointed to by this edge */
+      edgeptr[-1].ptr = makenode(prefix + 1);
+    }
+
+  numedges = edgeptr - edges;
+  if (numedges == 0)
+    {
+#ifdef CHECK_RECURSION
+      current_rec --;
+#endif
+      return 0;             /* Special node zero - no edges */
+    }
+
+  edgeptr[-1].last = 1;     /* Mark the last edge */
+
+  saved_position = (unsigned int*) hash_find (global_hashtable,
+                                             (void*)edges,
+                                             numedges*sizeof(Dawg_edge));
+  if (saved_position)
+    {
+      global_header.edgessaved += numedges;
+      global_header.nodessaved++;
+
+#ifdef CHECK_RECURSION
+      current_rec --;
+#endif
+      return *saved_position;
+    }
+  else
+    {
+      unsigned int node_pos;
+
+      node_pos = global_header.edgesused;
+      hash_add(global_hashtable,
+              (void*)edges,numedges*sizeof(Dawg_edge),
+              
(void*)(&global_header.edgesused),sizeof(global_header.edgesused));
+      global_header.edgesused += numedges;
+      global_header.nodesused++;
+      write_node (edges, sizeof(Dawg_edge), numedges, global_outfile);
+
+#ifdef CHECK_RECURSION
+      current_rec --;
+#endif
+      return node_pos;
+    }
+}
+
+
+
+
+int
+main(int argc, char* argv[])
+{
+  unsigned int dicsize;
+  char *uncompressed;
+  Dawg_edge rootnode = {0,0,0,0,0};
+  Dawg_edge specialnode = {0,0,0,0,0};
+
+  char* outfilename;
+  char outfilenamedefault[] = "dict.daw";
+  clock_t starttime, endtime;
+
+  if (argc < 2)
+    {
+      fprintf(stderr,"usage: %s uncompressed_dic [compressed_dic]\n",argv[0]);
+      exit(1);
+    }
+
+  dicsize = file_length (argv[1]);
+  if (dicsize < 0)
+    {
+      fprintf(stderr,"Cannot stat uncompressed dictionary %s\n",argv[1]);
+      exit(1);
+    }
+
+  outfilename = (argc == 3) ? argv[2] : outfilenamedefault;
+
+  if ((global_outfile = fopen (outfilename,"wb")) == NULL)
+    {
+      fprintf(stderr,"Cannot open output file %s\n",outfilename);
+      exit(1);
+    }
+
+  if ((uncompressed = load_uncompressed(argv[1], &dicsize)) == NULL)
+    {
+      fprintf(stderr,"Cannot load uncompressed dictionary into memory\n");
+      exit(1);
+    }
+
+  global_input = uncompressed;
+  global_endofinput = global_input + dicsize;
+
+#define SCALE 0.6
+  global_hashtable = hash_init((unsigned int)(dicsize * SCALE));
+#undef SCALE
+
+  skip_init_header(global_outfile,&global_header);
+
+  specialnode.last = 1;
+  write_node(&specialnode,sizeof(specialnode),1,global_outfile);
+  /*
+   * Call makenode with null (relative to stringbuf) prefix;
+   * Initialize string to null; Put index of start node on output
+   */
+  starttime=clock();
+  rootnode.ptr = makenode(global_endstring = global_stringbuf);
+  endtime=clock();
+  write_node(&rootnode,sizeof(rootnode),1,global_outfile);
+
+  fix_header(global_outfile,&global_header);
+
+  print_header_info(&global_header);
+  hash_destroy(global_hashtable);
+  free(uncompressed);
+  fclose(global_outfile);
+
+  printf(" Elapsed time is                 : %f s\n", 1.0*(endtime-starttime) 
/ CLOCKS_PER_SEC);
+#ifdef CHECK_RECURSION
+  printf(" Maximum recursion level reached : %d\n",max_rec);
+#endif
+  return 0;
+}
+
+
Index: eliot/dic/dic_search.c
diff -u /dev/null eliot/dic/dic_search.c:1.14.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/dic/dic_search.c      Wed Dec 28 16:47:35 2005
@@ -0,0 +1,581 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Elit 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file   dic_search.c
+ *  \brief  Dictionary lookup functions
+ *  \author Antoine Fraboulet
+ *  \date   2002
+ */
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dic_internals.h"
+#include "dic.h"
+#include "regexp.h"
+#include "dic_search.h"
+#include "libdic_a-er.h" /* generated by bison */
+#include "scanner.h"     /* generated by flex  */
+#include "automaton.h"
+
+/*
+ * shut down the compiler
+ */
+static int yy_init_globals (yyscan_t yyscanner )
+{
+  yy_init_globals(yyscanner);
+  return 0;
+}
+
+/**
+ * Dic_seel_edgeptr
+ * walk the dictionary until the end of the word
+ * @param dic : dictionnary
+ * @param s : current pointer to letters
+ * @param eptr : current edge in the dawg
+ */
+static Dawg_edge*
+Dic_seek_edgeptr(const Dictionary dic, const char* s, Dawg_edge *eptr)
+{
+  if (*s)
+    {
+      Dawg_edge *p = dic->dawg + eptr->ptr;
+      do {
+        if (p->chr == (unsigned)(*s & DIC_CHAR_MASK))
+          return Dic_seek_edgeptr (dic,s + 1, p);
+      } while (!(*p++).last);
+      return dic->dawg;
+    }
+  else
+    return eptr;
+}
+
+
+/**
+ * Dic_search_word_inner : direct application of Dic_seek_edgeptr
+ * @param dic : dictionary
+ * @param word : word to lookup
+ * @result 0 not a valid word, 1 ok
+ */
+static int Dic_search_word_inner(const Dictionary dic, const char* word)
+{
+    Dawg_edge *e;
+    e = Dic_seek_edgeptr(dic, word, dic->dawg + dic->root);
+    return e->term;
+}
+
+
+/**
+ * This method is a wrapper around the Dic_search_word_inner function.
+ * It simply converts the wchar_t* string into a char* one.
+ * XXX: This is a temporary hack until the dictionaries can handle multibyte
+ * characters properly... the Dic_search_word_inner function should disappear!
+ */
+int Dic_search_word(const Dictionary dic, const wchar_t* word)
+{
+    int res;
+    char *tmp_word;
+    size_t len;
+
+    // Get the needed length (we _can't_ use wstring::size())
+    len = wcstombs(NULL, word, 0);
+    if (len == (size_t)-1)
+        tmp_word = "";
+
+    // Convert the string
+    tmp_word = malloc(len + 1);
+    len = wcstombs(tmp_word, word, len + 1);
+
+    // Do the actual work
+    res = Dic_search_word_inner(dic, tmp_word);
+
+    // Release memory
+    free(tmp_word);
+    return res;
+}
+
+
+/**
+ * global variables for Dic_search_word_by_len :
+ *
+ * a pointer to the structure is passed as a parameter
+ * so that all the search_* variables appear to the functions
+ * as global but the code remains re-entrant.
+ * Should be better to change the algorithm ...
+ */
+
+struct params_7plus1_t {
+ Dictionary search_dic;
+ int search_len;
+ int search_wordlistlen;
+ int search_wordlistlenmax;
+ char search_wordtst[DIC_WORD_MAX];
+ char search_letters[DIC_LETTERS];
+ char (*search_wordlist)[RES_7PL1_MAX][DIC_WORD_MAX];
+};
+
+static void
+Dic_search_word_by_len(struct params_7plus1_t *params, int i, Dawg_edge 
*edgeptr)
+{
+  /* depth first search in the dictionary */
+  do {
+    /* we use a static array and not a real list so we have to stop if
+     * the array is full */
+    if (params->search_wordlistlen >= params->search_wordlistlenmax)
+      break;
+
+    /* the test is false only when reach the end-node */
+    if (edgeptr->chr)
+      {
+
+       /* is the letter available in search_letters */
+       if (params->search_letters[edgeptr->chr])
+         {
+           params->search_wordtst[i] = edgeptr->chr + 'A' - 1;
+           params->search_letters[edgeptr->chr] --;
+           if (i == params->search_len)
+             {
+               if ((edgeptr->term)
+                 /* && (params->search_wordlistlen < 
params->search_wordlistlenmax) */)
+                 
strcpy((*params->search_wordlist)[params->search_wordlistlen++],params->search_wordtst);
+             }
+           else /* if (params->search_wordlistlen < 
params->search_wordlistlenmax) */
+             {
+               Dic_search_word_by_len(params,i + 1, params->search_dic->dawg + 
edgeptr->ptr);
+             }
+           params->search_letters[edgeptr->chr] ++;
+           params->search_wordtst[i] = '\0';
+         }
+
+       /* the letter is of course available if we have a joker available */
+       if (params->search_letters[0])
+         {
+           params->search_wordtst[i] = edgeptr->chr + 'a' - 1;
+           params->search_letters[0] --;
+           if (i == params->search_len)
+             {
+               if ((edgeptr->term)
+                    /* && (params->search_wordlistlen < 
params->search_wordlistlenmax) */)
+                 
strcpy((*(params->search_wordlist))[params->search_wordlistlen++],params->search_wordtst);
+             }
+           else /* if (params->search_wordlistlen < 
params->search_wordlistlenmax) */
+             {
+               Dic_search_word_by_len(params,i + 1,params->search_dic->dawg + 
edgeptr->ptr);
+             }
+           params->search_letters[0] ++;
+           params->search_wordtst[i] = '\0';
+         }
+      }
+  } while (! (*edgeptr++).last);
+}
+
+void
+Dic_search_7pl1(const Dictionary dic, const char* rack,
+                char buff[DIC_LETTERS][RES_7PL1_MAX][DIC_WORD_MAX],
+                int joker)
+{
+  int i,j,wordlen;
+  const char* r = rack;
+  struct params_7plus1_t params;
+  Dawg_edge *root_edge;
+
+  for(i=0; i < DIC_LETTERS; i++)
+    for(j=0; j < RES_7PL1_MAX; j++)
+      buff[i][j][0] = '\0';
+
+  for(i=0; i<DIC_LETTERS; i++)
+    params.search_letters[i] = 0;
+
+  if (dic == NULL || rack == NULL)
+    return;
+
+  /*
+   * the letters are verified and changed to the dic internal
+   * representation (*r & DIC_CHAR_MASK)
+   */
+  for(wordlen=0; wordlen < DIC_WORD_MAX && *r; r++)
+    {
+      if (isalpha(*r))
+       {
+         params.search_letters[(int)*r & DIC_CHAR_MASK]++;
+          wordlen++;
+       }
+      else if (*r == '?')
+       {
+         if (joker)
+           {
+             params.search_letters[0]++;
+             wordlen++;
+           }
+         else
+           {
+             strncpy(buff[0][0],"** joker **",DIC_WORD_MAX);
+             return;
+           }
+       }
+    }
+
+  if (wordlen < 1)
+    return;
+
+  root_edge = dic->dawg + (dic->dawg[dic->root].ptr);
+
+  params.search_dic = dic;
+  params.search_wordlistlenmax = RES_7PL1_MAX;
+
+  /* search for all the words that can be done with the letters */
+  params.search_len = wordlen - 1;
+  params.search_wordtst[wordlen]='\0';
+  params.search_wordlist = & buff[0];
+  params.search_wordlistlen = 0;
+  Dic_search_word_by_len(&params,0,root_edge);
+
+  /* search for all the words that can be done with the letters +1 */
+  params.search_len = wordlen;
+  params.search_wordtst[wordlen + 1]='\0';
+  for(i='a'; i <= 'z'; i++)
+    {
+      params.search_letters[i & DIC_CHAR_MASK]++;
+
+      params.search_wordlist = & buff[i & DIC_CHAR_MASK];
+      params.search_wordlistlen = 0;
+      Dic_search_word_by_len(&params,0,root_edge);
+
+      params.search_letters[i & DIC_CHAR_MASK]--;
+    }
+}
+
+/****************************************/
+/****************************************/
+
+void
+Dic_search_Racc(const Dictionary dic, const char* word,
+                char wordlist[RES_RACC_MAX][DIC_WORD_MAX])
+{
+  /* search_racc will try to add a letter in front and at the end of a word */
+
+  int i,wordlistlen;
+  Dawg_edge *edge;
+  char wordtst[DIC_WORD_MAX];
+
+  for(i=0; i < RES_RACC_MAX; i++)
+    wordlist[i][0] = 0;
+
+  if (dic == NULL || wordlist == NULL)
+    return;
+
+  /* let's try for the front */
+  wordlistlen = 0;
+  strcpy(wordtst+1,word);
+  for(i='a'; i <= 'z'; i++)
+    {
+      wordtst[0] = i;
+      if (Dic_search_word_inner(dic,wordtst) && wordlistlen < RES_RACC_MAX)
+       strcpy(wordlist[wordlistlen++],wordtst);
+    }
+
+  /* add a letter at the end */
+  for(i=0; word[i]; i++)
+    wordtst[i] = word[i];
+
+  wordtst[i  ] = '\0';
+  wordtst[i+1] = '\0';
+
+  edge = Dic_seek_edgeptr(dic,word,dic->dawg + dic->root);
+
+  /* points to what the next letter can be */
+  edge = dic->dawg + edge->ptr;
+
+  if (edge != dic->dawg)
+    {
+      do {
+         if (edge->term && wordlistlen < RES_RACC_MAX)
+           {
+             wordtst[i] = edge->chr + 'a' - 1;
+             strcpy(wordlist[wordlistlen++],wordtst);
+           }
+      } while (!(*edge++).last);
+    }
+}
+
+/****************************************/
+/****************************************/
+
+
+void
+Dic_search_Benj(const Dictionary dic, const char* word,
+                char wordlist[RES_BENJ_MAX][DIC_WORD_MAX])
+{
+  int i,wordlistlen;
+  char wordtst[DIC_WORD_MAX];
+  Dawg_edge *edge0,*edge1,*edge2,*edgetst;
+
+  for(i=0; i < RES_BENJ_MAX; i++)
+    wordlist[i][0] = 0;
+
+  if (dic == NULL || word == NULL)
+    return;
+
+  wordlistlen = 0;
+
+  strcpy(wordtst+3,word);
+  edge0 = dic->dawg + (dic->dawg[dic->root].ptr);
+  do {
+    wordtst[0] = edge0->chr + 'a' - 1;
+    edge1 = dic->dawg + edge0->ptr;
+    do {
+      wordtst[1] = edge1->chr + 'a' - 1;
+      edge2  = dic->dawg + edge1->ptr;
+      do {
+       wordtst[2] = edge2->chr + 'a' - 1;
+       edgetst = Dic_seek_edgeptr(dic,word,edge2);
+       if (edgetst->term && wordlistlen < RES_BENJ_MAX)
+         strcpy(wordlist[wordlistlen++],wordtst);
+      } while (!(*edge2++).last);
+    } while (!(*edge1++).last);
+  } while (!(*edge0++).last);
+}
+
+
+/****************************************/
+/****************************************/
+
+struct params_cross_t {
+ Dictionary dic;
+ int wordlen;
+ int wordlistlen;
+ int wordlistlenmax;
+ char mask[DIC_WORD_MAX];
+};
+
+
+void
+Dic_search_cross_rec(struct params_cross_t *params,
+                    char wordlist[RES_CROS_MAX][DIC_WORD_MAX],
+                    Dawg_edge *edgeptr)
+{
+  Dawg_edge *current = params->dic->dawg + edgeptr->ptr;
+
+  if (params->mask[params->wordlen] == '\0' && edgeptr->term)
+    {
+      if (params->wordlistlen < params->wordlistlenmax)
+       strcpy(wordlist[params->wordlistlen++],params->mask);
+    }
+  else if (params->mask[params->wordlen] == '.')
+    {
+      do
+       {
+         params->mask[params->wordlen] = current->chr + 'a' - 1;
+         params->wordlen ++;
+         Dic_search_cross_rec(params,wordlist,current);
+         params->wordlen --;
+         params->mask[params->wordlen] = '.';
+       }
+      while (!(*current++).last);
+    }
+  else
+    {
+      do
+       {
+         if (current->chr == (unsigned int)(params->mask[params->wordlen] & 
DIC_CHAR_MASK))
+           {
+             params->wordlen ++;
+             Dic_search_cross_rec(params,wordlist,current);
+             params->wordlen --;
+             break;
+           }
+       }
+      while (!(*current++).last);
+    }
+}
+
+
+
+void
+Dic_search_Cros(const Dictionary dic, const char* mask,
+                char wordlist[RES_CROS_MAX][DIC_WORD_MAX])
+{
+  int  i;
+  struct params_cross_t params;
+
+  for(i=0; i < RES_CROS_MAX; i++)
+    wordlist[i][0] = 0;
+
+  if (dic == NULL || mask == NULL)
+    return;
+
+  for(i=0; i < DIC_WORD_MAX && mask[i]; i++)
+    {
+      if (isalpha(mask[i]))
+       params.mask[i] = (mask[i] & DIC_CHAR_MASK) + 'A' - 1;
+      else
+       params.mask[i] = '.';
+    }
+  params.mask[i] = '\0';
+
+  params.dic            = dic;
+  params.wordlen        = 0;
+  params.wordlistlen    = 0;
+  params.wordlistlenmax = RES_CROS_MAX;
+  Dic_search_cross_rec(&params, wordlist, dic->dawg + dic->root);
+}
+
+/****************************************/
+/****************************************/
+
+struct params_regexp_t {
+  Dictionary dic;
+  int minlength;
+  int maxlength;
+  automaton automaton;
+  struct search_RegE_list_t *charlist;
+  char word[DIC_WORD_MAX];
+  int  wordlen;
+  int  wordlistlen;
+  int  wordlistlenmax;
+};
+
+void
+Dic_search_regexp_rec(struct params_regexp_t *params,
+                     int state,
+                     Dawg_edge *edgeptr,
+                     char wordlist[RES_REGE_MAX][DIC_WORD_MAX])
+{
+  int next_state;
+  Dawg_edge *current;
+  /* if we have a valid word we store it */
+  if (automaton_get_accept(params->automaton,state) && edgeptr->term)
+    {
+      int l = strlen(params->word);
+      if (params->wordlistlen < params->wordlistlenmax &&
+         params->minlength <= l                        &&
+         params->maxlength >= l)
+       {
+         strcpy(wordlist[params->wordlistlen++],params->word);
+       }
+    }
+  /* we now drive the search by exploring the dictionary */
+  current = params->dic->dawg + edgeptr->ptr;
+  do {
+    /* the current letter is current->chr */
+    next_state = 
automaton_get_next_state(params->automaton,state,current->chr);
+    /* 1 : the letter appears in the automaton as is */
+    if (next_state)
+      {
+       params->word[params->wordlen] = current->chr + 'a' - 1;
+       params->wordlen ++;
+       Dic_search_regexp_rec(params,next_state,current,wordlist);
+       params->wordlen --;
+       params->word[params->wordlen] = '\0';
+      }
+  } while (!(*current++).last);
+}
+
+
+    /**
+     * function prototype for parser generated by bison
+     */
+int  regexpparse(yyscan_t scanner, NODE** root,
+                struct search_RegE_list_t *list,
+                struct regexp_error_report_t *err);
+
+void
+Dic_search_RegE(const Dictionary dic, const char* re,
+                char wordlist[RES_REGE_MAX][DIC_WORD_MAX],
+               struct search_RegE_list_t *list)
+{
+  int i,p,n,value;
+  int ptl[REGEXP_MAX+1];
+  int PS [REGEXP_MAX+1];
+  NODE* root;
+  yyscan_t scanner;
+  YY_BUFFER_STATE buf;
+  automaton a;
+  char stringbuf[250];
+  struct params_regexp_t params;
+  struct regexp_error_report_t report;
+
+  /* init */
+  for(i=0; i < RES_REGE_MAX; i++)
+    wordlist[i][0] = 0;
+
+  if (dic == NULL || re == NULL)
+    return;
+
+  /* (expr)# */
+  sprintf(stringbuf,"(%s)#",re);
+  for(i=0; i < REGEXP_MAX; i++)
+    {
+      PS[i] = 0;
+      ptl[i] = 0;
+    }
+
+  report.pos1 = 0;
+  report.pos2 = 0;
+  report.msg[0] = '\0';
+
+  /* parsing */
+  regexplex_init( &scanner );
+  buf   = regexp_scan_string( stringbuf, scanner );
+  root  = NULL;
+  value = regexpparse( scanner , &root, list, &report);
+  regexp_delete_buffer(buf,scanner);
+  regexplex_destroy( scanner );
+
+  if (value)
+    {
+#ifdef DEBUG_FLEX_IS_BROKEN
+      fprintf(stderr,"parser error at pos %d - %d : %s\n",
+             report.pos1, report.pos2, report.msg);
+#endif
+      regexp_delete_tree(root);
+      return ;
+    }
+
+  n = 1;
+  p = 1;
+  regexp_parcours(root, &p, &n, ptl);
+  PS [0] = p - 1;
+  ptl[0] = p - 1;
+
+  regexp_possuivante(root,PS);
+
+  if ((a = automaton_build(root->PP,ptl,PS,list)) != NULL)
+    {
+      params.dic            = dic;
+      params.minlength      = list->minlength;
+      params.maxlength      = list->maxlength;
+      params.automaton      = a;
+      params.charlist       = list;
+      memset(params.word,'\0',sizeof(params.word));
+      params.wordlen        = 0;
+      params.wordlistlen    = 0;
+      params.wordlistlenmax = RES_REGE_MAX;
+      Dic_search_regexp_rec(&params, automaton_get_init(a), dic->dawg + 
dic->root, wordlist);
+
+      automaton_delete(a);
+    }
+  regexp_delete_tree(root);
+}
+
+/****************************************/
+/****************************************/
+
Index: eliot/dic/dic_search.h
diff -u /dev/null eliot/dic/dic_search.h:1.10.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/dic/dic_search.h      Wed Dec 28 16:47:35 2005
@@ -0,0 +1,110 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Elit 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file dic_search.h
+ *  \brief  Dictionary lookup functions
+ *  \author Antoine Fraboulet
+ *  \date   2002
+ */
+
+#ifndef _DIC_SEARCH_H_
+#define _DIC_SEARCH_H_
+#if defined(__cplusplus)
+extern "C"
+  {
+#endif
+
+    /**
+     * number of results for Rack+1 search (Dic_search_7pl1)
+     */
+#define RES_7PL1_MAX 200
+
+    /**
+     * number of results for Extensions search (Dic_search_Racc)
+     */
+#define RES_RACC_MAX 100
+
+    /**
+     * number of results for Benjamin search (Dic_search_Benj)
+     */
+#define RES_BENJ_MAX 100
+
+    /**
+     * number of results for CrossWords search (Dic_search_Cros)
+     */
+#define RES_CROS_MAX 200
+
+    /**
+     * number of results for Regular Expression search (Dic_search_RegE)
+     */
+#define RES_REGE_MAX 200
+
+    /**
+     * Search for a word in the dictionnary
+     * @param dic : dictionary
+     * @param path : lookup word
+     * @return 1 present, 0 error
+     */
+int  Dic_search_word(Dictionary dic, const wchar_t* path);
+
+    /**
+     * Search for all feasible word with "rack" plus one letter
+     * @param dic : dictionary
+     * @param rack : letters
+     * @param wordlist : results
+     */
+void Dic_search_7pl1(Dictionary dic, const char* rack, char 
wordlist[DIC_LETTERS][RES_7PL1_MAX][DIC_WORD_MAX], int joker);
+
+    /**
+     * Search for all feasible word adding a letter in front or at the end
+     * @param dic : dictionary
+     * @param word : word
+     * @param wordlist : results
+     */
+void Dic_search_Racc(Dictionary dic, const char* word, char 
wordlist[RES_RACC_MAX][DIC_WORD_MAX]);
+
+    /**
+     * Search for benjamins
+     * @param dic : dictionary
+     * @param rack : letters
+     * @param wordlist : results
+     */
+void Dic_search_Benj(Dictionary dic, const char* word, char 
wordlist[RES_BENJ_MAX][DIC_WORD_MAX]);
+
+    /**
+     * Search for crosswords
+     * @param dic : dictionary
+     * @param rack : letters
+     * @param wordlist : results
+     */
+void Dic_search_Cros(Dictionary dic, const char* mask, char 
wordlist[RES_CROS_MAX][DIC_WORD_MAX]);
+
+    /**
+     * Search for words matching a regular expression
+     * @param dic : dictionary
+     * @param re : regular expression
+     * @param wordlist : results
+     */
+void Dic_search_RegE(Dictionary dic, const char* re, char 
wordlist[RES_REGE_MAX][DIC_WORD_MAX], struct search_RegE_list_t *list);
+
+#if defined(__cplusplus)
+  }
+#endif
+#endif /* _DIC_SEARCH_H_ */
Index: eliot/dic/listdic.c
diff -u /dev/null eliot/dic/listdic.c:1.6.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/dic/listdic.c Wed Dec 28 16:47:35 2005
@@ -0,0 +1,234 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Elit 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "dic_internals.h"
+#include "dic.h"
+
+
+static void
+print_dic_rec(FILE* out, Dictionary dic, char *buf, char* s, Dawg_edge i)
+{
+  if (i.term)  /* edge points at a complete word */
+    {
+      *s = '\0';
+      fprintf (out,"%s\n", buf);
+    }
+  if (i.ptr)
+    {           /* Compute index: is it non-zero ? */
+      Dawg_edge *p = dic->dawg + i.ptr;
+      do {                         /* for each edge out of this node */
+        *s = p->chr + 'a' - 1;
+        print_dic_rec (out,dic,buf,s + 1, *p);
+      }
+      while (!(*p++).last);
+    }
+}
+
+void
+dic_load(Dictionary* dic, char* filename)
+{
+  int res;
+  if ((res = Dic_load(dic, filename)) != 0)
+    {
+      switch (res) {
+      case 1: printf("chargement: problème d'ouverture de %s\n",filename); 
break;
+      case 2: printf("chargement: mauvais en-tete de dictionnaire\n"); break;
+      case 3: printf("chargement: problème 3 d'allocation mémoire\n"); break;
+      case 4: printf("chargement: problème 4 d'alocation mémoire\n"); break;
+      case 5: printf("chargement: problème de lecture des arcs du 
dictionnaire\n"); break;
+      default: printf("chargement: problème non-repertorié\n"); break;
+      }
+      exit(res);
+    }
+}
+
+void
+print_dic_list(char* filename, char* out)
+{
+  FILE* fout;
+  Dictionary dic;
+  static char buf[80];
+
+  dic_load(&dic,filename);
+
+  if (strcmp(out,"stdout") == 0)
+    print_dic_rec(stdout,dic,buf,buf,dic->dawg[dic->root]);
+  else if (strcmp(out,"stderr") == 0)
+    print_dic_rec(stderr,dic,buf,buf,dic->dawg[dic->root]);
+  else
+    {
+      if ((fout = fopen(out,"w")) == NULL)
+       return;
+      print_dic_rec(fout,dic,buf,buf,dic->dawg[dic->root]);
+      fclose(fout);
+    }
+  Dic_destroy(dic);
+}
+
+char
+b2h(int i)
+{
+  if (i < 10)
+    return i+'0';
+  return i-10+'a';
+}
+
+char*
+hexb(unsigned char h)
+{
+  static char buf[3];
+  buf[0] = b2h((h & 0xf0) >> 4);
+  buf[1] = b2h((h & 0x0f));
+  buf[2] = 0;
+  return buf;
+}
+
+char*
+hexl(unsigned int h)
+{
+  static char buf[9];
+  int i;
+  for(i=0; i<4; i++)
+    {
+      int l = h >> (24 - i*8);
+      buf[i*2+0] = b2h((l & 0xf0) >> 4);
+      buf[i*2+1] = b2h((l & 0x0f));
+    }
+  buf[8] = 0;
+  return buf;
+}
+
+char*
+offset(void* base, void* off)
+{
+  static char buf[20];
+  int o = (char*)off - (char*)base;
+  sprintf(buf,"%s",hexb(o));
+  return buf;
+}
+
+void
+print_header(char* filename)
+{
+  FILE* file;
+  Dict_header header;
+
+  if ((file = fopen(filename,"rb")) == NULL)
+    return;
+  if (fread(&header,sizeof(Dict_header),1,file) != 1)
+    return;
+  fclose(file);
+
+  printf("Dictionary header information\n");
+  printf("0x%s ident       : %s\n",offset(&header,&header.ident),header.ident);
+  printf("0x%s unused 1    : %6d %s\n",offset(&header,&header.unused_1)  
,header.unused_1  ,hexl(header.unused_1));
+  printf("0x%s unused 2    : %6d %s\n",offset(&header,&header.unused_2)  
,header.unused_2  ,hexl(header.unused_2));
+  printf("0x%s root        : %6d %s\n",offset(&header,&header.root)      
,header.root      ,hexl(header.root));
+  printf("0x%s words       : %6d %s\n",offset(&header,&header.nwords)    
,header.nwords    ,hexl(header.nwords));
+  printf("0x%s edges used  : %6d %s\n",offset(&header,&header.edgesused) 
,header.edgesused ,hexl(header.edgesused));
+  printf("0x%s nodes used  : %6d %s\n",offset(&header,&header.nodesused) 
,header.nodesused ,hexl(header.nodesused));
+  printf("0x%s nodes saved : %6d 
%s\n",offset(&header,&header.nodessaved),header.nodessaved,hexl(header.nodessaved));
+  printf("0x%s edges saved : %6d 
%s\n",offset(&header,&header.edgessaved),header.edgessaved,hexl(header.edgessaved));
+  printf("\n");
+  printf("sizeof(header) = 0x%s (%lu)\n", hexb(sizeof(header)), 
sizeof(header));
+}
+
+void
+print_node_hex(int i, Dictionary dic)
+{
+  unsigned int* pe;
+  Dawg_edge e = dic->dawg[i];
+  pe = (unsigned int*)&e;
+  printf("0x%s %s |%2d ptr=%2d t=%d l=%d f=%d chr=%d (%c)\n",
+        offset(&(dic->dawg[0]),&(dic->dawg[i])),hexl(*pe),i,
+        e.ptr, e.term, e.last, e.fill, e.chr, e.chr +'a' -1);
+}
+
+void
+print_dic_hex(char* filename)
+{
+  int i;
+  Dictionary dic;
+  dic_load(&dic,filename);
+
+  printf("offs binary       structure         \n");
+  printf("---- -------- |   ------------------\n");
+  for(i=0; i < (dic->nedges + 1); i++)
+    print_node_hex(i,dic);
+  Dic_destroy(dic);
+}
+
+void
+usage(char* name)
+{
+  printf("usage: %s [-a|-d|-h|-l] dictionnaire\n", name);
+  printf("  -a : print all\n");
+  printf("  -h : print header\n");
+  printf("  -d : print dic in hex\n");
+  printf("  -l : print dic word list\n");
+}
+
+
+int
+main(int argc, char *argv[])
+{
+  int arg_count;
+  int option_print_all      = 0;
+  int option_print_header   = 0;
+  int option_print_dic_hex  = 0;
+  int option_print_dic_list = 0;
+
+  if (argc < 3)
+    {
+      usage(argv[0]);
+      exit(1);
+    }
+
+  arg_count = 1;
+  while(argv[arg_count][0] == '-')
+    {
+      switch (argv[arg_count][1])
+       {
+       case 'a': option_print_all = 1; break;
+       case 'h': option_print_header = 1; break;
+       case 'd': option_print_dic_hex = 1; break;
+       case 'l': option_print_dic_list = 1; break;
+       default: usage(argv[0]); exit(2);
+         break;
+       }
+      arg_count++;
+    }
+
+  if (option_print_header || option_print_all)
+    {
+      print_header(argv[arg_count]);
+    }
+  if (option_print_dic_hex || option_print_all)
+    {
+      print_dic_hex(argv[arg_count]);
+    }
+  if (option_print_dic_list || option_print_all)
+    {
+      print_dic_list(argv[arg_count],"stdout");
+    }
+  return 0;
+}
Index: eliot/game/Makefile.am
diff -u /dev/null eliot/game/Makefile.am:1.11.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/Makefile.am      Wed Dec 28 16:47:35 2005
@@ -0,0 +1,46 @@
+# Eliot
+# Copyright (C) 1999  Antoine Fraboulet
+# address@hidden
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+noinst_LIBRARIES = libgame.a
+
+INCLUDES = -I$(top_srcdir)/dic
+
+libgame_a_SOURCES=           \
+       ai_percent.cpp ai_percent.h     \
+       ai_player.h                     \
+       tile.cpp tile.h                 \
+       bag.cpp bag.h                   \
+       coord.cpp coord.h               \
+       cross.cpp cross.h               \
+       board.cpp board.h               \
+       board_cross.cpp                 \
+       board_search.cpp                \
+       duplicate.cpp duplicate.h       \
+       encoding.cpp encoding.h         \
+       freegame.cpp freegame.h         \
+       game.cpp game.h                 \
+       game_factory.cpp game_factory.h \
+       player.cpp player.h             \
+       pldrack.cpp pldrack.h           \
+       rack.cpp rack.h                 \
+       results.cpp results.h           \
+       round.cpp round.h               \
+       training.cpp training.h         \
+       turn.cpp turn.h                 \
+       history.cpp history.h
+
Index: eliot/game/bag.cpp
diff -u /dev/null eliot/game/bag.cpp:1.5.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/bag.cpp  Wed Dec 28 16:47:35 2005
@@ -0,0 +1,138 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include <string>
+
+#include "tile.h"
+#include "bag.h"
+#include "debug.h"
+
+
+Bag::Bag()
+{
+    init();
+}
+
+
+void Bag::init()
+{
+    m_ntiles = 0;
+    const list<Tile>& allTiles = Tile::getAllTiles();
+    list<Tile>::const_iterator it;
+    for (it = allTiles.begin(); it != allTiles.end(); it++)
+    {
+        m_tilesMap[*it] = it->maxNumber();
+        m_ntiles += it->maxNumber();
+    }
+}
+
+
+unsigned int Bag::in(const Tile &iTile) const
+{
+    map<Tile, int>::const_iterator it = m_tilesMap.find(iTile);
+    if (it != m_tilesMap.end())
+        return (*it).second;
+    return 0;
+}
+
+
+unsigned int Bag::nVowels() const
+{
+    map<Tile, int>::const_iterator it;
+    int v = 0;
+
+    for (it = m_tilesMap.begin(); it != m_tilesMap.end(); it++)
+    {
+        if (it->first.isVowel())
+            v += it->second;
+    }
+    return v;
+}
+
+
+unsigned int Bag::nConsonants() const
+{
+    map<Tile, int>::const_iterator it;
+    int c = 0;
+
+    for (it = m_tilesMap.begin(); it != m_tilesMap.end(); it++)
+    {
+        if (it->first.isConsonant())
+            c += it->second;
+    }
+    return c;
+}
+
+
+void Bag::takeTile(const Tile &iTile)
+{
+    ASSERT(in(iTile),
+           (wstring(L"The bag does not contain the letter ") + 
iTile.toChar()).c_str());
+
+    m_tilesMap[iTile]--;
+    m_ntiles--;
+}
+
+
+void Bag::replaceTile(const Tile &iTile)
+{
+    ASSERT(in(iTile) < iTile.maxNumber(),
+           (wstring(L"Cannot replace tile: ") + iTile.toChar()).c_str());
+
+    m_tilesMap[iTile]++;
+    m_ntiles++;
+}
+
+
+Tile Bag::selectRandom()
+{
+    map<Tile, int>::const_iterator it;
+    int n;
+    double max = m_ntiles;
+
+    n = (int)(max * rand() / (RAND_MAX + 1.0));
+    for (it = m_tilesMap.begin(); it != m_tilesMap.end(); it++)
+    {
+        if (n < it->second)
+            return it->first;
+        n -= it->second;
+    }
+    ASSERT(false, "We should not come here");
+    return Tile::dummy();
+}
+
+
+void Bag::operator=(const Bag &iOther)
+{
+    m_tilesMap = iOther.m_tilesMap;
+    m_ntiles = iOther.m_ntiles;
+}
+
+
+void Bag::dumpAll() const
+{
+    map<Tile, int>::const_iterator it;
+    for (it = m_tilesMap.begin(); it != m_tilesMap.end(); it++)
+    {
+        if (it->second)
+            fprintf(stderr, "%c[%i] ", it->first.toChar(), it->second);
+    }
+    fprintf(stderr, "\n");
+}
Index: eliot/game/board.cpp
diff -u /dev/null eliot/game/board.cpp:1.11.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/board.cpp        Wed Dec 28 16:47:35 2005
@@ -0,0 +1,476 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include <wctype.h>
+#include "dic.h"
+#include "tile.h"
+#include "round.h"
+#include "bag.h"
+#include "rack.h"
+#include "results.h"
+#include "board.h"
+
+#define oo 0
+#define __ 1
+#define T2 2
+#define T3 3
+#define W2 2
+#define W3 3
+
+
+const int Board::m_tileMultipliers[BOARD_REALDIM][BOARD_REALDIM] =
+{
+    { oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo },
+    { oo,__,__,__,T2,__,__,__,__,__,__,__,T2,__,__,__,oo },
+    { oo,__,__,__,__,__,T3,__,__,__,T3,__,__,__,__,__,oo },
+    { oo,__,__,__,__,__,__,T2,__,T2,__,__,__,__,__,__,oo },
+    { oo,T2,__,__,__,__,__,__,T2,__,__,__,__,__,__,T2,oo },
+    { oo,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,oo },
+    { oo,__,T3,__,__,__,T3,__,__,__,T3,__,__,__,T3,__,oo },
+    { oo,__,__,T2,__,__,__,T2,__,T2,__,__,__,T2,__,__,oo },
+    { oo,__,__,__,T2,__,__,__,__,__,__,__,T2,__,__,__,oo },
+    { oo,__,__,T2,__,__,__,T2,__,T2,__,__,__,T2,__,__,oo },
+    { oo,__,T3,__,__,__,T3,__,__,__,T3,__,__,__,T3,__,oo },
+    { oo,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,oo },
+    { oo,T2,__,__,__,__,__,__,T2,__,__,__,__,__,__,T2,oo },
+    { oo,__,__,__,__,__,__,T2,__,T2,__,__,__,__,__,__,oo },
+    { oo,__,__,__,__,__,T3,__,__,__,T3,__,__,__,__,__,oo },
+    { oo,__,__,__,T2,__,__,__,__,__,__,__,T2,__,__,__,oo },
+    { oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo }
+};
+
+
+const int Board::m_wordMultipliers[BOARD_REALDIM][BOARD_REALDIM] =
+{
+    { oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo },
+    { oo,W3,__,__,__,__,__,__,W3,__,__,__,__,__,__,W3,oo },
+    { oo,__,W2,__,__,__,__,__,__,__,__,__,__,__,W2,__,oo },
+    { oo,__,__,W2,__,__,__,__,__,__,__,__,__,W2,__,__,oo },
+    { oo,__,__,__,W2,__,__,__,__,__,__,__,W2,__,__,__,oo },
+    { oo,__,__,__,__,W2,__,__,__,__,__,W2,__,__,__,__,oo },
+    { oo,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,oo },
+    { oo,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,oo },
+    { oo,W3,__,__,__,__,__,__,W2,__,__,__,__,__,__,W3,oo },
+    { oo,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,oo },
+    { oo,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,oo },
+    { oo,__,__,__,__,W2,__,__,__,__,__,W2,__,__,__,__,oo },
+    { oo,__,__,__,W2,__,__,__,__,__,__,__,W2,__,__,__,oo },
+    { oo,__,__,W2,__,__,__,__,__,__,__,__,__,W2,__,__,oo },
+    { oo,__,W2,__,__,__,__,__,__,__,__,__,__,__,W2,__,oo },
+    { oo,W3,__,__,__,__,__,__,W3,__,__,__,__,__,__,W3,oo },
+    { oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo,oo }
+};
+
+
+Board::Board():
+    m_tilesRow(BOARD_REALDIM, Tile::dummy()),
+    m_tilesCol(BOARD_REALDIM, Tile::dummy()),
+    m_jokerRow(BOARD_REALDIM, false),
+    m_jokerCol(BOARD_REALDIM, false),
+    m_crossRow(BOARD_REALDIM, Cross()),
+    m_crossCol(BOARD_REALDIM, Cross()),
+    m_pointRow(BOARD_REALDIM, -1),
+    m_pointCol(BOARD_REALDIM, -1),
+    m_testsRow(BOARD_REALDIM, 0)
+{
+    // No cross check allowed around the board
+    for (int i = 0; i < BOARD_REALDIM; i++)
+    {
+        m_crossRow[0][i].clear();
+        m_crossCol[0][i].clear();
+        m_crossRow[i][0].clear();
+        m_crossCol[i][0].clear();
+        m_crossRow[BOARD_REALDIM - 1][i].clear();
+        m_crossCol[BOARD_REALDIM - 1][i].clear();
+        m_crossRow[i][BOARD_REALDIM - 1].clear();
+        m_crossCol[i][BOARD_REALDIM - 1].clear();
+    }
+}
+
+
+wchar_t Board::getChar(int iRow, int iCol) const
+{
+    wchar_t letter = 0;
+    Tile tile = getTile(iRow, iCol);
+    if (!tile.isEmpty())
+    {
+        letter = tile.toChar();
+        if (isJoker(iRow, iCol))
+            letter = towlower(letter);
+    }
+    return letter;
+}
+
+int Board::getCharAttr(int iRow, int iCol) const
+{
+    int t = getTestChar(iRow, iCol);
+    int j = isJoker(iRow, iCol);
+    return  (t << 1) | j;
+}
+
+
+Tile Board::getTile(int iRow, int iCol) const
+{
+    return m_tilesRow[iRow][iCol];
+}
+
+
+bool Board::isJoker(int iRow, int iCol) const
+{
+    return m_jokerRow[iRow][iCol];
+}
+
+
+bool Board::isVacant(int iRow, int iCol) const
+{
+    if (iRow < 1 || iRow > BOARD_DIM ||
+        iCol < 1 || iCol > BOARD_DIM)
+    {
+        return false;
+    }
+    return m_tilesRow[iRow][iCol].isEmpty();
+}
+
+
+void Board::addRound(const Dictionary &iDic, const Round &iRound)
+{
+    Tile t;
+    int row, col;
+
+    row = iRound.getCoord().getRow();
+    col = iRound.getCoord().getCol();
+    if (iRound.getCoord().getDir() == Coord::HORIZONTAL)
+    {
+        for (int i = 0; i < iRound.getWordLen(); i++)
+        {
+            if (m_tilesRow[row][col + i].isEmpty())
+            {
+                t = iRound.getTile(i);
+                m_tilesRow[row][col + i] = t;
+                m_jokerRow[row][col + i] = iRound.isJoker(i);
+                m_tilesCol[col + i][row] = t;
+                m_jokerCol[col + i][row] = iRound.isJoker(i);
+            }
+        }
+    }
+    else
+    {
+        for (int i = 0; i < iRound.getWordLen(); i++)
+        {
+            if (m_tilesRow[row + i][col].isEmpty())
+            {
+                t = iRound.getTile(i);
+                m_tilesRow[row + i][col] = t;
+                m_jokerRow[row + i][col] = iRound.isJoker(i);
+                m_tilesCol[col][row + i] = t;
+                m_jokerCol[col][row + i] = iRound.isJoker(i);
+            }
+        }
+    }
+    buildCross(iDic);
+#ifdef DEBUG
+    checkDouble();
+#endif
+}
+
+
+void Board::removeRound(const Dictionary &iDic, const Round &iRound)
+{
+    int row, col;
+
+    row = iRound.getCoord().getRow();
+    col = iRound.getCoord().getCol();
+    if (iRound.getCoord().getDir() == Coord::HORIZONTAL)
+    {
+        for (int i = 0; i < iRound.getWordLen(); i++)
+        {
+            if (iRound.isPlayedFromRack(i))
+            {
+                m_tilesRow[row][col + i] = Tile::dummy();
+                m_jokerRow[row][col + i] = false;
+                m_tilesCol[col + i][row] = Tile::dummy();
+                m_jokerCol[col + i][row] = false;
+            }
+        }
+    }
+    else
+    {
+        for (int i = 0; i < iRound.getWordLen(); i++)
+        {
+            if (iRound.isPlayedFromRack(i))
+            {
+                m_tilesRow[row + i][col] = Tile::dummy();
+                m_jokerRow[row + i][col] = false;
+                m_tilesCol[col][row + i] = Tile::dummy();
+                m_jokerCol[col][row + i] = false;
+            }
+        }
+    }
+    buildCross(iDic);
+#ifdef DEBUG
+    checkDouble();
+#endif
+}
+
+
+/* XXX: There is duplicated code with board_search.c.
+ * We could probably factorize something... */
+int Board::checkRoundAux(Matrix<Tile> &iTilesMx,
+                         Matrix<Cross> &iCrossMx,
+                         Matrix<int> &iPointsMx,
+                         Matrix<bool> &iJokerMx,
+                         Round &iRound,
+                         bool firstturn)
+{
+    Tile t;
+    int row, col, i;
+    int l, p, fromrack;
+    int pts, ptscross, wordmul;
+    bool isolated = true;
+
+    fromrack = 0;
+    pts = 0;
+    ptscross = 0;
+    wordmul = 1;
+    row = iRound.getCoord().getRow();
+    col = iRound.getCoord().getCol();
+
+    /* Is the word an extension of another word? */
+    if (!iTilesMx[row][col - 1].isEmpty() ||
+        !iTilesMx[row][col + iRound.getWordLen()].isEmpty())
+    {
+        return 1;
+    }
+
+    for (i = 0; i < iRound.getWordLen(); i++)
+    {
+        t = iRound.getTile(i);
+        if (!iTilesMx[row][col + i].isEmpty())
+        {
+            /* There is already a letter on the board */
+            if (iTilesMx[row][col + i] != t)
+                return 2;
+
+            isolated = false;
+            iRound.setFromBoard(i);
+
+            if (!iJokerMx[row][col + i])
+                pts += t.getPoints();
+        }
+        else
+        {
+            /* The letter is not yet on the board */
+            if (iCrossMx[row][col + i].check(t))
+            {
+                /* A non-trivial cross-check means an anchor square */
+                if (!iCrossMx[row][col + i].isAny())
+                    isolated = false;
+
+                if (!iRound.isJoker(i))
+                    l = t.getPoints() * m_tileMultipliers[row][col + i];
+                else
+                    l = 0;
+                pts += l;
+                wordmul *= m_wordMultipliers[row][col + i];
+
+                p = iPointsMx[row][col + i];
+                if (p >= 0)
+                {
+                    ptscross += (p + l) * m_wordMultipliers[row][col + i];
+                }
+                fromrack++;
+                iRound.setFromRack(i);
+            }
+            else
+            {
+                /* The letter is not in the crosscheck */
+                return 3;
+            }
+        }
+    }
+
+    /* There must be at least 1 letter from the rack */
+    if (fromrack == 0)
+        return 4;
+
+    /* The word must cover at least one anchor square, except
+     * for the first turn */
+    if (isolated && !firstturn)
+        return 5;
+    /* The first word must be horizontal */
+    if (firstturn && iRound.getCoord().getDir() == Coord::VERTICAL)
+        return 6;
+    /* The first word must cover the H8 square */
+    if (firstturn
+        && (row != 8 || col > 8 || col + iRound.getWordLen() <= 8))
+    {
+        return 7;
+    }
+
+    /* Set the iPointsMx and bonus */
+    pts = ptscross + pts * wordmul + 50 * (fromrack == 7);
+    iRound.setPoints(pts);
+    iRound.setBonus(fromrack == 7);
+
+    return 0;
+}
+
+
+int Board::checkRound(Round &iRound, bool firstturn)
+{
+    if (iRound.getCoord().getDir() == Coord::HORIZONTAL)
+    {
+        return checkRoundAux(m_tilesRow, m_crossRow,
+                             m_pointRow, m_jokerRow,
+                             iRound, firstturn);
+    }
+    else
+    {
+        // XXX: ugly!
+        // Exchange the coordinates temporarily
+        iRound.accessCoord().swap();
+
+        int res = checkRoundAux(m_tilesCol, m_crossCol,
+                                m_pointCol, m_jokerCol,
+                                iRound, firstturn);
+
+        // Restore the coordinates
+        iRound.accessCoord().swap();
+
+        return res;
+    }
+}
+
+
+void Board::testRound(const Round &iRound)
+{
+    Tile t;
+    int row, col;
+
+    row = iRound.getCoord().getRow();
+    col = iRound.getCoord().getCol();
+    if (iRound.getCoord().getDir() == Coord::HORIZONTAL)
+    {
+        for (int i = 0; i < iRound.getWordLen(); i++)
+        {
+            if (m_tilesRow[row][col + i].isEmpty())
+            {
+                t = iRound.getTile(i);
+                m_tilesRow[row][col + i] = t;
+                m_jokerRow[row][col + i] = iRound.isJoker(i);
+                m_testsRow[row][col + i] = 1;
+
+                m_tilesCol[col + i][row] = t;
+                m_jokerCol[col + i][row] = iRound.isJoker(i);
+            }
+        }
+    }
+    else
+    {
+        for (int i = 0; i < iRound.getWordLen(); i++)
+        {
+            if (m_tilesRow[row + i][col].isEmpty())
+            {
+                t = iRound.getTile(i);
+                m_tilesRow[row + i][col] = t;
+                m_jokerRow[row + i][col] = iRound.isJoker(i);
+                m_testsRow[row + i][col] = 1;
+
+                m_tilesCol[col][row + i] = t;
+                m_jokerCol[col][row + i] = iRound.isJoker(i);
+            }
+        }
+    }
+}
+
+
+void Board::removeTestRound()
+{
+    for (int row = 1; row <= BOARD_DIM; row++)
+    {
+        for (int col = 1; col <= BOARD_DIM; col++)
+        {
+            if (m_testsRow[row][col])
+            {
+                m_tilesRow[row][col] = Tile::dummy();
+                m_testsRow[row][col] = 0;
+                m_jokerRow[row][col] = false;
+
+                m_tilesCol[col][row] = Tile::dummy();
+                m_jokerCol[col][row] = false;
+            }
+        }
+    }
+}
+
+
+char Board::getTestChar(int iRow, int iCol) const
+{
+    return m_testsRow[iRow][iCol];
+}
+
+
+int Board::getWordMultiplier(int iRow, int iCol) const
+{
+    if (iRow < BOARD_MIN || iRow > BOARD_MAX ||
+        iCol < BOARD_MIN || iCol > BOARD_MAX)
+        return 0;
+    return m_wordMultipliers[iRow][iCol];
+}
+
+
+int Board::getLetterMultiplier(int iRow, int iCol) const
+{
+    if (iRow < BOARD_MIN || iRow > BOARD_MAX ||
+        iCol < BOARD_MIN || iCol > BOARD_MAX)
+        return 0;
+    return m_tileMultipliers[iRow][iCol];
+}
+
+
+#ifdef DEBUG
+void Board::checkDouble()
+{
+    for (int row = BOARD_MIN; row <= BOARD_MAX; row++)
+    {
+        for (int col = BOARD_MIN; col <= BOARD_MAX; col++)
+        {
+            if (m_tilesRow[row][col] != m_tilesCol[col][row])
+                printf("tiles diff %d %d\n", row, col);
+
+            // The crossckecks and the points have no reason to be the same
+            // in both directions
+            /*
+            if (m_crossRow[row][col] != m_crossCol[col][row])
+            {
+                printf("cross diff %d %d\n",row,col);
+            }
+
+            if (m_pointRow[row][col] != m_pointCol[col][row])
+                printf("point diff %d %d\n",row,col);
+            */
+
+            if (m_jokerRow[row][col] != m_jokerCol[col][row])
+                printf("joker diff %d %d\n", row, col);
+        }
+    }
+}
+#endif
+
Index: eliot/game/board.h
diff -u /dev/null eliot/game/board.h:1.10.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/board.h  Wed Dec 28 16:47:35 2005
@@ -0,0 +1,152 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#ifndef _BOARD_H_
+#define _BOARD_H_
+
+#include "tile.h"
+#include "cross.h"
+#include <string>
+#include <vector>
+
+typedef struct _Dictionary*Dictionary;
+class Rack;
+class Round;
+class Results;
+
+using namespace std;
+
+#define BOARD_MIN 1
+#define BOARD_MAX 15
+#define BOARD_DIM 15
+#define BOARD_REALDIM (BOARD_DIM + 2)
+
+
+// Template matrix class for convenience.
+template <class T>
+class Matrix: public vector<vector<T> >
+{
+public:
+    // Construct a matrix with an initial value
+    Matrix(int iSize1, int iSize2, const T &iValue)
+    {
+        resize(iSize1, vector<T>(iSize2, iValue));
+    }
+    // Construct a square matrix with an initial value
+    Matrix(int iSize, const T &iValue)
+    {
+        resize(iSize, vector<T>(iSize, iValue));
+    }
+};
+
+
+class Board
+{
+public:
+    Board();
+    virtual ~Board() {}
+
+    /*************************
+     * Coordinates have to be BOARD_MIN <= int <= BOARD_MAX
+     *
+     * getChar returns an upper case letter for normal tiles and a
+     * lower case letter for jokers.
+     *
+     * getCharAttr tells the attributes of the tile
+     *   0 : normal played tile
+     *   1 : joker tile
+     *   2 : test tile for preview purpose
+     * Attributes can be combined with the or (|) operator
+     *************************/
+#define ATTR_NORMAL 0
+#define ATTR_JOKER  1
+#define ATTR_TEST   2
+
+    wchar_t getChar    (int iRow, int iCol) const;
+    int     getCharAttr(int iRow, int iCol) const;
+
+    Tile getTile(int iRow, int iCol) const;
+    bool isJoker(int iRow, int iCol) const;
+    bool isVacant(int iRow, int iCol) const;
+
+    void addRound(const Dictionary &iDic, const Round &iRound);
+    void removeRound(const Dictionary &iDic, const Round &iRound);
+    int checkRound(Round &iRound, bool iFirstTurn);
+
+    /*************************
+     *
+     *
+     *************************/
+    void testRound(const Round &iRound);
+    void removeTestRound();
+    char getTestChar(int iRow, int iCol) const;
+
+    /*************************
+     *
+     * board_search.c
+     *************************/
+    void search(const Dictionary &iDic, const Rack &iRack, Results &oResults);
+    void searchFirst(const Dictionary &iDic, const Rack &iRack, Results 
&oResults);
+
+    /*************************
+     *
+     * board_cross.c
+     *************************/
+    void buildCross(const Dictionary &iDic);
+
+    /*************************
+     *
+     *
+     *************************/
+    int getWordMultiplier(int iRow, int iCol) const;
+    int getLetterMultiplier(int iRow, int iCol) const;
+
+private:
+
+    Matrix<Tile> m_tilesRow;
+    Matrix<Tile> m_tilesCol;
+
+    Matrix<bool> m_jokerRow;
+    Matrix<bool> m_jokerCol;
+
+    Matrix<Cross> m_crossRow;
+    Matrix<Cross> m_crossCol;
+
+    Matrix<int> m_pointRow;
+    Matrix<int> m_pointCol;
+
+    Matrix<char> m_testsRow;
+
+    static const int m_tileMultipliers[BOARD_REALDIM][BOARD_REALDIM];
+    static const int m_wordMultipliers[BOARD_REALDIM][BOARD_REALDIM];
+
+    int checkRoundAux(Matrix<Tile> &iTilesMx,
+                      Matrix<Cross> &iCrossMx,
+                      Matrix<int> &iPointsMx,
+                      Matrix<bool> &iJokerMx,
+                      Round &iRound,
+                      bool firstturn);
+#ifdef DEBUG
+    void checkDouble();
+#endif
+
+};
+
+#endif
Index: eliot/game/coord.cpp
diff -u /dev/null eliot/game/coord.cpp:1.7.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/coord.cpp        Wed Dec 28 16:47:35 2005
@@ -0,0 +1,117 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Eliot 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file   coord.cpp
+ *  \brief  Eliot coordinate system
+ *  \author Antoine Fraboulet
+ *  \date   2005
+ */
+
+#include <string>
+#include <wchar.h>
+#include "coord.h"
+#include "board.h" // for BOARD_MIN and BOARD_MAX (TODO: remove this include)
+#include "debug.h"
+#include "encoding.h"
+
+
+Coord::Coord(int iRow, int iCol, Direction iDir)
+{
+    m_row = iRow;
+    m_col = iCol;
+    m_dir = iDir;
+}
+
+Coord::Coord(const wstring &iStr)
+{
+    setFromString(iStr);
+}
+
+bool Coord::isValid() const
+{
+    return (m_row >= BOARD_MIN && m_row <= BOARD_MAX &&
+            m_col >= BOARD_MIN && m_col <= BOARD_MAX);
+}
+
+void Coord::operator=(const Coord &iOther)
+{
+    m_dir = iOther.m_dir;
+    m_row = iOther.m_row;
+    m_col = iOther.m_col;
+}
+
+void Coord::swap()
+{
+    int tmp = m_col;
+    m_col = m_row;
+    m_row = tmp;
+}
+
+
+void Coord::setFromString(const wstring &iWStr)
+{
+    // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
+    // Temporary implementation: convert the wchar_t* string into a char* one
+    string iStr = convertToMb(iWStr);
+
+    char l[4];
+    int col;
+
+    if (sscanf(iStr.c_str(), "%1[a-oA-O]%2d", l, &col) == 2)
+    {
+        setDir(HORIZONTAL);
+    }
+    else if (sscanf(iStr.c_str(), "%2d%1[a-oA-O]", &col, l) == 2)
+    {
+        setDir(VERTICAL);
+    }
+    else
+    {
+        col = -1;
+        l[0] = 'A' - 1;
+    }
+    int row = toupper(*l) - 'A' + 1;
+    setCol(col);
+    setRow(row);
+}
+
+wstring Coord::toString() const
+{
+    ASSERT(isValid(), "Invalid coordinates");
+
+    wstring res;
+    wchar_t s[5];
+    swprintf(s, 4, L"%d", m_col);
+    if (getDir() == HORIZONTAL)
+    {
+        res = wstring(1, m_row + 'A' - 1) + s;
+    }
+    else
+    {
+        res = s + wstring(1, m_row + 'A' - 1);
+    }
+    return res;
+}
+
+
+/// Local Variables:
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/game/coord.h
diff -u /dev/null eliot/game/coord.h:1.5.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/coord.h  Wed Dec 28 16:47:35 2005
@@ -0,0 +1,73 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Eliot 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file   coord.h
+ *  \brief  Game coordinates system
+ *  \author Antoine Fraboulet
+ *  \date   2005
+ */
+
+#ifndef _COORD_H
+#define _COORD_H
+
+using std::string;
+using std::wstring;
+
+class Coord
+{
+public:
+
+    enum Direction {VERTICAL, HORIZONTAL};
+
+    // Construction, destruction
+    Coord(int iRow = -1, int iCol = -1, Direction iDir = HORIZONTAL);
+    Coord(const wstring &iStr);
+    virtual ~Coord() {}
+
+    // Accessors
+    void setRow(int iRow)       { m_row = iRow; }
+    void setCol(int iCol)       { m_col = iCol; }
+    void setDir(Direction iDir) { m_dir = iDir; }
+    int getRow() const          { return m_row; }
+    int getCol() const          { return m_col; }
+    Direction getDir() const    { return m_dir; }
+
+    bool isValid() const;
+    void operator=(const Coord &iOther);
+
+    // Swap the coordinates (without changing the direction)
+    void swap();
+
+    void setFromString(const wstring &iStr);
+    wstring toString() const;
+
+private:
+    Direction m_dir;
+    int m_row, m_col;
+
+};
+
+#endif
+
+
+/// Local Variables:
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/game/duplicate.cpp
diff -u /dev/null eliot/game/duplicate.cpp:1.14.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/duplicate.cpp    Wed Dec 28 16:47:35 2005
@@ -0,0 +1,286 @@
+/*****************************************************************************
+ * Copyright (C) 2005 Eliot
+ * Authors: Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include "dic.h"
+#include "tile.h"
+#include "rack.h"
+#include "round.h"
+#include "pldrack.h"
+#include "results.h"
+#include "player.h"
+#include "ai_player.h"
+#include "duplicate.h"
+#include "debug.h"
+
+
+Duplicate::Duplicate(const Dictionary &iDic): Game(iDic)
+{
+}
+
+
+Duplicate::~Duplicate()
+{
+}
+
+
+int Duplicate::setRackRandom(int p, bool iCheck, set_rack_mode mode)
+{
+    int res;
+    do
+    {
+        res = helperSetRackRandom(p, iCheck, mode);
+    } while (res == 2);
+    return res;
+}
+
+
+int Duplicate::play(const wstring &iCoord, const wstring &iWord)
+{
+    /* Perform all the validity checks, and fill a round */
+    Round round;
+    int res = checkPlayedWord(iCoord, iWord, round);
+    if (res != 0)
+    {
+        return res;
+    }
+
+    /* Everything is OK, we can play the word */
+    playRound(round, m_currPlayer);
+
+    /* Next turn */
+    // XXX: Should it be done by the interface instead?
+    endTurn();
+
+    return 0;
+}
+
+
+void Duplicate::duplicateAI(int n)
+{
+    ASSERT(0 <= n && n < getNPlayers(), "Wrong player number");
+    ASSERT(!m_players[n]->isHuman(), "AI requested for a human player");
+
+    AIPlayer *player = static_cast<AIPlayer*>(m_players[n]);
+    player->compute(*m_dic, m_board, m_history.getSize());
+
+    if (player->changesLetters())
+    {
+        // The AI player has nothing to play. This should not happen in
+        // duplicate mode, otherwise the implementation of the AI is buggy...
+        ASSERT(false, "AI player has nothing to play!");
+    }
+    else
+    {
+        playRound(player->getChosenRound(), n);
+    }
+}
+
+
+int Duplicate::start()
+{
+    ASSERT(getNPlayers(), "Cannot start a game without any player");
+
+    m_currPlayer = 0;
+
+    /* XXX: code similar with endTurnForReal() */
+    /* Complete the rack for the player that just played */
+    int res = setRackRandom(m_currPlayer, true, RACK_NEW);
+    /* End of the game? */
+    if (res == 1)
+    {
+        end();
+        return 1;
+    }
+
+    const PlayedRack& pld = m_players[m_currPlayer]->getCurrentRack();
+    /* All the players have the same rack */
+    for (int i = 0; i < getNPlayers(); i++)
+    {
+        if (i != m_currPlayer)
+        {
+            m_players[i]->setCurrentRack(pld);
+        }
+        /* Nobody has played yet in this round */
+        m_hasPlayed[i] = false;
+    }
+
+    /* Next turn */
+    // XXX: Should it be done by the interface instead?
+    endTurn();
+
+    return 0;
+}
+
+
+/*
+ * This function does not terminate the turn itself, but performs some
+ * checks to know whether or not it should be terminated (with a call to
+ * endTurnForReal()).
+ *
+ * For the turn to be terminated, all the players must have played.
+ * Since the AI players play after the human players, we check whether
+ * one of the human players has not played yet:
+ *   - if so, we have nothing to do (we are waiting for him)
+ *   - if not (all human players have played), the AI players can play,
+ *     and we finish the turn.
+ */
+int Duplicate::endTurn()
+{
+    int i;
+    for (i = 0; i < getNPlayers(); i++)
+    {
+        if (m_players[i]->isHuman() && !m_hasPlayed[i])
+        {
+            /* A human player has not played... */
+            m_currPlayer = i;
+            // XXX: check return code meaning
+            return 1;
+        }
+    }
+
+    /* If all the human players have played */
+    if (i == getNPlayers())
+    {
+        /* Make AI players play their turn */
+        for (i = 0; i < getNPlayers(); i++)
+        {
+            if (!m_players[i]->isHuman())
+            {
+                duplicateAI(i);
+            }
+        }
+
+        /* Next turn */
+        endTurnForReal();
+    }
+
+    // XXX: check return code meaning
+    return 0;
+}
+
+
+void Duplicate::playRound(const Round &iRound, int n)
+{
+    ASSERT(0 <= n && n < getNPlayers(), "Wrong player number");
+    Player *player = m_players[n];
+
+    /* Update the rack and the score of the current player */
+    player->addPoints(iRound.getPoints());
+    player->endTurn(iRound, m_history.getSize());
+
+    m_hasPlayed[n] = true;
+}
+
+
+/*
+ * This function really changes the turn, i.e. the best word is played and
+ * a new rack is given to the players.
+ * We suppose that all the players have finished to play for this turn (this
+ * should have been checked by endturn())
+ */
+int Duplicate::endTurnForReal()
+{
+    int res, i, imax;
+
+    /* Play the best word on the board */
+    imax = 0;
+    for (i = 1; i < getNPlayers(); i++)
+    {
+        if (m_players[i]->getLastRound().getPoints() >
+            m_players[imax]->getLastRound().getPoints())
+        {
+            imax = i;
+        }
+    }
+    m_currPlayer = imax;
+    helperPlayRound(m_players[imax]->getLastRound());
+
+    /* Complete the rack for the player that just played */
+    res = setRackRandom(imax, true, RACK_NEW);
+    /* End of the game? */
+    if (res == 1)
+    {
+        end();
+        return 1;
+    }
+
+    const PlayedRack& pld = m_players[imax]->getCurrentRack();
+    /* All the players have the same rack */
+    for (i = 0; i < getNPlayers(); i++)
+    {
+        if (i != imax)
+        {
+            m_players[i]->setCurrentRack(pld);
+        }
+        /* Nobody has played yet in this round */
+        m_hasPlayed[i] = false;
+    }
+
+    /* XXX: Little hack to handle the games with only AI players.
+     * This will have no effect when there is at least one human player */
+    endTurn();
+
+    return 0;
+}
+
+
+void Duplicate::end()
+{
+    m_finished = true;
+}
+
+
+int Duplicate::setPlayer(int n)
+{
+    ASSERT(0 <= n && n < getNPlayers(), "Wrong player number");
+
+    /* Forbid switching to an AI player */
+    if (!m_players[n]->isHuman())
+        return 1;
+
+    m_currPlayer = n;
+    return 0;
+}
+
+
+void Duplicate::prevHumanPlayer()
+{
+    if (getNHumanPlayers() == 0)
+        return;
+    // FIXME: possible infinite loop...
+    do
+    {
+        prevPlayer();
+    } while (!m_players[m_currPlayer]->isHuman() ||
+             m_hasPlayed[m_currPlayer]);
+}
+
+
+void Duplicate::nextHumanPlayer()
+{
+    if (getNHumanPlayers() == 0)
+        return;
+    // FIXME: possible infinite loop...
+    do
+    {
+        nextPlayer();
+    } while (!m_players[m_currPlayer]->isHuman() ||
+             m_hasPlayed[m_currPlayer]);
+}
+
Index: eliot/game/duplicate.h
diff -u /dev/null eliot/game/duplicate.h:1.10.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/duplicate.h      Wed Dec 28 16:47:35 2005
@@ -0,0 +1,85 @@
+/*****************************************************************************
+ * Copyright (C) 2005 Eliot
+ * Authors: Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#ifndef _DUPLICATE_H_
+#define _DUPLICATE_H_
+
+#include "game.h"
+
+using std::string;
+using std::wstring;
+
+
+/**
+ * This class handles the logic specific to a duplicate game.
+ * The trick in this mode is that the players will not necessarily play they
+ * word always in the same order, so we need to implement a "synchronization":
+ *   - when a human player wants to play a word, he plays it, and its score
+ *     and rack are updated. He cannot change his word afterwards.
+ *   - if there is still a human player who has not played for the current
+ *     turn, we wait for him
+ *   - if all the human players have played, it's the turn to the AI players
+ *     (currently handled in a loop, but we could imagine that they are running
+ *     in their own thread).
+ *   - once all the players have played, we can really end the turn:
+ *     the best word is played on the board, the history of the game is
+ *     updated, and the new rack is chosen.
+ *
+ * AI players play after human ones, because with the current implementation
+ * of the interfaces it is too easy for a player to see the rack of other
+ * players, and in particular a human player could take advantage of that to
+ * have more clues about the best word.
+ * TODO: better isolation of the players...
+ */
+class Duplicate: public Game
+{
+    friend class GameFactory;
+public:
+    virtual GameMode getMode() const { return kDUPLICATE; }
+    virtual string getModeAsString() const { return "Duplicate"; }
+
+    /*************************
+     * Game handling
+     *************************/
+    virtual int start();
+    virtual int setRackRandom(int, bool, set_rack_mode);
+    virtual int play(const wstring &iCoord, const wstring &iWord);
+    virtual int endTurn();
+
+    int setPlayer(int);
+    // Switch to the previous human player who has not played yet
+    void prevHumanPlayer();
+    // Switch to the next human player who has not played yet
+    void nextHumanPlayer();
+
+private:
+    // Private constructor and destructor to force using the GameFactory class
+    Duplicate(const Dictionary &iDic);
+    virtual ~Duplicate();
+
+    void playRound(const Round &iRound, int n);
+    int  endTurnForReal();
+    void end();
+    void duplicateAI(int n);
+
+    // m_hasPlayed[p] is true iff player p has played for this turn
+    map<int, bool> m_hasPlayed;
+};
+
+#endif /* _DUPLICATE_H_ */
Index: eliot/game/encoding.cpp
diff -u /dev/null eliot/game/encoding.cpp:1.1.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/encoding.cpp     Wed Dec 28 16:47:35 2005
@@ -0,0 +1,86 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Eliot 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file   encoding.cpp
+ *  \brief  Utility functions to ease manipulation of wide-character strings
+ *  \author Olivier Teuliere
+ *  \date   2005
+ */
+
+#include <stdlib.h>
+#include <wctype.h>
+#include "encoding.h"
+
+
+int _wtoi(const wchar_t *iWStr)
+{
+    int res = 0;
+    while (iswdigit(iWStr[0]))
+    {
+        res = 10 * res + (iWStr[0] - '0');
+        iWStr++;
+    }
+    return res;
+}
+
+
+wstring convertToWc(const string& iStr)
+{
+    // Get the needed length (we _can't_ use string::size())
+    size_t len = mbstowcs(NULL, iStr.c_str(), 0);
+    if (len == (size_t)-1)
+        return L"";
+
+    wchar_t *tmp = new wchar_t[len + 1];
+    len = mbstowcs(tmp, iStr.c_str(), len + 1);
+    wstring res = tmp;
+    delete[] tmp;
+
+    return res;
+}
+
+
+string convertToMb(const wstring& iWStr)
+{
+    // Get the needed length (we _can't_ use wstring::size())
+    size_t len = wcstombs(NULL, iWStr.c_str(), 0);
+    if (len == (size_t)-1)
+        return "";
+
+    char *tmp = new char[len + 1];
+    len = wcstombs(tmp, iWStr.c_str(), len + 1);
+    string res = tmp;
+    delete[] tmp;
+
+    return res;
+}
+
+
+string convertToMb(wchar_t iWChar)
+{
+    char res[MB_CUR_MAX + 1];
+    int len = wctomb(res, iWChar);
+    if (len == -1)
+        return "";
+    res[len] = '\0';
+
+    return res;
+}
+
Index: eliot/game/encoding.h
diff -u /dev/null eliot/game/encoding.h:1.1.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/encoding.h       Wed Dec 28 16:47:35 2005
@@ -0,0 +1,49 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Eliot 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file   encoding.h
+ *  \brief  Utility functions to ease manipulation of wide-character strings
+ *  \author Olivier Teuliere
+ *  \date   2005
+ */
+
+#ifndef _ENCODING_H_
+#define _ENCODING_H_
+
+#include <string>
+
+using std::string;
+using std::wstring;
+
+
+// Equivalent of atoi for wide-caracter strings
+int _wtoi(const wchar_t *iWStr);
+
+// Convert a multi-byte string into a wide-character string
+wstring convertToWc(const string& iStr);
+
+// Convert a wide-character string into a multi-byte string
+string convertToMb(const wstring& iWStr);
+
+// Convert a wide character into a multi-byte string
+string convertToMb(wchar_t iWChar);
+
+#endif
+
Index: eliot/game/freegame.cpp
diff -u /dev/null eliot/game/freegame.cpp:1.16.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/freegame.cpp     Wed Dec 28 16:47:35 2005
@@ -0,0 +1,257 @@
+/*****************************************************************************
+ * Copyright (C) 2005 Eliot
+ * Authors: Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include <iomanip>
+#include <wctype.h>
+#include "dic.h"
+#include "tile.h"
+#include "rack.h"
+#include "round.h"
+#include "pldrack.h"
+#include "results.h"
+#include "player.h"
+#include "ai_player.h"
+#include "freegame.h"
+
+#include "debug.h"
+
+
+FreeGame::FreeGame(const Dictionary &iDic): Game(iDic)
+{
+}
+
+
+FreeGame::~FreeGame()
+{
+}
+
+
+int FreeGame::setRackRandom(int p, bool iCheck, set_rack_mode mode)
+{
+    int res;
+    do
+    {
+        res = helperSetRackRandom(p, iCheck, mode);
+    } while (res == 2);
+    return res;
+}
+
+
+int FreeGame::play(const wstring &iCoord, const wstring &iWord)
+{
+    /* Perform all the validity checks, and fill a round */
+    Round round;
+
+    int res = checkPlayedWord(iCoord, iWord, round);
+    if (res != 0)
+    {
+        return res;
+    }
+
+    /* Update the rack and the score of the current player */
+    m_players[m_currPlayer]->addPoints(round.getPoints());
+    m_players[m_currPlayer]->endTurn(round, m_history.getSize());
+
+    /* Everything is OK, we can play the word */
+    helperPlayRound(round);
+
+    /* Next turn */
+    // XXX: Should it be done by the interface instead?
+    endTurn();
+
+    return 0;
+}
+
+
+void FreeGame::freegameAI(int n)
+{
+    ASSERT(0 <= n && n < getNPlayers(), "Wrong player number");
+    ASSERT(!m_players[n]->isHuman(), "AI requested for a human player");
+
+    AIPlayer *player = static_cast<AIPlayer*>(m_players[n]);
+
+    player->compute(*m_dic, m_board, m_history.getSize());
+    if (player->changesLetters())
+    {
+        helperPass(player->getChangedLetters(), n);
+        endTurn();
+    }
+    else
+    {
+        const Round &round = player->getChosenRound();
+        /* Update the rack and the score of the current player */
+        player->addPoints(round.getPoints());
+        player->endTurn(round, m_history.getSize());
+
+        helperPlayRound(round);
+        endTurn();
+    }
+}
+
+
+int FreeGame::start()
+{
+    ASSERT(getNPlayers(), "Cannot start a game without any player");
+
+    /* Set the initial racks of the players */
+    for (int i = 0; i < getNPlayers(); i++)
+    {
+        setRackRandom(i, false, RACK_NEW);
+    }
+
+    // XXX
+    m_currPlayer = 0;
+
+    /* If the first player is an AI, make it play now */
+    if (!m_players[0]->isHuman())
+    {
+        freegameAI(0);
+    }
+
+    return 0;
+}
+
+
+int FreeGame::endTurn()
+{
+    /* Complete the rack for the player that just played */
+    if (setRackRandom(m_currPlayer, false, RACK_NEW) == 1)
+    {
+        /* End of the game */
+        end();
+        return 1;
+    }
+
+    /* Next player */
+    nextPlayer();
+
+    /* If this player is an AI, make it play now */
+    if (!m_players[m_currPlayer]->isHuman())
+    {
+        freegameAI(m_currPlayer);
+    }
+
+    return 0;
+}
+
+
+// Adjust the scores of the players with the points of the remaining tiles
+void FreeGame::end()
+{
+    vector<Tile> tiles;
+
+    // TODO: According to the rules of the game in the ODS, a game can end in 3
+    // cases:
+    // 1) no more letter in the bag, and one player has no letter in his rack
+    // 2) the game is "blocked", no one can play
+    // 3) the players have used all the time they had (for example: 30 min
+    //    in total, for each player)
+    // We currently handle case 1, and cannot handle case 3 until timers are
+    // implemented.
+    // For case 2, we need both to detect a blocked situation (not easy...) and
+    // to handle it in the end() method (very easy).
+
+    /* Add the points of the remaining tiles to the score of the current
+     * player (i.e. the first player with an empty rack), and remove them
+     * from the score of the players who still have tiles */
+    for (int i = 0; i < getNPlayers(); i++)
+    {
+        if (i != m_currPlayer)
+        {
+            const PlayedRack &pld = m_players[i]->getCurrentRack();
+            pld.getAllTiles(tiles);
+            for (unsigned int j = 0; j < tiles.size(); j++)
+            {
+                m_players[i]->addPoints(- tiles[j].getPoints());
+                m_players[m_currPlayer]->addPoints(tiles[j].getPoints());
+            }
+        }
+    }
+
+    /* Lock game */
+    m_finished = true;
+}
+
+
+int FreeGame::pass(const wstring &iToChange, int n)
+{
+    if (m_finished)
+        return 3;
+
+    // According to the rules in the ODS, it is allowed to pass its turn (no
+    // need to change letters for that).
+    // TODO: However, if all the players pass their turn, the first one has to
+    // play, or change at least one letter. To implement this behaviour, we
+    // must also take care of blocked positions, where no one _can_ play (see
+    // also comment in the end() method).
+
+    // Convert the string into tiles
+    vector<Tile> tilesVect;
+    for (unsigned int i = 0; i < iToChange.size(); i++)
+    {
+        Tile tile(towupper(iToChange[i]));
+        tilesVect.push_back(tile);
+    }
+
+    int res = helperPass(tilesVect, n);
+    if (res == 0)
+        endTurn();
+    return res;
+}
+
+
+int FreeGame::helperPass(const vector<Tile> &iToChange, int n)
+{
+    ASSERT(0 <= n && n < getNPlayers(), "Wrong player number");
+
+    // It is forbidden to change letters when the bag does not contain at
+    // least 7 letters (this is explicitely stated in the ODS).
+    Bag bag;
+    realBag(bag);
+    if (bag.nTiles() < 7)
+    {
+        return 1;
+    }
+
+    Player *player = m_players[n];
+    PlayedRack pld = player->getCurrentRack();
+    Rack rack;
+    pld.getRack(rack);
+
+    for (unsigned int i = 0; i < iToChange.size(); i++)
+    {
+        /* Remove the letter from the rack */
+        if (!rack.in(iToChange[i]))
+        {
+            return 2;
+        }
+        rack.remove(iToChange[i]);
+    }
+
+    pld.reset();
+    pld.setOld(rack);
+
+    player->setCurrentRack(pld);
+
+    // FIXME: the letters to change should not be in the bag while generating
+    // the new rack!
+
+    return 0;
+}
+
Index: eliot/game/freegame.h
diff -u /dev/null eliot/game/freegame.h:1.9.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/freegame.h       Wed Dec 28 16:47:35 2005
@@ -0,0 +1,67 @@
+/*****************************************************************************
+ * Copyright (C) 2005 Eliot
+ * Authors: Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#ifndef _FREEGAME_H_
+#define _FREEGAME_H_
+
+#include "game.h"
+#include "tile.h"
+
+using std::string;
+using std::wstring;
+using std::vector;
+
+
+/**
+ * This class handles the logic specific to a "free" game.
+ *
+ * The algorithm is simple: players play at their turn, and they can either
+ * play a word or change letters (changing letters implies passing its turn).
+ *
+ * When a player has no more letters (end of the game), the points of the
+ * letters left in the racks of his opponents are added to his score, and
+ * removed from the score of the latters.
+ */
+class FreeGame: public Game
+{
+    friend class GameFactory;
+public:
+    virtual GameMode getMode() const { return kFREEGAME; }
+    virtual string getModeAsString() const { return "Free game"; }
+
+    /*************************
+     * Game handling
+     *************************/
+    virtual int start();
+    virtual int setRackRandom(int, bool, set_rack_mode);
+    virtual int play(const wstring &iCoord, const wstring &iWord);
+    virtual int endTurn();
+    int pass(const wstring &iToChange, int n);
+
+private:
+    // Private constructor and destructor to force using the GameFactory class
+    FreeGame(const Dictionary &iDic);
+    virtual ~FreeGame();
+
+    void freegameAI(int n);
+    void end();
+    int helperPass(const vector<Tile> &iToChange, int n);
+};
+
+#endif /* _FREEGAME_H_ */
Index: eliot/game/game.cpp
diff -u /dev/null eliot/game/game.cpp:1.27.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/game.cpp Wed Dec 28 16:47:35 2005
@@ -0,0 +1,855 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include "dic.h"
+#include "dic_search.h"
+#include "tile.h"
+#include "rack.h"
+#include "round.h"
+#include "pldrack.h"
+#include "results.h"
+#include "player.h"
+#include "ai_percent.h"
+#include "game.h"
+#include "game_factory.h"
+#include "turn.h"
+#include "encoding.h"
+
+#include "debug.h"
+
+
+const int Game::RACK_SIZE = 7;
+
+
+Game::Game(const Dictionary &iDic):
+    m_dic(&iDic)
+{
+    m_variant = kNONE;
+    m_points = 0;
+    m_currPlayer = -1;
+    m_finished = false;
+}
+
+
+Game::~Game()
+{
+    for (int i = 0; i < getNPlayers(); i++)
+    {
+        delete m_players[i];
+    }
+}
+
+
+const Player& Game::getPlayer(int iNum) const
+{
+    ASSERT(0 <= iNum && iNum < (int)m_players.size(), "Wrong player number");
+    return *(m_players[iNum]);
+}
+
+
+Game * Game::load(FILE *fin, const Dictionary &iDic)
+{
+    char buff[4096];
+    char delim[] = " \t\n|";
+    char *token;
+
+    // Check characteristic string
+    if (fgets(buff, sizeof(buff), fin) == NULL)
+        return NULL;
+    if ((token = strtok(buff, delim)) == NULL)
+        return NULL;
+    if (string(token) != IDENT_STRING)
+        return NULL;
+
+    int num;
+    char rack[20];
+    char word[20];
+    char ref[4];
+    int pts;
+    int player;
+    char *pos;
+    Tile tile;
+    Game *pGame = NULL;
+
+    while (fgets(buff, sizeof(buff), fin))
+    {
+        // Indication of game type
+        pos = strstr(buff, "Game type: ");
+        if (pos != NULL)
+        {
+            // No Game object should have been created yet
+            if (pGame != NULL)
+            {
+                delete pGame;
+                return NULL;
+            }
+            // Create the correct Game object
+            if (strstr(buff, "Training"))
+                pGame = GameFactory::Instance()->createTraining(iDic);
+            else if (strstr(buff, "Free game"))
+                pGame = GameFactory::Instance()->createFreeGame(iDic);
+            else if (strstr(buff, "Duplicate"))
+                pGame = GameFactory::Instance()->createDuplicate(iDic);
+            else
+                return NULL;
+            // Read next line
+            continue;
+        }
+
+        // Players type
+        pos = strstr(buff, "Player ");
+        if (pos != NULL && pGame != NULL)
+        {
+            int nb = 0;
+            char type[20];
+            if (sscanf(pos, "Player %d: %19s", &nb, type) > 1)
+            {
+                if (string(type) == "Human")
+                    pGame->addHumanPlayer();
+                else if (string(type) == "Computer")
+                    pGame->addAIPlayer();
+                else
+                    ;
+            }
+            // Read next line
+            continue;
+        }
+
+        // Last racks
+        pos = strstr(buff, "Rack ");
+        if (pos != NULL && pGame != NULL)
+        {
+            int nb = 0;
+            char letters[20];
+            if (sscanf(pos, "Rack %d: %19s", &nb, letters) > 1)
+            {
+                // Create the played rack
+                PlayedRack pldrack;
+                char *r = letters;
+                if (strchr(r, '+'))
+                {
+                    while (*r != '+')
+                    {
+                        pldrack.addOld(Tile(*r));
+                        r++;
+                    }
+                    r++;
+                }
+                while (*r)
+                {
+                    pldrack.addNew(Tile(*r));
+                    r++;
+                }
+
+                // Give the rack to the player
+                pGame->m_players[nb]->setCurrentRack(pldrack);
+            }
+            // Read next line
+            continue;
+        }
+
+        // Skip columns title
+        if (strstr(buff, "==") != NULL ||
+            strstr(buff, "| PTS | P |") != NULL)
+        {
+            continue;
+        }
+
+        if (string(buff) != "\n" && pGame != NULL)
+        {
+            char bonus = 0;
+            int res = sscanf(buff, "   %2d | %8s | %s | %3s | %3d | %1d | %c",
+                             &num, rack, word, ref, &pts, &player, &bonus);
+            if (res < 6)
+                continue;
+
+            // Integrity checks
+            // TODO: add more checks
+            if (pts < 0)
+                continue;
+            if (player < 0 || player > pGame->getNPlayers())
+                continue;
+            if (bonus && bonus != '*')
+                continue;
+
+            // Build a rack for the correct player
+            PlayedRack pldrack;
+            char *r = rack;
+            if (strchr(r, '+'))
+            {
+                while (*r != '+')
+                {
+                    pldrack.addOld(Tile(*r));
+                    r++;
+                }
+                r++;
+            }
+
+            while (*r)
+            {
+                pldrack.addNew(Tile(*r));
+                r++;
+            }
+
+            // Build a round
+            Round round;
+            // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
+//             round.accessCoord().setFromString(ref);
+            if (!round.getCoord().isValid())
+                continue;
+
+            round.setPoints(pts);
+            if (bonus == '*')
+                round.setBonus(1);
+
+            for (unsigned int i = 0; i < strlen(word); i++)
+            {
+                tile = Tile(word[i]);
+
+                if (round.getCoord().getDir() == Coord::HORIZONTAL)
+                {
+                    if (!pGame->m_board.getTile(round.getCoord().getRow(),
+                                                round.getCoord().getCol() + 
i).isEmpty())
+                    {
+                        round.addRightFromBoard(tile);
+                    }
+                    else
+                    {
+                        round.addRightFromRack(tile, islower(word[i]));
+                        pGame->m_bag.takeTile((islower(word[i])) ? 
Tile::Joker() : tile);
+                    }
+                }
+                else
+                {
+                    if (!pGame->m_board.getTile(round.getCoord().getRow() + i,
+                                                
round.getCoord().getCol()).isEmpty())
+                    {
+                        round.addRightFromBoard(tile);
+                    }
+                    else
+                    {
+                        round.addRightFromRack(tile, islower(word[i]));
+                        pGame->m_bag.takeTile((islower(word[i])) ? 
Tile::Joker() : tile);
+                    }
+                }
+            }
+
+            pGame->m_currPlayer = player;
+            // Update the rack for the player
+            pGame->m_players[player]->setCurrentRack(pldrack);
+            // End the turn for the current player (this creates a new rack)
+            pGame->m_players[player]->endTurn(round, num - 1);
+            // Add the points
+            pGame->m_players[player]->addPoints(pts);
+            // Play the round
+            pGame->helperPlayRound(round);
+        }
+    }
+
+    // Finalize the game
+    if (pGame)
+    {
+        // We don't really know whose turn it is, but at least we know that
+        // the game was saved while a human was to play.
+        for (int i = 0; i < pGame->getNPlayers(); i++)
+        {
+            if (pGame->getPlayer(i).isHuman())
+            {
+                pGame->m_currPlayer = i;
+                break;
+            }
+        }
+    }
+    return pGame;
+}
+
+
+void Game::save(ostream &out) const
+{
+    const string decal = "   ";
+    // "Header" of the game
+    out << IDENT_STRING << endl << endl;
+    out << "Game type: " << getModeAsString() << endl;
+    for (int i = 0; i < getNPlayers(); i++)
+    {
+        out << "Player " << i << ": ";
+        if (getPlayer(i).isHuman())
+            out << "Human" << endl;
+        else
+            out << "Computer" << endl;
+    }
+    out << endl;
+
+    // Title of the columns
+    char line[100];
+    out << decal << " N |   RACK   |    SOLUTION     | REF | PTS | P | BONUS" 
<< endl;
+    out << decal << "===|==========|=================|=====|=====|===|======" 
<< endl;
+
+    // Print the game itself
+    for (int i = 0; i < m_history.getSize(); i++)
+    {
+        const Turn& t = m_history.getTurn(i);
+        wstring word = t.getRound().getWord();
+        wstring coord = t.getRound().getCoord().toString();
+        sprintf(line, "%2d | %8s | %s%s | %3s | %3d | %1d | %c",
+                i + 1, convertToMb(t.getPlayedRack().toString()).c_str(),
+                convertToMb(word).c_str(),
+                string(15 - word.size(), ' ').c_str(),
+                convertToMb(coord).c_str(), t.getRound().getPoints(),
+                t.getPlayer(), t.getRound().getBonus() ? '*' : ' ');
+
+        out << decal << line << endl;
+    }
+    out << endl << decal << "Total: " << m_points << endl;
+
+    // Print current rack for all the players
+    out << endl;
+    for (int i = 0; i < getNPlayers(); i++)
+    {
+        wstring rack = getPlayer(i).getCurrentRack().toString();
+        out << "Rack " << i << ": " << convertToMb(rack) << endl;
+    }
+}
+
+
+/* This function plays a round on the board */
+int Game::helperPlayRound(const Round &iRound)
+{
+    /*
+     * We remove tiles from the bag only when they are played
+     * on the board. When going back in the game, we must only
+     * replace played tiles.
+     * We test a rack when it is set but tiles are left in the bag.
+     */
+
+    // History of the game
+  m_history.setCurrentRack(getCurrentPlayer().getLastRack());
+  m_history.playRound(m_currPlayer, m_history.getSize(),  iRound);
+
+    m_points += iRound.getPoints();
+
+    // Before updating the bag and the board, if we are playing a "joker game",
+    // we replace in the round the joker by the letter it represents
+    // This is currently done by a succession of ugly hacks :-/
+    if (m_variant == kJOKER)
+    {
+        for (int i = 0; i < iRound.getWordLen(); i++)
+        {
+            if (iRound.isPlayedFromRack(i) && iRound.isJoker(i))
+            {
+                // Is the represented letter still available in the bag?
+                // FIXME: this way to get the represented letter sucks...
+                Tile t(toupper(iRound.getTile(i).toChar()));
+                Bag bag;
+                realBag(bag);
+                // FIXME: realBag() does not give us a real bag in this
+                // particular case! This is because Player::endTurn() is called
+                // before Game::helperPlayRound(), which means that the rack
+                // of the player is updated, while the word is not actually
+                // played on the board yet. Since realBag() relies on
+                // Player::getCurrentRack(), it doesn't remove the letters of
+                // the current player, which are in fact available through
+                // Player::getLastRack().
+                // That's why we have to replace the letters of the current
+                // rack and remove the ones from the previous rack...
+                // There is a big design problem here, but i am unsure what is
+                // the best way to fix it.
+                vector<Tile> tiles;
+                getPlayer(m_currPlayer).getCurrentRack().getAllTiles(tiles);
+                for (unsigned int j = 0; j < tiles.size(); j++)
+                {
+                    bag.replaceTile(tiles[j]);
+                }
+                getPlayer(m_currPlayer).getLastRack().getAllTiles(tiles);
+                for (unsigned int j = 0; j < tiles.size(); j++)
+                {
+                    bag.takeTile(tiles[j]);
+                }
+
+                if (bag.in(t))
+                {
+                    // FIXME: A const_cast sucks too...
+                    const_cast<Round&>(iRound).setTile(i, t);
+                    // FIXME: This shouldn't be necessary either, this is only
+                    // needed because of the stupid way of handling jokers in
+                    // rounds
+                    const_cast<Round&>(iRound).setJoker(i, false);
+                }
+            }
+        }
+    }
+
+    // Update the bag and the board
+    for (int i = 0; i < iRound.getWordLen(); i++)
+    {
+        if (iRound.isPlayedFromRack(i))
+        {
+            if (iRound.isJoker(i))
+            {
+                m_bag.takeTile(Tile::Joker());
+            }
+            else
+                m_bag.takeTile(iRound.getTile(i));
+        }
+    }
+    m_board.addRound(*m_dic, iRound);
+
+    return 0;
+}
+
+
+int Game::back(int n)
+{
+    int i, j;
+    Player *player;
+
+    if (n < 0)
+    {
+//         debug("Game::back negative argument\n");
+        n = -n;
+    }
+//     debug("Game::back %d\n",n);
+    for (i = 0; i < n; i++)
+    {
+        if (m_history.getSize() > 0)
+        {
+            prevPlayer();
+            player = m_players[m_currPlayer];
+            const Round &lastround = m_history.getPreviousTurn().getRound();
+//             debug("Game::back last round 
%s\n",lastround.toString().c_str());
+            /* Remove the word from the board, and put its letters back
+             * into the bag */
+            m_board.removeRound(*m_dic, lastround);
+            for (j = 0; j < lastround.getWordLen(); j++)
+            {
+                if (lastround.isPlayedFromRack(j))
+                {
+                    if (lastround.isJoker(j))
+                        m_bag.replaceTile(Tile::Joker());
+                    else
+                        m_bag.replaceTile(lastround.getTile(j));
+                }
+            }
+            /* Remove the points of this round */
+            player->addPoints(- lastround.getPoints());
+            m_points -= lastround.getPoints();
+            /* Remove the turns */
+            player->removeLastTurn();
+            m_history.removeLastTurn();
+        }
+        else
+        {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+
+/*********************************************************
+ *********************************************************/
+
+void Game::realBag(Bag &ioBag) const
+{
+    vector<Tile> tiles;
+
+    /* Copy the bag */
+    ioBag = m_bag;
+
+    /* The real content of the bag depends on the game mode */
+    if (getMode() == kFREEGAME)
+    {
+        /* In freegame mode, replace the letters from all the racks */
+        for (int i = 0; i < getNPlayers(); i++)
+        {
+            getPlayer(i).getCurrentRack().getAllTiles(tiles);
+            for (unsigned int j = 0; j < tiles.size(); j++)
+            {
+                ioBag.takeTile(tiles[j]);
+            }
+        }
+    }
+    else
+    {
+        /* In training or duplicate mode, replace the rack of the current
+         * player only */
+        getPlayer(m_currPlayer).getCurrentRack().getAllTiles(tiles);
+        for (unsigned int j = 0; j < tiles.size(); j++)
+        {
+            ioBag.takeTile(tiles[j]);
+        }
+    }
+}
+
+
+bool Game::rackInBag(const Rack &iRack, const Bag &iBag) const
+{
+    const list<Tile>& allTiles = Tile::getAllTiles();
+    list<Tile>::const_iterator it;
+    for (it = allTiles.begin(); it != allTiles.end(); it++)
+    {
+        if (iRack.in(*it) > iBag.in(*it))
+            return false;
+    }
+    return true;
+}
+
+
+int Game::helperSetRackRandom(int p, bool iCheck, set_rack_mode mode)
+{
+    ASSERT(0 <= p && p < getNPlayers(), "Wrong player number");
+
+    int nold, min;
+
+    // Make a copy of the player's rack
+    PlayedRack pld = getPlayer(p).getCurrentRack();
+    nold = pld.nOld();
+
+    // Create a copy of the bag in which we can do everything we want,
+    // and remove from it the tiles of the racks
+    Bag bag;
+    realBag(bag);
+
+    // We may have removed too many letters from the bag (i.e. the 'new'
+    // letters of the player)
+    if (mode == RACK_NEW && nold != 0)
+    {
+        vector<Tile> tiles;
+        pld.getNewTiles(tiles);
+        for (unsigned int i = 0; i < tiles.size(); i++)
+        {
+            bag.replaceTile(tiles[i]);
+        }
+        pld.resetNew();
+    }
+    else
+    {
+        // RACK_NEW with an empty rack is equivalent to RACK_ALL
+        pld.reset();
+        // Do not forget to update nold, for the RACK_ALL case
+        nold = 0;
+    }
+
+    // Nothing in the rack, nothing in the bag --> end of the game
+    if (bag.nTiles() == 0 && pld.nTiles() == 0)
+    {
+        return 1;
+    }
+
+    // When iCheck is true, we must make sure that there are at least 2 vowels
+    // and 2 consonants in the rack up to the 15th turn, and at least one of
+    // them from the 16th turn.
+    // So before trying to fill the rack, we'd better make sure there is a way
+    // to complete the rack with these constraints...
+    min = 0;
+    if (iCheck)
+    {
+        int oldc, oldv;
+
+        if (bag.nVowels() == 0 || bag.nConsonants() == 0)
+        {
+            return 1;
+        }
+        // 2 vowels and 2 consonants are needed up to the 15th turn
+        if (bag.nVowels() > 1 && bag.nConsonants() > 1
+            && m_history.getSize() < 15)
+            min = 2;
+        else
+            min = 1;
+
+        // Count the remaining consonants and vowels in the rack
+        vector<Tile> tiles;
+        pld.getOldTiles(tiles);
+        oldc = 0;
+        oldv = 0;
+        for (unsigned int i = 0; i < tiles.size(); i++)
+        {
+            if (tiles[i].isConsonant())
+                oldc++;
+            if (tiles[i].isVowel())
+                oldv++;
+        }
+
+        // RACK_SIZE - nold is the number of letters to add
+        if (min > oldc + RACK_SIZE - nold ||
+            min > oldv + RACK_SIZE - nold)
+        {
+            // We cannot fill the rack with enough vowels or consonants!
+            return 3;
+        }
+    }
+
+    // Are we dealing with a normal game or a joker game?
+    if (m_variant == kJOKER)
+    {
+        // 1) Is there already a joker in the remaining letters of the rack?
+        bool jokerFound = false;
+        vector<Tile> tiles;
+        pld.getOldTiles(tiles);
+        for (unsigned int i = 0; i < tiles.size(); i++)
+        {
+            if (tiles[i].isJoker())
+            {
+                jokerFound = true;
+                break;
+            }
+        }
+
+        // 2) If there was no joker, we add one if possible
+        if (!jokerFound && bag.in(Tile::Joker()))
+        {
+            bag.takeTile(Tile::Joker());
+            pld.addNew(Tile::Joker());
+        }
+
+        // 3) Complete the rack normally... but without any joker!
+        Tile l;
+        while (bag.nTiles() != 0 && pld.nTiles() != RACK_SIZE)
+        {
+            l = bag.selectRandom();
+            if (!l.isJoker())
+            {
+                bag.takeTile(l);
+                pld.addNew(l);
+            }
+        }
+    }
+    else // Normal game
+    {
+        // Get new tiles from the bag
+        Tile l;
+        while (bag.nTiles() != 0 && pld.nTiles() != RACK_SIZE)
+        {
+            l = bag.selectRandom();
+            bag.takeTile(l);
+            pld.addNew(l);
+        }
+    }
+
+    if (iCheck && !pld.checkRack(min))
+        return 2;
+
+    m_players[p]->setCurrentRack(pld);
+
+    return 0;
+}
+
+
+int Game::helperSetRackManual(int p, bool iCheck, const wstring &iLetters)
+{
+    unsigned int i;
+    int min;
+
+    PlayedRack pld = getPlayer(p).getCurrentRack();
+    pld.reset();
+
+    if (iLetters.size() == 0)
+    {
+        return 0;
+    }
+
+    for (i = 0; i < iLetters.size() && iLetters[i] != '+'; i++)
+    {
+        Tile tile(iLetters[i]);
+        if (tile.isEmpty())
+        {
+            return 1;
+        }
+        pld.addOld(tile);
+    }
+
+    if (i < iLetters.size() && iLetters[i] == '+')
+    {
+        for (i++; i < iLetters.size(); i++)
+        {
+            Tile tile(iLetters[i]);
+            if (tile.isEmpty())
+            {
+                return 1;
+            }
+            pld.addNew(tile);
+        }
+    }
+
+    Rack rack;
+    pld.getRack(rack);
+    if (!rackInBag(rack, m_bag))
+    {
+        pld.reset();
+        return 1;
+    }
+
+    if (iCheck)
+    {
+        if (m_bag.nVowels() > 1 && m_bag.nConsonants() > 1
+            && m_history.getSize() < 15)
+            min = 2;
+        else
+            min = 1;
+        if (!pld.checkRack(min))
+            return 2;
+    }
+
+    m_players[p]->setCurrentRack(pld);
+
+    return 0;
+}
+
+/*********************************************************
+ *********************************************************/
+
+
+int Game::getNHumanPlayers() const
+{
+    int count = 0;
+    for (int i = 0; i < getNPlayers(); i++)
+        count += (getPlayer(i).isHuman() ? 1 : 0);
+    return count;
+}
+
+
+void Game::addHumanPlayer()
+{
+    // The ID of the player is its position in the m_players vector
+    m_players.push_back(new HumanPlayer(getNPlayers()));
+}
+
+
+void Game::addAIPlayer()
+{
+    m_players.push_back(new AIPercent(getNPlayers(), 0));
+}
+
+
+void Game::prevPlayer()
+{
+    ASSERT(getNPlayers() != 0, "Expected at least one player");
+
+    if (m_currPlayer == 0)
+        m_currPlayer = getNPlayers() - 1;
+    else
+        m_currPlayer--;
+}
+
+
+void Game::nextPlayer()
+{
+    ASSERT(getNPlayers() != 0, "Expected at least one player");
+
+    if (m_currPlayer == getNPlayers() - 1)
+        m_currPlayer = 0;
+    else
+        m_currPlayer++;
+}
+
+
+/*
+ * This function checks whether it is legal to play the given word at the
+ * given coordinates. If so, the function fills a Round object, also given as
+ * a parameter.
+ * Possible return values:
+ *  0: correct word, the Round can be used by the caller
+ *  1: no dictionary set
+ *  2: invalid coordinates (unreadable or out of the board)
+ *  3: word not present in the dictionary
+ *  4: not enough letters in the rack to play the word
+ *  5: word is part of a longer one
+ *  6: word overwriting an existing letter
+ *  7: invalid crosscheck, or word going out of the board
+ *  8: word already present on the board (no new letter from the rack)
+ *  9: isolated word (not connected to the rest)
+ * 10: first word not horizontal
+ * 11: first word not covering the H8 square
+ */
+int Game::checkPlayedWord(const wstring &iCoord,
+                          const wstring &iWord, Round &oRound)
+{
+    ASSERT(getNPlayers() != 0, "Expected at least one player");
+
+    int res;
+    vector<Tile> tiles;
+    Tile t;
+
+    /* Init the round with the given coordinates */
+    oRound.init();
+    oRound.accessCoord().setFromString(iCoord);
+    if (!oRound.getCoord().isValid())
+        return 2;
+
+    /* Check the existence of the word */
+    if (Dic_search_word(*m_dic, iWord.c_str()) == 0)
+        return 3;
+
+    /* Set the word */
+    // TODO: make this a Round_ function (Round_setwordfromchar for example)
+    // or a Tiles_ function (to transform a char* into a vector<Tile>)
+    // Adding a getter on the word could help too...
+    for (unsigned int i = 0; i < iWord.size(); i++)
+    {
+        tiles.push_back(Tile(iWord[i]));
+    }
+    oRound.setWord(tiles);
+    for (unsigned int i = 0; i < iWord.size(); i++)
+    {
+        if (islower(iWord[i]))
+            oRound.setJoker(i);
+    }
+
+    /* Check the word position, compute its points,
+     * and specify the origin of each letter (board or rack) */
+    res = m_board.checkRound(oRound, m_history.getSize() == 0);
+    if (res != 0)
+        return res + 4;
+
+    /* Check that the word can be formed with the tiles in the rack:
+     * we first create a copy of the rack, then we remove the tiles
+     * one by one */
+    Rack rack;
+    Player *player = m_players[m_currPlayer];
+    player->getCurrentRack().getRack(rack);
+
+    for (int i = 0; i < oRound.getWordLen(); i++)
+    {
+        if (oRound.isPlayedFromRack(i))
+        {
+            if (oRound.isJoker(i))
+                t = Tile::Joker();
+            else
+                t = oRound.getTile(i);
+
+            if (!rack.in(t))
+            {
+                return 4;
+            }
+            rack.remove(t);
+        }
+    }
+
+    return 0;
+}
+
+/****************************************************************/
+/****************************************************************/
+
+/// Local Variables:
+/// mode: c++
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/game/game.h
diff -u /dev/null eliot/game/game.h:1.26.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/game.h   Wed Dec 28 16:47:35 2005
@@ -0,0 +1,199 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#ifndef _GAME_H_
+#define _GAME_H_
+
+#include <string>
+#include <vector>
+#include <iostream>
+#include "bag.h"
+#include "board.h"
+#include "history.h"
+
+class Player;
+class PlayedRack;
+class Round;
+class Rack;
+class Turn;
+typedef struct _Dictionary * Dictionary;
+
+using namespace std;
+
+
+/*************************
+ * Ident string used to identify saved Eliot games
+ *************************/
+#define IDENT_STRING "Eliot"
+
+/**
+ * Parent class of all the Game types.
+ * It offers the common attributes (Board, Bag, etc...) as well as useful
+ * "helper" methods to factorize some code.
+ */
+class Game
+{
+public:
+    Game(const Dictionary &iDic);
+    virtual ~Game();
+
+    /// Game mode: each one of these modes is implemented in an inherited class
+    enum GameMode
+    {
+        kTRAINING,
+        kFREEGAME,
+        kDUPLICATE
+    };
+    virtual GameMode getMode() const = 0;
+    virtual string getModeAsString() const = 0;
+
+    /// Game variant: it slightly modifies the rules of the game
+    enum GameVariant
+    {
+        kNONE,      // Normal game rules
+        kJOKER      // Joker game
+    };
+
+    /**
+     * Accessors for the variant of the game.
+     * The variant can be changed during a game without any problem
+     * (though it seems rather useless...)
+     */
+    void setVariant(GameVariant iVariant)   { m_variant = iVariant; }
+    GameVariant getVariant() const          { return m_variant; }
+
+    /**
+     * Dictionary associated with the game.
+     * The dictionary can be changed during a game without problem
+     */
+    const Dictionary & getDic() const   { return *m_dic; }
+    void setDic(const Dictionary &iDic) { m_dic = &iDic; }
+
+    const Board&  getBoard() const { return m_board; }
+    const Bag&    getBag()   const { return m_bag; }
+    const Player& getPlayer(int iNum) const;
+    const Turn&   getTurn(int iNum) const;
+    const Player& getCurrentPlayer() const { return getPlayer(currPlayer()); };
+
+    /**
+     * Saved games handling.
+     *
+     * load() returns the loaded game, or NULL if there was a problem
+     * load() might need some more work to be robust enough to
+     * handle "hand written" files
+     */
+    static Game * load(FILE *fin, const Dictionary &iDic);
+    void save(ostream &out) const;
+
+    /*************************
+     * Playing the game
+     * the int parameter should be 0 <= int < getNTurns
+     *************************/
+    int back(int);
+
+    /*************************
+     * Set the rack for searching
+     *
+     * The int parameter is a boolean, if this parameter
+     * set the rack will check that there are at least
+     * 2 vowels and 2 consonants before the round 15.
+     *
+     * The setrackmanual parameter string has to contain
+     * 'a' <= char <= 'z' or 'A' <= char <= 'Z' or '?'
+     *
+     * return value
+     *    0 : the rack has been set
+     *    1 : the bag does not contain enough tiles
+     *    2 : the rack check was set on and failed
+     *    3 : the rack cannot be completed (Game_*_setrackrandom only)
+     *************************/
+    static const int RACK_SIZE;
+    enum set_rack_mode {RACK_ALL, RACK_NEW, RACK_MANUAL};
+    int setRack(int player, set_rack_mode mode, bool check, const wstring& 
str);
+
+    /** Getter for the history of the game  */
+    const History& getHistory() const { return m_history; }
+
+    /**
+     * Methods to access players.
+     * The int parameter should be 0 <= int < getNPlayers()
+     */
+    int  getNPlayers() const    { return m_players.size(); }
+    int  getNHumanPlayers() const;
+    virtual void addHumanPlayer();
+    // TODO: Ability to specify which kind of AI player is wanted
+    virtual void addAIPlayer();
+    int  currPlayer() const     { return m_currPlayer; }
+
+    /**
+     * Game handling
+     */
+    virtual int start() = 0;
+    virtual int play(const wstring &iCoord, const wstring &iWord) = 0;
+    virtual int endTurn() = 0;
+
+protected:
+    /// All the players, indexed by their ID
+    vector<Player*> m_players;
+    /// ID of the "current" player
+    int m_currPlayer;
+
+// TODO: check what should be private and what should be protected
+// private:
+
+    /// Variant
+    GameVariant m_variant;
+
+    /// Dictionary currently associated to the game
+    const Dictionary * m_dic;
+
+    /// Bag
+    Bag m_bag;
+
+    /// Board
+    Board m_board;
+
+    /**
+     * History of the game.
+     * The vector is indexed by the number of turns in the game
+     */
+    History m_history;
+
+    int m_points;
+
+    bool m_finished;
+
+    /*********************************************************
+     * Helper functions
+     *********************************************************/
+
+    int helperPlayRound(const Round &iRound);
+    int helperSetRackRandom(int p, bool iCheck, set_rack_mode mode);
+    int helperSetRackManual(int p, bool iCheck, const wstring &iLetters);
+
+    void prevPlayer();
+    void nextPlayer();
+    bool rackInBag(const Rack &iRack, const Bag &iBag) const;
+    void realBag(Bag &iBag) const;
+    int  checkPlayedWord(const wstring &iCoord,
+                         const wstring &iWord, Round &oRound);
+};
+
+#endif /* _GAME_H_ */
Index: eliot/game/history.cpp
diff -u /dev/null eliot/game/history.cpp:1.8.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/history.cpp      Wed Dec 28 16:47:35 2005
@@ -0,0 +1,181 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Eliot 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file   history.cpp
+ *  \brief  Game history  system
+ *  \author Antoine Fraboulet
+ *  \date   2005
+ */
+
+#include <string>
+#include "rack.h"
+#include "pldrack.h"
+#include "round.h"
+#include "turn.h"
+#include "debug.h"
+#include "history.h"
+
+/* ******************************************************** */
+/* ******************************************************** */
+/* ******************************************************** */
+
+
+History::History()
+{
+    Turn* t = new Turn();
+    m_history.clear();
+    m_history.push_back(t);
+}
+
+
+History::~History()
+{
+    for (unsigned int i = 0; i < m_history.size(); i++)
+    {
+        if (m_history[i] != NULL)
+        {
+            delete m_history[i];
+            m_history[i] = NULL;
+        }
+    }
+}
+
+
+int History::getSize() const
+{
+    return m_history.size() - 1;
+}
+
+
+const PlayedRack& History::getCurrentRack() const
+{
+    return m_history.back()->getPlayedRack();
+}
+
+
+void History::setCurrentRack(const PlayedRack &iPld)
+{
+    m_history.back()->setPlayedRack(iPld);
+}
+
+
+const Turn& History::getPreviousTurn() const
+{
+    int idx = m_history.size() - 2;
+    ASSERT(0 <= idx , "No previous turn");
+    return *(m_history[idx]);
+}
+
+
+const Turn& History::getTurn(unsigned int n) const
+{
+    ASSERT(0 <= n && n < m_history.size(), "Wrong turn number");
+    return *(m_history[n]);
+}
+
+/*
+ * This function increments the number of racks, and fills the new rack
+ * with the unplayed tiles from the previous one.
+ * 03 sept 2000 : We have to sort the tiles according to the new rules
+ */
+void History::playRound(int player, int turn, const Round& round)
+{
+    Rack rack;
+    Turn * current_turn;
+
+    current_turn = m_history.back();
+
+    /* set the number and the round */
+    current_turn->setNum(turn);
+    current_turn->setPlayer(player);
+    current_turn->setRound(round);
+
+    /* get what was the rack for the current turn */
+    current_turn->getPlayedRack().getRack(rack);
+
+    /* remove the played tiles from the rack */
+    for (int i = 0; i < round.getWordLen(); i++)
+    {
+        if (round.isPlayedFromRack(i))
+        {
+            if (round.isJoker(i))
+                rack.remove(Tile::Joker());
+            else
+                rack.remove(round.getTile(i));
+        }
+    }
+
+    /* create a new turn */
+    Turn * next_turn = new Turn();
+    PlayedRack pldrack;
+    pldrack.setOld(rack);
+    next_turn->setPlayedRack(pldrack);
+    m_history.push_back(next_turn);
+}
+
+
+void History::removeLastTurn()
+{
+    int idx = m_history.size();
+    ASSERT(0 < idx , "Wrong turn number");
+
+    if (idx > 1)
+    {
+        Turn *t = m_history.back();
+        m_history.pop_back();
+        delete t;
+    }
+
+    // Now we have the previous played round in back()
+    Turn* t = m_history.back();
+    t->setNum(0);
+    t->setPlayer(0);
+    t->setRound(Round());
+#ifdef BACK_REMOVE_RACK_NEW_PART
+    t->getPlayedRound().setNew(Rack());
+#endif
+}
+
+
+wstring History::toString() const
+{
+    wstring rs;
+#ifdef DEBUG
+    wchar_t buff[5];
+    swprintf(buff, 4, L"%4ld", m_history.size());
+    rs = L"history size = " + wstring(buff) + L"\n\n";
+#endif
+    for (unsigned int i = 0; i < m_history.size(); i++)
+    {
+        Turn *t = m_history[i];
+        rs += t->toString() + L"\n";
+    }
+    return rs;
+}
+
+/* ******************************************************** */
+/* ******************************************************** */
+/* ******************************************************** */
+
+
+/// Local Variables:
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/game/history.h
diff -u /dev/null eliot/game/history.h:1.8.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/history.h        Wed Dec 28 16:47:35 2005
@@ -0,0 +1,104 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Eliot 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file   history.h
+ *  \brief  Game history system
+ *  \author Antoine Fraboulet
+ *  \date   2005
+ */
+
+#ifndef _HISTORY_H
+#define _HISTORY_H
+
+#include <string>
+#include <vector>
+
+using std::wstring;
+using std::vector;
+
+class Round;
+class Turn;
+class PlayedRack;
+
+
+/**
+ * History stores all the turns that have been played
+ * This class is used many times in the game
+ *  - one for the complete game
+ *  - one for each of the players
+ *
+ * A History is never void (getSize() can be used as the is the current turn
+ * number for the complete game history).
+ *
+ * History starts at zero.
+ *
+ * The top of the history is an empty
+ * Turn until it has been filled and game is up to a new round.
+ *
+ * getCurrentRack() can/should be used to store the current played rack.
+ * setCurrentRack must be called whenever the current played rack is
+ * modified.
+ *
+ * History owns the turns that it stores. Do not delete a turn referenced by 
History
+ */
+
+class History
+{
+public:
+    History();
+    virtual ~History();
+
+    /// get the size of the history
+    int               getSize() const;
+
+    /// Get the (possibly incomplete) rack
+    const PlayedRack& getCurrentRack() const;
+
+    /// Set the current rack
+    void              setCurrentRack(const PlayedRack &iPld);
+
+    /// Get the previous turn
+    const Turn&       getPreviousTurn() const;
+
+    /// Get turn 'n'
+    const Turn&       getTurn(unsigned int) const;
+
+    /// Update the "history" with the given round and complete the turn.
+    /// A new turn is created with the remaining letters in the rack
+    void playRound(int player, int turn, const Round& round);
+
+    /// Remove last turn
+    void removeLastTurn();
+
+    /// String handling
+    wstring toString() const;
+
+private:
+    vector<Turn*> m_history;
+};
+
+#endif
+
+
+/// Local Variables:
+/// mode: c++
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/game/player.cpp
diff -u /dev/null eliot/game/player.cpp:1.12.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/player.cpp       Wed Dec 28 16:47:35 2005
@@ -0,0 +1,99 @@
+/*****************************************************************************
+ * Copyright (C) 2004-2005 Eliot
+ * Authors: Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include "tile.h"
+#include "rack.h"
+#include "pldrack.h"
+#include "round.h"
+#include "results.h"
+#include "board.h"
+#include "player.h"
+#include "turn.h"
+#include "history.h"
+
+#include "debug.h"
+
+
+Player::Player(int iId)
+{
+    m_id = iId;
+    m_score = 0;
+}
+
+
+Player::~Player()
+{
+}
+
+
+const PlayedRack & Player::getCurrentRack() const
+{
+    return m_history.getCurrentRack();
+}
+
+
+void Player::setCurrentRack(const PlayedRack &iPld)
+{
+    m_history.setCurrentRack(iPld);
+}
+
+
+const PlayedRack & Player::getLastRack() const
+{
+    return m_history.getPreviousTurn().getPlayedRack();
+}
+
+
+const Round & Player::getLastRound() const
+{
+    return m_history.getPreviousTurn().getRound();
+}
+
+
+void Player::endTurn(const Round &iRound, int iTurn)
+{
+    m_history.playRound(m_id,iTurn,iRound);
+}
+
+void Player::removeLastTurn()
+{
+    m_history.removeLastTurn();
+}
+
+wstring Player::toString() const
+{
+    wstring res;
+
+    wchar_t buff[6];
+    swprintf(buff, 5, L"Player %5d\n", m_id);
+    res = wstring(buff);
+    res += m_history.toString() + L"\n";
+    swprintf(buff, 5,  L"score %5d\n", m_score);
+    res += wstring(buff);
+    return res;
+}
+
+/****************************************************************/
+/****************************************************************/
+
+/// Local Variables:
+/// mode: c++
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/game/player.h
diff -u /dev/null eliot/game/player.h:1.16.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/player.h Wed Dec 28 16:47:35 2005
@@ -0,0 +1,100 @@
+/*****************************************************************************
+ * Copyright (C) 2004-2005 Eliot
+ * Authors: Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#ifndef _PLAYER_H_
+#define _PLAYER_H_
+
+#include <vector>
+#include <string>
+#include "pldrack.h"
+#include "history.h"
+
+class Turn;
+
+
+/**
+ * This class is the parent class for all the players involved in a game.
+ * It defines the common methods to update the rack, score, etc...
+ */
+class Player
+{
+public:
+    Player(int iId);
+    virtual ~Player();
+
+    // Pseudo RTTI
+    virtual bool isHuman() const = 0;
+
+    /**************************
+     * General getters
+     **************************/
+    // Get the (possibly incomplete) rack of the player
+    const PlayedRack & getCurrentRack() const;
+    // Get the previous rack
+    const PlayedRack & getLastRack() const;
+    // Get the previous round (corresponding to the previous rack...)
+    const Round & getLastRound() const;
+
+    void setCurrentRack(const PlayedRack &iPld);
+
+    const History& getHistory() const { return m_history; }
+    /// Remove last turn
+    void removeLastTurn();
+
+    /**************************
+     * Acessors for the score of the player
+     **************************/
+    // Add (or remove, if iPoints is negative) points to the score
+    // of the player
+    void addPoints(int iPoints) { m_score += iPoints; }
+    int  getPoints() const      { return m_score; }
+
+    // Update the player "history", with the given round.
+    // A new rack is created with the remaining letters
+    void endTurn(const Round &iRound, int iTurn);
+
+    wstring toString() const;
+
+private:
+    /// ID of the player
+    int m_id;
+
+    /// Score of the player
+    int m_score;
+
+    /// History of the racks and rounds for the player
+    History m_history;
+};
+
+
+/**
+ * Human player.
+ */
+class HumanPlayer: public Player
+{
+public:
+    HumanPlayer(int iId): Player(iId) {}
+    virtual ~HumanPlayer() {}
+
+    // Pseudo RTTI
+    virtual bool isHuman() const { return true; }
+};
+
+#endif
+
Index: eliot/game/pldrack.cpp
diff -u /dev/null eliot/game/pldrack.cpp:1.7.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/pldrack.cpp      Wed Dec 28 16:47:35 2005
@@ -0,0 +1,181 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include "rack.h"
+#include "pldrack.h"
+
+#include "debug.h"
+
+
+void PlayedRack::addOld(const Tile &t)
+{
+    m_oldTiles.push_back(t);
+}
+
+
+void PlayedRack::addNew(const Tile &t)
+{
+    m_newTiles.push_back(t);
+}
+
+
+void PlayedRack::getOldTiles(vector<Tile> &oTiles) const
+{
+    oTiles.clear();
+    for (int i = 0; i < nOld(); i++)
+        oTiles.push_back(m_oldTiles[i]);
+}
+
+
+void PlayedRack::getNewTiles(vector<Tile> &oTiles) const
+{
+    oTiles.clear();
+    for (int i = 0; i < nNew(); i++)
+        oTiles.push_back(m_newTiles[i]);
+}
+
+
+void PlayedRack::getAllTiles(vector<Tile> &oTiles) const
+{
+    oTiles.clear();
+    for (int i = 0; i < nOld(); i++)
+        oTiles.push_back(m_oldTiles[i]);
+    for (int j = 0; j < nNew(); j++)
+        oTiles.push_back(m_newTiles[j]);
+}
+
+
+void PlayedRack::reset()
+{
+    m_oldTiles.clear();
+    m_newTiles.clear();
+}
+
+
+void PlayedRack::resetNew()
+{
+    m_newTiles.clear();
+}
+
+
+void PlayedRack::getOld(Rack &oRack) const
+{
+    vector<Tile>::const_iterator it;
+    oRack.clear();
+    for (it = m_oldTiles.begin(); it != m_oldTiles.end(); it++)
+    {
+        oRack.add(*it);
+    }
+}
+
+
+void PlayedRack::getNew(Rack &oRack) const
+{
+    vector<Tile>::const_iterator it;
+    oRack.clear();
+    for (it = m_newTiles.begin(); it != m_newTiles.end(); it++)
+    {
+        oRack.add(*it);
+    }
+}
+
+
+void PlayedRack::getRack(Rack &oRack) const
+{
+    vector<Tile>::const_iterator it;
+    getOld(oRack);
+    for (it = m_newTiles.begin(); it != m_newTiles.end(); it++)
+    {
+        oRack.add(*it);
+    }
+}
+
+
+void PlayedRack::setOld(const Rack &iRack)
+{
+    list<Tile> l;
+    iRack.getTiles(l);
+
+    m_oldTiles.clear();
+    list<Tile>::const_iterator it;
+    for (it = l.begin(); it != l.end(); it++)
+    {
+        addOld(*it);
+    }
+}
+
+
+void PlayedRack::setNew(const Rack &iRack)
+{
+    list<Tile> l;
+    iRack.getTiles(l);
+
+    m_newTiles.clear();
+    list<Tile>::const_iterator it;
+    for (it = l.begin(); it != l.end(); it++)
+    {
+        addNew(*it);
+    }
+}
+
+
+bool PlayedRack::checkRack(int iMin) const
+{
+    vector<Tile>::const_iterator it;
+    int v = 0;
+    int c = 0;
+
+    for (it = m_oldTiles.begin(); it != m_oldTiles.end(); it++)
+    {
+        if (it->isVowel()) v++;
+        if (it->isConsonant()) c++;
+    }
+    for (it = m_newTiles.begin(); it != m_newTiles.end(); it++)
+    {
+        if (it->isVowel()) v++;
+        if (it->isConsonant()) c++;
+    }
+    return (v >= iMin) && (c >= iMin);
+}
+
+
+void PlayedRack::operator=(const PlayedRack &iOther)
+{
+    m_oldTiles = iOther.m_oldTiles;
+    m_newTiles = iOther.m_newTiles;
+}
+
+
+wstring PlayedRack::toString(bool iShowExtraSigns) const
+{
+    vector<Tile>::const_iterator it;
+    wstring s;
+
+    for (it = m_oldTiles.begin(); it != m_oldTiles.end(); it++)
+        s += it->toChar();
+
+    if (iShowExtraSigns && nOld() > 0 && nNew() > 0)
+        s += L"+";
+
+    for (it = m_newTiles.begin(); it != m_newTiles.end(); it++)
+        s += it->toChar();
+
+    return s;
+}
Index: eliot/game/pldrack.h
diff -u /dev/null eliot/game/pldrack.h:1.10.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/pldrack.h        Wed Dec 28 16:47:35 2005
@@ -0,0 +1,75 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#ifndef _PLAYEDRACK_H_
+#define _PLAYEDRACK_H_
+
+#include <vector>
+#include <string>
+#include "tile.h"
+
+class Rack;
+
+using namespace std;
+
+
+/**
+ * A Playedrack is an "improved" rack, allowing to differentiate new letters
+ * from letters that are left from the previous rack.
+ * This is useful, to be able to write a rack on the form ABC+DEFG, where
+ * A, B, C are the "old" letters and D, E, F, G are the "new" ones.
+ */
+class PlayedRack
+{
+public:
+    PlayedRack() {}
+    virtual ~PlayedRack() {}
+
+    void reset();
+    void resetNew();
+
+    void getOld(Rack &oRack) const;
+    void getNew(Rack &oRack) const;
+    void getRack(Rack &oRack) const;
+
+    void setOld(const Rack &iRack);
+    void setNew(const Rack &iRack);
+
+    int nTiles() const  { return nNew() + nOld(); }
+    int nNew() const    { return m_newTiles.size(); }
+    int nOld() const    { return m_oldTiles.size(); }
+
+    void addNew(const Tile &t);
+    void addOld(const Tile &t);
+    void getNewTiles(vector<Tile> &oTiles) const;
+    void getOldTiles(vector<Tile> &oTiles) const;
+    void getAllTiles(vector<Tile> &oTiles) const;
+
+    bool checkRack(int iMin) const;
+
+    void operator=(const PlayedRack &iOther);
+    wstring toString(bool iShowExtraSigns = true) const;
+
+private:
+    vector<Tile> m_oldTiles;
+    vector<Tile> m_newTiles;
+};
+
+#endif
Index: eliot/game/round.cpp
diff -u /dev/null eliot/game/round.cpp:1.8.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/round.cpp        Wed Dec 28 16:47:35 2005
@@ -0,0 +1,182 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include <string>
+#include <wctype.h>
+#include <wchar.h>
+#include "tile.h"
+#include "round.h"
+
+
+#define FROMBOARD 0x1
+#define FROMRACK  0x2
+#define JOKER     0x4
+
+
+Round::Round()
+{
+    init();
+}
+
+
+void Round::init()
+{
+    m_word.clear();
+    m_tileOrigin.clear();
+    m_coord.setRow(1);
+    m_coord.setCol(1);
+    m_coord.setDir(Coord::HORIZONTAL);
+    m_points = 0;
+    m_bonus  = 0;
+}
+
+
+void Round::setWord(const vector<Tile> &iTiles)
+{
+    m_word.clear();
+
+    vector<Tile>::const_iterator it;
+    for (it = iTiles.begin(); it != iTiles.end(); it++)
+    {
+        m_word.push_back(*it);
+        // XXX: always from rack?
+        m_tileOrigin.push_back(FROMRACK);
+    }
+}
+
+
+void Round::setFromRack(int iIndex)
+{
+    m_tileOrigin[iIndex] &= ~FROMBOARD;
+    m_tileOrigin[iIndex] |= FROMRACK;
+}
+
+
+void Round::setFromBoard(int iIndex)
+{
+    m_tileOrigin[iIndex] &= ~FROMRACK;
+    m_tileOrigin[iIndex] |= FROMBOARD;
+}
+
+
+void Round::setJoker(int iIndex, bool value)
+{
+    if (value)
+        m_tileOrigin[iIndex] |= JOKER;
+    else
+        m_tileOrigin[iIndex] &= ~JOKER;
+}
+
+
+bool Round::isJoker(int iIndex) const
+{
+     return m_tileOrigin[iIndex] & JOKER;
+}
+
+
+const Tile& Round::getTile(int iIndex) const
+{
+     return m_word[iIndex];
+}
+
+
+int Round::getWordLen() const
+{
+     return m_word.size();
+}
+
+
+bool Round::isPlayedFromRack(int iIndex) const
+{
+     return m_tileOrigin[iIndex] & FROMRACK;
+}
+
+
+void Round::addRightFromBoard(Tile c)
+{
+    m_word.push_back(c);
+    m_tileOrigin.push_back(FROMBOARD);
+}
+
+
+void Round::removeRightToBoard(Tile c)
+{
+    // c is unused.
+    m_word.pop_back();
+    m_tileOrigin.pop_back();
+}
+
+
+void Round::addRightFromRack(Tile c, bool iJoker)
+{
+    m_word.push_back(c);
+    char origin = FROMRACK;
+    if (iJoker)
+    {
+        origin |= JOKER;
+    }
+    m_tileOrigin.push_back(origin);
+}
+
+
+void Round::removeRightToRack(Tile c, bool iJoker)
+{
+    // c is unused.
+    m_word.pop_back();
+    m_tileOrigin.pop_back();
+}
+
+wstring Round::getWord() const
+{
+    wchar_t c;
+    wstring s;
+
+    for (int i = 0; i < getWordLen(); i++)
+    {
+        c = getTile(i).toChar();
+        if (isJoker(i))
+            c = towlower(c);
+        s += c;
+    }
+    return s;
+}
+
+wstring Round::toString() const
+{
+    wstring rs = L" ";
+
+    if (getWord().size() > 0)
+    {
+        rs  = getWord();
+        rs += wstring(16 - getWord().size(), ' ');
+        rs += getBonus() ? L'*' : L' ';
+        wchar_t buff[5];
+        swprintf(buff, 4, L"%4d", getPoints());
+        rs += buff;
+        rs += L" " + getCoord().toString();
+    }
+
+    return rs;
+}
+
+/// Local Variables:
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/game/round.h
diff -u /dev/null eliot/game/round.h:1.10.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/round.h  Wed Dec 28 16:47:35 2005
@@ -0,0 +1,95 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#ifndef _ROUND_H_
+#define _ROUND_H_
+
+#include <vector>
+#include "tile.h"
+#include "coord.h"
+
+using namespace std;
+
+
+/**
+ * A Round is the representation of a played word (or going to be played).
+ * It contains the word itself, of course, but also information of position
+ * on the board, origin of letters (board for a letter already placed, rack
+ * for a letter just being played), points, etc...
+ */
+class Round
+{
+public:
+
+    /*************************
+     *
+     *************************/
+    Round();
+    virtual ~Round() {}
+    void init();
+
+    /*************************
+     *
+     *************************/
+    void addRightFromBoard(Tile);
+    void removeRightToBoard(Tile);
+    void addRightFromRack(Tile, bool);
+    void removeRightToRack(Tile, bool);
+
+    /*************************
+     * General setters
+     *************************/
+    void setPoints(int iPoints)    { m_points = iPoints; }
+    void setBonus(bool iBonus)     { m_bonus = iBonus; }
+    void setTile(int iIndex, const Tile &iTile) { m_word[iIndex] = iTile; }
+    void setWord(const vector<Tile> &iTiles);
+    void setFromRack(int iIndex);
+    void setFromBoard(int iIndex);
+    void setJoker(int iIndex, bool value = true);
+
+    /*************************
+     * General getters
+     *************************/
+    bool isJoker         (int iIndex) const;
+    bool isPlayedFromRack(int iIndex) const;
+    const Tile& getTile  (int iIndex) const;
+
+    wstring getWord() const;
+    int getWordLen()  const;
+    int getPoints()   const       { return m_points; }
+    int getBonus()    const       { return m_bonus; }
+
+    /*************************
+     * Coordinates
+     *************************/
+    const Coord& getCoord() const { return m_coord; }
+    Coord& accessCoord()          { return m_coord; }
+
+    wstring toString() const;
+
+private:
+    vector<Tile> m_word;
+    vector<char> m_tileOrigin;
+    Coord m_coord;
+    int m_points;
+    int m_bonus;
+};
+
+#endif
Index: eliot/game/tile.cpp
diff -u /dev/null eliot/game/tile.cpp:1.5.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/tile.cpp Wed Dec 28 16:47:35 2005
@@ -0,0 +1,212 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include "tile.h"
+#include <wctype.h>
+
+
+/*************************
+ * French tiles
+ * Zero + 26 letters + joker
+ * tiles ares supposed to be contiguous and joker is separated
+ *************************/
+
+#define TILE_START     'A'
+#define TILE_END       'Z'
+#define TILE_JOKER     '?'
+#define TILE_DUMMY     '%'
+
+#define TILE_IDX_DUMMY   0
+#define TILE_IDX_START   1
+#define TILE_IDX_END    26
+#define TILE_IDX_JOKER  27
+
+#define TILES_NUMBER    28
+
+/* The jokers and the 'Y' can be considered both as vowels or consonants */
+const unsigned int Tiles_vowels[TILES_NUMBER] =
+{
+/* x A B C D  E F G H I J  K L M N O P Q R S T U V  W  X  Y  Z ? */
+   0,1,0,0,0, 1,0,0,0,1,0, 0,0,0,0,1,0,0,0,0,0,1,0, 0, 0, 1, 0,1
+};
+
+const unsigned int Tiles_consonants[TILES_NUMBER] =
+{
+/* x A B C D  E F G H I J  K L M N O P Q R S T U V  W  X  Y  Z ? */
+   0,0,1,1,1, 0,1,1,1,0,1, 1,1,1,1,0,1,1,1,1,1,0,1, 1, 1, 1, 1,1
+};
+
+const unsigned int Tiles_numbers[TILES_NUMBER] =
+{
+/* x A B C D  E F G H I J  K L M N O P Q R S T U V  W  X  Y  Z ? */
+   0,9,2,2,3,15,2,2,2,8,1, 1,5,3,6,6,2,1,6,6,6,6,2, 1, 1, 1, 1,2
+};
+
+const unsigned int Tiles_points[TILES_NUMBER] =
+{
+/* x A B C D  E F G H I J  K L M N O P Q R S T U V  W  X  Y  Z ? */
+   0,1,3,3,2, 1,4,2,4,1,8,10,1,2,1,1,3,8,1,1,1,1,4,10,10,10,10,0
+};
+
+/***************************
+ ***************************/
+
+list<Tile> Tile::m_tilesList;
+const Tile Tile::m_TheJoker(TILE_JOKER);
+const Tile Tile::m_TheDummy(0);
+
+
+Tile::Tile(wchar_t c)
+{
+    if (c == TILE_JOKER)
+    {
+        m_joker = true;
+        m_dummy = false;
+        m_char = TILE_JOKER;
+    }
+    else if (isalpha(c))
+    {
+        m_joker = islower(c);
+        m_dummy = false;
+        m_char = toupper(c);
+    }
+    else
+    {
+        m_joker = false;
+        m_dummy = true;
+        m_char = 0;
+    }
+}
+
+
+bool Tile::isVowel() const
+{
+    if (m_dummy)
+        return false;
+    if (m_joker)
+        return Tiles_vowels[TILE_IDX_JOKER];
+    return Tiles_vowels[TILE_IDX_START + m_char - TILE_START];
+}
+
+
+bool Tile::isConsonant() const
+{
+    if (m_dummy)
+        return false;
+    if (m_joker)
+        return Tiles_consonants[TILE_IDX_JOKER];
+    return Tiles_consonants[TILE_IDX_START + m_char - TILE_START];
+}
+
+
+unsigned int Tile::maxNumber() const
+{
+    if (m_dummy)
+        return false;
+    if (m_joker)
+        return Tiles_numbers[TILE_IDX_JOKER];
+    return Tiles_numbers[TILE_IDX_START + m_char - TILE_START];
+}
+
+
+unsigned int Tile::getPoints() const
+{
+    if (m_dummy)
+        return false;
+    if (m_joker)
+        return Tiles_points[TILE_IDX_JOKER];
+    return Tiles_points[TILE_IDX_START + m_char - TILE_START];
+}
+
+
+const list<Tile>& Tile::getAllTiles()
+{
+    if (Tile::m_tilesList.size() == 0)
+    {
+        // XXX: this should be filled from a "language file" instead
+        for (char i = TILE_START; i <= TILE_END; i++)
+            Tile::m_tilesList.push_back(Tile(i));
+        m_tilesList.push_back(Tile(TILE_JOKER));
+    }
+    return Tile::m_tilesList;
+}
+
+
+wchar_t Tile::toChar() const
+{
+    if (m_dummy)
+        return TILE_DUMMY;
+    if (m_joker)
+    {
+        if (iswalpha(m_char))
+            return tolower(m_char);
+        else
+            return TILE_JOKER;
+    }
+    return m_char;
+}
+
+int Tile::toCode() const
+{
+    if (m_dummy)
+        return TILE_IDX_DUMMY;
+    if (m_joker)
+        return TILE_IDX_DUMMY;
+    return (TILE_IDX_START + m_char - TILE_START);
+}
+
+bool Tile::operator <(const Tile &iOther) const
+{
+    if (iOther.m_dummy)
+        return false;
+    else if (m_dummy)
+        return true;
+    else if (m_joker)
+        return false;
+    else if (iOther.m_joker)
+        return true;
+    else
+        return m_char < iOther.m_char;
+}
+
+
+bool Tile::operator ==(const Tile &iOther) const
+{
+    if (m_dummy || iOther.m_dummy)
+        return m_dummy == iOther.m_dummy;
+    if (m_joker || iOther.m_joker)
+    {
+        if (m_joker != iOther.m_joker)
+            return false;
+        return m_char == iOther.m_char;
+    }
+    return m_char == iOther.m_char;
+//     return (m_joker && iOther.m_joker && m_char == iOther.m_char) ||
+//            (m_dummy && iOther.m_dummy) ||
+//            (!m_dummy && !iOther.m_dummy
+//             && !m_joker && !iOther.m_joker
+//             && m_char == iOther.m_char);
+}
+
+
+bool Tile::operator !=(const Tile &iOther) const
+{
+    return !(*this == iOther);
+}
+
Index: eliot/game/tile.h
diff -u /dev/null eliot/game/tile.h:1.6.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/tile.h   Wed Dec 28 16:47:35 2005
@@ -0,0 +1,75 @@
+/*****************************************************************************
+ * Copyright (C) 2005 Eliot
+ * Authors: Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#ifndef _TILE_H_
+#define _TILE_H_
+
+#include <list>
+
+using namespace std;
+
+/*************************
+ * A Tile is the internal representation
+ * used within the game library to
+ * handle letters
+ *************************/
+
+class Tile
+{
+public:
+
+  // a lowercase character always a joker
+  // - this permits to detect joker in already played games
+  // - we need to pay attention when inserting character taken
+  //   from user input
+
+    Tile(wchar_t c = 0);
+    virtual ~Tile() {}
+
+    bool isEmpty() const        { return m_dummy; }
+    bool isJoker() const        { return m_joker; }
+    bool isVowel() const;
+    bool isConsonant() const;
+    unsigned int maxNumber() const;
+    unsigned int getPoints() const;
+    wchar_t toChar() const;
+    int toCode() const;
+
+    static const Tile &dummy()  { return m_TheDummy; }
+    static const Tile &Joker()  { return m_TheJoker; }
+    static const list<Tile>& getAllTiles();
+
+    bool operator <(const Tile &iOther) const;
+    bool operator ==(const Tile &iOther) const;
+    bool operator !=(const Tile &iOther) const;
+
+private:
+    wchar_t m_char;
+    bool m_joker;
+    bool m_dummy;
+
+    // Special tiles are declared static
+    static const Tile m_TheJoker;
+    static const Tile m_TheDummy;
+
+    // List of available tiles
+    static list<Tile> m_tilesList;
+};
+
+#endif
Index: eliot/game/training.cpp
diff -u /dev/null eliot/game/training.cpp:1.14.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/training.cpp     Wed Dec 28 16:47:35 2005
@@ -0,0 +1,212 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include "dic.h"
+#include "tile.h"
+#include "rack.h"
+#include "round.h"
+#include "pldrack.h"
+#include "player.h"
+#include "training.h"
+
+#include "debug.h"
+
+
+Training::Training(const Dictionary &iDic): Game(iDic)
+{
+}
+
+
+Training::~Training()
+{
+}
+
+int Training::setRackRandom(int p, bool iCheck, set_rack_mode mode)
+{
+    int res;
+    m_results.clear();
+    do
+    {
+        res = helperSetRackRandom(p, iCheck, mode);
+    } while (res == 2);
+    // 0 : ok
+    // 1 : not enough tiles
+    // 2 : check failed (number of voyels before round 15)
+    return res;
+}
+
+int Training::setRackManual(bool iCheck, const wstring &iLetters)
+{
+    int res;
+    int p = m_currPlayer;
+    wstring::iterator it;
+    wstring uLetters; // uppercase letters
+    // letters can be lowercase or uppercase as they are
+    // coming from user input. We do not consider a lowercase
+    // letter to be a joker which has been assigned to a letter.
+    m_results.clear();
+    uLetters = iLetters;
+    for (it = uLetters.begin(); it != uLetters.end(); it ++)
+    {
+        *it = towupper(*it);
+    }
+    res = helperSetRackManual(p, iCheck, uLetters);
+    // 0 : ok
+    // 1 : not enough tiles
+    // 2 : check failed (number of voyels before round 15)
+    return res;
+}
+
+int Training::setRack(set_rack_mode iMode, bool iCheck, const wstring 
&iLetters)
+{
+    int res = 0;
+    switch(iMode)
+    {
+        case RACK_MANUAL:
+            res = setRackManual(iCheck, iLetters);
+            break;
+        case RACK_ALL:
+            res = setRackRandom(m_currPlayer, iCheck, iMode);
+            break;
+        case RACK_NEW:
+            res = setRackRandom(m_currPlayer, iCheck, iMode);
+            break;
+    }
+    return res;
+}
+
+int Training::play(const wstring &iCoord, const wstring &iWord)
+{
+    /* Perform all the validity checks, and fill a round */
+    Round round;
+    int res = checkPlayedWord(iCoord, iWord, round);
+    if (res != 0)
+    {
+        return res;
+    }
+
+    /* Update the rack and the score of the current player */
+    m_players[m_currPlayer]->addPoints(round.getPoints());
+    m_players[m_currPlayer]->endTurn(round, m_history.getSize());
+
+    /* Everything is OK, we can play the word */
+    helperPlayRound(round);
+
+    /* Next turn */
+    // XXX: Should it be done by the interface instead?
+    endTurn();
+
+    return 0;
+}
+
+
+int Training::start()
+{
+    if (getNPlayers() != 0)
+        return 1;
+
+    // Training mode implicitly uses 1 human player
+    Game::addHumanPlayer();
+    m_currPlayer = 0;
+    return 0;
+}
+
+int Training::endTurn()
+{
+    // Nothing to do?
+    return 0;
+}
+
+
+void Training::search()
+{
+    // Search for the current player
+    Rack r;
+    m_players[m_currPlayer]->getCurrentRack().getRack(r);
+//     debug("Training::search for %s\n",r.toString().c_str());
+    m_results.search(*m_dic, m_board, r, m_history.getSize());
+}
+
+
+int Training::playResult(int n)
+{
+    Player *player = m_players[m_currPlayer];
+    if (n >= m_results.size())
+        return 2;
+    const Round &round = m_results.get(n);
+
+    /* Update the rack and the score of the current player */
+    player->addPoints(round.getPoints());
+    player->endTurn(round, m_history.getSize());
+
+    int res = helperPlayRound(round);
+
+    if (res == 0)
+        m_results.clear();
+
+    /* Next turn */
+    // XXX: Should it be done by the interface instead?
+    endTurn();
+
+    return res;
+}
+
+
+void Training::addHumanPlayer()
+{
+    // We are not supposed to be here...
+    ASSERT(false, "Trying to add a human player in Training mode");
+}
+
+
+void Training::addAIPlayer()
+{
+    // We are not supposed to be here...
+    ASSERT(false, "Trying to add a AI player in Training mode");
+}
+
+
+void Training::testPlay(int num)
+{
+    ASSERT(0 <= num && num < m_results.size(), "Wrong result number");
+    m_testRound = m_results.get(num);
+    m_board.testRound(m_results.get(num));
+}
+
+
+void Training::removeTestPlay()
+{
+    m_board.removeTestRound();
+    m_testRound = Round();
+}
+
+wstring Training::getTestPlayWord() const
+{
+    return m_testRound.getWord();
+}
+
+/****************************************************************/
+/****************************************************************/
+
+/// Local Variables:
+/// mode: c++
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/game/training.h
diff -u /dev/null eliot/game/training.h:1.13.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/training.h       Wed Dec 28 16:47:35 2005
@@ -0,0 +1,90 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#ifndef _TRAINING_H_
+#define _TRAINING_H_
+
+#include <string>
+
+#include "game.h"
+#include "results.h"
+
+using std::string;
+using std::wstring;
+
+
+/**
+ * This class handles the logic specific to a training game.
+ * As its name indicates, it is not a game in the literal meaning of the word,
+ * in particular because the rack can be set at will.
+ * Note: No player should be added to this game, a human player is added
+ * automatically (in the start() method)
+ */
+class Training: public Game
+{
+    friend class GameFactory;
+public:
+    virtual GameMode getMode() const { return kTRAINING; }
+    virtual string getModeAsString() const { return "Training"; }
+
+    /*************************
+     * Game handling
+     *************************/
+    virtual int start();
+    virtual int play(const wstring &iCoord, const wstring &iWord);
+    virtual int endTurn();
+    void search();
+    int playResult(int);
+
+    virtual int setRackRandom(int, bool, set_rack_mode);
+    int setRackManual(bool iCheck, const wstring &iLetters);
+    int setRack(set_rack_mode iMode, bool iCheck, const wstring &iLetters);
+
+    /*************************
+     * Override the default behaviour of these methods, because in training
+     * we only want a human player
+     *************************/
+    virtual void addHumanPlayer();
+    virtual void addAIPlayer();
+
+    /*************************
+     * Functions to access the current search results
+     * The int parameter should be 0 <= int < getNResults
+     *************************/
+    const Results& getResults() const { return m_results; };
+
+    /// Place a temporary word on the board for preview purpose
+    void testPlay(int);
+    /// Remove the temporary word(s)
+    void removeTestPlay();
+    /// Get the temporary word
+    wstring getTestPlayWord() const;
+
+private:
+    // Private constructor and destructor to force using the GameFactory class
+    Training(const Dictionary &iDic);
+    virtual ~Training();
+
+    // Search results, with all the possible rounds
+    Round   m_testRound;
+    Results m_results;
+};
+
+#endif /* _TRAINING_H_ */
Index: eliot/game/turn.cpp
diff -u /dev/null eliot/game/turn.cpp:1.9.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/turn.cpp Wed Dec 28 16:47:35 2005
@@ -0,0 +1,71 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Eliot 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file   turn.cpp
+ *  \brief  Game turn (= id + pldrack + round)
+ *  \author Antoine Fraboulet
+ *  \date   2005
+ */
+
+#include <string>
+#include "pldrack.h"
+#include "round.h"
+#include "turn.h"
+
+
+Turn::Turn()
+{
+    m_num      = 0;
+    m_playerId = 0;
+    m_pldrack  = PlayedRack();
+    m_round    = Round();
+}
+
+Turn::Turn(int iNum, int iPlayerId,
+           const PlayedRack& iPldRack, const Round& iRound)
+    : m_num(iNum), m_playerId(iPlayerId), m_pldrack(iPldRack), m_round(iRound)
+{
+}
+
+#if 0
+void Turn::operator=(const Turn &iOther)
+{
+    m_num     = iOther.m_num;
+    m_pldrack = iOther.m_pldrack;
+    m_round   = iOther.m_round;
+}
+#endif
+
+wstring Turn::toString(bool iShowExtraSigns) const
+{
+    wstring rs = L"";
+    if (iShowExtraSigns)
+    {
+        // TODO
+    }
+    rs = rs + m_pldrack.toString() + L" " + m_round.toString();
+    return rs;
+}
+
+
+/// Local Variables:
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/game/turn.h
diff -u /dev/null eliot/game/turn.h:1.7.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/game/turn.h   Wed Dec 28 16:47:35 2005
@@ -0,0 +1,67 @@
+/* Eliot                                                                     */
+/* Copyright (C) 1999  Antoine Fraboulet                                     */
+/*                                                                           */
+/* This file is part of Eliot.                                               */
+/*                                                                           */
+/* Eliot 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 2 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Eliot 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, write to the Free Software               */
+/* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
*/
+
+/**
+ *  \file   turn.h
+ *  \brief  Game turn (= id + pldrack + round)
+ *  \author Antoine Fraboulet
+ *  \date   2005
+ */
+
+#ifndef _TURN_H
+#define _TURN_H
+
+class Turn
+{
+public:
+    Turn();
+    Turn(int iNum, int iPlayerId,
+         const PlayedRack& iPldRack, const Round& iRound);
+    virtual ~Turn() {};
+
+    void setNum(int iNum)                          { m_num = iNum; }
+    void setPlayer(int iPlayerId)                  { m_playerId = iPlayerId; }
+    void setPlayedRack(const PlayedRack& iPldRack) { m_pldrack = iPldRack; }
+    void setRound(const Round& iRound)             { m_round = iRound; }
+
+    int               getNum()        const { return m_num; }
+    int               getPlayer()     const { return m_playerId; }
+    const PlayedRack& getPlayedRack() const { return m_pldrack; }
+    const Round&      getRound()      const { return m_round; }
+
+#if 0
+    void operator=(const Turn &iOther);
+#endif
+    wstring toString(bool iShowExtraSigns = false) const;
+
+private:
+    int        m_num;
+    int        m_playerId;
+    PlayedRack m_pldrack;
+    Round      m_round;
+
+};
+
+#endif
+
+
+/// Local Variables:
+/// mode: hs-minor
+/// c-basic-offset: 4
+/// End:
Index: eliot/utils/eliottxt.cpp
diff -u /dev/null eliot/utils/eliottxt.cpp:1.12.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/utils/eliottxt.cpp    Wed Dec 28 16:47:35 2005
@@ -0,0 +1,894 @@
+/*****************************************************************************
+ * Copyright (C) 2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <string.h>
+#include <locale.h>
+#include <wctype.h>
+#include <wchar.h>
+#include <fstream>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include "dic.h"
+#include "dic_search.h"
+#include "game_io.h"
+#include "game_factory.h"
+#include "training.h"
+#include "duplicate.h"
+#include "freegame.h"
+#include "encoding.h"
+
+
+/* A static variable for holding the line. */
+static char *line_read = NULL;
+/* Wide version of the line */
+static wchar_t *wline_read = NULL;
+
+/**
+ * Read a string, and return a pointer to it.
+ * Returns NULL on EOF.
+ */
+wchar_t *rl_gets()
+{
+    // If the buffer has already been allocated, return the memory to the free
+    // pool
+    if (line_read)
+    {
+        free(line_read);
+        line_read = NULL;
+    }
+    if (wline_read)
+    {
+        free(wline_read);
+        wline_read = NULL;
+    }
+
+    // Get a line from the user
+    line_read = readline("commande> ");
+
+    // If the line has any text in it, save it on the history
+    if (line_read && *line_read)
+        add_history(line_read);
+
+    // Convert the line into wide characters
+    // Get the needed length (we _can't_ use string::size())
+    size_t len = mbstowcs(NULL, line_read, 0);
+    if (len == (size_t)-1)
+        return NULL;
+
+    wline_read = new wchar_t[len + 1];
+    len = mbstowcs(wline_read, line_read, len + 1);
+
+    return wline_read;
+}
+
+
+wchar_t * next_token_alpha(wchar_t *cmd, const wchar_t *delim, wchar_t **state)
+{
+    wchar_t *token = wcstok(cmd, delim, state);
+    if (token == NULL)
+        return NULL;
+    int i;
+    for (i = 0; token[i] && iswalpha(token[i]); i++)
+        ;
+    token[i] = L'\0';
+    return token;
+}
+
+
+wchar_t * next_token_alphanum(wchar_t *cmd, const wchar_t *delim, wchar_t 
**state)
+{
+    wchar_t *token = wcstok(cmd, delim, state);
+    if (token == NULL)
+        return NULL;
+    int i;
+    for (i = 0; token[i] && iswalnum(token[i]); i++)
+        ;
+    token[i] = L'\0';
+    return token;
+}
+
+
+wchar_t * next_token_alphaplusjoker(wchar_t *cmd, const wchar_t *delim, 
wchar_t **state)
+{
+    wchar_t *token = wcstok(cmd, delim, state);
+    if (token == NULL)
+        return NULL;
+    int i;
+    for (i = 0; token[i] && (iswalpha(token[i]) ||
+                             token[i] == L'?'   ||
+                             token[i] == L'+');
+         i++)
+        ;
+    token[i] = L'\0';
+    return token;
+}
+
+
+wchar_t * next_token_digit(wchar_t *cmd, const wchar_t *delim, wchar_t **state)
+{
+    wchar_t *token = wcstok(cmd, delim, state);
+    if (token == NULL)
+        return NULL;
+    int i;
+    for (i = 0; token[i] && (iswdigit(token[i]) || token[i] == L'-'); i++)
+        ;
+    token[i] = L'\0';
+    return token;
+}
+
+
+wchar_t * next_token_cross(wchar_t *cmd, const wchar_t *delim, wchar_t **state)
+{
+    wchar_t *token = wcstok(cmd, delim, state);
+    if (token == NULL)
+        return NULL;
+    int i;
+    for (i = 0; token[i] &&
+         (iswalpha(token[i]) || token[i] == L'.');
+         i++)
+        ;
+    token[i] = L'\0';
+    return token;
+}
+
+
+wchar_t * next_token_filename(wchar_t *cmd, const wchar_t *delim, wchar_t 
**state)
+{
+    wchar_t *token = wcstok(cmd, delim, state);
+    if (token == NULL)
+        return NULL;
+    int i;
+    for (i = 0; token[i] && (iswalnum(token[i]) ||
+                             token[i] == L'.' ||
+                             token[i] == L'_'); i++)
+        ;
+    token[i] = L'\0';
+    return token;
+}
+
+
+void eliottxt_get_cross(const Dictionary &iDic, wchar_t *cros)
+{
+    // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
+#if 0
+    wchar_t wordlist[RES_CROS_MAX][DIC_WORD_MAX];
+    Dic_search_Cros(iDic, cros, wordlist);
+    for (int i = 0; i < RES_CROS_MAX && wordlist[i][0]; i++)
+    {
+        printf("  %s\n", convertToMb(wordlist[i]).c_str());
+    }
+#endif
+}
+
+
+void help_training()
+{
+    printf("  ?    : aide -- cette page\n");
+    printf("  a [g|l|p|r|t] : afficher :\n");
+    printf("            g -- grille\n");
+    printf("            gj -- grille + jokers\n");
+    printf("            gm -- grille + valeur des cases\n");
+    printf("            gn -- grille + valeur des cases (variante)\n");
+    printf("            l -- lettres non jouées\n");
+    printf("            p -- partie\n");
+    printf("            r -- recherche\n");
+    printf("            s -- score\n");
+    printf("            S -- score de tous les joueurs\n");
+    printf("            t -- tirage\n");
+    printf("  d [] : vérifier le mot []\n");
+    printf("  *    : tirage aléatoire\n");
+    printf("  +    : tirage aléatoire ajouts\n");
+    printf("  t [] : changer le tirage\n");
+    printf("  j [] {} : jouer le mot [] aux coordonnées {}\n");
+    printf("  n [] : jouer le résultat numéro []\n");
+    printf("  r    : rechercher les meilleurs résultats\n");
+    printf("  s [] : sauver la partie en cours dans le fichier []\n");
+    printf("  q    : quitter le mode entraînement\n");
+}
+
+
+void help_freegame()
+{
+    printf("  ?    : aide -- cette page\n");
+    printf("  a [g|l|p|s|t] : afficher :\n");
+    printf("            g -- grille\n");
+    printf("            gj -- grille + jokers\n");
+    printf("            gm -- grille + valeur des cases\n");
+    printf("            gn -- grille + valeur des cases (variante)\n");
+    printf("            j -- joueur courant\n");
+    printf("            l -- lettres non jouées\n");
+    printf("            p -- partie\n");
+    printf("            s -- score\n");
+    printf("            S -- score de tous les joueurs\n");
+    printf("            t -- tirage\n");
+    printf("            T -- tirage de tous les joueurs\n");
+    printf("  d [] : vérifier le mot []\n");
+    printf("  j [] {} : jouer le mot [] aux coordonnées {}\n");
+    printf("  p [] : passer son tour en changeant les lettres []\n");
+    printf("  s [] : sauver la partie en cours dans le fichier []\n");
+    printf("  c [] : charger la partie du fichier []\n");
+    printf("  q    : quitter le mode partie libre\n");
+}
+
+
+void help_duplicate()
+{
+    printf("  ?    : aide -- cette page\n");
+    printf("  a [g|l|p|s|t] : afficher :\n");
+    printf("            g -- grille\n");
+    printf("            gj -- grille + jokers\n");
+    printf("            gm -- grille + valeur des cases\n");
+    printf("            gn -- grille + valeur des cases (variante)\n");
+    printf("            j -- joueur courant\n");
+    printf("            l -- lettres non jouées\n");
+    printf("            p -- partie\n");
+    printf("            s -- score\n");
+    printf("            S -- score de tous les joueurs\n");
+    printf("            t -- tirage\n");
+    printf("  d [] : vérifier le mot []\n");
+    printf("  j [] {} : jouer le mot [] aux coordonnées {}\n");
+    printf("  n [] : passer au joueur n°[]\n");
+    printf("  s [] : sauver la partie en cours dans le fichier []\n");
+    printf("  c [] : charger la partie du fichier []\n");
+    printf("  q    : quitter le mode duplicate\n");
+}
+
+
+void help()
+{
+    printf("  ?       : aide -- cette page\n");
+    printf("  e       : démarrer le mode entraînement\n");
+    printf("  d [] {} : démarrer une partie duplicate avec\n");
+    printf("                [] joueurs humains et {} joueurs IA\n");
+    printf("  l [] {} : démarrer une partie libre avec\n");
+    printf("                [] joueurs humains et {} joueurs IA\n");
+    printf("  D       : raccourci pour d 1 1\n");
+    printf("  L       : raccourci pour l 1 1\n");
+    printf("  q       : quitter\n");
+}
+
+
+void display_data(const Game &iGame, const wchar_t *delim, wchar_t **state)
+{
+    wchar_t *token;
+
+    token = next_token_alpha(NULL, delim, state);
+    if (token == NULL)
+    {
+        cout << "commande incomplète\n";
+        return;
+    }
+    switch (token[0])
+    {
+        case L'g':
+            switch (token[1])
+            {
+                case L'\0':
+                    GameIO::printBoard(cout, iGame);
+                    break;
+                case L'j':
+                    GameIO::printBoardJoker(cout, iGame);
+                    break;
+                case L'm':
+                    GameIO::printBoardMultipliers(cout, iGame);
+                    break;
+                case L'n':
+                    GameIO::printBoardMultipliers2(cout, iGame);
+                    break;
+                default:
+                    printf("commande inconnue\n");
+                    break;
+            }
+            break;
+        case L'j':
+            cout << "Joueur " << iGame.currPlayer() << endl;
+            break;
+        case L'l':
+            GameIO::printNonPlayed(cout, iGame);
+            break;
+        case L'p':
+            iGame.save(cout);
+            break;
+        case L'r':
+            token = next_token_digit(NULL, delim, state);
+            if (token == NULL)
+                GameIO::printSearchResults(cout,
+                                           static_cast<const Training&>(iGame),
+                                           10);
+            else
+                GameIO::printSearchResults(cout,
+                                           static_cast<const Training&>(iGame),
+                                           _wtoi(token));
+            break;
+        case L's':
+            GameIO::printPoints(cout, iGame);
+            break;
+        case L'S':
+            GameIO::printAllPoints(cout, iGame);
+            break;
+        case L't':
+            GameIO::printPlayedRack(cout, iGame, iGame.getHistory().getSize());
+            break;
+        case L'T':
+            GameIO::printAllRacks(cout, iGame);
+            break;
+        default:
+            cout << "commande inconnue\n";
+            break;
+    }
+}
+
+
+void loop_training(Training &iGame)
+{
+    wchar_t *token;
+    wchar_t *state;
+    wchar_t *commande = NULL;
+    wchar_t delim[] = L" \t";
+    int quit = 0;
+
+    cout << "mode entraînement\n";
+    cout << "[?] pour l'aide\n";
+    while (quit == 0)
+    {
+        commande = rl_gets();
+        token = wcstok(commande, delim, &state);
+        if (token)
+        {
+            switch (token[0])
+            {
+                case L'?':
+                    help_training();
+                    break;
+                case L'a':
+                    display_data(iGame, delim, &state);
+                    break;
+                case L'd':
+                    token = next_token_alpha(NULL, delim, &state);
+                    if (token == NULL)
+                        help_training();
+                    else
+                    {
+                        if (Dic_search_word(iGame.getDic(), token))
+                        {
+                            printf("le mot -%s- existe\n",
+                                   convertToMb(token).c_str());
+                        }
+                        else
+                        {
+                            printf("le mot -%s- n'existe pas\n",
+                                   convertToMb(token).c_str());
+                        }
+                    }
+                    break;
+               case L'j':
+                    token = next_token_alpha(NULL, delim, &state);
+                    if (token == NULL)
+                        help_training();
+                    else
+                    {
+                        int res;
+                        wchar_t *coord = next_token_alphanum(NULL, delim, 
&state);
+                        if (coord == NULL)
+                        {
+                            help_training();
+                            break;
+                        }
+                        if ((res = iGame.play(coord, token)) != 0)
+                        {
+                            fprintf(stderr, "Mot incorrect ou mal placé 
(%i)\n",
+                                    res);
+                            break;
+                        }
+                    }
+                    break;
+                case L'n':
+                    token = next_token_digit(NULL, delim, &state);
+                    if (token == NULL)
+                        help_training();
+                    else
+                    {
+                        int n = _wtoi(token);
+                        if (n <= 0)
+                            iGame.back(n == 0 ? 1 : -n);
+                        else
+                        {
+                            if (iGame.playResult(--n))
+                                printf("mauvais argument\n");
+                        }
+                    }
+                    break;
+                case L'r':
+                    iGame.search();
+                    break;
+                case L't':
+                    token = next_token_alphaplusjoker(NULL, delim, &state);
+                    if (token == NULL)
+                        help_training();
+                    else
+                        if (iGame.setRackManual(0, token))
+                            printf("le sac ne contient pas assez de 
lettres\n");
+                    break;
+                case L'x':
+                    token = next_token_cross(NULL, delim, &state);
+                    if (token == NULL)
+                        help_training();
+                    else
+                        eliottxt_get_cross(iGame.getDic(), token);
+                    break;
+                case L'*':
+                    iGame.setRackRandom(0, false, Game::RACK_ALL);
+                    break;
+                case L'+':
+                    iGame.setRackRandom(0, false, Game::RACK_NEW);
+                    break;
+                case L's':
+                    token = next_token_filename(NULL, delim, &state);
+                    if (token != NULL)
+                    {
+                        string filename = convertToMb(token);
+                        ofstream fout(filename.c_str());
+                        if (fout.rdstate() == ios::failbit)
+                        {
+                            printf("impossible d'ouvrir %s\n",
+                                   filename.c_str());
+                            break;
+                        }
+                        iGame.save(fout);
+                        fout.close();
+                    }
+                    break;
+                case L'q':
+                    quit = 1;
+                    break;
+                default:
+                    printf("commande inconnue\n");
+                    break;
+            }
+        }
+    }
+    printf("fin du mode entraînement\n");
+}
+
+
+void loop_freegame(FreeGame &iGame)
+{
+    wchar_t *token;
+    wchar_t *state;
+    wchar_t *commande = NULL;
+    wchar_t delim[] = L" \t";
+    int quit = 0;
+
+    printf("mode partie libre\n");
+    printf("[?] pour l'aide\n");
+    while (quit == 0)
+    {
+        commande = rl_gets();
+        token = wcstok(commande, delim, &state);
+        if (token)
+        {
+            switch (token[0])
+            {
+                case L'?':
+                    help_freegame();
+                    break;
+                case L'a':
+                    display_data(iGame, delim, &state);
+                    break;
+                case L'd':
+                    token = next_token_alpha(NULL, delim, &state);
+                    if (token == NULL)
+                        help_freegame();
+                    else
+                    {
+                        if (Dic_search_word(iGame.getDic(), token))
+                        {
+                            printf("le mot -%s- existe\n",
+                                   convertToMb(token).c_str());
+                        }
+                        else
+                        {
+                            printf("le mot -%s- n'existe pas\n",
+                                   convertToMb(token).c_str());
+                        }
+                    }
+                    break;
+               case L'j':
+                    token = next_token_alpha(NULL, delim, &state);
+                    if (token == NULL)
+                        help_freegame();
+                    else
+                    {
+                        int res;
+                        wchar_t *coord = next_token_alphanum(NULL, delim, 
&state);
+                        if (coord == NULL)
+                        {
+                            help_freegame();
+                            break;
+                        }
+                        if ((res = iGame.play(coord, token)) != 0)
+                        {
+                            fprintf(stderr, "Mot incorrect ou mal placé 
(%i)\n",
+                                    res);
+                            break;
+                        }
+                    }
+                    break;
+               case L'p':
+                    token = next_token_alpha(NULL, delim, &state);
+                    /* You can pass your turn without changing any letter */
+                    if (token == NULL)
+                        token = L"";
+
+                    if (iGame.pass(token, iGame.currPlayer()) != 0)
+                        break;
+                    break;
+                case L's':
+                    token = next_token_filename(NULL, delim, &state);
+                    if (token != NULL)
+                    {
+                        string filename = convertToMb(token);
+                        ofstream fout(filename.c_str());
+                        if (fout.rdstate() == ios::failbit)
+                        {
+                            printf("impossible d'ouvrir %s\n",
+                                   filename.c_str());
+                            break;
+                        }
+                        iGame.save(fout);
+                        fout.close();
+                    }
+                    break;
+                case L'q':
+                    quit = 1;
+                    break;
+                default:
+                    printf("commande inconnue\n");
+                    break;
+            }
+        }
+    }
+    printf("fin du mode partie libre\n");
+}
+
+
+void loop_duplicate(Duplicate &iGame)
+{
+    wchar_t *token;
+    wchar_t *state;
+    wchar_t *commande = NULL;
+    wchar_t delim[] = L" \t";
+    int quit = 0;
+
+    printf("mode duplicate\n");
+    printf("[?] pour l'aide\n");
+    while (quit == 0)
+    {
+        commande = rl_gets();
+        token = wcstok(commande, delim, &state);
+        if (token)
+        {
+            switch (token[0])
+            {
+                case L'?':
+                    help_duplicate();
+                    break;
+                case L'a':
+                    display_data(iGame, delim, &state);
+                    break;
+                case L'd':
+                    token = next_token_alpha(NULL, delim, &state);
+                    if (token == NULL)
+                        help_duplicate();
+                    else
+                    {
+                        if (Dic_search_word(iGame.getDic(), token))
+                        {
+                            printf("le mot -%s- existe\n",
+                                   convertToMb(token).c_str());
+                        }
+                        else
+                        {
+                            printf("le mot -%s- n'existe pas\n",
+                                   convertToMb(token).c_str());
+                        }
+                    }
+                    break;
+                case L'j':
+                    token = next_token_alpha(NULL, delim, &state);
+                    if (token == NULL)
+                        help_duplicate();
+                    else
+                    {
+                        int res;
+                        wchar_t *coord = next_token_alphanum(NULL, delim, 
&state);
+                        if (coord == NULL)
+                        {
+                            help_duplicate();
+                            break;
+                        }
+                        if ((res = iGame.play(coord, token)) != 0)
+                        {
+                            fprintf(stderr, "Mot incorrect ou mal placé 
(%i)\n",
+                                    res);
+                            break;
+                        }
+                    }
+                    break;
+                case L'n':
+                    token = next_token_digit(NULL, delim, &state);
+                    if (token == NULL)
+                        help_duplicate();
+                    else
+                    {
+                        int res = iGame.setPlayer(_wtoi(token));
+                        if (res == 1)
+                            fprintf(stderr, "Numéro de joueur invalide\n");
+                        else if (res == 2)
+                            fprintf(stderr, "Impossible de choisir un joueur 
non humain\n");
+                    }
+                    break;
+                case L's':
+                    token = next_token_filename(NULL, delim, &state);
+                    if (token != NULL)
+                    {
+                        string filename = convertToMb(token);
+                        ofstream fout(filename.c_str());
+                        if (fout.rdstate() == ios::failbit)
+                        {
+                            printf("impossible d'ouvrir %s\n",
+                                   filename.c_str());
+                            break;
+                        }
+                        iGame.save(fout);
+                        fout.close();
+                    }
+                    break;
+                case L'q':
+                    quit = 1;
+                    break;
+                default:
+                    printf("commande inconnue\n");
+                    break;
+            }
+        }
+    }
+    printf("fin du mode duplicate\n");
+}
+
+
+void main_loop(const Dictionary &iDic)
+{
+    wchar_t *token;
+    wchar_t *state;
+    wchar_t *commande = NULL;
+    wchar_t delim[] = L" \t";
+    int quit = 0;
+
+    printf("[?] pour l'aide\n");
+    while (quit == 0)
+    {
+        commande = rl_gets();
+        token = wcstok(commande, delim, &state);
+        if (token)
+        {
+            switch (token[0])
+            {
+                case L'?':
+                    help();
+                    break;
+                case L'c':
+                    token = next_token_filename(NULL, delim, &state);
+                    if (token == NULL)
+                    {}
+                    else
+                    {
+                        string filename = convertToMb(token);
+                        fprintf(stderr, "chargement de -%s-\n",
+                                filename.c_str());
+                        FILE* fin;
+                        if ((fin = fopen(filename.c_str(), "r")) == NULL)
+                        {
+                            printf("impossible d'ouvrir %s\n",
+                                   filename.c_str());
+                            break;
+                        }
+                        Game *game = Game::load(fin, iDic);
+                        fclose(fin);
+                        if (game == NULL)
+                        {
+                            fprintf(stderr, "erreur pendant le chargement\n");
+                        }
+                        else
+                        {
+                            if (game->getMode() == Game::kTRAINING)
+                                loop_training((Training&)*game);
+                            else if (game->getMode() == Game::kFREEGAME)
+                                loop_freegame((FreeGame&)*game);
+                            else
+                                loop_duplicate((Duplicate&)*game);
+                        }
+                    }
+                    break;
+                case L'e':
+                {
+                    // New training game
+                    Training *game = 
GameFactory::Instance()->createTraining(iDic);
+                    game->start();
+                    loop_training(*game);
+                    GameFactory::Instance()->releaseGame(*game);
+                    break;
+                }
+                case L'd':
+                {
+                    int i;
+                    // New duplicate game
+                    token = next_token_digit(NULL, delim, &state);
+                    if (token == NULL)
+                    {
+                        help();
+                        break;
+                    }
+                    Duplicate *game = 
GameFactory::Instance()->createDuplicate(iDic);
+                    for (i = 0; i < _wtoi(token); i++)
+                        game->addHumanPlayer();
+                    token = next_token_digit(NULL, delim, &state);
+                    if (token == NULL)
+                    {
+                        help();
+                        break;
+                    }
+                    for (i = 0; i < _wtoi(token); i++)
+                        game->addAIPlayer();
+                    game->start();
+                    loop_duplicate(*game);
+                    GameFactory::Instance()->releaseGame(*game);
+                    break;
+                }
+                case L'l':
+                {
+                    int i;
+                    // New free game
+                    token = next_token_digit(NULL, delim, &state);
+                    if (token == NULL)
+                    {
+                        help();
+                        break;
+                    }
+                    FreeGame *game = 
GameFactory::Instance()->createFreeGame(iDic);
+                    for (i = 0; i < _wtoi(token); i++)
+                        game->addHumanPlayer();
+                    token = next_token_digit(NULL, delim, &state);
+                    if (token == NULL)
+                    {
+                        help();
+                        break;
+                    }
+                    for (i = 0; i < _wtoi(token); i++)
+                        game->addAIPlayer();
+                    game->start();
+                    loop_freegame(*game);
+                    GameFactory::Instance()->releaseGame(*game);
+                    break;
+                }
+                case L'D':
+                {
+                    // New duplicate game
+                    Duplicate *game = 
GameFactory::Instance()->createDuplicate(iDic);
+                    game->addHumanPlayer();
+                    game->addAIPlayer();
+                    game->start();
+                    loop_duplicate(*game);
+                    GameFactory::Instance()->releaseGame(*game);
+                    break;
+                }
+                case L'L':
+                {
+                    // New free game
+                    FreeGame *game = 
GameFactory::Instance()->createFreeGame(iDic);
+                    game->addHumanPlayer();
+                    game->addAIPlayer();
+                    game->start();
+                    loop_freegame(*game);
+                    GameFactory::Instance()->releaseGame(*game);
+                    break;
+                }
+                case L'q':
+                    quit = 1;
+                    break;
+                default:
+                    printf("commande inconnue\n");
+                    break;
+            }
+        }
+    }
+}
+
+
+int main(int argc, char *argv[])
+{
+    char dic_path[100];
+
+    // Let the user choose the locale
+    setlocale(LC_ALL, "");
+
+    Dictionary dic = NULL;
+
+    if (argc != 2 && argc != 3)
+    {
+        fprintf(stdout, "Usage: eliot /chemin/vers/ods4.dawg [random_seed]\n");
+        exit(1);
+    }
+    else
+        strcpy(dic_path, argv[1]);
+
+    switch (Dic_load(&dic, dic_path))
+    {
+        case 0:
+            /* Normal case */
+            break;
+        case 1:
+            printf("chargement: problème d'ouverture de %s\n", argv[1]);
+            exit(1);
+            break;
+        case 2:
+            printf("chargement: mauvais en-tete de dictionnaire\n");
+            exit(2);
+            break;
+        case 3:
+            printf("chargement: problème 3 d'allocation mémoire\n");
+            exit(3);
+            break;
+        case 4:
+            printf("chargement: problème 4 d'alocation mémoire\n");
+            exit(4);
+            break;
+        case 5:
+            printf("chargement: problème de lecture des arcs du 
dictionnaire\n");
+            exit(5);
+            break;
+        default:
+            printf("chargement: problème non-repertorié\n");
+            exit(6);
+            break;
+    }
+
+    if (argc == 3)
+        srand(atoi(argv[2]));
+    else
+        srand(time(NULL));
+
+    main_loop(dic);
+    GameFactory::Destroy();
+
+    Dic_destroy(dic);
+
+    // Free the readline static variable and its wide equivalent
+    if (line_read)
+        free(line_read);
+    if (wline_read)
+        free(wline_read);
+
+    return 0;
+}
Index: eliot/utils/game_io.cpp
diff -u /dev/null eliot/utils/game_io.cpp:1.7.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/utils/game_io.cpp     Wed Dec 28 16:47:35 2005
@@ -0,0 +1,223 @@
+/*****************************************************************************
+ * Copyright (C) 1999-2005 Eliot
+ * Authors: Antoine Fraboulet <address@hidden>
+ *          Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include <iomanip>
+#include <string>
+#include "stdlib.h"
+
+#include "game_io.h"
+#include "game.h"
+#include "training.h"
+#include "player.h"
+#include "encoding.h"
+
+using namespace std;
+
+
+void GameIO::printBoard(ostream &out, const Game &iGame)
+{
+    int row, col;
+
+    out << "   ";
+    for (col = BOARD_MIN; col <= BOARD_MAX; col++)
+        out << setw(3) << col - BOARD_MIN + 1;
+    out << endl;
+    for (row = BOARD_MIN; row <= BOARD_MAX; row++)
+    {
+        out << " " << (char)(row - BOARD_MIN + 'A') << " ";
+        for (col = BOARD_MIN; col <= BOARD_MAX; col++)
+        {
+            char l = iGame.getBoard().getChar(row, col);
+            out << setw(3) << (l ? l : '-');
+        }
+        out << endl;
+    }
+}
+
+
+void GameIO::printBoardJoker(ostream &out, const Game &iGame)
+{
+    int row,col;
+
+    out << "   ";
+    for (col = BOARD_MIN; col <= BOARD_MAX; col++)
+        out << setw(3) << col - BOARD_MIN + 1;
+    out << endl;
+
+    for (row = BOARD_MIN; row <= BOARD_MAX; row++)
+    {
+        out << " " << (char)(row - BOARD_MIN + 'A') << " ";
+        for (col = BOARD_MIN; col <= BOARD_MAX; col++)
+        {
+            char l = iGame.getBoard().getChar(row, col);
+            bool j = (iGame.getBoard().getCharAttr(row, col) & ATTR_JOKER);
+
+            out << " " << (j ? '.' : (l ? ' ' : '-'));
+            out << (l ? l : '-');
+        }
+        out << endl;
+    }
+}
+
+
+void GameIO::printBoardMultipliers(ostream &out, const Game &iGame)
+{
+    int row, col;
+
+    out << "   ";
+    for (col = BOARD_MIN; col <= BOARD_MAX; col++)
+        out << setw(3) << col - BOARD_MIN + 1;
+    out << endl;
+
+    for (row = BOARD_MIN; row <= BOARD_MAX; row++)
+    {
+        out << " " << (char)(row - BOARD_MIN + 'A') << " ";
+        for (col = BOARD_MIN; col <= BOARD_MAX; col++)
+        {
+            char l = iGame.getBoard().getChar(row, col);
+            if (l != 0)
+                out << "  " << l;
+            else
+            {
+                int wm = iGame.getBoard().getWordMultiplier(row, col);
+                int tm = iGame.getBoard().getLetterMultiplier(row, col);
+
+                if (wm > 1)
+                    out << "  " << ((wm == 3) ? '@' : '#');
+                else if (tm > 1)
+                    out << "  " << ((tm == 3) ? '*' : '+');
+                else
+                    out << "  -";
+            }
+        }
+        out << endl;
+    }
+}
+
+
+void GameIO::printBoardMultipliers2(ostream &out, const Game &iGame)
+{
+    int row, col;
+
+    out << "   ";
+    for (col = BOARD_MIN; col <= BOARD_MAX; col++)
+        out << setw(3) << col - BOARD_MIN + 1;
+    out << endl;
+
+    for (row = BOARD_MIN; row <= BOARD_MAX; row++)
+    {
+        out << " " << (char)(row - BOARD_MIN + 'A') << " ";
+        for (col = BOARD_MIN; col <= BOARD_MAX; col++)
+        {
+            wchar_t l = iGame.getBoard().getChar(row, col);
+            int wm = iGame.getBoard().getWordMultiplier(row, col);
+            int tm = iGame.getBoard().getLetterMultiplier(row, col);
+
+            if (wm > 1)
+                out << " " << ((wm == 3) ? '@' : '#');
+            else if (tm > 1)
+                out << " " << ((tm == 3) ? '*' : '+');
+            else
+                out << " -";
+            out << (l ? convertToMb(l) : "-");
+        }
+        out << endl;
+    }
+}
+
+
+void GameIO::printNonPlayed(ostream &out, const Game &iGame)
+{
+    const list<Tile>& allTiles = Tile::getAllTiles();
+    list<Tile>::const_iterator it;
+
+    for (it = allTiles.begin(); it != allTiles.end(); it++)
+    {
+        if (iGame.getBag().in(it->toChar()) > 9)
+            out << " ";
+        out << setw(2) << convertToMb(it->toChar());
+    }
+    out << endl;
+
+    for (it = allTiles.begin(); it != allTiles.end(); it++)
+    {
+        out << " " << iGame.getBag().in(it->toChar());
+    }
+    out << endl;
+}
+
+
+void GameIO::printPlayedRack(ostream &out, const Game &iGame, int n)
+{
+    out << 
convertToMb(iGame.getCurrentPlayer().getCurrentRack().toString(false)) << endl;
+}
+
+
+void GameIO::printAllRacks(ostream &out, const Game &iGame)
+{
+    for (int j = 0; j < iGame.getNPlayers(); j++)
+    {
+        out << "Joueur " << j << ": ";
+        out << 
convertToMb(iGame.getPlayer(j).getCurrentRack().toString(false)) << endl;
+    }
+}
+
+
+static void searchResultLine(ostream &out, const Training &iGame, int num)
+{
+    const Results &res = iGame.getResults();
+    Round r = res.get(num);
+    wstring word = r.getWord();
+    if (word.size() == 0)
+        return;
+    out << convertToMb(word) << string(16 - word.size(), ' ')
+        << (r.getBonus() ? '*' : ' ')
+        << setw(4) << r.getPoints()
+        << ' ' << convertToMb(r.getCoord().toString());
+}
+
+
+void GameIO::printSearchResults(ostream &out, const Training &iGame, int num)
+{
+    int size = (int) iGame.getResults().size();
+    for (int i = 0; i < num && i < size; i++)
+    {
+        out << setw(3) << i + 1 << ": ";
+        searchResultLine(out, iGame, i);
+        out << endl;
+    }
+}
+
+
+void GameIO::printPoints(ostream &out, const Game &iGame)
+{
+    out << iGame.getPlayer(0).getPoints() << endl;
+}
+
+
+void GameIO::printAllPoints(ostream &out, const Game &iGame)
+{
+    for (int i = 0; i < iGame.getNPlayers(); i++)
+    {
+        out << "Joueur " << i << ": "
+            << setw(4) << iGame.getPlayer(i).getPoints() << endl;
+    }
+}
+
Index: eliot/utils/ncurses.cpp
diff -u /dev/null eliot/utils/ncurses.cpp:1.19.2.1
--- /dev/null   Wed Dec 28 16:47:36 2005
+++ eliot/utils/ncurses.cpp     Wed Dec 28 16:47:35 2005
@@ -0,0 +1,892 @@
+/*****************************************************************************
+ * Copyright (C) 2005 Eliot
+ * Authors: Olivier Teuliere  <address@hidden>
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *****************************************************************************/
+
+#include "config.h"
+#if ENABLE_NLS
+#   include <libintl.h>
+#   define _(String) gettext(String)
+#else
+#   define _(String) String
+#endif
+
+#include <ctype.h>
+#include <fstream>
+
+#include "ncurses.h"
+#include "dic.h"
+#include "dic_search.h"
+#include "game_factory.h"
+#include "training.h"
+#include "duplicate.h"
+#include "freegame.h"
+#include "player.h"
+#include "history.h"
+#include "turn.h"
+#include "encoding.h"
+
+using namespace std;
+
+
+CursesIntf::CursesIntf(WINDOW *win, Game& iGame)
+    : m_win(win), m_game(&iGame), m_state(DEFAULT), m_dying(false),
+    m_boxStart(0), m_boxLines(0), m_boxLinesData(0), m_boxY(0),
+    m_showDots(false)
+{
+}
+
+
+CursesIntf::~CursesIntf()
+{
+    GameFactory::Instance()->releaseGame(*m_game);
+}
+
+
+void CursesIntf::drawBox(WINDOW *win, int y, int x, int h, int w,
+                         const string& iTitle)
+{
+    if (w > 3 && h > 2)
+    {
+        int i_len = iTitle.size();
+
+        if (i_len > w - 2) i_len = w - 2;
+
+        mvwaddch(win, y, x,    ACS_ULCORNER);
+        mvwhline(win, y, x+1,  ACS_HLINE, ( w-i_len-2)/2);
+        mvwprintw(win,y, x+1+(w-i_len-2)/2, "%s", iTitle.c_str());
+        mvwhline(win, y, x+(w-i_len)/2+i_len,
+                 ACS_HLINE, w - 1 - ((w-i_len)/2+i_len));
+        mvwaddch(win, y, x+w-1,ACS_URCORNER);
+
+        mvwvline(win, y+1, x,     ACS_VLINE, h-2);
+        mvwvline(win, y+1, x+w-1, ACS_VLINE, h-2);
+
+        mvwaddch(win, y+h-1, x,     ACS_LLCORNER);
+        mvwhline(win, y+h-1, x+1,   ACS_HLINE, w - 2);
+        mvwaddch(win, y+h-1, x+w-1, ACS_LRCORNER);
+    }
+}
+
+
+void CursesIntf::clearRect(WINDOW *win, int y, int x, int h, int w)
+{
+    for (int i = 0; i < h; i++)
+    {
+        mvwhline(win, y + i, x, ' ', w);
+    }
+}
+
+
+void CursesIntf::boxPrint(WINDOW *win, int y, int x, const char *fmt, ...)
+{
+    if (y < m_boxStart || y - m_boxStart >= m_boxLines)
+        return;
+
+    va_list vl_args;
+    char *buf = NULL;
+    va_start(vl_args, fmt);
+    vasprintf(&buf, fmt, vl_args);
+    va_end(vl_args);
+
+    if (buf == NULL)
+    {
+        return;
+    }
+    mvwprintw(win, m_boxY + y - m_boxStart, x, "%s", buf);
+}
+
+
+void CursesIntf::drawStatus(WINDOW *win, int y, int x,
+                            const string& iMessage, bool error)
+{
+    if (error)
+        wattron(win, COLOR_PAIR(COLOR_YELLOW));
+    mvwprintw(win, y, x, iMessage.c_str());
+    whline(win, ' ', COLS - x - 1 - iMessage.size());
+    if (error)
+        wattron(win, COLOR_PAIR(COLOR_WHITE));
+}
+
+
+void CursesIntf::drawBoard(WINDOW *win, int y, int x) const
+{
+    // Box around the board
+    drawBox(win, y + 1, x + 3, 17, 47, "");
+
+    // Print the coordinates
+    for (int i = 0; i < 15; i++)
+    {
+        mvwaddch(win, y + i + 2, x + 1,  'A' + i);
+        mvwaddch(win, y + i + 2, x + 51, 'A' + i);
+        mvwprintw(win, y,      x + 3 * i + 5, "%d", i + 1);
+        mvwprintw(win, y + 18, x + 3 * i + 5, "%d", i + 1);
+    }
+
+    // The board itself
+    for (int row = 1; row < 16; row++)
+    {
+        for (int col = 1; col < 16; col++)
+        {
+            // Handle colors
+            int wm = m_game->getBoard().getWordMultiplier(row, col);
+            int lm = m_game->getBoard().getLetterMultiplier(row, col);
+            if (wm == 3)
+                wattron(win, COLOR_PAIR(COLOR_RED));
+            else if (wm == 2)
+                wattron(win, COLOR_PAIR(COLOR_MAGENTA));
+            else if (lm == 3)
+                wattron(win, COLOR_PAIR(COLOR_BLUE));
+            else if (lm == 2)
+                wattron(win, COLOR_PAIR(COLOR_CYAN));
+            else
+                wattron(win, COLOR_PAIR(COLOR_WHITE));
+
+            // Empty square
+            mvwprintw(win, y + row + 1, x + 3 * col + 1, "   ");
+
+            // Now add the letter
+            char c = m_game->getBoard().getChar(row, col);
+            if (c)
+            {
+                if (islower(c))
+                    mvwaddch(win, y + row + 1, x + 3 * col + 2,
+                             c | A_BOLD | COLOR_PAIR(COLOR_GREEN));
+                else
+                    mvwaddch(win, y + row + 1, x + 3 * col + 2, c);
+            }
+            else
+            {
+                // Empty square... should we display a dot?
+                if (m_showDots)
+                    mvwaddch(win, y + row + 1, x + 3 * col + 2, '.');
+            }
+        }
+    }
+    wattron(win, COLOR_PAIR(COLOR_WHITE));
+}
+
+
+void CursesIntf::drawScoresRacks(WINDOW *win, int y, int x) const
+{
+    drawBox(win, y, x, m_game->getNPlayers() + 2, 25, _(" Scores "));
+    for (int i = 0; i < m_game->getNPlayers(); i++)
+    {
+        if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer())
+            attron(A_BOLD);
+        mvwprintw(win, y + i + 1, x + 2,
+                  _("Player %d: %d"), i, m_game->getPlayer(i).getPoints());
+        if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer())
+            attroff(A_BOLD);
+    }
+
+    // Distance between the 2 boxes
+    int yOff = m_game->getNPlayers() + 3;
+
+    drawBox(win, y + yOff, x, m_game->getNPlayers() + 2, 25, _(" Racks "));
+    for (int i = 0; i < m_game->getNPlayers(); i++)
+    {
+        if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer())
+            attron(A_BOLD);
+        string rack = 
convertToMb(m_game->getPlayer(i).getCurrentRack().toString(false));
+        mvwprintw(win, y + yOff + i + 1, x + 2,
+                  _("Player %d: %s"), i, rack.c_str());
+        if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer())
+            attroff(A_BOLD);
+        // Force to refresh the whole rack
+        whline(win, ' ', 7 - rack.size());
+    }
+
+    // Display a message when the search is complete
+    if (m_game->getMode() == Game::kTRAINING &&
+        static_cast<Training*>(m_game)->getHistory().getSize())
+    {
+        mvwprintw(win, y + 2*yOff - 1, x + 2, _("Search complete"));
+    }
+    else
+        mvwhline(win, y + 2*yOff - 1, x + 2, ' ', strlen(_("Search 
complete")));
+}
+
+
+void CursesIntf::drawResults(WINDOW *win, int y, int x)
+{
+    if (m_game->getMode() != Game::kTRAINING)
+        return;
+    Training *tr_game = static_cast<Training*>(m_game);
+
+    int h = 17;
+    drawBox(win, y, x, h, 25, _(" Search results "));
+    m_boxY = y + 1;
+    m_boxLines = h - 2;
+    m_boxLinesData = tr_game->getHistory().getSize();
+
+    int i;
+    const Results& res = tr_game->getResults();
+    for (i = m_boxStart; i < res.size() &&
+                         i < m_boxStart + m_boxLines; i++)
+    {
+        const Round &r = res.get(i);
+        wstring coord = r.getCoord().toString();
+        boxPrint(win, i, x + 1, "%3d %s%s %3s",
+                 r.getPoints(),
+                 r.getWord().c_str(),
+                 string(h - 3 - r.getWordLen(), ' ').c_str(),
+                 convertToMb(coord).c_str());
+    }
+    // Complete the list with empty lines, to avoid trails
+    for (; i < m_boxStart + m_boxLines; i++)
+    {
+        boxPrint(win, i, x + 1, string(23, ' ').c_str());
+    }
+}
+
+
+void CursesIntf::drawHistory(WINDOW *win, int y, int x)
+{
+    // To allow pseudo-scrolling, without leaving trails
+    clear();
+
+    drawBox(win, y, x, LINES - y, COLS - x, _(" History of the game "));
+    m_boxY = y + 1;
+    m_boxLines = LINES - y - 2;
+    m_boxLinesData = m_game->getHistory().getSize();
+
+    // Heading
+    boxPrint(win, m_boxStart, x + 2,
+             _(" N |   RACK   |    SOLUTION     | REF | PTS | P | BONUS"));
+    mvwhline(win, y + 2, x + 2, ACS_HLINE, 55);
+
+    int i;
+    for (i = m_boxStart + 0; i < m_game->getHistory().getSize() &&
+                         i < m_boxStart + m_boxLines; i++)
+    {
+        const Turn& t = m_game->getHistory().getTurn(i);
+        const Round& r = t.getRound();
+        string word = convertToMb(r.getWord());
+        string coord = convertToMb(r.getCoord().toString());
+        boxPrint(win, i + 2, x + 2,
+                 "%2d   %8s   %s%s   %3s   %3d   %1d   %c",
+                 i + 1, convertToMb(t.getPlayedRack().toString()).c_str(),
+                 word.c_str(), string(15 - word.size(), ' ').c_str(),
+                 coord.c_str(), r.getPoints(),
+                 t.getPlayer(), r.getBonus() ? '*' : ' ');
+    }
+    mvwvline(win, y + 1, x + 5,  ACS_VLINE, min(i + 2 - m_boxStart, 
m_boxLines));
+    mvwvline(win, y + 1, x + 16, ACS_VLINE, min(i + 2 - m_boxStart, 
m_boxLines));
+    mvwvline(win, y + 1, x + 34, ACS_VLINE, min(i + 2 - m_boxStart, 
m_boxLines));
+    mvwvline(win, y + 1, x + 40, ACS_VLINE, min(i + 2 - m_boxStart, 
m_boxLines));
+    mvwvline(win, y + 1, x + 46, ACS_VLINE, min(i + 2 - m_boxStart, 
m_boxLines));
+    mvwvline(win, y + 1, x + 50, ACS_VLINE, min(i + 2 - m_boxStart, 
m_boxLines));
+}
+
+
+void CursesIntf::drawHelp(WINDOW *win, int y, int x)
+{
+    // To allow pseudo-scrolling, without leaving trails
+    clear();
+
+    drawBox(win, y, x, LINES - y, COLS - x, _(" Help "));
+    m_boxY = y + 1;
+    m_boxLines = LINES - y - 2;
+
+    int n = 0;
+    boxPrint(win, n++, x + 2, _("[Global]"));
+    boxPrint(win, n++, x + 2, _("   h, H, ?          Show/hide help box"));
+    boxPrint(win, n++, x + 2, _("   y, Y             Show/hide history of the 
game"));
+    boxPrint(win, n++, x + 2, _("   e, E             Show/hide dots on empty 
squares of the board"));
+    boxPrint(win, n++, x + 2, _("   d, D             Check the existence of a 
word in the dictionary"));
+    boxPrint(win, n++, x + 2, _("   j, J             Play a word"));
+    boxPrint(win, n++, x + 2, _("   s, S             Save the game"));
+    boxPrint(win, n++, x + 2, _("   l, L             Load a game"));
+    boxPrint(win, n++, x + 2, _("   q, Q             Quit"));
+    boxPrint(win, n++, x + 2, "");
+
+    boxPrint(win, n++, x + 2, _("[Training mode]"));
+    boxPrint(win, n++, x + 2, _("   *                Take a random rack"));
+    boxPrint(win, n++, x + 2, _("   +                Complete the current rack 
randomly"));
+    boxPrint(win, n++, x + 2, _("   t, T             Set the rack manually"));
+    boxPrint(win, n++, x + 2, _("   c, C             Compute all the possible 
words"));
+    boxPrint(win, n++, x + 2, _("   r, R             Show/hide search 
results"));
+    boxPrint(win, n++, x + 2, "");
+
+    boxPrint(win, n++, x + 2, _("[Duplicate mode]"));
+    boxPrint(win, n++, x + 2, _("   n, N             Switch to the next human 
player"));
+    boxPrint(win, n++, x + 2, "");
+
+    boxPrint(win, n++, x + 2, _("[Free game mode]"));
+    boxPrint(win, n++, x + 2, _("   p, P             Pass your turn (with or 
without changing letters)"));
+    boxPrint(win, n++, x + 2, "");
+
+    boxPrint(win, n++, x + 2, _("[Miscellaneous]"));
+    boxPrint(win, n++, x + 2, _("   <up>, <down>     Navigate in a box line by 
line"));
+    boxPrint(win, n++, x + 2, _("   <pgup>, <pgdown> Navigate in a box page by 
page"));
+    boxPrint(win, n++, x + 2, _("   Ctrl-l           Refresh the screen"));
+
+    m_boxLinesData = n;
+}
+
+
+void CursesIntf::playWord(WINDOW *win, int y, int x)
+{
+    drawBox(win, y, x, 4, 32, _(" Play a word "));
+    mvwprintw(win, y + 1, x + 2, _("Played word:"));
+    mvwprintw(win, y + 2, x + 2, _("Coordinates:"));
+    wrefresh(win);
+
+    // TRANSLATORS: Align the : when translating "Played word:" and
+    // "Coordinates:". For example:
+    // Pl. word   :
+    // Coordinates:
+    int l1 = strlen(_("Played word:"));
+    int l2 = strlen(_("Coordinates:"));
+    int xOff;
+    if (l1 > l2)
+        xOff = l1 + 3;
+    else
+        xOff = l2 + 3;
+
+    string word, coord;
+    if (readString(win, y + 1, x + xOff, 15, word) &&
+        readString(win, y + 2, x + xOff, 3, coord))
+    {
+        int res = m_game->play(convertToWc(coord), convertToWc(word));
+        if (res)
+        {
+            drawStatus(win, LINES - 1, 0, _("Incorrect or misplaced word"));
+        }
+    }
+    m_state = DEFAULT;
+    clearRect(win, y, x, 4, 32);
+}
+
+
+void CursesIntf::checkWord(WINDOW *win, int y, int x)
+{
+    drawBox(win, y, x, 4, 32, _(" Dictionary "));
+    mvwprintw(win, y + 1, x + 2, _("Enter the word to check:"));
+    wrefresh(win);
+
+    string word;
+    if (readString(win, y + 2, x + 2, 15, word))
+    {
+        int res = Dic_search_word(m_game->getDic(), convertToWc(word).c_str());
+        char s[100];
+        if (res)
+            snprintf(s, 100, _("The word '%s' exists"), word.c_str());
+        else
+            snprintf(s, 100, _("The word '%s' does not exist"), word.c_str());
+        drawStatus(win, LINES - 1, 0, s);
+    }
+    m_state = DEFAULT;
+    clearRect(win, y, x, 4, 32);
+}
+
+
+void CursesIntf::saveGame(WINDOW *win, int y, int x)
+{
+    drawBox(win, y, x, 4, 32, _(" Save the game "));
+    mvwprintw(win, y + 1, x + 2, _("Enter the file name:"));
+    wrefresh(win);
+
+    string filename;
+    if (readString(win, y + 2, x + 2, 28, filename, kFILENAME))
+    {
+        ofstream fout(filename.c_str());
+        char s[100];
+        if (fout.rdstate() == ios::failbit)
+        {
+            snprintf(s, 100, _("Cannot open file %s for writing"),
+                     filename.c_str());
+        }
+        else
+        {
+            m_game->save(fout);
+            fout.close();
+            snprintf(s, 100, _("Game saved in %s"), filename.c_str());
+        }
+        drawStatus(win, LINES - 1, 0, s);
+    }
+    m_state = DEFAULT;
+    clearRect(win, y, x, 4, 32);
+}
+
+
+void CursesIntf::loadGame(WINDOW *win, int y, int x)
+{
+    drawBox(win, y, x, 4, 32, _(" Load a game "));
+    mvwprintw(win, y + 1, x + 2, _("Enter the file name:"));
+    wrefresh(win);
+
+    string filename;
+    if (readString(win, y + 2, x + 2, 28, filename, kFILENAME))
+    {
+        char s[100];
+        FILE *fin;
+        if ((fin = fopen(filename.c_str(), "r")) == NULL)
+        {
+            snprintf(s, 100, _("Cannot open file %s for reading"),
+                     filename.c_str());
+        }
+        else
+        {
+            Game *loaded = Game::load(fin, m_game->getDic());
+            if (loaded == NULL)
+            {
+                snprintf(s, 100, _("Invalid saved game"));
+            }
+            else
+            {
+                snprintf(s, 100, _("Game loaded"));
+                GameFactory::Instance()->releaseGame(*m_game);
+                m_game = loaded;
+            }
+            fclose(fin);
+        }
+        drawStatus(win, LINES - 1, 0, s);
+    }
+    m_state = DEFAULT;
+    clearRect(win, y, x, 4, 32);
+}
+
+
+void CursesIntf::passTurn(WINDOW *win, int y, int x, FreeGame &iGame)
+{
+    drawBox(win, y, x, 4, 32, _(" Pass your turn "));
+    mvwprintw(win, y + 1, x + 2, _("Enter the letters to change:"));
+    wrefresh(win);
+
+    string letters;
+    if (readString(win, y + 2, x + 2, 7, letters))
+    {
+        int res = iGame.pass(convertToWc(letters), m_game->currPlayer());
+        if (res)
+        {
+            drawStatus(win, LINES - 1, 0, _("Cannot pass the turn"));
+        }
+    }
+    m_state = DEFAULT;
+    clearRect(win, y, x, 4, 32);
+}
+
+
+void CursesIntf::setRack(WINDOW *win, int y, int x, Training &iGame)
+{
+    drawBox(win, y, x, 4, 32, _(" Set rack "));
+    mvwprintw(win, y + 1, x + 2, _("Enter the new letters:"));
+    wrefresh(win);
+
+    string letters;
+    if (readString(win, y + 2, x + 2, 7, letters, kJOKER))
+    {
+        iGame.setRackManual(false, convertToWc(letters));
+    }
+    m_state = DEFAULT;
+    clearRect(win, y, x, 4, 32);
+}
+
+
+bool CursesIntf::readString(WINDOW *win, int y, int x, int n, string &oString,
+                            unsigned int flag)
+{
+    int c;
+    wmove(win, y, x);
+    curs_set(1);
+    while ((c = getch()) != 0)
+    {
+        if (c == 0x1b )  // Esc
+        {
+            curs_set(0);
+            return false;
+        }
+        else if (c == KEY_ENTER || c == 0xD)
+        {
+            curs_set(0);
+            return true;
+        }
+        else if (c == 0x0c)  // Ctrl-L
+        {
+//             clear();
+            redraw(win);
+            wmove(win, y, x);
+        }
+        else if (c == KEY_BACKSPACE && oString.size() > 0)
+        {
+            x--;
+            mvwprintw(win, y, x, " ");
+            wmove(win, y, x);
+            oString.erase(oString.size() - 1);
+        }
+        else if (isalnum(c) && oString.size() < (unsigned int)n)
+        {
+            mvwprintw(win, y, x, "%c", c);
+            x++;
+            oString += (char)c;
+        }
+        else
+        {
+            if (flag & kJOKER && c == '?')
+            {
+                mvwprintw(win, y, x, "%c", c);
+                x++;
+                oString += (char)c;
+            }
+            if (flag & kFILENAME)
+            {
+                if (c == '/' || c == '.' || c == '-' || c == '_' || c == ' ')
+                {
+                    mvwprintw(win, y, x, "%c", c);
+                    x++;
+                    oString += (char)c;
+                }
+            }
+        }
+//         else
+//             mvwprintw(win, 0, 0, "%3d", c);
+    }
+    curs_set(0);
+    return 0;
+}
+
+
+int CursesIntf::handleKeyForGame(int iKey, Training &iGame)
+{
+    switch (iKey)
+    {
+        case '*':
+            iGame.setRackRandom(0, false, Game::RACK_ALL);
+            return 1;
+
+        case '+':
+            iGame.setRackRandom(0, false, Game::RACK_NEW);
+            return 1;
+
+        case 't':
+        case 'T':
+            setRack(m_win, 22, 10, iGame);
+            return 1;
+
+        case 'c':
+        case 'C':
+            iGame.search();
+            return 1;
+
+        default:
+            return 2;
+    }
+}
+
+
+int CursesIntf::handleKeyForGame(int iKey, Duplicate &iGame)
+{
+    switch (iKey)
+    {
+        case 'n':
+        case 'N':
+            iGame.nextHumanPlayer();
+            return 1;
+
+        default:
+            return 2;
+    }
+}
+
+
+int CursesIntf::handleKeyForGame(int iKey, FreeGame &iGame)
+{
+    switch (iKey)
+    {
+        case 'p':
+        case 'P':
+            passTurn(m_win, 22, 10, iGame);
+            return 1;
+
+        default:
+            return 2;
+    }
+}
+
+
+int CursesIntf::handleKey(int iKey)
+{
+    if (m_state == DEFAULT)
+    {
+        int res;
+        if (m_game->getMode() == Game::kTRAINING)
+        {
+            res = handleKeyForGame(iKey, (Training&)*m_game);
+        }
+        else if (m_game->getMode() == Game::kDUPLICATE)
+        {
+            res = handleKeyForGame(iKey, (Duplicate&)*m_game);
+        }
+        else
+        {
+            res = handleKeyForGame(iKey, (FreeGame&)*m_game);
+        }
+
+        if (res != 2)
+            return res;
+    }
+    else // m_state is in {HELP, RESULTS, HISTORY}
+    {
+        switch (iKey)
+        {
+            case KEY_HOME:
+                if (m_boxLinesData <= m_boxLines && m_boxStart > 0)
+                    return 0;
+                m_boxStart = 0;
+                return 1;
+            case KEY_END:
+                if (m_boxLinesData <= m_boxLines &&
+                    m_boxStart < m_boxLinesData - 1)
+                    return 0;
+                m_boxStart = m_boxLinesData - 1;
+                return 1;
+            case KEY_UP:
+                if (m_boxLinesData <= m_boxLines || m_boxStart <= 0)
+                    return 0;
+                m_boxStart--;
+                return 1;
+            case KEY_DOWN:
+                if (m_boxLinesData <= m_boxLines ||
+                    m_boxStart >= m_boxLinesData - 1)
+                    return 0;
+                m_boxStart++;
+                return 1;
+            case KEY_PPAGE:
+                if (m_boxLinesData <= m_boxLines)
+                    return 0;
+                m_boxStart -= m_boxLines;
+                if (m_boxStart < 0)
+                    m_boxStart = 0;
+                return 1;
+            case KEY_NPAGE:
+                if (m_boxLinesData <= m_boxLines)
+                    return 0;
+                m_boxStart += m_boxLines;
+                if (m_boxStart > m_boxLinesData - 1)
+                    m_boxStart = m_boxLinesData - 1;
+                return 1;
+        }
+    }
+
+    switch (iKey)
+    {
+        // Toggle help
+        case 'h':
+        case 'H':
+        case '?':
+            if (m_state == HELP)
+                m_state = DEFAULT;
+            else
+                m_state = HELP;
+            m_boxStart = 0;
+            clear();
+            return 1;
+
+        // Toggle history
+        case 'y':
+        case 'Y':
+            if (m_state == HISTORY)
+                m_state = DEFAULT;
+            else
+                m_state = HISTORY;
+            m_boxStart = 0;
+            clear();
+            return 1;
+
+        // Toggle results (training mode only)
+        case 'r':
+        case 'R':
+            if (m_game->getMode() != Game::kTRAINING)
+                return 0;
+            if (m_state == RESULTS)
+                m_state = DEFAULT;
+            else
+                m_state = RESULTS;
+            m_boxStart = 0;
+            clear();
+            return 1;
+
+        // Toggle dots display
+        case 'e':
+        case 'E':
+            m_showDots = !m_showDots;
+            return 1;
+
+        // Check a word in the dictionary
+        case 'd':
+        case 'D':
+            if (m_state != DEFAULT)
+                return 0;
+            checkWord(m_win, 22, 10);
+            return 1;
+
+        // Play a word
+        case 'j':
+        case 'J':
+            if (m_state != DEFAULT)
+                return 0;
+            playWord(m_win, 22, 10);
+            return 1;
+
+        // Ctrl-L should clear and redraw the screen
+        case 0x0c:
+            clear();
+            return 1;
+
+        case 'l':
+        case 'L':
+            if (m_state != DEFAULT)
+                return 0;
+            loadGame(m_win, 22, 10);
+            return 1;
+
+        case 's':
+        case 'S':
+            if (m_state != DEFAULT)
+                return 0;
+            saveGame(m_win, 22, 10);
+            return 1;
+
+        // Quit
+        case 'q':
+        case 'Q':
+        case 0x1b: // Esc
+            m_dying = true;
+            return 0;
+
+        default:
+            return 0;
+    }
+}
+
+
+void CursesIntf::redraw(WINDOW *win)
+{
+    if (m_state == DEFAULT)
+    {
+        drawScoresRacks(win, 3, 54);
+        drawBoard(win, 2, 0);
+    }
+    else if (m_state == RESULTS)
+    {
+        drawResults(win, 3, 54);
+        drawBoard(win, 2, 0);
+    }
+    else if (m_state == HELP)
+    {
+        drawHelp(win, 1, 0);
+    }
+    else if (m_state == HISTORY)
+    {
+        drawHistory(win, 1, 0);
+    }
+
+    // Title
+    attron(A_REVERSE);
+    string mode;
+    if (m_game->getMode() == Game::kTRAINING)
+        mode = _("Training mode");
+    else if (m_game->getMode() == Game::kFREEGAME)
+        mode = _("Free game mode");
+    else if (m_game->getMode() == Game::kDUPLICATE)
+        mode = _("Duplicate mode");
+    string variant = "";
+    if (m_game->getVariant() == Game::kJOKER)
+        variant = string(" - ") + _("Joker game");
+    string title = "Eliot (" + mode + variant + ") " + _("[h for help]");
+    mvwprintw(win, 0, 0, title.c_str());
+    whline(win, ' ', COLS - title.size());
+    attroff(A_REVERSE);
+
+    wrefresh(win);
+}
+
+
+int main(int argc, char ** argv)
+{
+#ifdef HAVE_SETLOCALE
+    // Set locale via LC_ALL
+    setlocale(LC_ALL, "");
+#endif
+
+#if ENABLE_NLS
+    // Set the message domain
+    bindtextdomain(PACKAGE, LOCALEDIR);
+    textdomain(PACKAGE);
+#endif
+
+    srand(time(NULL));
+
+    Game *game = GameFactory::Instance()->createFromCmdLine(argc, argv);
+    if (game == NULL)
+        return 1;
+
+    game->start();
+
+    // Initialize the ncurses library
+    WINDOW *wBoard = initscr();
+    keypad(wBoard, true);
+    // Take input chars one at a time
+    cbreak();
+    // Do not do NL -> NL/CR
+    nonl();
+    // Hide the cursor
+    curs_set(0);
+
+    if (has_colors())
+    {
+        start_color();
+
+        // Simple color assignment
+        init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK);
+        init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK);
+        init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK);
+        init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK);
+
+        init_pair(COLOR_BLUE, COLOR_BLACK, COLOR_BLUE);
+        init_pair(COLOR_CYAN, COLOR_BLACK, COLOR_CYAN);
+        init_pair(COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA);
+        init_pair(COLOR_RED, COLOR_BLACK, COLOR_RED);
+    }
+
+    // Do not echo
+    noecho();
+
+    // mainIntf will take care of destroying game for us.
+    CursesIntf mainIntf(wBoard, *game);
+    mainIntf.redraw(wBoard);
+
+    while (!mainIntf.isDying())
+    {
+        int c = getch();
+        if (mainIntf.handleKey(c) == 1)
+        {
+            mainIntf.redraw(wBoard);
+        }
+    }
+
+    delwin(wBoard);
+
+    // Exit the ncurses library
+    endwin();
+
+    GameFactory::Destroy();
+
+    return 0;
+}




reply via email to

[Prev in Thread] Current Thread [Next in Thread]