Monopoly app bouwen in Salesforce – deel 6: Kans en Algemeen Fonds

In de vorige aflevering van deze serie hebben we een flow gebouwd die het effect van een Rule afhandelt voor de actieve speler. In dit deel gaan we deze invoegen in de hoofdflow en zoomen we in op hoe het werkt bij een Kans of Algemeen Fonds vakje waar er niet één regel aan het vakje gekoppeld is, maar er een stapel kaartjes met elk een regel is.

Verder gaan we dit keer in op:

  • Hoe zorgen we dat de volgende keer dat een speler op Kans of Algemeen Fonds uitkomt, de volgende Rule van de ‘stapel’ wordt uitgevoerd?
  • Hoe schudden we de stapel wanneer deze helemaal leeg is?

Uitvoeren van een Rule opnemen in de Roll and Move flow

We openen nu weer de SCR – Roll and Move flow die we in deel 4 gebouwd hebben. Hierin willen we nadat de speler op een nieuwe Position arriveert het effect verwerken van een Rule als die aan deze Position is gekoppeld. In deze flow zien we dat dit (Update Player’s Position) op 2 verschillende plaatsen kan gebeuren.

We hebben een paar stappen nodig om de eventuele Rule te kunnen ophalen die we nodig hebben voor de SCR – Execute Rule flow. Om te zorgen dat we dit niet op 2 plaatsen hoeven te bouwen, maken we ook hiervan weer een subflow. Op zichzelf zou puur het ophalen van een record wel in een autolaunched flow kunnen, maar dan hebben we in de hoofdflow nog een Decision element nodig om vast te stellen of er wel een Rule record gevonden is en we de SCR – Execute Rule flow wel moeten uitvoeren.

Dat kan efficiënter door de SCR – Execute Rule weer als subflow op te nemen in de flow die controleert of er een Rule aan de Position hangt. Om een Screenflow als subflow te kunnen hebben, moet de flow waarin je deze subflow opneemt zelf dus ook een Screenflow zijn.

Nu we deze gelaagdheid gaan aanbrengen, kunnen we het beste beginnen het flowlandschap te documenteren. Ik gebruik hiervoor weer Drawio, dat ik ook voor het uittekenen van het datamodel gebruikt heb. Het onderstaande schema volstaat nu. Als het landschap complexer wordt, zullen we zien of deze opzet nog verbeterd moet worden.

Controleren of er een Rule aan de Position gekoppeld is

De SCR – Check for and Execute Position Rule flow krijgt één input variabele mee van de ‘parent’ flow: inputPlayer.

De Player record is op dat moment al bijgewerkt, dus de nieuwe Position van die Player is ook via de Position lookup toegankelijk. Voor die Position gaan we op de aanwezigheid van Position Rules checken.

Laat ik eerst uitleggen hoe ik de architectuur voor de stapels Kans en Algemeen Fonds kaarten heb bedacht. In de aflevering waarin we het datamodel hebben bepaald, legde ik al uit dat we een many-to-many relatie gebruiken tussen Position en Rule. Hierdoor kan één Rule een Relatie hebben met verschillende Positions en kunnen er ook meerdere Rules tegelijk aan één Position worden gekoppeld.

Zo kun je dus een ‘stapel’ Rule kaartjes koppelen aan elk Kans vakje op het bord en kan elk Kans kaartje gekoppeld worden alle Kans vakjes.

In Salesforce maken we zo’n many-to-many relatie met behulp van een junction object. Dat is gewoon een custom object zoals alle anderen, maar de twee master-detail relaties en de manier waarop we het gebruiken, bepaalt dat we het kunnen definiëren als een junction object.

  • Position Rule 1 kan Rule A aan Position A koppelen
  • Position Rule 2 kan Rule A aan Position B koppelen
  • Position Rule 3 kan Rule B aan Position A koppelen

Om de kaartjes in een specifieke volgorde op de stapel te kunnen hebben liggen, heb ik ook een Index veld op het Position Rule object toegevoegd. Om de bovenste kaart van de Kans of Algemeen Fonds stapel te pakken, sorteren we op deze Index in ons Get Records element. Zo kunnen we er zeker van zijn dat de speler steeds de bovenste kaart van de stapel, die met het laagste getal voor Index trekt.

