• Aucun résultat trouvé

Zig et Puce

N/A
N/A
Protected

Academic year: 2022

Partager "Zig et Puce"

Copied!
8
0
0

Texte intégral

(1)

Zig et Puce

( par Claudio Baiocchi. Email : claudio.baiocchi-p314@poste.it ) Résumé

Suivant Diophante, Problème H118, on s’intéresse au problème :

Le résultat k (1, 2, 3, 4, 5 ou 6) du lancer d’un dé supposé parfait est écrit au tableau.

Zig et Puce chacun à son tour écrivent sur une même ligne un nombre choisi parmi l’ensemble des 24 premiers nombres naturels 1 à 24, distinct de tous ceux qui ont déjà été écrits, y compris k, tel que la somme des deux derniers nombres inscrits est un carré parfait. La partie s’arrête quand l’un des deux joueurs est bloqué faute d’obtenir un carré ou parce que les 24 entiers ont tous été écrits. Le gagnant est donc celui ou celle qui écrit le dernier nombre. Zig commence. Quelle est la probabilité de gain de Puce ?

Pour ce problème on va proposer un traitement informatique. N’importe quel langage qui admet la récurrence est suffisant pour transformer l’ordinateur en un redoutable adversaire ; par ailleurs l’approche récursive qu’on va exploiter se révèle à la fois plus simple et plus efficace que toute approche de type “force brute”.

Un joueur stupide

Parmi les langages de programmation qui permettent de construire un adversaire informatique le choix s'est porté sur JavaScript car les programmes écrits dans ce langage sont1 universels : il devraient marcher partout, peu importe si on travaille sous Windows, Mac, Linux... En fait les programmes, qu’on peut copier-coller chacun dans son répertoire, doivent être enregistrés avec extension .htm (ou .html) et ce sera le navigateur qui va se charger de l’exécution2 . On commence par un programme de jeux très peu performant mais, en revanche, simple à décrire.

Les nombres précédés par deux barres obliques // à la fin de quelques unes des lignes du programme, renvoient à la fin du texte.

Le Programme stupide

<html><head><script language=javascript> // 1

var free=new Array(25), last, next, History, dice, newGame=true ; // 2

function allowed(){ // 3 if ( next<1 || next>24 ) return false;

if ( !free[next] ) return false;

for( var x=2; x<7; x++ ) { if (last+next == x*x) return true };

return false }

function execute(){ free[next]=false; last=next } // 4

function check_end(){ var diff; // 5 for(var x=2; x<7; x++){

diff=x*x-last; if ( diff>0 && diff<25 && free[diff] ){

next=diff; return false // 6 } };

return true

(2)

}

function ComputerPlays(){ // 7 History+=next+'; '; execute();

if( !check_end() ) return true;

alert(History+'\nVous avez perdu !');

return false }

function input(){ var ok=false,pre='',print='Votre choix ?',you; // 8 while( !ok ){

you=parseInt(prompt(History+pre+print,next));

pre='Non ! '; ok=allowed();

}; next=you }

function HumanPlays(){ // 9 input(); History+=next+'; '; execute();

if( !check_end() ) return true alert(History+'\nBravo !');

return false }

