INFORMATIQUE

TP 8

Réciproque du jeu de George Noël


1. Objectif

Lorsque nous avons réalisé, la semaine dernière, le jeu de George Noël, c'est l'ordinateur qui avait le beau rôle. En effet, c'est lui qui faisait chercher le joueur :

2. Mise en place des composants

La fiche principale, que l'on peut nommer "fmNoel" ne contient que 4 composants :

Les quatre composants sont :

 

Enregistrer tout dans un nouveau dossier nommé "Réciproque George Noel" (unit 1 = uGeorgeNoel ; Project1 = GeorgeNoel)

 

3. Déclarations des variables globales

Nous aurons besoin de quelques variables qu'il faut déclarer dans le paragraphe Var, juste avant le paragraphe Implementation :

var
   fmNoel : TflNoel;
   Nombre, NombreDepart : integer;
   n : byte;

Implementation

La variable fmNoel correspond à la fiche : c'est Delphi lui-même qui la déclare.
La variable Nombre correspond au nombre à 5 chiffres que l'ordinateur va proposer.
La variable NombreDepart est le premier des codes à 5 chiffres proposés par l'ordinateur. Ce nombre servira de repère au cas où nous nous tromperions dans nos réponses et que l'ordinateur tournerait en rond : au bout d'un certain temps, il aura essayé tous les codes possibles et retombera sur le code du début. Il saura alors qu'il y avait forcément une erreur dans les réponses du joueur et s'arrêtera.
La variable n correspond au numéro de l'essai.

4. Gestion de l'affichage des données dans la grille

Comme d'habitude, il nous faut améliorer la présentation de l'affichage dans la grille. La colonne de gauche contiendra les n° des essais. Les 5 colonnes suivantes contiendront les 5 chiffres de chacun des essais de l'ordinateur. Les 2 dernières colonnes serviront à saisir les réponses.

Voici le code, toujours un peu difficile à comprendre, qui permet de bien centrer les données au milieu de chacune des cellules (choisir, dans les événements de la grille, l'événement OnDrawCell) :

procedure TfmNoel.GrilleDrawCell(Sender: TObject; ACol, ARow: Integer;
                   Rect: TRect; State: TGridDrawState);
var ch : string;
begin
   with grille.canvas do
   if (ARow=0)or(Acol=0) then
   begin
      brush.style:=bsSolid;
      brush.color:=clSilver;
      rectangle(Rect.left,rect.top,rect.right,rect.bottom);
      brush.style:=bsClear;
      Font.style:=[fsBold];
      if ARow=0 then font.size:=12 else font.size:=8;
      ch:=Grille.cells[ACol,ARow];
      if ACol<6 then TextOut((Rect.Left+Rect.Right-TextWidth(ch))div 2,
                             (Rect.Top+Rect.Bottom-TextHeight(ch))div 2,ch);
      if ACol=6 then
      begin
         TextOut((Rect.Left+Rect.Right-TextWidth('Trop'))div 2,4,'Trop');
         TextOut((Rect.Left+Rect.Right-TextWidth('petits'))div 2,22,'petits');
      end;
      if ACol=7 then
      begin
         TextOut((Rect.Left+Rect.Right-TextWidth('Trop'))div 2,4,'Trop');
         TextOut((Rect.Left+Rect.Right-TextWidth('grands'))div 2,22,'grands');
      end;
   end
   else
   begin
      ch:=Grille.cells[ACol,ARow];
      brush.style:=bsSolid;
      brush.color:=clWindow;
      rectangle(Rect.left-1,rect.top-1,rect.right+1,rect.bottom+1);
      brush.style:=bsClear;
      Font.style:=[fsBold];
      TextOut((Rect.Left+Rect.Right-TextWidth(ch))div 2,
              (Rect.Top+Rect.Bottom-TextHeight(ch))div 2,ch);
   end;
end;

5. Initialisation au lancement de l'application

Comme d'habitude, il faut commencer par initialiser un certain nombre de choses (événement OnActivate de la fiche fmNoel) :

procedure TfmNoel.FormActivate(Sender: TObject);
var k : integer;
begin
   Grille.RowHeights[0]:=50;
   for k:=65 to 69 do grille.cells[k-64,0]:=chr(k);
   for k:=1 to 14 do grille.cells[0,k]:=inttostr(k);
end;

6. Décomposition d'un nombre en 5 chiffres

Il s'agit de permettre à l'ordinateur de considérer que le code est un nombre compris entre 00000 et 99999 et de pouvoir décomposer ce nombre en 5 morceaux pour l'afficher dans 5 cellules différentes. Nous allons pour cela définir une procédure interne (non liée à un événement mais appelée par d'autres procédures) :