Na dit Get Records element gebruiken we een Decision om te controleren of er wel een Position Rule gevonden is. Er zijn immers ook Positions die geen Position Rules hebben. Daarbij hoeft de rest van deze flow helemaal niet uitgevoerd te worden.

Is er wel een Position Rule gevonden, dan is de volgende stap om de Rule zelf op te halen. Dit is puur nodig omdat de SCR – Execute Rule subflow een record variabele van het object Rule nodig heeft als input en zonder deze query hebben we die record variabele niet.

Ook na dit Get Records element gebruiken we weer een null check. Bevestigt die dat we inderdaad een Rule record hebben, dan kunnen we de subflow uitvoeren.

Subflows invoegen

Deze subflow voegen we in in de SCR – Roll and Move flow op 2 plaatsen.

Bovenste Rule van de stapel verwijderen

Wanneer een Kans of Algemeen Fonds kaart gebruikt is, gaat deze van de stapel. Mijn eerste idee was om de Position Rules te verwijderen, maar dan moeten we ze telkens als de stapel leeg is weer opnieuw aanmaken. Omdat we bij zowel Kans als Algemeen Fonds voor 3 vakjes per Rule alle nieuwe Position Rules zouden moeten aanmaken, lijkt dat me meer gedoe dan de gebruikte regels behouden en slechts even ‘aan de kant zetten’.

Dus voegen we in de SCR – Check for and Execute Position Rule flow een update toe voor alle Position Rules van de zojuist gebruikte Rule.

Ook passen we het Get Position Rule element aan.

De aflegstapel schudden

Wanneer een stapel leeg is, moet die opnieuw geschud worden. Ik verwacht dat we dit niet zelf met Flow Builder kunnen bouwen of dat we die flow onnodig complex moeten maken.

Ik heb daarom gegoogled of ik een kant en klare Apex Action kan vinden die ongeveer doet wat ik wil.

Omdat ik zelf wel een beetje Apex kan lezen en schrijven en omdat dit niet een opdracht voor een echte klant is, durf ik het nu wel aan om wat ik op internet gevonden heb zelf om te bouwen naar wat ik nodig heb. Omdat ik zelf geen developer ben, zou ik dat bij een project voor een klant altijd door een echte developer laten maken.

Ook voor deze Action die je als een element in de Flow zult gebruiken, zal ik de code delen. Een developer die dit leest zal ongetwijfeld dingen in deze code zien die beter kunnen.

cls_deckShuffler

public class cls_deckShuffler {
    @InvocableMethod(
        label = 'Generate random order'
        description = 'Generates a list of integers in random order '
    )
    public static List<Response> reshuffle( List<Request> requests ) {
        List<Response> responsesList = new List<Response>();
      
        for ( Request req : requests ) {

            list<Position_Rule__c> inputList = req.inputPositionRulesList;
            list<Position_Rule__c> outputlist = new list<Position_Rule__c>();
            integer currentIndex = inputList.size();
            Position_Rule__c temporaryValue;
            integer randomIndex;
            // While there remain elements to shuffle...
            while (0 != currentIndex) {
                // Pick a remaining element...
                randomIndex = integer.valueOf(Math.floor(Math.random() * currentIndex));
                currentIndex -= 1;
                // And swap it with the current element.
                temporaryValue = inputList[currentIndex];
                inputList[currentIndex] = inputList[randomIndex];
                inputList[randomIndex] = temporaryValue;
            }
            Integer newIndex = 1;
            for(Position_Rule__c pr : inputList) {
                pr.Index__c = newIndex;
                newIndex = newIndex + 1;
                outputList.add(pr);
            }
            Response resp = new Response();
            resp.outputPositionRulesList = outputList;
            responsesList.add(resp);
        }   // End requests loop
        return responsesList;
    }   // End converter method

    public class Request {
        @InvocableVariable(
            label = 'Collection of Position Rules to reshuffle'
            required = true
        )
        public list<Position_Rule__c> inputPositionRulesList;
    }   // End Request Class
        
    public class Response {
        @InvocableVariable(
            label = 'Collection of reshuffled Position Rules'
        )
        public list<Position_Rule__c> outputPositionRulesList;
    }   // End Response Class        
}   // End cls_DiceRoller

