Vous êtes sur la page 1sur 7

Université de Nice-Sophia

Algorithmique Antipolis
& Programmation Deug MIAS-MI 1
2002–2003

TP N 3

Procédures et fonctions

Buts :
– Manipuler des objets complexes
– Tableaux à deux dimensions
– Usage des sentinelles
– Utilisation des constantes
– Apprendre à modifier un programme

1 Le jeu « Puissance 4 »
Ce jeu se joue à deux joueurs sur une grille rectangulaire supposée verticale. Chacun à
leur tour les joueurs posent un pion en haut d’une colonne non pleine : ce pion glisse vers le bas jusqu’à
être arrêté par un autre pion ou par le bas de la grille. Le gagnant est le premier qui aligne 4 de ses pions,
horizontalement, verticalement ou en diagonale.
Voici un exemple de partie en cours :

1 2 3 4 5 6 7
. . . . . . .
. . . . . . .
. . . . . . .
. . o x o . .
. o x o x . .
x x x o o . .
C’est à X de jouer. Il peut gagner en jouant en colonne 5 mais, s’il ne le voit pas et commet l’erreur
de jouer en colonne 2, c’est O qui pourra gagner en jouant lui aussi en colonne 2 !
Nous avons programmé pour vous un gestionnaire de partie pour ce jeu : un programme qui demande
alternativement à deux joueurs humains 1 quel coup ils jouent, affiche l’état de la partie et détermine qui
gagne. Ce programme, trop long et trop complexe pour que vous l’écriviez vous- même, vous est fourni
dans les Documents de cours et il est reproduit à la fin de cette feuille de TP. Votre premier travail
sera de le comprendre, le second sera d’y faire quelques modifications pour en améliorer la
programmation ou la qualité.

2 Comprendre un programme
2.1 Objets complexes
Les objets définis par la classe instanciable Power4Game sont, comme leur nom l’indique, des
parties en cours de « Puissance 4 ». Le constructeur sera chargé de créer la partie dans son état initial
(personne n’a encore joué) et la classe exportera une seule méthode d’instance, l’ordre de « jouer la
partie » ! Regardez la classe Power4 pour voir comment la classe Power4Game est utilisée et
vérifiez que toutes les autres méthodes de Power4Game sont déclarées private.
1. Programmer un jeu « humain contre ordinateur » n’est pas encore de votre niveau : attendez la fin du semestre !
Les attributs d’une partie en cours seront, très naturellement, l’état de la grille de jeu (les coups déjà
joués), les noms des joueurs (pour l’affichage des dialogues), celui dont c’est le tour de jouer et le
résultat provisoire (la partie est-elle finie? y a-t-il un gagnant ?).

2.2 Codage des joueurs


Comme beaucoup de langages, Java ne dispose pas de type énuméré et, pour traduire une déclaration
comme :
type
joueur = (joueur1, joueur2, personne)
il faudra donner explicitement un codage par l’ordinal de chacune des trois constantes (ici, 0, 1 et 2) et
considérer ce type joueur comme un type entier.

2.3 Tableaux à deux dimensions


La grille de jeu est une matrice rectangulaire qui contient des entiers (ces entiers codent l’état de
chaque case). Représenter une telle matrice ne pose aucun problème, il suffit de la considérer ligne par
ligne : chaque case étant de type int, chaque ligne sera représentée par un tableau de type int[] et,
comme on peut définir des tableaux de n’importe quoi, on dira que la matrice est un tableau de lignes,
donc de type int[][]. La ligne d’indice line sera donc grid[][line] et sa valeur dans la
colonne d’indice column sera simplement grid[column][line].
Les informaticiens n’utilisent pas le terme « matrice » pour désigner une telle structure, ils parlent
plutôt de tableaux à deux dimensions 2.

2.4 Usage des sentinelles


