• Let op Twee constructors met dezelfde toekenningen is dubbele code (boete Onnodige of redundante code). Laat de ene de andere oproepen met : this(...) (uitleg).
  • Geef een constructorparameter een andere naam dan de instantievariabele, bv. lengteIn. lengte = lengte; compileert, maar doet niets (waarschuwing CS1717).
  • Een klasse die toevalsgetallen nodig heeft, krijgt één private static Random. Je geeft hem niet meer mee vanuit Main (uitleg).
  • Hoort iets bij alle objecten samen (een teller, een prijs voor iedereen), dan is het static, en je spreekt het aan via de klassenaam: Pokemon.Info().

Meetlat constructor (Essential)

De kast voor je kot heb je gevonden met de Meetlat uit hoofdstuk 9. Eén ding stoorde: je kon een meetlat maken zonder lengte, en dan gaf ze overal 0. Met een constructor kan dat niet meer.

Stappenplan:

  1. Verwijder de write-only property BeginLengte.
  2. Voeg een constructor toe met één parameter van het type double: de lengte in meter. Hij stopt die waarde in de private instantievariabele lengte, die er al staat. Noem de parameter niet lengte maar bv. lengteIn.
  3. De vier read-only properties (LengteInM, LengteInCm, LengteInKm en LengteInVoet) blijven zoals ze zijn: ze lezen lengte al.

Testcode. Zet deze code in je Main. Je klasse werkt als je exact deze uitvoer krijgt.

Meetlat muur = new Meetlat(2.4);
Meetlat kast = new Meetlat(2.1);
Console.WriteLine($"Je muur is {muur.LengteInM} m hoog, of {muur.LengteInCm} cm.");
Console.WriteLine($"De kast is {kast.LengteInVoet:F2} voet hoog.");
if (kast.LengteInM < muur.LengteInM)
{
    Console.WriteLine("De kast past onder het plafond.");
}

Meetlat fietstocht = new Meetlat(1850);
Console.WriteLine($"Van je kot naar de campus is {fietstocht.LengteInKm} km.");
Je muur is 2,4 m hoog, of 240 cm.
De kast is 6,89 voet hoog.
De kast past onder het plafond.
Van je kot naar de campus is 1,85 km.

Voeg daarna Meetlat leeg = new Meetlat(); toe aan Main. Wat zegt de compiler, en waarom is dat hier net goed?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een constructor heeft de naam van de klasse en geen returntype. Hij loopt één keer: bij new.
  • Een constructor met een parameter verplicht wie de klasse gebruikt om die waarde mee te geven. Zo bestaat er geen meetlat zonder lengte. Zie Welke constructors moet ik nu eigenlijk allemaal voorzien?.

Inschrijven voor de cantus (Essential) (GPT)

Tip

Deze oefening werd gemaakt (en aangepast nadien) met behulp van volgende GPT en heeft daarom de tag (GPT) achter de titel staan.

Je studentenclub organiseert een cantus, en jij schrijft het programma voor de inschrijvingen. Zonder voor- en achternaam kom je er niet in. Geboortejaar en e-mailadres zijn handig, maar niet verplicht.

Deel 1: de klasse.

  • Klassenaam: Persoon
  • Properties (auto): Voornaam (string), Achternaam (string), Geboortejaar (int) en Email (string).
  • Constructor met twee parameters: de voornaam en de achternaam.
    • Is een van beide leeg (""), dan werpt de constructor een ArgumentException op met de boodschap Voornaam en achternaam mogen niet leeg zijn. Een ArgumentException is een exception die .NET al kent voor een parameter met een ongeldige waarde. Je werpt ze op zoals een gewone: throw new ArgumentException("...");.
    • Anders vult hij Voornaam en Achternaam in.
  • Methode ToonInformatie(): toont {Voornaam} {Achternaam} geboren in {Geboortejaar} heeft emailadres: {Email}.

Geboortejaar en Email staan niet in de constructor. Die geef je mee met object initializer syntax: tussen accolades achter de haakjes van new. Dat werkt ook achter een constructor met parameters.

Testcode. Zet deze code in je Main. Je klasse werkt als je exact deze uitvoer krijgt.