test_deckShuffler

@isTest
private class test_deckShuffler {
    @testSetup static void testSetup() {
        List<Rule__c> rulesList = new List<Rule__c>();
        List<Position_Rule__c> positionRulesList = new List<Position_Rule__c>();
        for (integer i = 0; i < 9; i++) {
            Rule__c rule = new Rule__c(
                Name = String.valueOf(i)
            );
            rulesList.add(rule);
        }
        insert rulesList;
        Position__c pos = new Position__c(
            Name = 'Test Position'
        );
        insert pos;
        for (Rule__c ru : rulesList) {
            Position_Rule__c posRule = new Position_Rule__c(
                Position__c = pos.Id,
                Rule__c = ru.Id,
                Index__c = Integer.valueOf(ru.Name)
            );
            positionRulesList.add(posRule);
        }
        insert positionRulesList;
    }
    @isTest
    static void unitTest1() {
        Test.startTest();
        cls_deckShuffler.Request req = new cls_deckShuffler.Request();
        req.inputPositionRulesList = [SELECT Id, Name, Index__c, Rule__r.Name FROM Position_Rule__c];
        cls_deckShuffler.Request[] requests = new cls_deckShuffler.Request[] {};
            requests.add(req);
        cls_deckShuffler.Response[] testResults = cls_deckShuffler.reshuffle(requests);
        List<Position_Rule__c> results = testResults[0].outputPositionRulesList;
        for(Position_Rule__c pr : results) {
            system.assertNotEquals(Integer.valueOf(pr.Rule__r.Name), pr.Index__c, 'Position Rule ' + pr.Index__c + ' was not shuffled');
        }
        Test.stopTest();
    }
}   // End test_deckShuffler

Deze Action verzorgt slechts een deel van de volledige oplossing die we nodig hebben. In de Action gebeurt het volgende:

  • Vanuit de flow geven we de Action een record collection van Position Rules als input mee
  • Deze worden in een nieuwe, willekeurige volgorde gezet
  • Dan worden in een Loop de nieuwe Index waarden aan deze records toegekend
  • De Action slaat deze wijziging niet zelf op in de database, maar geeft een nieuwe record collection met de geüpdatete Position Rules als output

In de Flow moeten we dus nog het volgende doen:

  • Alle Position Rules ophalen voor {!inputPlayer.Position__c}
  • Deze als input meegeven aan de Action
  • Na de Action de Position Rules ophalen van elke Rule uit die stapel voor de andere Kans Positions op het bord
  • Die updaten zodat voor elke Rule de 3 daarbij behorende Position Rules dezelfde Index__c hebben, zodat de stapel overal op dezelfde volgorde ligt.

Dat ziet er als volgt uit

Na de Action wijs ik de output daarvan toe aan een eigen updatePositionRules collectie, zodat ik in één keer alle Position Rules kan updaten, ook die voor de andere Kans vakjes. (of Algemeen Fonds), want de Action heeft ze maar voor één van de drie Positions bewerkt.

Om de Position Rules van alle Kans regels waarmee ze aan de andere twee Kans vakjes gekoppeld zijn ook in de nieuwe volgorde te zetten, moeten we een aantal stappen doorlopen die wel met een Flow uitgevoerd kunnen worden.

Eerste moeten we een text collection vullen met de Id’s van alle Rules die aan deze Position Rules gekoppeld zijn. Die collection met Id’s gebruiken we in een volgend Get records element om de nog niet geschudde Position Rules op te halen. Dankzij het Transform element hoeven we daarvoor geen Loop te gebruiken

Vervolgens voeren we een Loop uit op deze Get Other Position Rules collectie.

Daarbinnen filteren we allereerst uit de updatePositionRules collectie de Position Rule voor dezelfde Rule als die van de huidige nog niet geschudde Rule.

Omdat een gefilterde record collection zelf ook nog een collection is, zelfs al heeft die maar één element, moeten we hier ook een Loop voor gebruiken.

In deze binnenste Loop wijzen we aan de nog niet geschudde Position Rule dezelfde Index waarde toe als die van de al wel bijgewerkte Position Rule.