Pour savoir si le coup d’un joueur le fait gagner, il va être nécessaire de se déplacer dans la grille de
jeu et de tester la valeur de chaque case. Ce problème s’apparente à celui de la recherche séquentielle
(chercher une valeur dans une liste ou un tableau) et, comme lui, est compliqué par le fait qu’il ne faut
pas dépasser les bornes. La façon la plus simple de s’en sortir est de placer des sentinelles, valeurs
fictives, juste hors des bornes, pour lesquelles le résultat de la recherche est connu d’avance et qui
permettront de s’arrêter sans avoir à tester qu’on reste dans les bornes.
Ici, on va entourer la grille de jeu de deux lignes et de deux colonnes supplémentaires dans
lesquelles, bien sûr, il sera toujours interdit de jouer ! Ainsi, quand on cherchera des alignements, la
présence de ces cases vides arrêtera la recherche sans que d’autres tests soit nécessaires. Et, évidemment,
on n’affichera pas ces cases fictives quand on affichera la grille de jeu.

2.5 Utilisation des constantes


Java ne connaissant ni les types énumérés ni les intervalles, un très grand nombre des valeurs
manipulées par un programme seront des entiers, soit comme valeur effective, soit comme co- dage, et il
serait très facile de s’y perdre : Qu’est-ce que « 2 » dans un programme ? Le nombre entier par lequel on
multiplie une valeur pour la doubler? un indice dans un tableau ? la troi- sième constante d’un type
énuméré? Et supposons que ce soit le dernier cas et que, mettant le programme au point, on s’aperçoive
qu’il faut modifier le type énuméré et que cette constante devienne la cinquième : comment savoir quels
sont, dans le programme, les « 2 » qu’il faut rem- placer par des « 4 »?
2. Beaucoup de langages de programmation (et notre langage algorithmique) permettent d’utiliser l’écriture équiva- lente
grid[column,line] ; ce n’est pas le cas de Java.
Toutes ces difficultés sont résolues par l’usage systématique de constantes nommées. Le pro- gramme
donné ne lésine pas là-dessus et c’est ainsi qu’il faut procéder 3. Examinons en détail ces déclarations :
– les lignes 7 à 9 codent le type énuméré « joueur » mais aussi les indices du tableau de la ligne 10
qui dit quel caractère doit être affiché selon l’état de la case ;
– comme les lignes 12 et 13 déclarent une trop grande grille (à cause des sentinelles), les lignes 14 et
17 donnent des noms explicites aux indices où commence et finit le plateau de jeu réel ;
– les lignes 19 et 20 précisent que, quand un tableau de deux éléments représente un vecteur, son
premier indice correspond à l’horizontale, son second à la verticale (ces constantes se- ront
utilisées entre les lignes 74 et 83) ;
– les lignes 21 à 24 ne servent qu’à indiquer la signification de la ligne 25, les constantes qu’elles
définissent ne seront plus jamais utilisées ensuite !
– enfin, cette ligne 25 précise les directions dans lesquelles on cherchera des alignements de quatre
pions.
Dans un premier temps, les meilleurs d’entre vous penseront sans doute que ce travail de déclaration
est fastidieux, qu’ils ne sont pas des imbéciles et qu’ils n’ont pas de temps à perdre avec ça. Ce n’est pas
grave, ils changeront d’avis quand ils s’apercevront qu’ils ne sont plus les meilleurs.

2.6 Variables d’instance


La variable impactLine déclarée en ligne 34 joue un rôle un peu particulier : elle ne sert qu’à
mémoriser la ligne d’arrivée d’un pion, déterminée par la procédure play, le temps qu’elle soit utilisée
comme argument de isWinning en ligne 127. Une programmation peu soigneuse pourrait la mettre
avec les attributs (elle se déclare exactement de la même façon) et, bien sûr, ça marcherait. Cependant, il
est difficile de la considérer comme un attribut au sens « objet » du terme, elle n’est qu’un intermédiaire,
non une caractéristique de la partie en cours et, d’ailleurs, on serait bien en peine de la faire initialiser par
le constructeur !
Le mieux est de la considérer comme une sorte de variable locale, tout comme les indices de boucles
ou celles qui servent à échanger deux valeurs. Si l’on tient à la terminologie objet, on pourra
éventuellement parler de variable d’instance.