try
{
    Persoon jana = new Persoon("Jana", "Peeters") { Geboortejaar = 2007, Email = "jana.peeters@kotmail.be" };
    jana.ToonInformatie();

    Persoon anoniem = new Persoon("", "Janssens") { Geboortejaar = 2006 };
    anoniem.ToonInformatie();
}
catch (ArgumentException e)
{
    Console.WriteLine($"Inschrijving geweigerd: {e.Message}");
}
Jana Peeters geboren in 2007 heeft emailadres: jana.peeters@kotmail.be
Inschrijving geweigerd: Voornaam en achternaam mogen niet leeg zijn.

Waarom verschijnt er geen tweede lijn met Janssens?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2: het lek. Zet deze lijnen onder de catch:

Persoon mo = new Persoon("Mo", "Janssens") { Achternaam = "" };
mo.ToonInformatie();

Wat toont het programma? Hoe komt een lege achternaam langs je controle? Los het op zodat de achternaam en de voornaam na de constructor niet meer kunnen veranderen. Wat zegt de compiler daarna over deze twee lijnen?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een exception in de constructor houdt een fout object tegen: het wordt nooit afgewerkt. Zet de try rond de lijn met new.
  • De initializer loopt pas ná de constructor. Een controle in de constructor helpt dus niet als de property daarna nog een public set heeft. Zie Object initializer syntax.
  • Een property met een private set kan je niet in een initializer zetten.

Koffie in de blokperiode

Het is blok, en de koffieautomaat in de bib draait overuren. Wie niets kiest, krijgt een espresso. Wie enkel een soort kiest, krijgt een medium met gewone melk.

Specificaties:

  • Klassenaam: Koffie
  • Properties:
    • Soort, Grootte (beide string) en HaverMelk (bool): auto-properties met een private set.
    • Suikerklontjes (int): auto-property met een public set.
    • Prijs (double): read-only. Een "klein" kost 1,80 euro, een "medium" 2,20 euro en een "groot" 2,60 euro. Havermelk kost 0,40 euro extra. Suiker is gratis.
  • Drie constructors:
    • Koffie(string soortIn, string grootteIn, bool haverMelkIn): vult de drie properties in. Dit is de enige constructor met toekenningen.
    • Koffie(string soortIn): een medium zonder havermelk. Gebruik : this(...).
    • Koffie(): een espresso. Ook met : this(...). Welke van de twee andere constructors roep je op?
  • Methode ToonBon(): toont bv. groot latte met havermelk (0 suiker): 3,00 euro. Zonder havermelk valt met havermelk weg.

Testcode. Zet deze code in je Main. Je klasse werkt als je exact deze uitvoer krijgt.

Koffie eerste = new Koffie();
Koffie tweede = new Koffie("cappuccino");
Koffie derde = new Koffie("latte", "groot", true);
Koffie vierde = new Koffie("americano", "klein", false) { Suikerklontjes = 2 };
Koffie vijfde = new Koffie { Suikerklontjes = 1 };

eerste.ToonBon();
tweede.ToonBon();
derde.ToonBon();
vierde.ToonBon();
vijfde.ToonBon();
medium espresso (0 suiker): 2,20 euro
medium cappuccino (0 suiker): 2,20 euro
groot latte met havermelk (0 suiker): 3,00 euro
klein americano (2 suiker): 1,80 euro
medium espresso (1 suiker): 2,20 euro

Kijk naar vijfde: er staan geen ronde haakjes achter new Koffie. Welke constructor wordt daar opgeroepen?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Met : this(...) staan de toekenningen maar op één plaats. Moet er later iets bij (bv. een property Suikersoort), dan pas je één constructor aan. Zie Constructors hergebruiken met this().
  • Een initializer werkt achter elke constructor, met of zonder parameters. Gebruik je de default constructor, dan mogen de ronde haakjes weg.

Voorspel de uitvoer: een frietje na de les (Essential)

Na de laatste les haal je met je klasgenoten friet. Iemand schreef er een klasse voor, met een WriteLine in elke constructor en in een set, zodat je ziet wat er wanneer gebeurt:

internal class Friet
{
    public Friet() : this("middel")
    {
        Console.WriteLine("A: default");
    }

    public Friet(string formaat) : this(formaat, "mayo")
    {
        Console.WriteLine($"B: {formaat}");
    }