function game(){ var again=true;

while ( again ) {

again = HumanPlays(); if ( again ) again = ComputerPlays(); // 10 } }

while( newGame ){ // 11 dice=Math.floor(1+6*Math.random());

for( var x=1; x<25; x++ ) { free[x]=true };

free[dice]=false; last=dice;

History='Random='+dice+'; ';

check_end(); game();

newGame=confirm('Une autre partie ?');

}

</script></head><body></body></html>

Commentaires

Le programme s’identifie avec le joueur Puce donc, après avoir tiré au sort le numéro initial, il laisse à l’adversaire humain le choix du coup suivant. Pour ce qui concerne les détails du programme, l’action des lignes se terminant avec //n (n de 1 à 11) est détaillée par :

1. Ici on ouvre la boite pour le programme JavaScript. Elle est fermée dans la toute dernière ligne.

2. Déclaration des variables globales, dont la valeur est connue dans tout le programme : a. la variable free est un vecteur. Pour tout x entre 1 et 24, la valeur free[x] est

true si le nombre x n’a pas encore été affiché, et false sinon.

b. la variable last est le dernier nombre affiché, next est le prochain.

c. la variable History (attention aux majuscules !) est la liste des nombres affichés.

d. la variable newGame vaut true si il faut commencer une nouvelle partie.

3. Ici on contrôle si la valeur de next peut être affichée.

4. Lorsque next est joué, sa valeur devient inutilisable et est transférée dans last.

(3)

5. S’il n’y a plus d’éléments affichables, il faut arrêter la partie. On remarquera que la stratégie suivie choisit la voie la plus courte : en fait. . .

6. On sort du cycle dès qu’un nombre affichable a été trouvé ; une stratégie meilleure mais plus lourde devrait construire toute la liste des de choix permis : à la fois pour la soumettre à l’adversaire et pour pouvoir choisir sa propre stratégie de jeu.

7. Ici l’ordinateur joue la valeur de next choisie au point 5 et évalue une valeur (false ou true) qui dit si la partie est finie.

8. Ici on nous demande notre choix. . .

9. Qu’on accepte seulement si permis par les règles ; encore, on contrôle la fin de la partie.

10. Tant que la partie peut continuer on alterne les joueurs.

11. Le vrai départ : on lance le dé, on invite l’adversaire à choisir, on alterne les adversaires jusqu’à la fin de la partie . On recommence et on continue tant que le joueur en a assez.

Un joueur moins stupide

Programmer l’ordinateur pour en faire un bon adversaire dans un jeu à deux personnes est en général une tache difficile ; et d’ailleurs lui apprendre une stratégie est possible uniquement si on sait déjà jouer...

Dans notre jeu (et en général dans tout jeu qu’on commence à apprendre) au contraire, on connaît uniquement les règles du jeu pour effectuer les coups admis et refuser les coups non admis; et le but du jeu qui permet de contrôler si la partie est terminée. En d’autre mots : on vient de construire un <<arbitre-exécuteur>> qui sait effectuer les coups admis (les fonction allowed() et execute() ) et un <<juge>> qui sait établir si la partie continue ou non (la

fonction check_end() ).

On va maintenant essayer une opération de corruption, en forçant arbitre et juge à nous aider.

Chaque coup sera soumis à l’arbitre en le priant, si le coup est admis, de se borner à simuler le résultat3 ; ensuite on va annoter l’avis du juge (qui, lui aussi, ne devra pas, le cas échéant, interrompre la partie) ; finalement on annule le coup. A la fin de la boucle, si parmi les coups admis il existe des coups gagnants, c’est parmi ces coups que l’ordinateur choisira.

C’est une corruption qui n’est pas si <<immorale>> : par comparaison au jeu des échecs, on n’est pas en train de enseigner l’importance de la reine, ni celle de la conquête du centre ; on dit seulement que, s’il existe un coup “échec et mat”, c’est ce coup qu’il faut choisir ! On ne va pas détailler le programme correspondant (on veut faire beaucoup mieux !) et on se borne à expliciter un exemple du gain fourni par cette stratégie : si le dé a donné 2 et si (tout bêtement) Zig a choisi 7, le juge corrompu dira à Puce que, parmi 9 et 18 (les deux choix admis) le choix 9 est pire que le choix 18, car choisissant 18 on gagne immédiatement la partie.

Une solution par récurrence

Revenons à notre adversaire débutant dans le jeu d’échecs, dans le stade où il vient juste d’apprendre à chercher l’éventuel mat en un coup. Lorsqu’il n’y a pas de tels coups, avant de chercher s’il existe des “mats en deux”, il peut se demander s’il existe des coups à éviter car dangereux ; voire qui permettraient à l’adversaire de donner mat en un. C’est presque

(4)

nécessaire, car l’arborescence du jeu croît très rapidement, et il est bon d’éliminer dès que possible l’étude de variantes inutiles.

Est-ce que un programme peut faire ça aisément ? Rien de plus facile. Une boucle suffit : on simule chacun de nos coups admis, et on fait semblant que c’est notre adversaire qui a corrompu le juge... Si le juge lui dit qu’il peut gagner, notre coup doit être écarté.

On remarquera que le juge corrompu (par nous) est en fait un juge plus savant que celui de départ, car il voit la fin de la partie un coup à l’avance . Dans cette nouvelle version, la corruption donne lieu à un juge encore plus savant : il voit la fin de la partie deux coups4 à l’avance. On va maintenant procéder par récurrence pour construire des juges de plus en plus savants.

Le Programme récursif

<html><head><script language=javascript>

var free=new Array(36), last, next, History, newGame=true ;

function allowed(choice) {

if ( !free[choice] ) return false;

for( var x=2; x<7; x++ ) { if(last+choice == x*x) return true };

return false }

function execute(c){ free[next]=false; last=next; History+=c+last+';' }

function input(){ var ok=false, pre='', print='Votre choix ?', you;

while( !ok ){

you=parseInt(prompt(History+pre+print,next));

pre='Non ! '; ok=allowed(you);

};

return you }

function Zig() { next=input(); execute('Z') }

function what_if(choice) { var max=-1, diff ; free[choice]=false;

for(var x=2; x<7; x++) { diff=Math.max(x*x-choice);

if(free[diff]) max=Math.max(max,-what_if(diff));

};

free[choice]=true;

return max }

function AnyonePlays(){ var max=-2, diff, best next=0;

for(var x=2; x<7; x++) { diff=Math.max(x*x-last);

if(free[diff]) { free[diff]=false;

best=-what_if(diff);

free[diff]=true;

if(best>max){ max=best; next=diff } } };

return next!=0 }

(5)

function Puce(){ execute('P') }

function game(){ var again=true;

while ( again ) {

again = AnyonePlays();

if(!again) return History+' je gagne!';

Zig();

again = (AnyonePlays());

if(next==0) return History+' bravo!';

Puce() } }

while( newGame ){

for( var x=0; x<36; x++ ) { free[x]=(x>0 && x<25) };

last=Math.floor(1+6*Math.random());

History='Random = '+last+';';

if(what_if(last)==1)alert(History+'vous gagnerez!');

free[last]=false; alert(game());

document.write(History+'<br>');

newGame=confirm('Une autre partie ?') }

</script></head><body></body></html>

Commentaires

On va se borner à quelques remarques.

La fonction AnyonePlays() remplace les deux ComputerPlays() et HumanPlays() ; on reviendra sur elle pour l’étudier plus de près .Pour le moment on remarque qu’elle suggère à n’importe quel joueur le meilleur choix ; suggestion que Zig, à travers la fonction Zig(), peut refuser, tandis que Puce (qui s’identifie avec l’ordinateur) accepte automatiquement à travers la function Puce().

Les autres variantes étant essentiellement cosmétiques, on va examiner en détail le coeur du programme, à savoir le couple de fonctions what_if() et AnyonePlays().

Dans cette dernière fonction, comme on avait déjà dit, on fait une boucle sur les choix admis.

Toute valeur admise diff est “jouée” par la commande free[diff]=false (plus tard le coup sera annulé par free[diff]=true). Ensuite c’est le tour de notre adversaire, et on fait appel à un juge (plus ou moins corrompu) qui fournit la valeur 1 lorsque qui doit jouer peut gagner et -1 sinon. La valeur ainsi obtenue est changée de signe, car cette valeur concerne notre ennemi ; donc la valeur 1, sa victoire, est la défaite pour nous ; tandis que la valeur -1, sa défaite, est notre victoire.

Si la valeur qu’on vient d’obtenir est meilleure que max (le mieux jusqu’ici) on met à jour max et on mémorise dans la variable next le choix diff qui l’a engendrée. La valeur finale de la fonction AnyonePlays() est établie dans la dernière ligne et vaut true si un choix admis a été trouvé, ou bien faux si la valeur de next est restée 0, comme on l’avait fixée au départ. Ceci, en particulier, permet de supprimer le vieux contrôle check_end().

Pour ce qui concerne la fonction what_if(), la structure est presque identique : le choix choice proposée par AnyonePlays() est “jouée” par la commande free[choice]=false (plus tard le coup sera annulé par free[choice]=true).

(6)

Maintenant la récurrence entre en jeu : pour tout choix admis diff, on fait appel au “juge plus savant”.

Un dernier mot sur la commande: max=Math.max(max,-what_if(diff)) qui signifie que, par analogie à ce qui se passait dans la fonction AnyonePlays(), la valeur de max est

remplacée par le maximum entre max et l’opposée de la valeur du choix diff.

Remarques finales

En jouant beaucoup de parties contre l’ordinateur et en étudiant les suggestions du

programme, on s’aperçoit que Puce n’a pas d’espoir si Zig joue suivant la stratégie5 : à tout x répondre avec 25-x.

Voici les séquences obtenues pour les 6 valeurs du lancer du dé et qui aboutissent toutes au gain de Zig:

Random = 1

Z24;P12;Z13;P3;Z22;P14;Z11;P5;Z20;P16;Z9;P7;Z18;

Random = 2

Z23;P13;Z12;P4;Z21;P15;Z10;P6;Z19;P17;Z8;P1;Z24;

Random = 3

Z22;P14;Z11;P5;Z20;P16;Z9;P7;Z18;

Random =4

Z21;P15;Z10;P6;Z19;P17;Z8;P1;Z24;P12;Z13;P3;Z22;P14;Z11;P5;Z20;P16;Z9;P7;Z18;

Random = 5

Z20;P16;Z9;P7;Z18;

Random = 6

Z19;P17;Z8;P1;Z24;P12;Z13;P3;Z22;P14;Z11;P5;Z20;P16;Z9;P7;Z18;

On remarque que le dernier programme peut être amélioré de plusieurs façons . Par exemple, lorsque la victoire est assurée, on pourrait chercher le chemin le plus court pour gagner . Plus important encore : quand c’est l’adversaire qui peut gagner, on devrait chercher le chemin le plus long6 .

Une variante plus intéressante est la programmation de la version « Misère » du jeu selon laquelle le perdant est le dernier à pouvoir inscrire un nombre dont la somme avec le

(7)

précédent est un carré. Aucune stratégie ne semble évidente a priori alors qu’avec la version

de base, une personne intuitive peut trouver d’emblée la bonne stratégie : « à tout x, jouer 25 – x ». Il y a très peu de modifications à apporter au programme. Mis à part la nécessité d’échanger les commentaires bravo et je gagne dans la fonction game, on doit simplement forcer la fonction what_if() à donner résultat +1 (au lieu que -1) lorsque, quand un

joueur vient d’afficher choice, aucune valeur affichable ne reste à l’adversaire.

Pour cela il suffit de choisir -2 comme valeur de départ pour max ; à la fin, si max vaut encore -2, on lui assigne la valeur +1 :

function what_if(choice) { var max=-2, diff ; free[choice]=false;

for(var x=2; x<7; x++) { diff=Math.max(x*x-choice);

if(free[diff]) max=Math.max(max,-what_if(diff));

};

free[choice]=true;

if(max==-2) max=1 return max

}

On constate que dans cette variante « Misère » du jeu, Puce a la possibilité de gagner si le lancer du dé donne le nombre 5. Dans les autres cas, c’est encore Zig le gagnant comme le montre le tableau des séquences de jeux:

Random = 1

Z8;P17;Z19;P6;Z10;P15;Z21;P4;Z5;P11;Z14;P2;Z7;P9;Z16;P20;

Random = 2

Z23;P13;Z3;P1;Z8;P17;Z19;P6;Z10;P15;Z21;P4;Z5;P11;Z14;P22;

Random =3

(8)

Z22;P14;Z11;P5;Z20;P16;Z9;P7;Z2;P23;Z13;P12;Z24;P1;Z8;P17;Z19;P6;Z10;P15;Z21;P4;

Random = 4

Z5;P11;Z14;P2;Z7;P9;Z16;P20;

Random = 5

Z4;P21;Z15;P10;Z6;P19;Z17;P8;Z1;P3;Z13;P12;Z24;

Random = 6

Z3;P1;Z24;P12;Z13;P23;Z2;P7;Z9;P16;Z20;P5;Z4;P21;Z15;P10;

On termine en remarquant que la technique utilisée a une validité presque

universelle . Lorsqu’on a un juge pour un jeu donné, on peut augmenter l’efficacité du jugement par un procédé récursif. Naturellement il faut introduire un critère d’arrêt pour que dans tous les cas la routine récursive se termine7 .

_________________________________________________________________________

1théoriquement ! Les deux programmes présentés ont été testés sous Windows et marchent bien avec Firefox ; sous IExplorer on a remarqué quelques dysfonctionnements.

2bienentendu : pourvu qu’on ait autorisé l’exécution des scripts. . .

3au sens que, d’ici peu, on lui demandera d’annuler les effets du coup

4dans le jeu d’échec on parlerait respectivement d ' un semi-coup et de deux semi-coups

5toujour applicable si employée dès le départ

6comme ça, on va “prolonger l’agonie”, mais ce n’est pas pour un instinct sadique : il faut laisser à l’adversaires plus de possibilités de se tromper !

7ici la chose est assurée par le fait que, après au plus 23 choix, il n’existe plus de nombres à afficher

Références

Documents relatifs

E 1 Puce reçoit une première liste de 2019 entiers compris entre 0 et n qu’il doit recopier sur une même colonne puis en face de chaque entier il doit écrire sur une deuxième

Chacun à tour de rôle prend une carte présente sur la table et la garde en main.. Celui dont le produit est le plus proche de

Il y a 70 façons de répartir les huit nombres en deux ensembles, dont on peut déterminer lequel a un produit plus proche de 300.. A partir de cela, on peut élaborer une

= 40320 et la cible C est 300; on est donc dans le deuxième cas et (pour chacun des deux joueurs) la stratégie consiste à essayer de garder son propre produit suffisamment petit

Si Zig commence (ce qui semble conforme à l'énoncé), il ne peut pas être assuré que Puce n'aura plus, au sixième coup, d'autre choix que l'un des chiffres bleus, car il ne peut faire

Comme il y a un nombre impair de cases impaires, on pourrait croire a priori que Zig, intervenant le premier et remplissant une case vide de plus que Puce qui est de surcroit

Zig dispose de 20 € en euros et joue à la roulette.Celle-ci dispose de cases numérotées de 0 à 36 alternativement rouges et noires, à l'exception du zéro, vert. Zig rentre alors

Zig commence la partie en retirant un pion noir de son choix puis il fait glisser un pion blanc sur la case adjacente vide qui partage un côté commun avec la case de départ.. Puis