Téléchargez New Entropy ! | |
Téléchargez le source ! |
1. MOTEUR DU JEU
1.1. Définition des principaux Types
La première étape dans l'élaboration de New Entropy fut la définition des principaux Types nécessaires. Le jeu se base en effet sur une grille, des pions et des joueurs qui ne sont pas des Types simples en Pascal. En voici donc une rapide description :
1.1.1. Tpion
Ce type, comme son nom l'indique, défini les pions utilisés dans le jeu New Entropy.
Les valeurs possibles sont : Vide, Rouge, Vert, Orange, Bleu, Jaune, Blanc et Noir. Il s'agit de toutes les couleurs données dans le sujet plus la valeur Vide qui correspond à l'absence de pion.
1.1.2. Tgrille
Il s'agit de la définition du tablier. C'est un tableau de pions de 7 cases sur 7. Les cases inoccupées sont à la valeur 'Vide'.
1.1.3. Tgenre
Ce type permet de différencier les joueurs 'humain' des joueurs 'ordinateurs. Les valeurs possibles sont donc : Humain ou Ordinateur.
1.1.4. Tjoueur
C'est un enregistrement (record) qui contient le nom du joueur, son genre (humain/ordinateur) et son score.
1.1.5. Tposition
Enfin, le type Tposition simplifie notamment le passage de quelques
paramètres car il définit un couple de coordonnées
de la grille.
1.2. Score d'une grille
La première routine que j'ai développée pour New Entropy fut le calcul du score d'une grille. C'est en effet une routine importante dans New Entropy car elle est nécessaire pour déterminer le gagnant d'une partie et je savais déjà qu'elle serait nécessaire à l'élaboration d'une intelligence artificielle efficace.
C'est la fonction contenue dans le fichier 'score.inc' :
function score(Vgrille:Tgrille; IA:boolean):word;
Les paramètres d'entrée sont évidemment une grille, et un booléen dont j'expliquerai plus loin l'intérêt.
La sortie est un mot. Ainsi, le score maximum que peut renvoyer la fonction est 216-1soit 65535 ce qui est amplement suffisant pour une grille de New Entropy (nous avons cherché les meilleures grilles possibles et notre record plafonne actuellement à 30000 points !).
Le calcul est effectué comme défini dans le sujet. Je vais donc détailler le calcul d'une ligne (c'est la même chose pour les colonnes).
La première boucle est un 'for...do' qui porte sur la longueur des combinaisons à tester : 2,3,4,5,6 ou 7. Ensuite, une seconde boucle permet d'envisager toutes les symétries de la longueur en cours sur la ligne. Par exemple il y a 6 symétries possibles de longueur 2 tandis qu'il n'y en a qu'une seule de longueur 7. C'est de nouveau un 'for...do' qui va de 1 à 8 moins la longueur en cours. En effet pour les symétries de longueur 2 on va bien de 1 à 6 et pour la symétrie de longueur 7, de 1 à 1 !
Enfin, pour tester la validité de la symétrie, 2 indices sont initialisés aux extrémités de la combinaison en cours. Une boucle les fait progresser en sens inverse tant que les pions pointés sont les mêmes sans êtres vides. Si les indices se croisent, c'est que la combinaison est entièrement symétrique. Il est intéressant de remarquer que cette technique valide une combinaison de longueur impaire même si le pion central est absent (Vide). Ceci est juste car quelle que soit la valeur prise plus tard par ce pion, la combinaison sera validée. Ainsi, la combinaison : Vert/Bleu/Vide/Bleu/Vert compte pour 5 points.
Le score est directement donné par la valeur de la longueur en
cours.
1.3. Recherche des attaques valides
Lors d'une partie de New Entropy, tester la validité d'une défense est immédiat : il suffit que la case soit vide. Pour une attaque, c'est un petit peu plus long. J'utilise donc une variable globale :
attaque:record
nombre:byte;
coup:array [1..13] of record
position:Tposition;
score:word;
end;
end;
Cette variable au moment de l'attaque, le nombres de coups possibles dans la variable nombre (maximum 13) et les positions correspondantes dans l'enregistrement du tableau 'coup'. Pour remplir à chaque tour cet enregistrement, j'utilise une routine contenue dans le fichier 'attaques.inc' :
procedure attaques(debut:Tposition);
Cette procédure compte le nombre d'attaques possibles sur la grille globale à partir de la position 'debut' et trouve les positions correspondantes. Ces informations sont renvoyées dans la variable globale 'attaque' définie plus haut. Seul le champ 'score' n'est pas rempli car il n'est utilisé que par l'intelligence artificielle (voir plus loin).
Trouver toutes les attaques possibles n'est pas bien difficile. La première
attaque évidente est de rester à la position 'debut'. Ensuite,
4 boucles effectuent les tests de déplacement dans les 4 directions
jusqu'à buter sur un pion déjà posé sur la
grille ou arriver à la limite du tablier.
1.4. Gestion d'une manche
Une partie se divise en 2 manches où les joueurs sont respectivement attaquant et défenseur. Il est donc intéressant d'utiliser une routine pour une manche :
function manche(var attaquant,defenseur:Tjoueur):boolean;
Les paramètres d'entrées sont deux joueurs (attaquant/défenseur) et la sortie de cette fonction est un booléen. Ce dernier est 'Vrai' si la manche s'est terminée ou 'Faux' si l'un des joueurs a abandonné (voir plus loin).
Une manche se décompose en 49 tours de jeu. C'est sur ce chiffre que porte la boucle de la fonction.
A chaque tour de jeu, un pion est pioché aléatoirement à l'aide de la fonction :
function tablier:Tpion;
Le défenseur s'il est 'humain' est invité à choisir une case vide de la grille :
procedure choisir(var position:Tposition);
Les attaques sont calculées grâce à la procédure 'attaques' puis l'attaquant 'humain' peut joueur en choisissant à son tour une case jusqu'à ce que celle-ci fasse partie des attaques possibles.
Enfin, en fin de manche, l'attaquant marque ses points.
1.5. Gestion d'une partie
Maintenant que la manche est implémentée, la partie elle-même est très facile à gérer :
procedure jouer;
Ce sous programme n'a plus qu'à gérer les deux manches en tenant compte des éventuels abandons et à afficher les résultats :
procedure resultats;
1.6. Adversaire ordinateur aléatoire
Comme le sujet invite à le faire, j'ai d'abord réalisé un adversaire ordinateur qui joue aléatoirement. Les routines nécessaires sont contenues dans le fichier 'RND.inc' :
procedure RND_attaque(var hasard:Tposition);
procedure RND_defense(var hasard:Tposition);
Ces procédures renvoient dans le paramètre hasard une position valide.
Pour la défense, il suffit de trouver aléatoirement une
case vide et pour la l'attaque de piocher dans la variable globale 'attaque'
un coup.
2. AMELIORATIONS
2.1. Intelligence Artificielle
Après quelques parties contre l'adversaire ordinateur aléatoire, on parvient facilement à gagner la partie. Il devient donc évident que l'élaboration d'un adversaire plus puissant est importante pour la 'durée de vie' de New Entropy !
Les routines nécessaires de l'intelligence artificielle sont contenues dans le fichier 'IA.inc'.
2.1.1. Attaque
L'attaque intelligente n'est pas trop difficile à réaliser car on comprend immédiatement le but de celle-ci : marquer le plus de points !
procedure IA_attaque(piece:Tpion; var meilleur:Tposition; IAide:boolean);
Cette procédure attend en entrée la pièce posée et renvoie la meilleure attaque trouvée sur la grille en utilisant la variable globale 'attaque' contenant toutes les attaques possibles.
Pour trouver le meilleur coup, la procédure joue tous les coups de la variable globale 'attaque', calcule le score de la grille obtenue et place ce dernier dans le champ 'score'.
C'est ici qu'est utilisé le paramètre booléen IA de la fonction 'score' : si celui-ci est 'Vrai', le score des lignes et des colonnes est incrémenté de façon à ne jamais annuler le produit.
Ceci évite des erreurs en début de partie : l'ordinateur ne fait plus d'alignements évidents.
Enfin, un coup est pioché aléatoirement parmi les meilleurs coups (ceux qui ont le score maximum).
Le paramètre d'entrée booléen IAide est une option amusante. L'intelligence artificielle est mise à profit du joueur 'humain' : les meilleurs coups sont présentés à l'utilisateur pour qu'il effectue sont choix en conséquence. Bien entendu, ceci est une option qui est paramétrable (voir le menu plus loin). C'est en plus un bon moyen pour apprendre à jouer à New Entropy.
2.1.2. Défense
La défense est plus dure mais aussi plus intéressante à programmer. En effet, on ne peut pas calculer la qualité d'une défense par le simple placement sur la grille. Il faut envisager toutes les contre-attaques possibles !
procedure IA_defense(piece:Tpion; var meilleur:Tposition; IAide:boolean);
Cette procédure attend également en entrée la pièce piochée et renvoie en sortie la meilleure défense trouvée sur la grille globale.
Afin de réaliser ceci, la procédure teste toutes les défenses possibles. Pour chacune de ces défenses, la meilleure contre-attaque possible est cherchée en utilisant la procédure IA_Attaque ! Enfin, la meilleure défense est choisie aléatoirement parmi les plus mauvaises contre-attaques (score minimum).
Cette technique est particulièrement efficace car même si le joueur humain joue parfaitement bien, il ne marquera que très peu de points.
Tout comme la procédure d'attaque, le paramètre d'entrée
booléen IAide permet au joueur de visualiser les meilleurs coups.
2.2. Graphisme
Le graphisme est une partie importante à mon goût dans l'amélioration d'un jeu. Elle donne plus envie à l'utilisateur de jouer.
J'ai opté pour un mode graphique différent de celui vu au cours de l'année. En effet, celui que nous avons étudié offre une résolution de 640x480 en 16 couleurs. Le mode graphique '13h' que j'ai choisi permet quant à lui de travailler en 320x200 mais avec 256 couleurs ! Les dégradés obtenus sont bien plus beaux et la mémoire requise est moins importante.
Il est ainsi possible de travailler sur des 'pages virtuelles'. On remarque en effet que 320x200<216 et il est ainsi possible de copier tout un écran en mémoire, un peu comme les procédures getimage et putimage de l'unité 'graph' sauf que ces dernières ne permettent pas de sauver un écran complet.
Ces pages virtuelles ont de nombreux avantages :
- On peut construire tout l'écran dans une page virtuelle et ne l'afficher que lorsque tout est près.
- On évite les clignotements lors de l'affichage.
- Il est possible de garder dans les pages des décors.
- Les copies d'écran sont rapides.
L'unité graphique 'Mode13.pas' contient toutes les routines nécessaires à l'utilisation de ce mode graphique.
L'unité 'PCX.pas' permet de charger des images au format PCX
dans les pages virtuelles. Ceci simplifie le travail du graphisme car un
logiciel quelconque de dessin permet de sauver vers ce format.
Exemple du graphisme sous New Entropy.
2.3. Souris
La souris est un périphérique très important dans New Entropy. Je l'ai d'ailleurs mise en place dès le début pour faciliter la programmation et les tests sur le jeu. Il est en effet très fastidieux de saisir les couples de coordonnées au clavier...
L'unité Sourunit.pas s'occupe de la gestion de la souris dans
le mode graphique 13h.
2.4. Abandon
C'est une des dernières améliorations que j'ai apportée à New Entropy. L'abandon est géré par une fonction du même nom qui affiche une boite de dialogue invitant celui-ci à abandonner. Pour accéder à cette boite de dialogue, il suffit de cliquer sur le bouton droit lors de la saisie d'une case (dans la procédure choisir vue plus haut).
Boite de dialogue d'abandon dans le style Microsoft® Windows
2.5 Menu
Enfin, j'ai également mis en place un menu de sélection. Il permet de régler le genre (humain/ordinateur) et le nom de joueurs, le niveau de l'intelligence artificielle en attaque et défense (facile/dur), l'option d'AIDE (oui/non), de consulter les informations (règles du jeu), de jouer et ... de quitter !
Le texte change de couleur lorsque la souris passe dessus afin de bien choisir l'option désirée.