    public Friet(string formaat, string saus)
    {
        Console.WriteLine($"C: {formaat} met {saus}");
    }

    private bool zout;
    public bool Zout
    {
        get
        {
            return zout;
        }
        set
        {
            Console.WriteLine($"D: zout = {value}");
            zout = value;
        }
    }
}

In Main:

Console.WriteLine("Bestelling 1");
Friet eerste = new Friet();
Console.WriteLine("Bestelling 2");
Friet tweede = new Friet("groot") { Zout = true };
Console.WriteLine("Bestelling 3");
Friet derde = new Friet("klein", "stoofvleessaus");

Schrijf eerst op papier wat het programma toont, lijn per lijn. Typ de code pas daarna over en vergelijk.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Stevens constructors (Essential)

Stagiair Steven moest vier kleine klassen afwerken, telkens met een constructor erbij. Hij liet een A.I. het werk doen en leverde alles in zonder één keer op “Start” te klikken.

Lees elk stuk eerst zonder Visual Studio en antwoord op papier: compileert het? Zo nee, waar zit de fout en waarom? Zo ja, wat toont het programma? Typ de code pas daarna over en kijk of je gelijk had.

Deel 1. Een rugzak die weet hoeveel liter erin kan:

internal class Rugzak
{
    private double inhoudInLiter;

    public void Rugzak(double inhoud)
    {
        inhoudInLiter = inhoud;
    }

    public double InhoudInLiter
    {
        get { return inhoudInLiter; }
    }
}

In Main:

Rugzak schoolRugzak = new Rugzak(25);
Console.WriteLine($"Deze rugzak kan {schoolRugzak.InhoudInLiter} liter aan.");

Compileert dit? Zo niet, wat moet er anders?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2. Een boek met een titel. Steven had zijn Main al geschreven voor de A.I. de constructor toevoegde:

internal class Boek
{
    public Boek(string titelIn)
    {
        Titel = titelIn;
    }

    public string Titel { get; private set; }
    public int AantalPaginas { get; set; }
}

In Main:

Boek roman = new Boek("De Avonden");
roman.AantalPaginas = 316;

Boek notitieboek = new Boek();
notitieboek.AantalPaginas = 80;

Console.WriteLine($"{roman.Titel} telt {roman.AantalPaginas} pagina's.");
Console.WriteLine($"{notitieboek.Titel} telt {notitieboek.AantalPaginas} pagina's.");

Compileert dit? Zo niet, los het op twee manieren op: één keer door enkel Main aan te passen, en één keer door enkel de klasse aan te passen.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 3. Een pizzeria wil weten hoeveel bestellingen er vandaag al waren, en per tafel hoeveel pizza’s die bestelde:

internal class Bestelling
{
    private static int aantalBestellingen = 0;
    private int aantalPizzas;

    public Bestelling(int aantalPizzasIn)
    {
        aantalPizzas = aantalPizzasIn;
        aantalBestellingen++;
    }

    public static void ToonOverzicht()
    {
        Console.WriteLine($"Vandaag al {aantalBestellingen} bestellingen.");
        Console.WriteLine($"Deze bestelling telt {aantalPizzas} pizza's.");
    }
}

In Main:

Bestelling tafel1 = new Bestelling(3);
Bestelling tafel2 = new Bestelling(2);
Bestelling.ToonOverzicht();

Compileert dit? Zo niet, welke regel is het probleem? Pas de klasse en Main aan zodat het programma eerst het aantal bestellingen toont en daarna het aantal pizza’s van elke tafel.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 4. Een spaarpot:

internal class Spaarpot
{
    private double bedrag;

    public Spaarpot(double bedrag)
    {
        bedrag = bedrag;
    }

    public double Bedrag
    {
        get { return bedrag; }
    }

    public void StopErIn(double extra)
    {
        bedrag = bedrag + extra;
    }
}

In Main:

Spaarpot pot = new Spaarpot(20);
pot.StopErIn(5);
Console.WriteLine($"In de spaarpot zit {pot.Bedrag} euro.");