Dan verlaten we de binnenste loop, maar voor we ook de buitenste loop verlaten voegen we de nu ook bijgewerkte Position Rule toe aan de updatePositionRules collectie.

Nadat we ook de buitenste loop verlaten hebben, voeren we de update uit op alle Position Rules voor de 16 Rules van deze stapel maal de 3 vakjes waarbij je een Rule van die stapel moet trekken.

Testen

Nu wil ik natuurlijk gaan testen of dit allemaal werkt. Daarvoor is wel wat voorbereiding nodig.

Ik zal nu alle Rules voor de Kans en Algemeen Fonds kaarten moeten aanmaken en deze met Position Rules aan de Kans en Algemeen Fonds vakjes moeten koppelen.

Omdat het eigenlijk één stapel is zal ik dezelfde Kans Rule aan elk Kans vakje op het bord moeten koppelen met dezelfde Index waarde.

Elke Kans kaart wordt dus aan 3 Positions gekoppeld door middel van een Position Rule. Omdat de stapel altijd op dezelfde volgorde ligt, moet de Index__c bij elke Position Rule voor dezelfde Rule gelijk zijn.

De 3 Position Rules voor de Kans kaart Verlaat de Gevangenis zonder betalen hebben allemaal Index__c = 16.

Je kunt de excel hieronder downloaden als je zelf mee bouwt en de Rules ook wilt importeren. Zodra de Rules zijn geïmporteerd, importeren we de Position Rules.

In dit bestand heb ik enkele regels geel gemarkeerd. De SCR – Execute Rule flow die we in de vorige aflevering gebouwd hebben, kan die regels nog niet afhandelen zoals ze bedoeld zijn. Daar moeten we dus later nog oplossingen voor verzinnen.

Omdat we geen garantie hebben dat de speler na een worp met de dobbelstenen op Kans of Algemeen Fonds terecht komt, zullen we voor de test een speler handmatig op een Kans vakje zetten en dan deze flow uitvoeren middels de Debug knop in Flow Builder. Ik kies een Player die niet de Bank is als de input variabele van mijn test Flow Interview.

Van links naar rechts: de Player record vóór de regel is uitgevoerd, de Position Rule vóór de regel is uitgevoerd en de Debug run van de flow.

Tijdens deze test ontdek ik dat een Index van 1.000 niet is toegestaan voor Position Rules. Dus moet ik de flow aanpassen om de Index niet te updaten naar 1.000, maar naar 999.

Na deze aanpassing voeren we dezelfde Debug run nog eens uit en nu kun je het resultaat van de flow echt zien.

Ook kunnen we via de record page van deze Rule verifiëren dat deze Rule inderdaad bij elk Kans vakje verwijderd is van de stapel

Ook zullen we de Flow om de Kans stapel te schudden testen.

Om te beginnen ga ik de Index van elke Position Rule van deze Position op 999 zetten. Dan maken we een screenshot van de pagina van de Position waar onze test Player staat.

Dan voeren we de ALF – Shuffle Deck of Cards flow uit met dezelfde Player die we in de test van zojuist ook gebruikt hebben.

In de eerste test run kreeg ik een foutmelding, want de flow zocht records met een Index groter dan 999 en vindt er dus geen. Dit heeft te maken met de aanpassing die we naar aanleiding van de andere test gemaakt hebben. Dus passen we in het Get Position Rules element het filter aan van Greater naar Greater or Equal.

Daarna verloopt de test zonder problemen. Hieronder zien we de Position Rules vóór en ná het uitvoeren van de Flow.

Tot slot controleren we of de Position Rules voor dezelfde Rule overal dezelfde Index hebben gekregen. En ja, bij alle drie de Kans vakjes ligt Go directly to jail Do not pass Go Do not collect € 20.000 bovenop de stapel.

Tijdens het maken van Flows is het dus altijd van belang om alles goed testen. Wanneer je een aanpassing maakt, test hierna dan opnieuw.

Volgende keer

In de volgende twee afleveringen gaan we in op:

  • Assets kopen van de bank
  • Huur betalen

Waar we later nog op terug moeten komen:

  • De Rules die op dit moment nog niet uitgevoerd kunnen worden met de SCR – Execute Rule flow.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *