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 :
Aujourd'hui, il s'agit d'inverser les rôles, enfaisant deviner à l'ordinateur, la combinaison secrète :
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 :