Steven verwacht In de spaarpot zit 25 euro. Compileert dit? Wat toont het programma echt, en waarom?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een constructor heeft geen returntype. Met void ervoor is het een gewone methode. Zie Default constructors.
  • Schrijf je zelf een constructor, dan is de gratis default constructor weg. Heb je hem nodig, schrijf hem er dan bij en hergebruik de andere constructor met this(...). Zie Overloaded constructors en Constructors hergebruiken met this().
  • Een static methode heeft geen object en kan dus geen instantievariabelen lezen. Zie Static tegenover non-static.
  • Deel 4 compileert, met één waarschuwing. Lees in de Error List ook de waarschuwingen, niet enkel de fouten.

Digitale kluis: code vergeten (Essential)

Potloodtekening: de robot probeert een kluis te kraken met een stethoscoop, het stokmannetje leunt ertegen met de sleutel.

Je kotgenoot bewaart zijn laptop in een digitale kluis met een code van 0 tot en met 9999. Hij is de code vergeten. Voor je hem helpt kraken, bouw je zelf zo’n kluis na.

Deel 1: de kluis.

  • Klassenaam: DigitaleKluis
  • Instantievariabelen: private int code (de echte code) en private int aantalPogingen (begint op 0).
  • Constructor met één int: de code. Hij vult die in via de property Code.
  • Properties:
    • CanShowCode (bool): auto-property. Mag de code getoond worden?
    • Code (int): full property met een private set. De get geeft de echte code terug als CanShowCode true is, en anders altijd -666.
    • CodeLevel (int): read-only, code / 1000 (gehele deling). Een code van 4500 heeft level 4.
  • Methode TryCode(int ingevoerdeCode): probeert een code, toont wat er gebeurt en geeft true terug bij de juiste code, anders false. Elke poging telt, ook een juiste. Werk deze stappen in deze volgorde af:
Stap Situatie Wat er gebeurt Teruggeven
1 aantalPogingen is al 10 Toon Je hebt je 10 pogingen opgebruikt. Er wordt niets meer geteld. false
2 Anders Verhoog aantalPogingen met 1, en kijk verder naar stap 3, 4 of 5.
3 ingevoerdeCode is -666 Toon CHEATER false
4 ingevoerdeCode is de code Toon Geldige code. Aantal pogingen = {aantalPogingen} true
5 Een andere code Toon Geen geldige code. false

Testcode deel 1. Zet deze code in je Main. Je klasse werkt als je exact deze uitvoer krijgt.

DigitaleKluis kluis = new DigitaleKluis(4500);
Console.WriteLine($"Code: {kluis.Code}, level {kluis.CodeLevel}");
kluis.CanShowCode = true;
Console.WriteLine($"Code: {kluis.Code}");

kluis.TryCode(-666);
kluis.TryCode(2000);
kluis.TryCode(4500);
for (int i = 0; i < 8; i++)
{
    kluis.TryCode(1234);
}
Code: -666, level 4
Code: 4500
CHEATER
Geen geldige code.
Geldige code. Aantal pogingen = 3
Geen geldige code.
Geen geldige code.
Geen geldige code.
Geen geldige code.
Geen geldige code.
Geen geldige code.
Geen geldige code.
Je hebt je 10 pogingen opgebruikt.

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2: kraken met brute force. Voeg aan de klasse een static methode toe die een kluis probeert te kraken door te gokken.

  • Signatuur: public static void BruteForce(DigitaleKluis kluis)
  • Werking: gok hoogstens 10 keer een willekeurige code van 0 tot en met 9999 en probeer ze met kluis.TryCode(...). Lukt het, toon dan Gekraakt! en stop met gokken.
  • De klasse krijgt daarvoor één private static Random. Waarom static? BruteForce is static en heeft geen eigen object: een gewone instantievariabele kan hij niet gebruiken.

Test in Main:

DigitaleKluis laptop = new DigitaleKluis(4500);
DigitaleKluis.BruteForce(laptop);

