Fun with methods: een verhaalgenerator

Een verhaalgenerator is een methode die hulpmethoden gebruikt om “automatisch” een verhaal te verzinnen. We pakken het bottom-up aan: eerst de kleinste methoden, daarna de methoden die ze gebruiken. Op het einde schrijf je zelf het laatste stuk.

Alles wat je hier nodig hebt, zag je in hoofdstuk 7: methoden met een returntype, parameters en optionele parameters. Elke methode die toeval nodig heeft, maakt zelf een Random aan, zoals bij de oefening Roulette.

Naamgenerator

We maken eerst een methode die namen verzint, voor het hoofdpersonage van ons verhaal. De methode maakt een naam met een gevraagde lengte. Een naam bestaat hier uit willekeurige hoofdletters van A tot en met Z.

Een eerste versie:

static string GenereerNaamEenvoudig(int lengte)
{
    Random generator = new Random();
    string naam = "";
    for (int i = 0; i < lengte; i++)
    {
        naam += (char)generator.Next('A', 'Z' + 1);
    }
    return naam;
}

Een char is intern een getal. generator.Next('A', 'Z' + 1) geeft dus een getal van de code van A tot en met die van Z, en (char) maakt er weer een letter van. De bovengrens van Next doet zelf niet mee: vandaar de + 1.

Je roept de methode zo op:

string naam = GenereerNaamEenvoudig(10);
Console.WriteLine(naam);

Naamgenerator versie 2

De eerste versie maakt vaak onuitspreekbare namen, zoals QXZTRKWB. Beter is een naam waarin medeklinkers en klinkers elkaar afwisselen (bijvoorbeeld ADILO). Daarvoor maken we eerst enkele hulpmethoden.

IsKlinker

Een methode die zegt of een teken (char) een klinker is: A, E, I, O of U. Dat gaat snel met een switch:

static bool IsKlinker(char teken)
{
    switch (teken)
    {
        case 'A':
        case 'E':
        case 'I':
        case 'O':
        case 'U':
            return true;
        default:
            return false;
    }
}

GenereerKlinker

Een methode die enkel klinkers maakt:

static char GenereerKlinker()
{
    Random generator = new Random();
    switch (generator.Next(0, 5))
    {
        case 0:
            return 'A';
        case 1:
            return 'E';
        case 2:
            return 'I';
        case 3:
            return 'O';
        default:
            return 'U';
    }
}

GenereerMedeklinker

Dezelfde aanpak met een switch met de 21 medeklinkers werkt. Korter, maar minder goed, is deze versie: ze blijft letters trekken tot het geen klinker is. Meestal lukt dat in één of twee keer, maar je weet nooit op voorhand hoeveel keer de loop draait.

static char GenereerMedeklinker()
{
    Random generator = new Random();
    char teken = 'A';
    while (IsKlinker(teken))
    {
        teken = (char)generator.Next('A', 'Z' + 1);
    }
    return teken;
}

Schrijf gerust zelf de versie met de switch.

Betere naamgenerator

De nieuwe naamgenerator begint willekeurig met een klinker of een medeklinker, en wisselt daarna telkens af:

static string GenereerNaam(int lengte = 6)
{
    Random generator = new Random();
    string naam = "";
    bool klinker = generator.Next(0, 2) == 0;
    for (int i = 0; i < lengte; i++)
    {
        if (klinker)
        {
            naam += GenereerKlinker();
        }
        else
        {
            naam += GenereerMedeklinker();
        }
        klinker = !klinker;
    }
    return naam;
}

Zinnen maken

Een zin bestaat minstens uit:

  • een onderwerp
  • een werkwoord
  • een lijdend voorwerp

Bijvoorbeeld: Tim gooit de bal.

Voor het onderwerp hebben we al GenereerNaam. We hebben nog een methode nodig voor werkwoorden en een voor lijdende voorwerpen.

Werkwoord

Deze methode werkt als een klein woordenboek met werkwoorden:

static string GenereerWerkwoord()
{
    Random generator = new Random();
    switch (generator.Next(0, 10))
    {
        case 0:
            return "roept";
        case 1:
            return "gooit";
        case 2:
            return "aait";
        case 3:
            return "eet";
        case 4:
            return "pakt";
        case 5:
            return "kijkt naar";
        case 6:
            return "ledigt";
        case 7:
            return "vecht met";
        case 8:
            return "beklimt";
        default:
            return "begraaft";
    }
}

Lijdend voorwerp

Dezelfde soort methode:

static string GenereerVoorwerp()
{
    Random generator = new Random();
    switch (generator.Next(0, 10))
    {
        case 0:
            return "een bal";
        case 1:
            return "de hond";
        case 2:
            return "de kat";
        case 3:
            return "een lepel";
        case 4:
            return "het kind";
        case 5:
            return "het boek";
        case 6:
            return "de computer";
        case 7:
            return "een vork";
        case 8:
            return "het scherm";
        default:
            return "een frietkot";
    }
}

Korte zin

We combineren de drie methoden tot een zin. Het onderwerp is een optionele parameter: geef je er geen mee, dan verzint de methode zelf een naam.

static string GenereerKorteZin(string onderwerp = "")
{
    if (onderwerp == "")
    {
        onderwerp = GenereerNaam();
    }
    return onderwerp + " " + GenereerWerkwoord() + " " + GenereerVoorwerp();
}

Verhaalgenerator

Alle puzzelstukjes liggen klaar.

Voegwoorden

Een zin heeft vaak een tweede deel dat met een voegwoord (bijvoorbeeld en) achter de eerste zin geplakt wordt. Ook deze methode gebruikt de gekende aanpak. De spaties en komma’s zitten in het voegwoord zelf:

static string GenereerVoegwoord()
{
    Random generator = new Random();
    switch (generator.Next(0, 5))
    {
        case 0:
            return " en ";
        case 1:
            return ", maar ";
        case 2:
            return ", dus ";
        case 3:
            return ", of ";
        default:
            return ", want ";
    }
}

Lange zin

Deze methode plakt zoveel korte zinnen achter de eerste als je meegeeft:

static string GenereerLangeZin(int aantalBijzinnen)
{
    string zin = GenereerKorteZin();
    for (int i = 0; i < aantalBijzinnen; i++)
    {
        zin += GenereerVoegwoord() + GenereerKorteZin();
    }
    return zin;
}

GenereerLangeZin(2) geeft bijvoorbeeld:

ECAGUX kijkt naar een vork en NONAJE pakt de kat, maar AGESUS begraaft de kat

Het verhaal maken: dat doe je zelf

  • In GenereerLangeZin heeft elk deel van de zin een ander onderwerp. Beter is dat het onderwerp overal hetzelfde blijft. Geef GenereerLangeZin daarom ook een optionele parameter onderwerp, die ze doorgeeft aan GenereerKorteZin. Geef je geen onderwerp mee, dan verzint ze er zelf één, en gebruikt ze dat in de hele zin. Laat de zin eindigen op een punt.
  • Maak een methode GenereerVerhaal die een gevraagd aantal lange zinnen achter elkaar zet, allemaal met hetzelfde hoofdpersonage. Elke zin krijgt een willekeurig aantal bijzinnen, van 0 tot en met 2.
  • Kan je hier en daar bijvoeglijke naamwoorden toevoegen? Dat is een extra beschrijving van het onderwerp of het lijdend voorwerp, zoals “de groene bal” of “de stomme Eric”.

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.