procedure RemplirEssai;
var k : byte;
essai : integer;
begin
   Essai:=Nombre;
   for k:=5 downto 1 do
   begin
      fmNoel.grille.cells[k,n]:=IntToStr(essai mod 10);
      essai:=essai div 10;
   end;
end;

Laméthode consiste à décomposer le nombre de droite à gauce, d'où l'explication du début de la boucle : For k:= 5 downto 1 do

Le nombre entier "essai" va être successivement dépouillé de ses 5 chiffres en commençant par le chiffre des unités. Pour connaître le chiffre des unités du nombre "essai", il suffit de calculer essai mod 10. L'opérateur mod donne le reste de la division. Ainsi, si on divise un nombre par 10 et si on regarde le reste, on obtient bien le chiffre des unités du nombre. Ensuite, le nombre "essai" est tronqué en le divisant par 10 (opérateur div). La boucle se répète 5 fois pour découper les 5 chiffres...

7. Lancement d'une partie

En réponse à un clic sur le bouton btDebut, il s'agit de faire proposer un premier essai à l'ordinateur et d'initialiser les données de la partie :

procedure TfmNoel.btDebutClick(Sender: TObject);
var k : integer;
begin
   for k:=0 to 7 do Grille.Cols[k].clear;
   for k:=65 to 69 do grille.cells[k-64,0]:=chr(k);
   for k:=1 to 14 do grille.cells[0,k]:=inttostr(k);
   Randomize;
   NombreDepart:=random(100000);
   Nombre:=NombreDepart;
   n:=1;
   RemplirEssai;
   grille.row:=n;grille.col:=6;
   Grille.setFocus;
end;

8. Blocage de la saisie dans les colonnes 6 et 7 de la grille

Le joueur ne devra donner ses réponses que sur la dernière ligne où l'ordinateur a fait son essai. Pour éviter tout déplacement intempestif dans la grille et empêcher la saisie n'importe où, voici le code qu'on peut proposer en réponse à l'événement OnClick de la grille (cet événement ne produit chaque fois qu'on change de cellule) :

procedure TfmNoel.GrilleClick(Sender: TObject);
begin
   if (grille.row>n)or(grille.row<n) then grille.row:=n;
   if grille.col<6 then grille.col:=6;
end;

La première ligne indique qu'il est impossible de se déplacer dans la grille en dehors de la ligne n.
La seconde ligne indique qu'il est impossible de se déplacer à gauche de la colonne 6.

9. Calcul des réponses de l'ordinateur

C'est évidemment là le nœud du problème. L'algorithme utilisé est rudimentaire mais terriblement efficace : l'ordinateur recherche tous les nombres possibles, un à un, jusqu'à ce qu'il en trouve un qui corresponde aux réponses faites aux essais précédents (compatible avec les essais précédents). Lorsqu'il arrive à 99999, il passe à 00000 et continue sa recherche jusqu'à ce qu'un nouvel essai soit compatible ... ou que tous les essais potentiels aient été tentés : dans ce cas, c'est le joueur que nous sommes qui a indiqué une ou plusieurs mauvaises réponses !

Voici ce code décomposé en plusieurs procédures ou fonctions. Seule la dernière procédure correspond à une réponse à un événement (événement OnClick du bouton btOK) :

function TropPetits(fleche,cible:integer):string;
var k,tp : byte;
begin
   tp:=0;with fmNoel.grille do
   for k:=1 to 5 do
      if StrToInt(cells[k,fleche])<StrToInt(cells[k,cible]) then inc(tp);
   result:=IntToStr(tp);
end;

function TropGrands(fleche,cible:integer):string;
var k,tg : byte;
begin
   tg:=0;with fmNoel.grille do
   for k:=1 to 5 do
      if StrToInt(cells[k,fleche])>StrToInt(cells[k,cible]) then inc(tg);
   result:=IntToStr(tg);
end;

function Compatible:boolean;
var k : byte;
begin
   result:=true;
   for k:=1 to n-1 do
      result:=result and (TropPetits(k,n)=fmNoel.grille.cells[6,k])
      and (TropGrands(k,n)=fmNoel.Grille.cells[7,k]);
end;


procedure TfmNoel.btOKClick(Sender: TObject);
begin
   if StrToInt(grille.cells[6,n])+StrToInt(grille.cells[7,n])>0 then
   begin
      inc(n);
      repeat
         Nombre:=(Nombre+1) mod 100000;
         RemplirEssai;
      until Compatible or (Nombre=NombreDepart);
      if Nombre=NombreDepart then
      begin
         ShowMessage('Erreur dans les réponses !');
      end;
   end
   else ShowMessage('Ouf !');
   grille.setfocus;grille.col:=6;grille.row:=n;
end;

10. Améliorations

De nombreuses améliorations sont possibles. Voici quelques pistes :

<< TP précédent      TP suivant >>

Retour à la liste des TP