Je ziet waarschijnlijk tien keer Geen geldige code. Waarom lukt het (bijna) nooit? Test daarna met een kluis met code 5, en verander in BruteForce tijdelijk de grenzen van je Next naar 0 tot en met 9. Zie je nu wel Gekraakt!?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 3: voor wie verder wil. code is private. Probeer in Main Console.WriteLine(laptop.code);. Probeer daarna in BruteForce kluis.TryCode(kluis.code). Wat zegt de compiler telkens? En had BruteForce met CanShowCode nog een andere achterdeur?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een static methode hoort bij de klasse en krijgt het object waarop ze werkt als parameter. Je roept ze op via de klassenaam: DigitaleKluis.BruteForce(laptop).
  • Eén private static Random voor de hele klasse. Zie static en Random.
  • private beschermt tegen code buiten de klasse, niet tegen code in de klasse zelf.

Puntenrekenaar: hoeveel moet ik halen?

Je permanente evaluatie is binnen. Hoeveel moet je nu nog halen op het examen om te slagen? Daarvoor heb je geen object nodig: je stopt er getallen in en krijgt een getal terug, net zoals bij Math.Pow. Een klus voor een static class.

Specificaties:

  • Klassenaam: Puntenrekenaar, een static class.
  • Static property AantalBerekeningen (int, private set): telt hoe vaak een van de twee methoden opgeroepen werd.
  • Static methode double NaarTwintig(double score, double maximum): zet een score om naar een score op 20. 39 op 60 wordt 13.
  • Static methode double NodigOpExamen(double peOpTwintig, int procentExamen): hoeveel je op 20 nodig hebt op het examen om samen 10 op 20 te halen. procentExamen zegt hoeveel procent het examen meetelt; de permanente evaluatie telt voor de rest. Gebruik deze formule:
    • deelExamen = procentExamen / 100.0
    • deelPe = (100 - procentExamen) / 100.0
    • nodig = (10 - peOpTwintig * deelPe) / deelExamen
  • Beide methoden tonen met Debug.WriteLine wat ze berekend hebben, bv. NaarTwintig: 39 op 60 is 13 op 20.

Testcode. Zet deze code in je Main. Je klasse werkt als je exact deze uitvoer krijgt.

double programmeren = Puntenrekenaar.NaarTwintig(39, 60);
Console.WriteLine($"Programmeren, permanente evaluatie: {programmeren:F1} op 20");
Console.WriteLine($"Nodig op het examen (telt 60%): {Puntenrekenaar.NodigOpExamen(programmeren, 60):F1} op 20");

double wiskunde = Puntenrekenaar.NaarTwintig(9, 30);
Console.WriteLine($"Wiskunde, permanente evaluatie: {wiskunde:F1} op 20");
Console.WriteLine($"Nodig op het examen (telt 50%): {Puntenrekenaar.NodigOpExamen(wiskunde, 50):F1} op 20");

Console.WriteLine($"Aantal berekeningen: {Puntenrekenaar.AantalBerekeningen}");
Programmeren, permanente evaluatie: 13,0 op 20
Nodig op het examen (telt 60%): 8,0 op 20
Wiskunde, permanente evaluatie: 6,0 op 20
Nodig op het examen (telt 50%): 14,0 op 20
Aantal berekeningen: 4

Daarna:

  1. Start je programma met de debugger (F5). Waar vind je de tekst van Debug.WriteLine?
  2. Schrijf in Main Puntenrekenaar rekenaar = new Puntenrekenaar();. Wat zegt de compiler?
  3. Haal die lijn weg en voeg aan de klasse een gewone methode toe: public void ToonUitleg(). Wat zegt de compiler nu?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Een static class is een bibliotheek van methoden, zoals Math. Je roept alles op via de klassenaam. Zie Static methoden en klassen.
  • Schrijf 100.0 en niet 100: 60 / 100 is een gehele deling en geeft 0.
  • Debug.WriteLine schrijft naar het venster Output, niet naar de console. Zie Intermezzo: Debug.WriteLine.

Pokémon deel 2 (Essential)

We bouwen verder op de Pokemon-klasse uit hoofdstuk 10: de base-stats met hun controle, ShowInfo uit de Pokémontester, en GeneratorPokemon en Battle uit de Pokémon-battler. Maak een kopie van dat project: een deel van je oude Main zal straks niet meer compileren.

Deel 1: constructors. Base-stats veranderen nooit (het dna, weet je nog?). Toch kon iedereen ze tot nu toe aanpassen.

  1. Geef de set van de zes base-stats een private ervoor. De controle (1 tot en met 255) blijft staan.
  2. Voeg een constructor toe met zes int-parameters, in de volgorde HP, attack, defense, special attack, special defense en speed. Hij vult de base-stats in via de properties, zodat de controle ook hier werkt.
  3. Voeg een default constructor toe die alle zes base-stats op 10 zet. Gebruik : this(...).

