Quelques raccourcis :
Ceci est un petit site de support pour le cours 420-SF2-RE – Structures de données et programmation orientée objet.
Vous trouverez aussi des liens sur divers langages (dont C#, notre outil de prédilection dans ce cours) un peu partout dans https://h-deb.ca/
Les diverses sections de cette page (en fonction desquelles vous trouverez quelques liens dans l'encadré à droite) vous mèneront elles-aussi sur des pistes qui vous permettront d'explorer un peu plus par vous-mêmes, de valider vos acquis et d'enrichir votre apprentissage
![]() |
Cliquez sur cette cible pour le plan de cours, sous forme électronique |
![]() |
Je corrige les programmes en appliquant des codes de correction. Vous trouverez ici la liste des codes les plus fréquents. |
![]() |
Ma stratégie de correction en tant que telle (pour le code, à tout le moins) est résumée ici. |
![]() |
Cliquez sur cette cible pour un résumé des principales règles de programmatique en vigueur dans ce cours. |
![]() |
Cliquez sur cette cible pour les normes appliquées dans ce cours, en ce qui a trait au pseudocode |
| Date | Séance | Détails | ||||
|---|---|---|---|---|---|---|
|
22 janvier |
Au menu :
Notre exercice fut : Écrivez un programme console C# qui :
Ensuite, dessinez à l'écran le rectangle correspondant Nous en avons profité pour faire une brève révision de 420SF1, puis pour apporter des ajustements menant à un glissement vers la matière au menu de notre cours. Notre version initiale fut : Nous avons ensuite introduit l'idée d'une classe Rectangle, pour que le code qui dessine des rectangles ait bel et bien... des rectangles! Le code du programme principal résultant fut : ... et celui de la classe Rectangle en tant que telle fut : Notez qu'en ce premier cours, nous investissons nos efforts sur deux aspects, soit :
Ça fait un gros cours pour démarrer la session, mais nous réinvestirons le tout cours après cours, alors ça va entrer doucement dans notre tête, et s'intégrer à notre praxis émergente 🙂.
En fait, notre Triangle n'était pas centré lors de l'affichage, mais considérez cet ajout comme un bonbon! À titre de « petit bonbon », nous avons vu en début de séance que les fonctions qui se limitent à un seul return peuvent être écrites de manière simplifiée, et nous avons appris que votre chic prof, souhaitant vous encourager à écrire de courtes fonctions qui font une et une seule chose (et le font bien!), acceptera cette syntaxe si elle est bien utilisée. Ainsi, ceci : ... peut s'écrire de manière équivalente sous la forme suivante : ... alors que ceci : ... pourra s'écrire comme suit D'autres simplifications d'écriture viendront plus tard dans la session. Quelques nouveaux termes de vocabulaire utilisés aujourd'hui : classe, instance, attribut d'instance, propriété d'instance (avec volets get, set et init), constructeur par défaut, constructeur paramétrique, qualifications d'accès private et public (il y en a d'autres), encapsulation (à peine), membre d'instance (non-static), membre de classe : static, invariant, précondition et postcondition... Ouf! |
|||||
|
26 janvier |
Au menu :
À titre de simplification (et de bonbon de fin de séance), j'ai aussi montré :
Quelques nouveaux termes de vocabulaire utilisés aujourd'hui : invariant, précondition et postcondition Une solution possible est disponible ici. Vous remarquerez que les mutateurs (les volets set et init des propriétés) sont exprimés un peu différemment sur cet exemple que ce que nous avons fait en classe, mais je vous explique ce que ça signifie dès la séance S02. |
|||||
|
29 janvier |
Au menu :
À titre bonbons aujourd'hui, j'ai aussi montré :
Quelques trucs pour vous aider... Si vous cherchez un exemple simpliste de programme de test interactif, vous pouvez utiliser celui-ci (vous pouvez aussi vous en écrire un plus à votre goût; je ne ramasserai que la classe Crypteur après tout) : ... notez que je suppose que vous pouvez écrire la fonction Poursuivre puisque nous l'avons fait à quelques reprises cet automne. Si vous souhaitez transformer une string en char[], examinez les méthodes d'instance de cette string (portez attention en particulier à celles dont le nom commence par To...). Si vous souhaitez transformer une string en majuscules, examinez les méthodes d'instance de cette string (portez attention en particulier à celles dont le nom commence par To...). Si vous souhaitez transformer un char[] en string, sachez que string expose un constructeur paramétrique acceptant un char[] en paramètre. Par exemple, comparez les deux exemples ci-dessous (l'un des deux est plus pertinent que l'autre pour vos fins) :
Si vous souhaitez modifier un char, alors... que diriez-vous de la simple arithmétique? Par exemple : Quelques trucs pour vous aider... Si vous cherchez un exemple simpliste de programme de test interactif, vous pouvez utiliser celui-ci (vous pouvez aussi vous en écrire un plus à votre goût; je ne ramasserai que la classe Crypteur après tout) : ... notez que je suppose que vous pouvez écrire la fonction Poursuivre puisque nous l'avons fait à quelques reprises cet automne. Si vous souhaitez transformer une string en char[], examinez les méthodes d'instance de cette string (portez attention en particulier à celles dont le nom commence par To...). Si vous souhaitez transformer une string en majuscules, examinez les méthodes d'instance de cette string (portez attention en particulier à celles dont le nom commence par To...). Si vous souhaitez transformer un char[] en string, sachez que string expose un constructeur paramétrique acceptant un char[] en paramètre. Par exemple, comparez les deux exemples ci-dessous (l'un des deux est plus pertinent que l'autre pour vos fins) :
Si vous souhaitez modifier un char, alors... que diriez-vous de la simple arithmétique? Par exemple : |
|||||
|
2 février |
Au menu :
|
|||||
|
5 février |
Au menu :
|
|||||
|
9 février |
Au menu :
|
|||||
|
12 février |
Au menu :
|
|||||
|
16 février |
Au menu :
|
|||||
|
19 février |
Au menu :
|
|||||
|
23 février |
Au menu :
|
|||||
|
26 février |
Au menu :
|
|||||
|
2 mars |
Jour de mise à niveau (cours suspendus) |
|||||
|
5 mars |
Jour de mise à niveau (cours suspendus) |
|||||
|
9 mars |
Au menu :
|
|||||
|
12 mars |
Au menu :
|
|||||
|
16 mars |
Au menu :
|
|||||
|
19 mars |
Au menu :
|
|||||
|
23 mars |
Au menu :
|
|||||
|
26 mars |
Au menu :
|
|||||
|
30 mars |
Au menu :
|
|||||
|
2 avril |
Au menu :
|
|||||
|
6 avril |
Jour férié (lundi de Pâques) |
|||||
|
9 avril |
Au menu :
|
|||||
|
13 avril |
Au menu :
|
|||||
|
16 avril |
Au menu :
|
|||||
|
20 avril |
Au menu :
|
|||||
|
23 avril |
Au menu :
|
|||||
|
27 avril |
Au menu :
|
|||||
|
30 avril |
Au menu :
|
|||||
|
4 mai |
Au menu :
|
|||||
|
7 mai |
s/o |
Journée d'examen de français / formation générale (cours suspendus) |
||||
|
11 mai |
Au menu :
|
|||||
|
14 mai |
Au menu :
|
|||||
|
18 mai |
s/o |
Jour férié (Journée nationale des Patriotes) |
||||
|
19 mai |
Au menu :
Attention : mardi selon l'horaire du lundi |
Vous trouverez ici quelques documents, la plupart petits, qui peuvent vous donner un petit coup de pouce occasionnel.
Vous trouverez aussi des exemples de code C# dans la section Divers – C# du site, mais notez que je n'ai pas nécessairement harmonisé ces exemples (écrits pour des cours plus avancés, sous forme de survols) aux standards de programmation appliqués dans le présent cours. À lire avec prudence et discrimination, donc.
Si vous cherchez les énoncés des travaux pratiques, vous pourrez entre autres les trouver ci-dessous (note : ce tableau est celui de H2021; je ne l'ai pas mis à jour pour H2024).
| Travail | Nom | Rôle (probable) |
|---|---|---|
Labo 00 |
À venir |
Travail sur l'encapsulation et la conception de classes |
Labo 01 |
À venir |
Travail sur l'héritage et le polymorphisme |
Labo 02 |
À venir |
Travail sur les interfaces, l'encapsulation et le code générique |
Labo 03 |
À venir |
Travail sur les dictionnaires, les fonctions génériques et les entrées / sorties |
|
Production finale d'intégration |
À venir |
Quelques solutionnaires suivent. En espérant que ça vous aide à organiser vos idées!
L'exercice 1a de la séance S04 porte sur une classe Point générale dont chaque instance représente un point dans n'importe quel quadrant du plan cartésien. Un programme de test pour cet exercice suit.
//---------------------------------------------------------
// Programme de test de la classe Point
//
// Ce programme vérifie que la classe Point fait
// correctement son travail
//
// par Pierre Prud'homme, 2013 (retouché par Patrice Roy, 2020 puis 2023)
//---------------------------------------------------------
using System;
// test du constructeur par défaut
Point pDéfaut = new ();
AfficherPoint("Test du constructeur par défaut", pDéfaut);
AfficherSéparateur();
// test du constructeur paramétrique
Point pQuadrant1 = new (1.1f, 1.1f);
Point pQuadrant2 = new (-2.2f, 2.2f);
Point pQuadrant3 = new (-3.3f, -3.3f);
Point pQuadrant4 = new (4.4f, -4.4f);
AfficherPoint("Test du constructeur paramétrique pour Q1", pQuadrant1);
AfficherSéparateur();
AfficherPoint("Test du constructeur paramétrique pour Q2", pQuadrant2);
AfficherSéparateur();
AfficherPoint("Test du constructeur paramétrique pour Q3", pQuadrant3);
AfficherSéparateur();
AfficherPoint("Test du constructeur paramétrique pour Q4", pQuadrant4);
AfficherSéparateur();
// test du mutateur de x et de y
pQuadrant1.X = 10.10f;
pQuadrant1.Y = 10.10f;
AfficherPoint("Test du mutateur de Q1", pQuadrant1);
AfficherSéparateur();
TesterDistance();
// --------------------------
// fin du programme principal
// --------------------------
static bool AssezProches(float f0, float f1) => Math.Abs(f0 - f1) <= Math.Pow(10, -6);
static void TesterDistanceEx(Point p0, Point p1, float attendu)
{
float dist = p0.Distance(p1);
if (AssezProches(dist, attendu))
{
Console.WriteLine($"Distance entre [{p0.X},{p0.Y}] et [{p1.X},{p1.Y}] environ {attendu} comme prévu");
}
else
{
Console.WriteLine($"ERREUR : distance entre [{p0.X},{p0.Y}] et [{p1.X},{p1.Y}] == {dist}, mais {attendu} est attendu");
}
}
static void TesterDistance()
{
TesterDistanceEx(new Point(0,0), new Point(0,0), 0);
TesterDistanceEx(new Point(0,0), new Point(1,0), 1);
TesterDistanceEx(new Point(0,0), new Point(0,1), 1);
TesterDistanceEx(new Point(0,0), new Point(1,1), (float) Math.Sqrt(2));
}
static void AfficherPoint(string message, Point p)
{
Console.WriteLine($"{message} : le point vaut [{p.X}, {p.Y}]");
}
static string CréerSéparateur(int nbCar) => new string('-', nbCar);
static void AfficherSéparateur()
{
const int NB_CAR_LIGNE = 72;
Console.WriteLine($"\n{CréerSéparateur(NB_CAR_LIGNE)}\n");
}L'exercice 1b de la séance S04 porte sur une classe PointQuadrant1 générale dont chaque instance représente un point situé dans le premier quadrant du plan cartésien. Un programme de test pour cet exercice suit.
//---------------------------------------------------------
// Programme de test de la classe PointQuadrant1
//
// Ce programme vérifie que la classe Point fait
// correctement son travail
//
// par Pierre Prud'homme, 2013 (retouché par Patrice Roy, 2020 puis 2023)
//---------------------------------------------------------
using System;
TesterConstructeurParamétrique();
TesterMutateurs();
// --------------------------
// fin du programme principal
// --------------------------
static void AfficherPoint(string message, PointQuadrant1 p)
{
Console.WriteLine($"{message} :");
Console.WriteLine($"Le point vaut [{p.X}, {p.Y}]");
}
static string CréerSéparateur(int nbCar) => new string('-', nbCar);
static void AfficherSéparateur()
{
const int NB_CAR_LIGNE = 72;
Console.WriteLine($"\n{CréerSéparateur(NB_CAR_LIGNE)}\n");
}
static void TesterConstructeurParamétrique()
{
// tests du constructeur paramétrique
TesterPoint(1.1f, 1.1f);
TesterPoint(-2.2f, 2.2f);
TesterPoint(-3.3f, -3.3f);
TesterPoint(4.4f, -4.4f);
}
static void TesterPoint(float x, float y)
{
Console.WriteLine($"Point reçu : [{x}, {y}]");
try
{
PointQuadrant1 p = new (x, y);
AfficherPoint("Test réussi du constructeur paramétrique pour PointQuadrant1", p);
}
catch (CoordonnéeXInvalideException)
{
Console.WriteLine("Point invalide en x lors de la construction");
}
catch (CoordonnéeYInvalideException)
{
Console.WriteLine("Point invalide en y lors de la construction");
}
AfficherSéparateur();
}
static void TesterMutateurs()
{
PointQuadrant1 p = new (0, 0);
ModifierPoint(p, 11.0f, p.Y);
ModifierPoint(p, p.X, 11.0f);
ModifierPoint(p, 111.0f, 111.0f);
ModifierPoint(p, -22.2f, p.Y);
ModifierPoint(p, p.X, -22.2f);
ModifierPoint(p, -222.0f, -222.0f);
}
static void ModifierPoint(PointQuadrant1 p, float x, float y)
{
Console.WriteLine($"Modification du point : [{x}, {y}]");
try
{
p.X = x;
p.Y = y;
Console.WriteLine("Test réussi de la mutation du point");
}
catch (CoordonnéeXInvalideException)
{
Console.WriteLine("Point invalide en x lors de la modification");
}
catch (CoordonnéeYInvalideException)
{
Console.WriteLine("Point invalide en y lors de la modification");
}
AfficherPoint("Après mutation ", p);
AfficherSéparateur();
}Le code vu en classe suit.
Notez que cette classe est en évolution alors que la session progresse, et que nous la séparerons éventuellement en plusieurs classes.
using System;
namespace z
{
class Algos
{
public static bool EstEntreInclusif(int val, int min, int max) =>
min <= val && val <= max;
static char[] voyelles = { 'a', 'e', 'i', 'o', 'u', 'y' };
//
// Ceci est un cas raisonnable de double point de sortie pour une fonction :
// sortir dès qu'on a trouvé la réponse permet d'économiser du temps, peut-être
// même _beaucoup_ de temps
//
public static bool EstDans(char c, char [] cars)
{
foreach(char ch in cars)
if(ch == c)
return true;
return false;
}
public static bool ContientSeulementConsonnnes(string s)
{
foreach(char c in s)
if(!EstConsonne(c))
return false;
return true;
}
public static bool EstVoyelle(char c) => EstDans(char.ToLower(c), voyelles);
public static bool EstConsonne(char c) => char.IsAsciiLetter(c) && !EstVoyelle(c);
}
}using System;
namespace z
{
class ForceInvalideException : Exception { }
class NomInvalideException : Exception { }
class Personnage
{
public int Vie { get; private set; }
public bool EstMort => Vie <= 0;
public bool EstVivant => !EstMort;
public string Nom { get; private init; }
public int Force { get; private init; }
public Personnage(string nom, int vie, int force)
{
Nom = nom;
Vie = vie;
Force = force;
}
}
}using System;
namespace z
{
class Héros : Personnage
{
public Héros(string nom, int force)
: base(nom, GénérerVieInitiale(), ValiderForce(force))
{
}
static int GénérerVieInitiale()
{
const int VIE_MIN = 50,
VIE_MAX = 100;
return new Random().Next(VIE_MIN, VIE_MAX + 1); // bof
}
const int FORCE_MIN = 10,
FORCE_MAX = 20;
static bool EstForceValide(int candidate) =>
Algos.EstEntreInclusif(candidate, FORCE_MIN, FORCE_MAX);
static int ValiderForce(int candidate) =>
EstForceValide(candidate) ? candidate : throw new ForceInvalideException();
}
}using System;
namespace z
{
class Monstre : Personnage
{
public Monstre(string nom, int vie, int force)
: base(ValiderNom(nom), vie, ValiderForce(force))
{
}
const int LG_MAX_NOM = 5;
static bool EstNomValide(string candidat) =>
candidat.Length <= LG_MAX_NOM &&
Algos.ContientSeulementConsonnes(candidat);
static string ValiderNom(string candidat) =>
EstNomValide(candidat) ? candidat : throw new NomInvalideException();
const int FORCE_MIN = 15,
FORCE_MAX = 25;
static bool EstForceValide(int candidate) =>
Algos.EstEntreInclusif(candidate, FORCE_MIN, FORCE_MAX);
static int ValiderForce(int candidate) =>
EstForceValide(candidate) ? candidate : throw new ForceInvalideException();
}
}Le code vu en classe suit.
Notez que cette classe est en évolution alors que la session progresse, et que nous la séparerons éventuellement en plusieurs classes. Depuis la version précédente, rien n'a changé.
using System;
namespace z
{
class Algos
{
public static bool EstEntreInclusif(int val, int min, int max) =>
min <= val && val <= max;
static char[] voyelles = { 'a', 'e', 'i', 'o', 'u', 'y' };
//
// Ceci est un cas raisonnable de double point de sortie pour une fonction :
// sortir dès qu'on a trouvé la réponse permet d'économiser du temps, peut-être
// même _beaucoup_ de temps
//
public static bool EstDans(char c, char [] cars)
{
foreach(char ch in cars)
if(ch == c)
return true;
return false;
}
public static bool ContientSeulementConsonnnes(string s)
{
foreach(char c in s)
if(!EstConsonne(c))
return false;
return true;
}
public static bool EstVoyelle(char c) => EstDans(char.ToLower(c), voyelles);
public static bool EstConsonne(char c) => char.IsAsciiLetter(c) && !EstVoyelle(c);
}
}Depuis la version précédente, nous avons ajouté la méthode Frapper (notez la qualification protected). Nous avons choisi d'offrir une fonction générale à deux paramètres qui pourra être appelée par d'autres de manière à préserver le côté privé du mutateur de Vie.
using System;
namespace z
{
class ForceInvalideException : Exception { }
class NomInvalideException : Exception { }
class Personnage
{
public int Vie { get; private set; }
public bool EstMort => Vie <= 0;
public bool EstVivant => !EstMort;
public string Nom { get; private init; }
public int Force { get; private init; }
public Personnage(string nom, int vie, int force)
{
Nom = nom;
Vie = vie;
Force = force;
}
protected void Frapper(Personnage autre, int dégâts)
{
autre.Vie -= dégâts;
}
}
}Depuis la version précédente, nous avons ajouté la méthode Frapper. Nous avons choisi d'offrir une fonction spécialisée à un paramètre qui déléguera le travail plus général vers la méthode à deux paramètres du parent.
using System;
namespace z
{
class Héros : Personnage
{
public Héros(string nom, int force)
: base(nom, GénérerVieInitiale(), ValiderForce(force))
{
}
static int GénérerVieInitiale()
{
const int VIE_MIN = 50,
VIE_MAX = 100;
return new Random().Next(VIE_MIN, VIE_MAX + 1); // bof
}
const int FORCE_MIN = 10,
FORCE_MAX = 20;
static bool EstForceValide(int candidate) =>
Algos.EstEntreInclusif(candidate, FORCE_MIN, FORCE_MAX);
static int ValiderForce(int candidate) =>
EstForceValide(candidate) ? candidate : throw new ForceInvalideException();
public void Frapper(Personnage autre)
{
const int PCT_MIN = 50,
PCT_MAX = 100;
int dégâts = Force * new Random().Next(PCT_MIN, PCT_MAX + 1) / 100;
Frapper(autre, dégâts);
}
}
}Depuis la version précédente, nous avons ajouté la méthode Frapper. Nous avons choisi d'offrir une fonction spécialisée à un paramètre qui déléguera le travail plus général vers la méthode à deux paramètres du parent.
using System;
namespace z
{
class Monstre : Personnage
{
public Monstre(string nom, int vie, int force)
: base(ValiderNom(nom), vie, ValiderForce(force))
{
}
const int LG_MAX_NOM = 5;
static bool EstNomValide(string candidat) =>
candidat.Length <= LG_MAX_NOM &&
Algos.ContientSeulementConsonnes(candidat);
static string ValiderNom(string candidat) =>
EstNomValide(candidat) ? candidat : throw new NomInvalideException();
const int FORCE_MIN = 15,
FORCE_MAX = 25;
static bool EstForceValide(int candidate) =>
Algos.EstEntreInclusif(candidate, FORCE_MIN, FORCE_MAX);
static int ValiderForce(int candidate) =>
EstForceValide(candidate) ? candidate : throw new ForceInvalideException();
public void Frapper(Personnage autre)
{
const int PCT_MIN = 50,
PCT_MAX = 75;
int dégâts = Force * new Random().Next(PCT_MIN, PCT_MAX + 1) / 100;
Frapper(autre, dégâts);
}
}
}Le code de notre programme de test suit.
using System;
Héros héros = new ("Adam", 18);
Monstre monstre = new ("GRR", 75, 20);
Tour àQui = ChoisirProtagoniste();
while(héros.EstVivant && monstre.EstVivant)
{
Console.WriteLine($"Avant le choc, {héros.Nom} a {héros.Vie} vie");
Console.WriteLine($"Avant le choc, {monstre.Nom} a {monstre.Vie} vie");
if(àQui == Tour.héros)
{
Console.WriteLine($"{héros.Nom} frappe {monstre.Nom}");
héros.Frapper(monstre);
àQui = Tour.monstre;
}
else
{
Console.WriteLine($"{monstre.Nom} frappe {héros.Nom}");
monstre.Frapper(héros);
àQui = Tour.héros;
}
Console.WriteLine($"Après le choc, {héros.Nom} a {héros.Vie} vie");
Console.WriteLine($"Après le choc, {monstre.Nom} a {monstre.Vie} vie");
Console.WriteLine(new string('-', 60));
}
if (héros.EstMort)
Console.WriteLine($"{héros.Nom} est mort... snif!");
if (monstre.EstMort)
Console.WriteLine($"{monstre.Nom} est mort... ouf!");
//////////////////////////
static Tour ChoisirProtagoniste() =>
new Random().Next() % 2 == 0? Tour.héros : Tour.monstre;
enum Tour { héros, monstre }Le code vu en classe suit.
using System;
namespace S04ex04
{
class Point
{
int x;
int y;
static bool EstXValide(int x) => x >= 0;
static bool EstYValide(int y) => y >= 0;
public int X
{
get => x;
set
{
x = EstXValide(value) ? value : throw new ArgumentException();
}
}
public int Y
{
get => y;
set
{
y = EstYValide(value) ? value : throw new ArgumentException();
}
}
public Point(int x, int y)
{
X = x;
Y = y;
}
public Point() : this(0,0)
{
}
}
}using System;
namespace S04ex04
{
class Carré
{
public Point Position
{
get; private init;
}
public int Côté
{
get; private init;
}
public ConsoleColor Couleur
{
get; private init;
}
public Carré(Point pos, int côté, ConsoleColor couleur)
{
Position = pos;
Côté = côté;
Couleur = couleur;
}
public void Dessiner()
{
ConsoleColor avant = Console.ForegroundColor;
Console.ForegroundColor = Couleur;
for (int ligne = 0; ligne != Côté; ++ligne)
{
for(int col = 0; col != Côté; ++col)
{
Console.SetCursorPosition(Position.X + col, Position.Y + ligne);
Console.Write('#');
}
}
Console.ForegroundColor = avant;
}
}
}using System;
Carré[] carrés = new Carré[]
{
new Carré(new Point(2, 5), 5, ConsoleColor.Blue),
new Carré(new Point(15, 8), 3, ConsoleColor.Green),
new Carré(new Point(7, 12), 6, ConsoleColor.Yellow)
};
Random r = new ();
for(int i = 0; i != 10; ++i)
{
Console.Clear();
foreach (Carré c in carrés)
c.Dessiner();
System.Threading.Thread.Sleep(1000);
foreach (Carré c in carrés)
{
try
{
c.Position.X += r.Next(0, 5) - 2;
c.Position.Y += r.Next(0, 5) - 2;
}
catch(ArgumentException)
{
}
}
}Nous avons fait trois petites structures de données dans ce cours, soit :
Le code de la classe suit :
using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace z
{
class Tableau
{
int[] Éléments { get; set; }
public int Count { get; private set; } = 0;
public int Capacity => Éléments.Length;
public bool EstVide => Count == 0;
public bool EstPlein => Count == Capacity;
public Tableau()
{
Éléments = new int[0]; // ou Éléments = null
}
public void Add(int valeur)
{
if (EstPlein)
Croître();
Éléments[Count] = valeur;
++Count;
}
private void Croître()
{
int nouvelleCap = Capacity == 0? 16 : Capacity * 2;
int[] nouvTab = new int[nouvelleCap];
for (int i = 0; i != Count; ++i)
nouvTab[i] = Éléments[i];
Éléments = nouvTab;
}
public int Find(int valeur)
{
for (int i = 0; i != Count; ++i)
if (valeur.Equals(Éléments[i]))
return i;
return -1;
}
public void RemoveAt(int indice)
{
if(indice >= Count)
throw new IndexOutOfRangeException();
for (; indice < Count - 1; ++indice)
Éléments[indice] = Éléments[indice + 1];
--Count;
}
public void Remove(int valeur)
{
int indice = Find(valeur);
if(indice != -1)
RemoveAt(indice);
}
public int At(int indice)
{
return Éléments[indice];
}
}
}Le code de la classe suit (je ne répète pas la classe Tableau par souci d'économie) :
class PileVideException : Exception { }
class Pile
{
Tableau Substrat { get; init; }
public Pile()
{
Substrat = new();
}
public bool EstVide { get => Substrat.EstVide; }
public void Push(int valeur) // en français : Empiler
{
Substrat.Add(valeur);
}
public int Peek()
{
if (EstVide)
throw new PileVideException();
return Substrat.At(Substrat.Count - 1);
}
public int Pop()
{
int valeur = Peek();
Substrat.RemoveAt(Substrat.Count - 1);
return valeur;
}
}Le code de la classe suit :
class PileVideException : Exception { }
class Pile
{
class Noeud
{
public int Valeur { get; init; }
public Noeud Prédécesseur { get; set; }
public Noeud(int valeur)
{
Valeur = valeur;
Prédécesseur = null;
}
}
Noeud Tête { get; set; }
public Pile()
{
Tête = null;
}
public bool EstVide { get => Tête == null; }
public void Push(int valeur) // en français : Empiler
{
Noeud p = new(valeur);
p.Prédécesseur = Tête;
Tête = p;
}
public int Peek()
{
if (EstVide)
throw new PileVideException();
return Tête.Valeur;
}
public int Pop()
{
int valeur = Peek();
Tête = Tête.Prédécesseur;
return valeur;
}
}Un exemple de code implémentant une solution à ce problème suit.
namespace z
{
internal static class Algos
{
static char[] voyelles = { 'a', 'e', 'i', 'o', 'u', 'y' };
static bool EstDans(char c, char[] vals)
{
foreach(char ch in vals)
if (ch == c)
return true;
return false;
}
public static bool EstSeulementConsonnes(string s)
{
foreach(char c in s)
if(!EstConsonne(c))
return false;
return true;
}
public static bool EstVoyelle(char c) =>
EstDans(char.ToLower(c), voyelles);
public static bool EstConsonne(char c) =>
char.IsAsciiLetter(c) && !EstVoyelle(c);
public static bool EstEntreInclusif(int val, int min, int max) =>
min <= val && val <= max;
}
}namespace ActivitéS13
{
internal class Arme
{
public string Nom { get; init; }
public int Effet { get; init; }
public Arme(string nom, int effet)
{
Nom = nom;
Effet = effet;
}
public int AppliquerEffet(int dégâts) => dégâts + Effet;
}
}namespace z
{
class ForceInvalideException : Exception { }
class NomInvalideException : Exception { }
internal class Personnage
{
public string Nom { get; private init; }
public int Vie { get; private set; }
public int Force { get; private init; }
public bool EstVivant => !EstMort;
public bool EstMort => Vie <= 0;
public Personnage(string nom, int vie, int force)
{
Nom = nom;
Vie = vie;
Force = force;
}
protected static void Frapper(Personnage autre, int dégâts)
{
autre.Vie -= dégâts;
}
public virtual void Frapper(Personnage autre) // ICI
{
Frapper(autre, 1); // bof (on fera mieux bientôt)
}
}
}using static z.Algos;
namespace z
{
internal class Héros : Personnage
{
const int FORCE_MIN = 10,
FORCE_MAX = 20;
static bool EstForceValide(int candidate) =>
EstEntreInclusif(candidate, FORCE_MIN, FORCE_MAX);
static int ValiderForce(int candidate) =>
EstForceValide(candidate) ?
candidate : throw new ForceInvalideException();
// bornes pour la valeur initiale seulement
const int VIE_MIN = 50,
VIE_MAX = 100;
public Héros(string nom, int force)
: this(nom, force, null)
{
}
public Héros(string nom, int force, Arme arme)
: base(nom, new Random().Next(VIE_MIN, VIE_MAX + 1),
ValiderForce(force))
{
ZeArme = arme;
}
public override void Frapper(Personnage p) // ICI
{
const int PCT_DÉGÂTS_MIN = 50,
PCT_DÉGÂTS_MAX = 100;
int dégâts =
(int) (Force *
(new Random().Next(PCT_DÉGÂTS_MIN, PCT_DÉGÂTS_MAX + 1) /
100.0));
Frapper
(
p, ZeArme == null? dégâts : ZeArme.AppliquerEffet(dégâts)
);
}
public Arme ZeArme { get; init; }
}
}using static z.Algos;
namespace z
{
internal class Monstre : Personnage
{
const int LG_NOM_MAX = 5;
static bool EstNomValide(string candidat) =>
candidat.Length <= LG_NOM_MAX &&
Algos.EstSeulementConsonnes(candidat);
static string ValiderNom(string candidat) =>
EstNomValide(candidat) ?
candidat : throw new NomInvalideException();
const int FORCE_MIN = 15,
FORCE_MAX = 25;
static bool EstForceValide(int candidate) =>
EstEntreInclusif(candidate, FORCE_MIN, FORCE_MAX);
static int ValiderForce(int candidate) =>
EstForceValide(candidate) ?
candidate : throw new ForceInvalideException();
public Monstre(string nom, int force, int vie)
: base(ValiderNom(nom), vie, ValiderForce(force))
{
}
public override void Frapper(Personnage p) // ICI
{
const int PCT_DÉGÂTS_MIN = 50,
PCT_DÉGÂTS_MAX = 75;
int dégâts =
(int)(Force *
(new Random().Next(PCT_DÉGÂTS_MIN, PCT_DÉGÂTS_MAX + 1) /
100.0));
Frapper(p, dégâts);
}
}
}namespace ActivitéS13
{
internal class GroupePersos
{
Random Dé { get; init; } = new();
Personnage [] Persos { get; init; }
protected GroupePersos(Personnage[] persos)
{
Persos = persos; // pas bon, mais on s'en reparle
}
protected int NbÉléments => Persos.Length;
protected int CompterMorts()
{
int n = 0;
foreach (Personnage p in Persos)
if (p.EstMort)
++n;
return n;
}
public void Attaquer(GroupePersos autres)
{
Personnage deQui = Persos[Dé.Next(NbÉléments)];
while(deQui.EstMort) // peut être long...
deQui = Persos[Dé.Next(NbÉléments)];
Personnage versQui = autres.Persos[Dé.Next(autres.NbÉléments)];
while(versQui.EstMort) // peut être long...
versQui = autres.Persos[Dé.Next(autres.NbÉléments)];
Console.WriteLine($"{deQui.Nom} frappe {versQui.Nom}");
Console.WriteLine($"\tAvant le coup, {versQui.Nom} a {versQui.Vie} vie");
deQui.Frapper(versQui);
Console.WriteLine($"\tAprès le coup, {versQui.Nom} a {versQui.Vie} vie");
}
public bool EstDéfait =>
CompterMorts() == NbÉléments;
public void PrésenterVivants()
{
foreach (Personnage p in Persos)
if (p.EstVivant)
Console.Write($"{p.Nom} avec {p.Vie} vies; ");
}
}
}namespace ActivitéS13
{
class ArméeMalforméeException : Exception { }
internal class Armée : GroupePersos
{
static bool EstArméeValide(Héros[] h) =>
h.Length != 0 && h.Length % 2 == 0;
static Héros[] ValiderArmée(Héros[] h) =>
EstArméeValide(h)? h : throw new ArméeMalforméeException();
public Armée(Héros[] héros) : base(ValiderArmée(héros))
{
}
}
}namespace ActivitéS13
{
internal class Horde : GroupePersos
{
class HordeMalforméeException : Exception { }
static bool EstHordeValide(Monstre[] m) => m.Length != 0;
static Monstre[] ValiderHorde(Monstre[] m) =>
EstHordeValide(m) ? m : throw new HordeMalforméeException();
public Horde(Monstre[] monstres) : base(ValiderHorde(monstres))
{
}
}
}using ActivitéS13;
using z;
Armée armée = new(new Héros[]
{
new Héros("Bill le petit", 15),
new ("Valentin", 18),
new ("Galahad", 17, new Arme("Halebarde", 8)),
new ("Brute immonde", 20, new Arme("Gourdin", 3))
});
Horde horde = new(new Monstre[]
{
new("GRRR", 24, 50),
new("HSSSS", 22, 44),
new("BRK", 20, 40),
new("FFFT", 19, 15),
new("prkkk", 21, 38)
});
bool tourHéros = new Random().Next() % 2 == 0;
while (!armée.EstDéfait && !horde.EstDéfait)
{
if (tourHéros)
{
armée.Attaquer(horde);
}
else
{
horde.Attaquer(armée);
}
Console.WriteLine(new string('-', 70));
tourHéros = !tourHéros;
}
//Console.WriteLine($"Victoire de {(héros.EstVivant? héros.Nom : monstre.Nom)}");
if (horde.EstDéfait)
{
Console.Write("Victoire de l'armée. Encore vivants : ");
armée.PrésenterVivants();
}
else
{
Console.Write("Victoire de la horde. Encore vivants : ");
horde.PrésenterVivants();
}