3 Modifier un programme
À présent que vous avez à peu près compris comment marche ce programme, nous allons voir si vous
êtes capables de lui faire subir quelques améliorations ou modifications mineures. Les exercices proposés
sont tous indépendants et ils doivent être réalisés sans bouleverser le programme de façon importante.
a. Modifiez le programme pour que, quand il affiche l’état de la partie, il dise aussi à qui c’est le tour de
jouer.
b. La convention qui a été choisie est de numéroter les lignes de bas en haut. Ce n’est pas la convention
usuelle en informatique. Modifiez le programme pour qu’elles soient numérotées de haut en bas.
c. Les lignes 98 et 99 sont un peu violentes car elles peuvent interrompre la partie pour une simple faute
de frappe. Modifiez le programme pour que, si un joueur joue dans une colonne pleine, on lui signale son
erreur et on lui demande un autre choix.
d. La méthode playable est un peu lourde puisqu’elle réexamine toutes les têtes de colonne à
chaque coup alors qu’elles ne changent pas souvent. Modifiez le programme pour que la fin de partie par
impossibilité de jouer (partie nulle) soit décelée plus économiquement.
e. (difficile) On change les règles en déclarant qu’un alignement de quatre pions est aussi ga- gnant s’il
est réalisé selon la marche du cavalier aux échecs (c’est-à-dire selon une pente 2, –2, 1/2 ou –1/2).
Modifiez le programme pour tenir compte de cette nouvelle règle. Faites-le de telle sorte qu’on puisse
revenir très facilement aux anciennes règles.
3. Il aurait même été encore plus propre de les déclarer private.
f. (difficile) Il n’est pas facile de repérer les alignements. Modifiez le programme de telle sorte que,
quand un joueur gagne, l’alignement qu’il vient de compléter soit mis en majuscules.
g. (programme d’imitation, facultatif) Le jeu japonais appelé gomoku se joue avec les règles de notre «
morpion » (un joueur a les X, l’autre les O, ils jouent chacun leur tour en posant leur pion sur une
intersection d’un quadrillage, le premier qui aligne cinq a gagné) à la seule différence que la zone de jeu
est un carré .
Dans la variante la plus jouée, appelée gomoku ninuki, une règle de prise est ajoutée : quand un
joueur, au moment où il joue, prend deux pions adverses en tenaille avec un autre de ses pions, ils sont
considérés comme prisonniers et retirés du jeu (par exemple, si X joue sur le point dans
la position X O O . , la situation devient X . . X ; en revanche, O peut sans danger jouer
sur le point dans une position X O . X ). La prise peut être multiple et, comme l’alignement,
elle se fait dans n’importe quelle direction. Le gagnant est le premier qui a aligné cinq ou fait dix
prisonniers. Programmez le jeu de base ou la variante.

La classe instanciable Power4Game