Naam, Nummer en Type houden hun public set. Die geef je mee met een initializer, ook achter de constructor met zes parameters.

Testcode deel 1.

Pokemon pikachu = new Pokemon(35, 55, 40, 50, 50, 90) { Naam = "Pikachu", Nummer = 25, Type = "electric" };
Pokemon oefenpop = new Pokemon { Naam = "Oefenpop" };
for (int i = 0; i < 50; i++)
{
    pikachu.VerhoogLevel();
}
pikachu.ShowInfo();
oefenpop.ShowInfo();

try
{
    Pokemon fout = new Pokemon(35, 55, 40, 50, 50, 300);
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}
Pikachu (#25, electric), level 50
Base stats: HP 35, attack 55, defense 40, sp. attack 50, sp. defense 50, speed 90
Full stats: HP 95, attack 60, defense 45, sp. attack 55, sp. defense 55, speed 95
Total 320, average 53,33
Oefenpop (#0, onbekend), level 0
Base stats: HP 10, attack 10, defense 10, sp. attack 10, sp. defense 10, speed 10
Full stats: HP 10, attack 5, defense 5, sp. attack 5, sp. defense 5, speed 5
Total 60, average 10,00
Speed_Base moet tussen 1 en 255 liggen.

Zet daarna HP_Base = 40 in de initializer van oefenpop. Wat zegt de compiler?

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

Deel 2: static. In je Program.cs staat nog de GeneratorPokemon uit de battler. Wat zegt de compiler nu over die methode, en waarom?

Dat lossen we op door GeneratorPokemon en Battle naar de klasse Pokemon te verhuizen. Ze blijven static: ze horen bij geen enkele Pokémon in het bijzonder. In de klasse komen er ook tellers bij over alle Pokémon samen.

  • Static properties met een private set: TellerLevel (hoe vaak VerhoogLevel in totaal geleveld heeft), TellerBattles, TellerGelijkspel en TellerGeneraties (hoe vaak GeneratorPokemon opgeroepen werd).
  • Static property NoLevelingAllowed (bool) met een public set.
  • VerhoogLevel: is NoLevelingAllowed true, dan werpt ze een Exception op met de boodschap Levelen is niet toegelaten. Anders verhoogt ze het level en TellerLevel.
  • public static Pokemon GeneratorPokemon(string naam): zoals in de battler (base-stats van 20 tot en met 150, level van 1 tot en met 50), maar nu met de constructor met zes parameters. De Random komt niet meer uit Main: de klasse krijgt één private static Random. Het level zet de methode rechtstreeks met Level: binnen de klasse mag dat, want de private set hoort bij de klasse. Ze verhoogt TellerGeneraties.
  • public static int Battle(Pokemon poke1, Pokemon poke2): zoals in de battler, maar ze verhoogt TellerBattles (na de controle op null) en bij gelijkspel ook TellerGelijkspel.
  • public static void Info(): toont de tellers, zoals in de uitvoer hieronder.

Testcode deel 2.

Pokemon pikachu = new Pokemon(35, 55, 40, 50, 50, 90) { Naam = "Pikachu" };
Pokemon bulbasaur = new Pokemon(45, 49, 49, 65, 65, 45) { Naam = "Bulbasaur" };
Pokemon spiegel = new Pokemon(35, 55, 40, 50, 50, 90) { Naam = "Spiegel-Pikachu" };
for (int i = 0; i < 10; i++)
{
    pikachu.VerhoogLevel();
    bulbasaur.VerhoogLevel();
    spiegel.VerhoogLevel();
}

Console.WriteLine($"Pikachu tegen Bulbasaur: {Pokemon.Battle(pikachu, bulbasaur)}");
Console.WriteLine($"Pikachu tegen Spiegel-Pikachu: {Pokemon.Battle(pikachu, spiegel)}");

Pokemon wild = Pokemon.GeneratorPokemon("Wilde Rattata");
Console.WriteLine($"{wild.Naam} verschijnt.");

Pokemon.NoLevelingAllowed = true;
try
{
    pikachu.VerhoogLevel();
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}

Pokemon.Info();
Pikachu tegen Bulbasaur: 1
Pikachu tegen Spiegel-Pikachu: 0
Wilde Rattata verschijnt.
Levelen is niet toegelaten.
Aantal keer geleveld: 30
Aantal battles: 2, waarvan 1 gelijkspel
Aantal gegenereerd: 1

Waarom telt TellerLevel de levels van wild niet mee?

Tip

Student Kevin Van Rooy maakte volgende applicatie waarbij bovenstaande opgave als initiële inspiratie diende:

Knap gedaan! (click to enlarge)

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • De default constructor gebruikt de constructor met zes parameters via : this(...). De toekenningen staan maar één keer in je code.
  • Een constructor vult properties in via hun set, dus de controle uit hoofdstuk 10 werkt meteen ook bij new.
  • Een private set is bereikbaar overal in de klasse, ook in een static methode. Daarom verhuist GeneratorPokemon naar Pokemon.
  • Tellers over alle Pokémon samen zijn static. Zie Static properties.

Pokémon natures (PRO)

Pokémon hebben een nature (aard). Die verhoogt één stat met 10% en verlaagt een andere met 10%. Bouw verder op je klasse uit Pokémon deel 2.

Specificaties:

  1. Enums: een enum Nature met de 25 natures uit de tabel hieronder, en een enum StatType met Attack, Defense, SpecialAttack, SpecialDefense en Speed. HP staat er niet in: een nature verandert de HP niet.
  2. Property: Nature (van het type Nature), met een private set.
  3. Constructor: elke nieuwe Pokémon krijgt een willekeurige nature. Doe dat in de constructor met zes parameters en gebruik dezelfde private static Random als GeneratorPokemon. Waarom krijgt een Pokémon die met new Pokemon() gemaakt wordt dan ook een nature?
  4. Helpermethode: private double BerekenNatureModifier(StatType stat). Ze geeft 1.10 terug als de nature deze stat verhoogt, 0.90 als ze deze stat verlaagt, en anders 1.0. Vijf natures (Bashful, Docile, Hardy, Quirky en Serious) verhogen en verlagen dezelfde stat. Die veranderen niets: geef 1.0 terug.
  5. Full-stats: vermenigvuldig het resultaat van elke full-stat, behalve HP_Full, met die factor en cast terug naar int.
Nature Verhoogt met 10% Verlaagt met 10%
Adamant Attack Sp. Atk
Bashful Sp. Atk Sp. Atk
Bold Defense Attack
Brave Attack Speed
Calm Sp. Def Attack
Careful Sp. Def Sp. Atk
Docile Defense Defense
Gentle Sp. Def Defense
Hardy Attack Attack
Hasty Speed Defense
Impish Defense Sp. Atk
Jolly Speed Sp. Atk
Lax Defense Sp. Def
Lonely Attack Defense
Mild Sp. Atk Defense
Modest Sp. Atk Attack
Naive Speed Sp. Def
Naughty Attack Sp. Def
Quiet Sp. Atk Speed
Quirky Sp. Def Sp. Def
Rash Sp. Atk Sp. Def
Relaxed Defense Speed
Sassy Sp. Def Speed
Serious Speed Speed
Timid Speed Attack

Meer info over Natures

Testen. Maak in een lus drie keer een Pikachu (dezelfde base-stats als in deel 1), laat hem 50 keer levelen en toon zijn nature en Speed_Full. Zonder nature was dat 95. Bij jou komen er andere natures uit, bijvoorbeeld:

Pikachu is Jolly: speed 104
Pikachu is Brave: speed 85
Pikachu is Hardy: speed 95

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Wat elke Pokémon bij zijn geboorte krijgt, hoort in de hoofdconstructor. Via : this(...) krijgen de andere constructors het vanzelf mee.
  • Eén helpermethode voor de factor, zodat de vijf full-stats elk maar één vermenigvuldiging hebben.

Project: GreenRide (Final Essentials)

Je bent net aangenomen bij GreenRide, een nieuwe start-up met elektrische deelsteps in ’t Stad. Er is nog geen software: jij schrijft de klasse die de steps beheert, de winst bijhoudt en ervoor zorgt dat niemand vertrekt met een lege batterij.

Denk bij elk gegeven na: hoort het bij één step, of bij het hele bedrijf? Wat bij het hele bedrijf hoort, is static.

De klasse DeelStep

Gegevens van het bedrijf (static):

  • PrijsPerMinuut (double): de ritprijs per minuut, een auto-property met een public set en beginwaarde 0.25 euro. De directie kan ze veranderen, bv. als het regent.
  • TotaalWinst (double): hoeveel euro alle steps samen al opgebracht hebben. Private set.
  • AantalInGebruik (int): hoeveel steps er op dit moment rondrijden. Private set.
  • volgNummerGenerator (private static int, begint op 1): het nummer dat de volgende step krijgt.

Gegevens van één step:

  • ID (int): het unieke nummer van de step. Private set.
  • BatterijNiveau (int): full property met een private set. Een waarde onder 0 wordt 0, een waarde boven 100 wordt 100.
  • IsBezig (bool): is de step verhuurd? Private set.

Constructors:

  • DeelStep(int batterij): voor een step die uit onderhoud komt. Zet BatterijNiveau op de meegegeven waarde, geef de step als ID het nummer van volgNummerGenerator en verhoog die daarna met 1.
  • DeelStep(): een nieuwe step met 100% batterij. Gebruik : this(...).

Methoden:

  • StartRit():
    • Is de step al onderweg, toon dan Step {ID} is al onderweg.
    • Is de batterij 10% of minder, toon dan Step {ID} kan niet vertrekken: batterij te laag ({BatterijNiveau}%).
    • Anders: IsBezig wordt true, AantalInGebruik gaat 1 omhoog, en je toont Step {ID} vertrekt met {BatterijNiveau}% batterij.
  • EindigRit(int aantalMinuten):
    • Was de step niet onderweg, toon dan Step {ID} was niet onderweg.
    • Anders: IsBezig wordt false en AantalInGebruik gaat 1 omlaag. De ritprijs is aantalMinuten * PrijsPerMinuut en gaat bij TotaalWinst. Elke minuut rijden kost 1% batterij. Toon Step {ID} terug. Ritprijs: {ritprijs:F2} euro. Resterende batterij: {BatterijNiveau}%

Het dashboard in Main

  1. De vloot: maak drie steps. Step 1 en 2 zijn nieuw, step 3 komt uit onderhoud met 5% batterij.
  2. De ochtendspits: laat step 1, 2 en 3 vertrekken en toon hoeveel steps er in gebruik zijn.
  3. Het begint te regenen: de directie zet DeelStep.PrijsPerMinuut = 0.50;.
  4. De aankomst: step 1 stopt na 20 minuten, step 2 na 10 minuten. Toon opnieuw hoeveel steps er in gebruik zijn.
  5. De balans: toon de totale winst en de batterij van step 1.

Zo hoort het eruit te zien:

Step 1 vertrekt met 100% batterij.
Step 2 vertrekt met 100% batterij.
Step 3 kan niet vertrekken: batterij te laag (5%).
Steps in gebruik: 2
Step 1 terug. Ritprijs: 10,00 euro. Resterende batterij: 80%
Step 2 terug. Ritprijs: 5,00 euro. Resterende batterij: 90%
Steps in gebruik: 0
Totale winst: 15,00 euro
Batterij step 1: 80%

Deze oplossing is afgeschermd.
Studenten krijgen de sleutel van hun lesgever. Heb je de sleutel al eens op dit toestel gebruikt, dan verschijnt de oplossing hier vanzelf.

  • Static of niet? De prijs, de winst en het aantal steps onderweg gaan over het hele bedrijf; de batterij en het ID over één step. Een static property spreek je aan via de klassenaam: DeelStep.PrijsPerMinuut.
  • Een static teller (volgNummerGenerator) geeft elk nieuw object een eigen nummer. Was hij niet static, dan kreeg elke step nummer 1.
  • De batterijcontrole staat in de set, dus ze werkt in de constructor én in EindigRit.
  • De ritprijs gebruikt de prijs op het moment van afrekenen: beide ritten kosten 0,50 euro per minuut, ook al vertrokken ze toen de prijs nog 0,25 was.