1 import unsa.Console ;
2
3 public class Power4Game {
4
5 //
CONSTANTES
6 // joueurs
7 final static int PLAYER_1 = 0 ;
8 final static int PLAYER_2 = 1 ;
9 final static int EMPTY = 2 ;
10 final static char[] SYMBOL = new char[] { ’ x ’ , ’ o ’ , ’ . ’ } ;
11 // coordonnées
12 final static int GRID_WIDTH = 9 ; // 7 + sentinelles
13 final static int GRID_HEIGHT = 8 ; // 6 + sentinelles
14 final static int FIRST_COL = 1 ;
15 final static int LAST_COL = GRID_WIDTH - 2 ;
16 final static int TOP_LINE = GRID_HEIGHT - 2 ;
17 final static int BOTTOM_LINE = 1 ;
18 // directions
19 final static int H = 0 ; // horizontalement
20 final static int V = 1 ; // verticalement
21 final static int[] HOR = new int[] {1, 0} ; // horizontale
22 final static int[] VER = new int[] {0, 1} ; // verticale
23 final static int[] UP = new int[] {1, 1} ; // diagonale montante
24 final static int[] DOWN = new int[] {1, -1} ; // diagonale descendante
25 final static int[][] DIRECTION = new int[][] {HOR, VER, UP, DOWN} ;
26
27 // Attributs
28 private int[][] grid ; // grille de jeu
29 private String[] name ; // noms des joueurs
30 private int player ; // qui doit jouer
31 private int result ; // vainqueur ou partie nulle (si EMPTY)
32
33 // Variable d’instance
34 private int impactLine ; // ligne atteinte par le dernier pion joué
35
37 public Power4Game (String[] name) { // noms des joueurs
38 // tout mettre à vide, y compris les sentinelles !
39 grid = new int[GRID_WIDTH][GRID_HEIGHT] ;
40 for (int i = 0 ; i < GRID_WIDTH ; i++) {
41 for (int k = 0 ; k < GRID_HEIGHT ; k++)
42 grid[i][k] = EMPTY ;
43 }
44 this.name = name ;
45 player = PLAYER_1 ;
46 result = EMPTY ;
47 }
48
49 // Fonctions
50 private boolean isFree (int column) {
51 return grid[column][TOP_LINE] == EMPTY ;
52 }
53 private boolean playable () {
54 // cherche s’il existe une colonne libre
55 int column = FIRST_COL ;
56 while (column <= LAST_COL) {
57 if (isFree(column))
58 return true ;
59 column++ ;
60 }
61 return false ;
62 }
63 private boolean isWinning (int column, int line) {
64 // cherche s’il existe une direction alignant 4
65 int i = 0 ;
66 while (i < DIRECTION.length) {
67 if (isWinning(column, line, DIRECTION[i]))
68 return true ;
69 i++ ;
70 }
71 return false ;
72 }
73 private boolean isWinning (int column, int line, int[] dir) {
74 int nextColumn = column + dir[H] ; int nextLine = line + ;
dir[V]
75 int forward = 0 ; // nombre de cases du joueur vers l’avant
76 while (grid[nextColumn][nextLine] == player)
77 { nextColumn += dir[H] ; nextLine += dir[V] ; forward+
+ ;
78 }
79 // reculer
80 nextColumn = column - dir[H] ; nextLine = line - dir[V] ;
81 int backward = 0 ; // nombre de cases du joueur vers l’arrière
82 while (grid[nextColumn][nextLine] == player) {
83 nextColumn -= dir[H] ; nextLine -= dir[V] ; backward++ ;
84 }
85 return backward + 1 + forward >= 4 ;
86 }
87
88 // Procédures
89 private void nextPlayer () {
90 // préférer un test clair à une astuce fondée sur les valeurs
91 // arbitraires des constantes comme ‘player = 1 - player‘
92 if (player == PLAYER_1)
93 player = PLAYER_2 ;
94 else
95 player = PLAYER_1 ;
96 }
98 if (!isFree(column))
99 throw new RuntimeException(" colonne p l e i n e " ) ;
100 impactLine = TOP_LINE ; // on sait qu’elle est libre
101 while (impactLine > BOTTOM_LINE && grid[column][impactLine - 1] == EMPTY)
102 impactLine-- ;
103 // on est en bas ou au-dessus d’un pion
104 grid[column][impactLine] = player ;
105 }
106
107 // Affichage
108 private void printBoard () {
109 // numéros des colonnes
110 for (int column = FIRST_COL ; column <= LAST_COL ; column++)
111 System.out.print(" " + column + " ") ;
112 System.out.println() ;
113 // état de la partie
114 for (int line = TOP_LINE ; line >= BOTTOM_LINE ; line--) {
115 for (int column = FIRST_COL ; column <= LAST_COL ; column++)
116 System.out.print(" " + SYMBOL[grid[column][line]] + " ") ;
117 System.out.println() ;
118 }
119 }
120
121 // Jeu d’une partie
122 public void playGame () {
123 while (result == EMPTY && playable()) {
124 printBoard() ;
125 int column = Console.readInt(name[player] + " joue en colonne ?") ;
126 play(column) ; // la variable ‘impactLine‘ a reçu une valeur
127 if (isWinning(column, impactLine))
128 result = player ;
129 else
130 nextPlayer() ;
131 }
132 printBoard() ;
133 if (result == EMPTY)
134 System.out.println(" p a r t i e n u l l e ") ;
135 else
136 System.out.println(name[result] + " gagne" ) ;
137 }
138 }

La classe exécutable Power4

public class Power4 {


1
2 public static void main (String[ ] args) {
3 // les noms des joueurs sont donnés en arguments d’exécution
4 Power4Game game = new Power4Game(args) ; game.playGame() ;
5 }
}
6
7
8

Vous aimerez peut-être aussi